From f22f7a3b08810f4038270b2cef8c8198371a382f Mon Sep 17 00:00:00 2001 From: Andrew Starr-Bochicchio Date: Sat, 29 Oct 2016 21:39:13 +0100 Subject: [PATCH 1/1] Import libtorrent-rasterbar_1.1.1.orig.tar.gz [dgit import orig libtorrent-rasterbar_1.1.1.orig.tar.gz] --- AUTHORS | 26 + CMakeLists.txt | 389 + COPYING | 28 + ChangeLog | 1491 + Jamfile | 779 + Jamroot.jam | 0 LICENSE | 83 + Makefile.am | 130 + Makefile.in | 1039 + NEWS | 1 + README.rst | 59 + aclocal.m4 | 1403 + bindings/Makefile.am | 4 + bindings/Makefile.in | 669 + bindings/README.txt | 3 + bindings/python/Jamfile | 166 + bindings/python/Makefile.am | 48 + bindings/python/Makefile.in | 541 + bindings/python/client.py | 345 + bindings/python/compile_flags.in | 1 + bindings/python/link_flags.in | 1 + bindings/python/make_torrent.py | 53 + bindings/python/rss_reader.py | 35 + bindings/python/setup.py | 139 + bindings/python/simple_client.py | 36 + bindings/python/src/alert.cpp | 776 + bindings/python/src/big_number.cpp | 42 + bindings/python/src/boost_python.hpp | 13 + bindings/python/src/bytes.hpp | 25 + bindings/python/src/converters.cpp | 51 + bindings/python/src/create_torrent.cpp | 238 + bindings/python/src/datetime.cpp | 102 + bindings/python/src/entry.cpp | 179 + bindings/python/src/error_code.cpp | 89 + bindings/python/src/fingerprint.cpp | 27 + bindings/python/src/gil.hpp | 148 + bindings/python/src/ip_filter.cpp | 32 + bindings/python/src/magnet_uri.cpp | 85 + bindings/python/src/module.cpp | 56 + bindings/python/src/optional.hpp | 31 + bindings/python/src/peer_info.cpp | 163 + bindings/python/src/session.cpp | 956 + bindings/python/src/session_settings.cpp | 307 + bindings/python/src/string.cpp | 69 + bindings/python/src/torrent_handle.cpp | 527 + bindings/python/src/torrent_info.cpp | 316 + bindings/python/src/torrent_status.cpp | 138 + bindings/python/src/utility.cpp | 94 + bindings/python/src/version.cpp | 21 + build-aux/compile | 347 + build-aux/config.guess | 1421 + build-aux/config.rpath | 543 + build-aux/config.sub | 1807 ++ build-aux/depcomp | 791 + build-aux/install-sh | 501 + build-aux/ltmain.sh | 11149 ++++++++ build-aux/missing | 215 + build-aux/test-driver | 148 + configure | 22884 ++++++++++++++++ configure.ac | 705 + docs/building.html | 762 + docs/building.rst | 628 + docs/client_test.html | 112 + docs/client_test.png | Bin 0 -> 664722 bytes docs/client_test.rst | 45 + docs/complete_bit_prefixes.png | Bin 0 -> 7795 bytes docs/contributing.html | 136 + docs/contributing.rst | 58 + docs/cwnd.png | Bin 0 -> 18998 bytes docs/cwnd_thumb.png | Bin 0 -> 30207 bytes docs/delays.png | Bin 0 -> 10875 bytes docs/delays_thumb.png | Bin 0 -> 10938 bytes docs/dht_extensions.html | 148 + docs/dht_extensions.rst | 68 + docs/dht_rss.html | 435 + docs/dht_rss.rst | 397 + docs/dht_sec.html | 305 + docs/dht_sec.rst | 256 + docs/dht_store.html | 538 + docs/dht_store.rst | 481 + docs/disk_cache.diagram | 12 + docs/disk_cache.png | Bin 0 -> 1658 bytes docs/disk_io.png | Bin 0 -> 4393 bytes docs/examples.html | 763 + docs/examples.rst | 52 + docs/extension_protocol.html | 498 + docs/extension_protocol.rst | 324 + docs/features.html | 374 + docs/features.rst | 336 + docs/hacking.diagram | 30 + docs/hacking.html | 252 + docs/hacking.png | Bin 0 -> 6260 bytes docs/hash_distribution.png | Bin 0 -> 9035 bytes docs/im_thumb.jpg | Bin 0 -> 12093 bytes docs/img/bg.png | Bin 0 -> 2773 bytes docs/img/blue_bottom.png | Bin 0 -> 13137 bytes docs/img/blue_top.png | Bin 0 -> 2953 bytes docs/img/dotline.gif | Bin 0 -> 181 bytes docs/img/minus.gif | Bin 0 -> 262 bytes docs/img/orange.png | Bin 0 -> 24833 bytes docs/index.html | 195 + docs/index.rst | 196 + docs/ip_id_v4.png | Bin 0 -> 5825 bytes docs/ip_id_v6.png | Bin 0 -> 6416 bytes docs/manual-ref.html | 2868 ++ docs/manual-ref.rst | 1034 + docs/merkle_tree.png | Bin 0 -> 18792 bytes docs/our_delay_base.png | Bin 0 -> 34999 bytes docs/our_delay_base_thumb.png | Bin 0 -> 55460 bytes docs/projects.html | 222 + docs/projects.rst | 234 + docs/python_binding.html | 208 + docs/python_binding.rst | 140 + docs/read_disk_buffers.diagram | 16 + docs/read_disk_buffers.png | Bin 0 -> 3444 bytes docs/reference-Alerts.html | 3489 +++ docs/reference-Bdecoding.html | 396 + docs/reference-Bencoding.html | 431 + docs/reference-Core.html | 5408 ++++ docs/reference-Create_Torrents.html | 483 + docs/reference-Custom_Storage.html | 649 + docs/reference-Error_Codes.html | 1315 + docs/reference-Filter.html | 245 + docs/reference-Plugins.html | 778 + docs/reference-Settings.html | 6800 +++++ docs/reference-Storage.html | 643 + docs/reference-Utility.html | 491 + docs/reference-ed25519.html | 120 + docs/reference.html | 317 + docs/rst.css | 241 + docs/session_stats_peers.png | Bin 0 -> 3902 bytes docs/settings.rst | 3224 +++ docs/stats_counters.rst | 2031 ++ docs/storage.png | Bin 0 -> 4773 bytes docs/streaming.html | 174 + docs/streaming.rst | 126 + docs/style.css | 154 + docs/todo.html | 10423 +++++++ docs/troubleshooting.dot | 108 + docs/troubleshooting.html | 90 + docs/troubleshooting.png | Bin 0 -> 373382 bytes docs/troubleshooting.rst | 24 + docs/troubleshooting_thumb.png | Bin 0 -> 40618 bytes docs/tuning.html | 516 + docs/tuning.rst | 463 + docs/tutorial.html | 473 + docs/tutorial.rst | 310 + docs/udp_tracker_protocol.html | 605 + docs/udp_tracker_protocol.rst | 320 + docs/utp.html | 370 + docs/utp.rst | 347 + docs/utp_stack.diagram | 9 + docs/utp_stack.png | Bin 0 -> 833 bytes docs/write_disk_buffers.diagram | 15 + docs/write_disk_buffers.png | Bin 0 -> 3127 bytes docs/ziptorrent_thumb.gif | Bin 0 -> 6827 bytes ed25519/readme.md | 165 + ed25519/src/add_scalar.cpp | 59 + ed25519/src/fe.cpp | 1504 + ed25519/src/fe.h | 41 + ed25519/src/fixedint.h | 6 + ed25519/src/ge.cpp | 470 + ed25519/src/ge.h | 74 + ed25519/src/key_exchange.cpp | 83 + ed25519/src/keypair.cpp | 19 + ed25519/src/precomp_data.h | 1392 + ed25519/src/sc.cpp | 812 + ed25519/src/sc.h | 13 + ed25519/src/seed.cpp | 63 + ed25519/src/sha512.cpp | 282 + ed25519/src/sha512.h | 22 + ed25519/src/sign.cpp | 34 + ed25519/src/verify.cpp | 80 + ed25519/test.c | 149 + examples/CMakeLists.txt | 42 + examples/Jamfile | 38 + examples/Makefile.am | 34 + examples/Makefile.in | 796 + examples/bt-get.cpp | 75 + examples/bt-get2.cpp | 149 + examples/client_test.cpp | 2399 ++ examples/cmake/FindLibtorrentRasterbar.cmake | 93 + examples/connection_tester.cpp | 1100 + examples/dump_torrent.cpp | 216 + examples/make_torrent.cpp | 443 + examples/print.cpp | 475 + examples/print.hpp | 45 + examples/run_cmake.sh.in | 8 + examples/session_view.cpp | 207 + examples/session_view.hpp | 76 + examples/simple_client.cpp | 81 + examples/stats_counters.cpp | 49 + examples/torrent_view.cpp | 384 + examples/torrent_view.hpp | 78 + examples/upnp_test.cpp | 116 + include/libtorrent/ConvertUTF.h | 165 + include/libtorrent/Makefile.am | 202 + include/libtorrent/Makefile.in | 812 + include/libtorrent/add_torrent_params.hpp | 423 + include/libtorrent/address.hpp | 80 + include/libtorrent/alert.hpp | 324 + include/libtorrent/alert_manager.hpp | 214 + include/libtorrent/alert_types.hpp | 2515 ++ include/libtorrent/alloca.hpp | 54 + include/libtorrent/allocator.hpp | 59 + include/libtorrent/announce_entry.hpp | 202 + include/libtorrent/assert.hpp | 115 + .../aux_/alert_manager_variadic_emplace.hpp | 55 + .../libtorrent/aux_/allocating_handler.hpp | 153 + include/libtorrent/aux_/byteswap.hpp | 76 + include/libtorrent/aux_/cpuid.hpp | 46 + .../libtorrent/aux_/disable_warnings_pop.hpp | 44 + .../libtorrent/aux_/disable_warnings_push.hpp | 78 + include/libtorrent/aux_/escape_string.hpp | 106 + include/libtorrent/aux_/file_progress.hpp | 81 + include/libtorrent/aux_/merkle.hpp | 48 + include/libtorrent/aux_/openssl.hpp | 95 + include/libtorrent/aux_/proxy_settings.hpp | 145 + include/libtorrent/aux_/session_call.hpp | 108 + include/libtorrent/aux_/session_impl.hpp | 1278 + include/libtorrent/aux_/session_interface.hpp | 351 + include/libtorrent/aux_/session_settings.hpp | 88 + include/libtorrent/aux_/time.hpp | 52 + include/libtorrent/bandwidth_limit.hpp | 105 + include/libtorrent/bandwidth_manager.hpp | 99 + include/libtorrent/bandwidth_queue_entry.hpp | 78 + include/libtorrent/bandwidth_socket.hpp | 48 + include/libtorrent/bdecode.hpp | 420 + include/libtorrent/bencode.hpp | 460 + include/libtorrent/bitfield.hpp | 264 + include/libtorrent/block_cache.hpp | 548 + include/libtorrent/bloom_filter.hpp | 85 + include/libtorrent/broadcast_socket.hpp | 161 + include/libtorrent/bt_peer_connection.hpp | 460 + include/libtorrent/buffer.hpp | 254 + include/libtorrent/build_config.hpp | 63 + include/libtorrent/chained_buffer.hpp | 141 + include/libtorrent/choker.hpp | 52 + include/libtorrent/close_reason.hpp | 157 + include/libtorrent/config.hpp | 710 + include/libtorrent/copy_ptr.hpp | 69 + include/libtorrent/crc32c.hpp | 50 + include/libtorrent/create_torrent.hpp | 490 + include/libtorrent/deadline_timer.hpp | 58 + include/libtorrent/debug.hpp | 210 + include/libtorrent/disk_buffer_holder.hpp | 129 + include/libtorrent/disk_buffer_pool.hpp | 195 + include/libtorrent/disk_interface.hpp | 119 + include/libtorrent/disk_io_job.hpp | 247 + include/libtorrent/disk_io_thread.hpp | 627 + include/libtorrent/disk_job_pool.hpp | 77 + include/libtorrent/disk_observer.hpp | 52 + include/libtorrent/ed25519.hpp | 32 + include/libtorrent/entry.hpp | 334 + include/libtorrent/enum_net.hpp | 194 + include/libtorrent/error.hpp | 52 + include/libtorrent/error_code.hpp | 585 + include/libtorrent/export.hpp | 96 + include/libtorrent/extensions.hpp | 515 + include/libtorrent/extensions/lt_trackers.hpp | 63 + .../extensions/metadata_transfer.hpp | 69 + include/libtorrent/extensions/smart_ban.hpp | 62 + include/libtorrent/extensions/ut_metadata.hpp | 65 + include/libtorrent/extensions/ut_pex.hpp | 69 + include/libtorrent/file.hpp | 361 + include/libtorrent/file_pool.hpp | 149 + include/libtorrent/file_storage.hpp | 655 + include/libtorrent/fingerprint.hpp | 130 + include/libtorrent/gzip.hpp | 131 + include/libtorrent/hasher.hpp | 126 + include/libtorrent/heterogeneous_queue.hpp | 218 + include/libtorrent/hex.hpp | 75 + include/libtorrent/http_connection.hpp | 252 + include/libtorrent/http_parser.hpp | 176 + include/libtorrent/http_seed_connection.hpp | 132 + include/libtorrent/http_stream.hpp | 128 + .../libtorrent/http_tracker_connection.hpp | 110 + include/libtorrent/i2p_stream.hpp | 244 + include/libtorrent/identify_client.hpp | 73 + include/libtorrent/instantiate_connection.hpp | 57 + include/libtorrent/invariant_check.hpp | 86 + include/libtorrent/io.hpp | 173 + include/libtorrent/io_service.hpp | 72 + include/libtorrent/io_service_fwd.hpp | 70 + include/libtorrent/ip_filter.hpp | 359 + include/libtorrent/ip_voter.hpp | 131 + include/libtorrent/kademlia/dht_observer.hpp | 85 + include/libtorrent/kademlia/dht_storage.hpp | 218 + include/libtorrent/kademlia/dht_tracker.hpp | 177 + .../libtorrent/kademlia/direct_request.hpp | 95 + include/libtorrent/kademlia/dos_blocker.hpp | 95 + include/libtorrent/kademlia/find_data.hpp | 104 + include/libtorrent/kademlia/get_item.hpp | 92 + include/libtorrent/kademlia/get_peers.hpp | 108 + include/libtorrent/kademlia/item.hpp | 137 + include/libtorrent/kademlia/msg.hpp | 112 + include/libtorrent/kademlia/node.hpp | 253 + include/libtorrent/kademlia/node_entry.hpp | 86 + include/libtorrent/kademlia/node_id.hpp | 81 + include/libtorrent/kademlia/observer.hpp | 180 + include/libtorrent/kademlia/put_data.hpp | 95 + include/libtorrent/kademlia/refresh.hpp | 71 + include/libtorrent/kademlia/routing_table.hpp | 270 + include/libtorrent/kademlia/rpc_manager.hpp | 143 + .../kademlia/traversal_algorithm.hpp | 146 + include/libtorrent/lazy_entry.hpp | 414 + include/libtorrent/link.hpp | 77 + include/libtorrent/linked_list.hpp | 158 + include/libtorrent/lsd.hpp | 122 + include/libtorrent/magnet_uri.hpp | 87 + include/libtorrent/max.hpp | 126 + include/libtorrent/natpmp.hpp | 182 + include/libtorrent/network_thread_pool.hpp | 83 + include/libtorrent/operations.hpp | 105 + include/libtorrent/packet_buffer.hpp | 142 + include/libtorrent/parse_url.hpp | 57 + include/libtorrent/part_file.hpp | 118 + include/libtorrent/pe_crypto.hpp | 152 + include/libtorrent/peer.hpp | 74 + include/libtorrent/peer_class.hpp | 149 + include/libtorrent/peer_class_set.hpp | 70 + include/libtorrent/peer_class_type_filter.hpp | 141 + include/libtorrent/peer_connection.hpp | 1294 + include/libtorrent/peer_connection_handle.hpp | 151 + .../libtorrent/peer_connection_interface.hpp | 75 + include/libtorrent/peer_id.hpp | 48 + include/libtorrent/peer_info.hpp | 452 + include/libtorrent/peer_list.hpp | 283 + include/libtorrent/peer_request.hpp | 58 + include/libtorrent/performance_counters.hpp | 466 + include/libtorrent/piece_block_progress.hpp | 57 + include/libtorrent/piece_picker.hpp | 844 + include/libtorrent/platform_util.hpp | 14 + include/libtorrent/proxy_base.hpp | 267 + include/libtorrent/puff.hpp | 31 + include/libtorrent/random.hpp | 40 + include/libtorrent/receive_buffer.hpp | 295 + include/libtorrent/request_blocks.hpp | 55 + include/libtorrent/resolve_links.hpp | 89 + include/libtorrent/resolver.hpp | 95 + include/libtorrent/resolver_interface.hpp | 77 + include/libtorrent/rss.hpp | 290 + include/libtorrent/session.hpp | 300 + include/libtorrent/session_handle.hpp | 1090 + include/libtorrent/session_settings.hpp | 1579 ++ include/libtorrent/session_stats.hpp | 68 + include/libtorrent/session_status.hpp | 223 + include/libtorrent/settings_pack.hpp | 1690 ++ include/libtorrent/sha1.hpp | 38 + include/libtorrent/sha1_hash.hpp | 353 + include/libtorrent/sliding_average.hpp | 118 + include/libtorrent/socket.hpp | 197 + include/libtorrent/socket_io.hpp | 172 + include/libtorrent/socket_type.hpp | 353 + include/libtorrent/socket_type_fwd.hpp | 42 + include/libtorrent/socks5_stream.hpp | 267 + include/libtorrent/ssl_stream.hpp | 337 + include/libtorrent/stack_allocator.hpp | 113 + include/libtorrent/stat.hpp | 289 + include/libtorrent/stat_cache.hpp | 87 + include/libtorrent/storage.hpp | 712 + include/libtorrent/storage_defs.hpp | 90 + include/libtorrent/string_util.hpp | 109 + include/libtorrent/tailqueue.hpp | 184 + include/libtorrent/thread.hpp | 95 + include/libtorrent/thread_pool.hpp | 165 + include/libtorrent/time.hpp | 122 + include/libtorrent/timestamp_history.hpp | 87 + include/libtorrent/tommath.h | 575 + include/libtorrent/tommath_class.h | 1057 + include/libtorrent/tommath_private.h | 119 + include/libtorrent/tommath_superclass.h | 76 + include/libtorrent/torrent.hpp | 1750 ++ include/libtorrent/torrent_handle.hpp | 1308 + include/libtorrent/torrent_info.hpp | 641 + include/libtorrent/torrent_peer.hpp | 277 + include/libtorrent/torrent_peer_allocator.hpp | 103 + include/libtorrent/torrent_status.hpp | 526 + include/libtorrent/tracker_manager.hpp | 416 + include/libtorrent/udp_socket.hpp | 332 + include/libtorrent/udp_tracker_connection.hpp | 143 + include/libtorrent/uncork_interface.hpp | 59 + include/libtorrent/union_endpoint.hpp | 134 + include/libtorrent/upnp.hpp | 418 + include/libtorrent/utf8.hpp | 78 + include/libtorrent/utp_socket_manager.hpp | 178 + include/libtorrent/utp_stream.hpp | 510 + include/libtorrent/vector_utils.hpp | 70 + include/libtorrent/version.hpp | 57 + include/libtorrent/web_connection_base.hpp | 161 + include/libtorrent/web_peer_connection.hpp | 160 + include/libtorrent/xml_parse.hpp | 77 + libtorrent-rasterbar-cmake.pc.in | 6 + libtorrent-rasterbar.pc.in | 16 + m4/._libtool.m4 | Bin 0 -> 239 bytes m4/._ltoptions.m4 | Bin 0 -> 239 bytes m4/._ltsugar.m4 | Bin 0 -> 239 bytes m4/._lt~obsolete.m4 | Bin 0 -> 239 bytes m4/ax_boost_base.m4 | 286 + m4/ax_boost_chrono.m4 | 119 + m4/ax_boost_python.m4 | 120 + m4/ax_boost_random.m4 | 123 + m4/ax_boost_system.m4 | 114 + m4/ax_check_openssl.m4 | 124 + m4/ax_pthread.m4 | 383 + m4/ax_python_devel.m4 | 327 + m4/gettext-lib.m4 | 818 + m4/iconv.m4 | 103 + m4/libtool.m4 | 8369 ++++++ m4/ltoptions.m4 | 437 + m4/ltsugar.m4 | 124 + m4/ltversion.m4 | 23 + m4/lt~obsolete.m4 | 99 + m4/pkgconfig.m4 | 155 + setup.py | 5 + src/ConvertUTF.cpp | 539 + src/Makefile.am | 160 + src/Makefile.in | 1160 + src/alert.cpp | 2053 ++ src/alert_manager.cpp | 205 + src/allocator.cpp | 211 + src/announce_entry.cpp | 134 + src/assert.cpp | 377 + src/bandwidth_limit.cpp | 94 + src/bandwidth_manager.cpp | 226 + src/bandwidth_queue_entry.cpp | 79 + src/bdecode.cpp | 1086 + src/bitfield.cpp | 163 + src/block_cache.cpp | 1915 ++ src/bloom_filter.cpp | 76 + src/broadcast_socket.cpp | 438 + src/bt_peer_connection.cpp | 3669 +++ src/chained_buffer.cpp | 202 + src/choker.cpp | 419 + src/close_reason.cpp | 176 + src/cpuid.cpp | 93 + src/crc32c.cpp | 126 + src/create_torrent.cpp | 748 + src/disk_buffer_holder.cpp | 95 + src/disk_buffer_pool.cpp | 588 + src/disk_io_job.cpp | 86 + src/disk_io_thread.cpp | 3596 +++ src/disk_job_pool.cpp | 110 + src/entry.cpp | 736 + src/enum_net.cpp | 1149 + src/error_code.cpp | 353 + src/escape_string.cpp | 623 + src/file.cpp | 2371 ++ src/file_pool.cpp | 349 + src/file_progress.cpp | 171 + src/file_storage.cpp | 1098 + src/gzip.cpp | 279 + src/hasher.cpp | 137 + src/hex.cpp | 101 + src/http_connection.cpp | 956 + src/http_parser.cpp | 597 + src/http_seed_connection.cpp | 475 + src/http_stream.cpp | 151 + src/http_tracker_connection.cpp | 602 + src/i2p_stream.cpp | 533 + src/identify_client.cpp | 422 + src/instantiate_connection.cpp | 150 + src/ip_filter.cpp | 98 + src/ip_voter.cpp | 194 + src/kademlia/dht_storage.cpp | 576 + src/kademlia/dht_tracker.cpp | 449 + src/kademlia/dos_blocker.cpp | 108 + src/kademlia/find_data.cpp | 182 + src/kademlia/get_item.cpp | 229 + src/kademlia/get_peers.cpp | 338 + src/kademlia/item.cpp | 235 + src/kademlia/msg.cpp | 145 + src/kademlia/node.cpp | 1189 + src/kademlia/node_entry.cpp | 90 + src/kademlia/node_id.cpp | 238 + src/kademlia/put_data.cpp | 122 + src/kademlia/refresh.cpp | 111 + src/kademlia/routing_table.cpp | 1314 + src/kademlia/rpc_manager.cpp | 502 + src/kademlia/traversal_algorithm.cpp | 638 + src/lazy_bdecode.cpp | 662 + src/lsd.cpp | 316 + src/lt_trackers.cpp | 395 + src/magnet_uri.cpp | 294 + src/merkle.cpp | 69 + src/metadata_transfer.cpp | 617 + src/mpi.cpp | 10089 +++++++ src/natpmp.cpp | 709 + src/packet_buffer.cpp | 219 + src/parse_url.cpp | 134 + src/part_file.cpp | 426 + src/pe_crypto.cpp | 463 + src/peer_class.cpp | 140 + src/peer_class_set.cpp | 75 + src/peer_connection.cpp | 6888 +++++ src/peer_connection_handle.cpp | 341 + src/peer_list.cpp | 1399 + src/performance_counters.cpp | 167 + src/piece_picker.cpp | 3778 +++ src/platform_util.cpp | 149 + src/proxy_base.cpp | 55 + src/proxy_settings.cpp | 78 + src/puff.cpp | 844 + src/random.cpp | 103 + src/receive_buffer.cpp | 433 + src/request_blocks.cpp | 303 + src/resolve_links.cpp | 141 + src/resolver.cpp | 141 + src/rss.cpp | 706 + src/session.cpp | 438 + src/session_call.cpp | 100 + src/session_handle.cpp | 1028 + src/session_impl.cpp | 7330 +++++ src/session_settings.cpp | 42 + src/session_stats.cpp | 552 + src/settings_pack.cpp | 801 + src/sha1.cpp | 320 + src/smart_ban.cpp | 408 + src/socket_io.cpp | 161 + src/socket_type.cpp | 400 + src/socks5_stream.cpp | 551 + src/stat.cpp | 49 + src/stat_cache.cpp | 97 + src/storage.cpp | 1920 ++ src/string_util.cpp | 281 + src/thread.cpp | 195 + src/time.cpp | 59 + src/timestamp_history.cpp | 107 + src/torrent.cpp | 12323 +++++++++ src/torrent_handle.cpp | 809 + src/torrent_info.cpp | 1849 ++ src/torrent_peer.cpp | 304 + src/torrent_peer_allocator.cpp | 134 + src/torrent_status.cpp | 114 + src/tracker_manager.cpp | 452 + src/udp_socket.cpp | 1523 + src/udp_tracker_connection.cpp | 789 + src/upnp.cpp | 1567 ++ src/ut_metadata.cpp | 680 + src/ut_pex.cpp | 707 + src/utf8.cpp | 207 + src/utp_socket_manager.cpp | 450 + src/utp_stream.cpp | 3776 +++ src/version.cpp | 43 + src/web_connection_base.cpp | 216 + src/web_peer_connection.cpp | 1091 + src/xml_parse.cpp | 196 + test/Jamfile | 235 + test/Makefile.am | 242 + test/Makefile.in | 2130 ++ test/bdecode_benchmark.cpp | 184 + test/bittorrent_peer.cpp | 562 + test/bittorrent_peer.hpp | 118 + test/corrupt.gz | Bin 0 -> 296 bytes test/dht_server.cpp | 177 + test/dht_server.hpp | 42 + test/enum_if.cpp | 100 + test/http.py | 233 + test/main.cpp | 474 + test/make_torrent.cpp | 203 + 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 | 166 + test/peer_server.hpp | 47 + test/print_alerts.cpp | 73 + test/print_alerts.hpp | 43 + test/settings.cpp | 75 + test/settings.hpp | 37 + test/setup_transfer.cpp | 935 + test/setup_transfer.hpp | 112 + test/socks.py | 282 + test/swarm_suite.cpp | 239 + test/swarm_suite.hpp | 45 + test/test.cpp | 93 + test/test.hpp | 165 + test/test_alert_manager.cpp | 323 + test/test_auto_unchoke.cpp | 157 + test/test_bandwidth_limiter.cpp | 517 + test/test_bdecode.cpp | 1280 + test/test_bencoding.cpp | 645 + test/test_bitfield.cpp | 190 + test/test_block_cache.cpp | 476 + test/test_bloom_filter.cpp | 133 + test/test_buffer.cpp | 321 + test/test_checking.cpp | 314 + test/test_crc32.cpp | 64 + test/test_create_torrent.cpp | 67 + test/test_dht.cpp | 2651 ++ test/test_dht_storage.cpp | 320 + test/test_direct_dht.cpp | 140 + test/test_dos_blocker.cpp | 97 + test/test_enum_net.cpp | 103 + test/test_fast_extension.cpp | 1022 + test/test_fence.cpp | 205 + test/test_file.cpp | 394 + test/test_file_progress.cpp | 110 + test/test_file_storage.cpp | 222 + test/test_gzip.cpp | 77 + test/test_hasher.cpp | 77 + test/test_heterogeneous_queue.cpp | 300 + test/test_http_connection.cpp | 227 + test/test_http_parser.cpp | 571 + test/test_identify_client.cpp | 50 + test/test_ip_filter.cpp | 303 + test/test_ip_voter.cpp | 216 + test/test_linked_list.cpp | 198 + test/test_lsd.cpp | 116 + test/test_magnet.cpp | 392 + test/test_merkle.cpp | 107 + test/test_packet_buffer.cpp | 166 + test/test_part_file.cpp | 147 + test/test_pe_crypto.cpp | 192 + test/test_peer_classes.cpp | 116 + test/test_peer_list.cpp | 948 + test/test_peer_priority.cpp | 108 + test/test_pex.cpp | 170 + test/test_piece_picker.cpp | 1869 ++ test/test_primitives.cpp | 163 + test/test_priority.cpp | 424 + test/test_privacy.cpp | 324 + test/test_random.cpp | 65 + test/test_read_piece.cpp | 163 + test/test_receive_buffer.cpp | 255 + test/test_recheck.cpp | 118 + test/test_remap_files.cpp | 557 + test/test_resolve_links.cpp | 132 + test/test_resume.cpp | 742 + test/test_session.cpp | 260 + test/test_settings_pack.cpp | 141 + test/test_sha1_hash.cpp | 112 + test/test_sliding_average.cpp | 114 + test/test_socket_io.cpp | 171 + test/test_ssl.cpp | 625 + test/test_stat_cache.cpp | 80 + test/test_storage.cpp | 1357 + test/test_string.cpp | 312 + test/test_tailqueue.cpp | 162 + test/test_threads.cpp | 134 + test/test_time.cpp | 100 + test/test_time_critical.cpp | 41 + test/test_timestamp_history.cpp | 57 + test/test_torrent.cpp | 395 + test/test_torrent_info.cpp | 951 + test/test_torrents/backslash_path.torrent | 1 + test/test_torrents/base.torrent | 1 + test/test_torrents/creation_date.torrent | 1 + test/test_torrents/duplicate_files.torrent | 1 + .../test_torrents/duplicate_web_seeds.torrent | 1 + test/test_torrents/empty_httpseed.torrent | 1 + test/test_torrents/empty_path.torrent | 1 + test/test_torrents/empty_path_multi.torrent | 1 + test/test_torrents/hidden_parent_path.torrent | 1 + test/test_torrents/httpseed.torrent | 1 + test/test_torrents/invalid_file_size.torrent | 1 + test/test_torrents/invalid_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_root_hash.torrent | 1 + test/test_torrents/invalid_root_hash2.torrent | 1 + test/test_torrents/invalid_symlink.torrent | 1 + test/test_torrents/long_name.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_name.torrent | 1 + test/test_torrents/pad_file.torrent | 1 + test/test_torrents/pad_file_no_path.torrent | 1 + test/test_torrents/parent_path.torrent | 1 + test/test_torrents/root_hash.torrent | 1 + test/test_torrents/sample.torrent | Bin 0 -> 505 bytes test/test_torrents/single_multi_file.torrent | 1 + test/test_torrents/slash_path.torrent | 1 + test/test_torrents/slash_path2.torrent | 1 + test/test_torrents/slash_path3.torrent | 1 + test/test_torrents/string.torrent | 1 + test/test_torrents/symlink1.torrent | 1 + test/test_torrents/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_space.torrent | 1 + .../url_seed_multi_space_nolist.torrent | 1 + test/test_torrents/whitespace_url.torrent | 1 + test/test_tracker.cpp | 588 + test/test_transfer.cpp | 457 + test/test_upnp.cpp | 243 + test/test_url_seed.cpp | 68 + test/test_utf8.cpp | 272 + test/test_utils.cpp | 54 + test/test_utils.hpp | 44 + test/test_utp.cpp | 152 + 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 | 105 + 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 | 385 + test/udp_tracker.cpp | 242 + test/udp_tracker.hpp | 42 + test/utf8_test.txt | 150 + test/web_seed_suite.cpp | 428 + test/web_seed_suite.hpp | 44 + test/web_server.py | 192 + test/zeroes.gz | Bin 0 -> 538 bytes tools/Jamfile | 39 + tools/Makefile.am | 31 + tools/Makefile.in | 704 + tools/fuzz_torrent.cpp | 415 + tools/parse_bandwidth_log.py | 24 + tools/parse_buffer_log.py | 90 + tools/parse_dht_log.py | 329 + tools/parse_dht_rtt.py | 52 + tools/parse_dht_stats.py | 53 + tools/parse_disk_buffer_log.py | 92 + tools/parse_disk_log.py | 121 + tools/parse_memory_log.py | 126 + tools/parse_peer_log.py | 72 + tools/parse_sample.py | 109 + tools/parse_session_stats.py | 605 + tools/parse_utp_log.py | 319 + 743 files changed, 325855 insertions(+) create mode 100644 AUTHORS create mode 100644 CMakeLists.txt create mode 100644 COPYING create mode 100644 ChangeLog create mode 100644 Jamfile create mode 100644 Jamroot.jam create mode 100644 LICENSE create mode 100644 Makefile.am create mode 100644 Makefile.in create mode 100644 NEWS create mode 100644 README.rst create mode 100644 aclocal.m4 create mode 100644 bindings/Makefile.am create mode 100644 bindings/Makefile.in create mode 100644 bindings/README.txt create mode 100644 bindings/python/Jamfile create mode 100644 bindings/python/Makefile.am create mode 100644 bindings/python/Makefile.in create mode 100755 bindings/python/client.py create mode 100644 bindings/python/compile_flags.in create mode 100644 bindings/python/link_flags.in create mode 100755 bindings/python/make_torrent.py create mode 100755 bindings/python/rss_reader.py create mode 100644 bindings/python/setup.py create mode 100755 bindings/python/simple_client.py create mode 100644 bindings/python/src/alert.cpp create mode 100644 bindings/python/src/big_number.cpp create mode 100644 bindings/python/src/boost_python.hpp create mode 100644 bindings/python/src/bytes.hpp create mode 100644 bindings/python/src/converters.cpp create mode 100644 bindings/python/src/create_torrent.cpp create mode 100644 bindings/python/src/datetime.cpp create mode 100644 bindings/python/src/entry.cpp create mode 100644 bindings/python/src/error_code.cpp create mode 100644 bindings/python/src/fingerprint.cpp create mode 100644 bindings/python/src/gil.hpp create mode 100644 bindings/python/src/ip_filter.cpp create mode 100644 bindings/python/src/magnet_uri.cpp create mode 100644 bindings/python/src/module.cpp create mode 100644 bindings/python/src/optional.hpp create mode 100644 bindings/python/src/peer_info.cpp create mode 100644 bindings/python/src/session.cpp create mode 100644 bindings/python/src/session_settings.cpp create mode 100644 bindings/python/src/string.cpp create mode 100644 bindings/python/src/torrent_handle.cpp create mode 100644 bindings/python/src/torrent_info.cpp create mode 100644 bindings/python/src/torrent_status.cpp create mode 100644 bindings/python/src/utility.cpp create mode 100644 bindings/python/src/version.cpp create mode 100755 build-aux/compile create mode 100755 build-aux/config.guess create mode 100644 build-aux/config.rpath create mode 100755 build-aux/config.sub create mode 100755 build-aux/depcomp create mode 100755 build-aux/install-sh create mode 100644 build-aux/ltmain.sh create mode 100755 build-aux/missing create mode 100755 build-aux/test-driver create mode 100755 configure create mode 100644 configure.ac create mode 100644 docs/building.html create mode 100644 docs/building.rst create mode 100644 docs/client_test.html create mode 100644 docs/client_test.png create mode 100644 docs/client_test.rst create mode 100644 docs/complete_bit_prefixes.png create mode 100644 docs/contributing.html create mode 100644 docs/contributing.rst create mode 100644 docs/cwnd.png create mode 100644 docs/cwnd_thumb.png create mode 100644 docs/delays.png create mode 100644 docs/delays_thumb.png create mode 100644 docs/dht_extensions.html create mode 100644 docs/dht_extensions.rst create mode 100644 docs/dht_rss.html create mode 100644 docs/dht_rss.rst create mode 100644 docs/dht_sec.html create mode 100644 docs/dht_sec.rst create mode 100644 docs/dht_store.html create mode 100644 docs/dht_store.rst create mode 100644 docs/disk_cache.diagram create mode 100644 docs/disk_cache.png create mode 100644 docs/disk_io.png create mode 100644 docs/examples.html create mode 100644 docs/examples.rst create mode 100644 docs/extension_protocol.html create mode 100644 docs/extension_protocol.rst create mode 100644 docs/features.html create mode 100644 docs/features.rst create mode 100644 docs/hacking.diagram create mode 100644 docs/hacking.html create mode 100644 docs/hacking.png create mode 100644 docs/hash_distribution.png create mode 100644 docs/im_thumb.jpg create mode 100644 docs/img/bg.png create mode 100644 docs/img/blue_bottom.png create mode 100644 docs/img/blue_top.png create mode 100644 docs/img/dotline.gif create mode 100644 docs/img/minus.gif create mode 100644 docs/img/orange.png create mode 100644 docs/index.html create mode 100644 docs/index.rst create mode 100644 docs/ip_id_v4.png create mode 100644 docs/ip_id_v6.png create mode 100644 docs/manual-ref.html create mode 100644 docs/manual-ref.rst create mode 100644 docs/merkle_tree.png create mode 100644 docs/our_delay_base.png create mode 100644 docs/our_delay_base_thumb.png create mode 100644 docs/projects.html create mode 100644 docs/projects.rst create mode 100644 docs/python_binding.html create mode 100644 docs/python_binding.rst create mode 100644 docs/read_disk_buffers.diagram create mode 100644 docs/read_disk_buffers.png create mode 100644 docs/reference-Alerts.html create mode 100644 docs/reference-Bdecoding.html create mode 100644 docs/reference-Bencoding.html create mode 100644 docs/reference-Core.html create mode 100644 docs/reference-Create_Torrents.html create mode 100644 docs/reference-Custom_Storage.html create mode 100644 docs/reference-Error_Codes.html create mode 100644 docs/reference-Filter.html create mode 100644 docs/reference-Plugins.html create mode 100644 docs/reference-Settings.html create mode 100644 docs/reference-Storage.html create mode 100644 docs/reference-Utility.html create mode 100644 docs/reference-ed25519.html create mode 100644 docs/reference.html create mode 100644 docs/rst.css create mode 100644 docs/session_stats_peers.png create mode 100644 docs/settings.rst create mode 100644 docs/stats_counters.rst create mode 100644 docs/storage.png create mode 100644 docs/streaming.html create mode 100644 docs/streaming.rst create mode 100644 docs/style.css create mode 100644 docs/todo.html create mode 100644 docs/troubleshooting.dot create mode 100644 docs/troubleshooting.html create mode 100644 docs/troubleshooting.png create mode 100644 docs/troubleshooting.rst create mode 100644 docs/troubleshooting_thumb.png create mode 100644 docs/tuning.html create mode 100644 docs/tuning.rst create mode 100644 docs/tutorial.html create mode 100644 docs/tutorial.rst create mode 100644 docs/udp_tracker_protocol.html create mode 100644 docs/udp_tracker_protocol.rst create mode 100644 docs/utp.html create mode 100644 docs/utp.rst create mode 100644 docs/utp_stack.diagram create mode 100644 docs/utp_stack.png create mode 100644 docs/write_disk_buffers.diagram create mode 100644 docs/write_disk_buffers.png create mode 100644 docs/ziptorrent_thumb.gif create mode 100644 ed25519/readme.md create mode 100644 ed25519/src/add_scalar.cpp create mode 100644 ed25519/src/fe.cpp create mode 100644 ed25519/src/fe.h create mode 100644 ed25519/src/fixedint.h create mode 100644 ed25519/src/ge.cpp create mode 100644 ed25519/src/ge.h create mode 100644 ed25519/src/key_exchange.cpp create mode 100644 ed25519/src/keypair.cpp create mode 100644 ed25519/src/precomp_data.h create mode 100644 ed25519/src/sc.cpp create mode 100644 ed25519/src/sc.h create mode 100644 ed25519/src/seed.cpp create mode 100644 ed25519/src/sha512.cpp create mode 100644 ed25519/src/sha512.h create mode 100644 ed25519/src/sign.cpp create mode 100644 ed25519/src/verify.cpp create mode 100644 ed25519/test.c create mode 100644 examples/CMakeLists.txt create mode 100644 examples/Jamfile create mode 100644 examples/Makefile.am create mode 100644 examples/Makefile.in create mode 100644 examples/bt-get.cpp create mode 100644 examples/bt-get2.cpp create mode 100644 examples/client_test.cpp create mode 100644 examples/cmake/FindLibtorrentRasterbar.cmake create mode 100644 examples/connection_tester.cpp create mode 100644 examples/dump_torrent.cpp create mode 100644 examples/make_torrent.cpp create mode 100644 examples/print.cpp create mode 100644 examples/print.hpp create mode 100644 examples/run_cmake.sh.in create mode 100644 examples/session_view.cpp create mode 100644 examples/session_view.hpp create mode 100644 examples/simple_client.cpp create mode 100644 examples/stats_counters.cpp create mode 100644 examples/torrent_view.cpp create mode 100644 examples/torrent_view.hpp create mode 100644 examples/upnp_test.cpp create mode 100644 include/libtorrent/ConvertUTF.h create mode 100644 include/libtorrent/Makefile.am create mode 100644 include/libtorrent/Makefile.in create mode 100644 include/libtorrent/add_torrent_params.hpp create mode 100644 include/libtorrent/address.hpp create mode 100644 include/libtorrent/alert.hpp create mode 100644 include/libtorrent/alert_manager.hpp create mode 100644 include/libtorrent/alert_types.hpp create mode 100644 include/libtorrent/alloca.hpp create mode 100644 include/libtorrent/allocator.hpp create mode 100644 include/libtorrent/announce_entry.hpp create mode 100644 include/libtorrent/assert.hpp create mode 100644 include/libtorrent/aux_/alert_manager_variadic_emplace.hpp create mode 100644 include/libtorrent/aux_/allocating_handler.hpp create mode 100644 include/libtorrent/aux_/byteswap.hpp create mode 100644 include/libtorrent/aux_/cpuid.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_/escape_string.hpp create mode 100644 include/libtorrent/aux_/file_progress.hpp create mode 100644 include/libtorrent/aux_/merkle.hpp create mode 100644 include/libtorrent/aux_/openssl.hpp create mode 100644 include/libtorrent/aux_/proxy_settings.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_/time.hpp create mode 100644 include/libtorrent/bandwidth_limit.hpp create mode 100644 include/libtorrent/bandwidth_manager.hpp create mode 100644 include/libtorrent/bandwidth_queue_entry.hpp create mode 100644 include/libtorrent/bandwidth_socket.hpp create mode 100644 include/libtorrent/bdecode.hpp create mode 100644 include/libtorrent/bencode.hpp create mode 100644 include/libtorrent/bitfield.hpp create mode 100644 include/libtorrent/block_cache.hpp create mode 100644 include/libtorrent/bloom_filter.hpp create mode 100644 include/libtorrent/broadcast_socket.hpp create mode 100644 include/libtorrent/bt_peer_connection.hpp create mode 100644 include/libtorrent/buffer.hpp create mode 100644 include/libtorrent/build_config.hpp create mode 100644 include/libtorrent/chained_buffer.hpp create mode 100644 include/libtorrent/choker.hpp create mode 100644 include/libtorrent/close_reason.hpp create mode 100644 include/libtorrent/config.hpp create mode 100644 include/libtorrent/copy_ptr.hpp create mode 100644 include/libtorrent/crc32c.hpp create mode 100644 include/libtorrent/create_torrent.hpp create mode 100644 include/libtorrent/deadline_timer.hpp create mode 100644 include/libtorrent/debug.hpp create mode 100644 include/libtorrent/disk_buffer_holder.hpp create mode 100644 include/libtorrent/disk_buffer_pool.hpp create mode 100644 include/libtorrent/disk_interface.hpp create mode 100644 include/libtorrent/disk_io_job.hpp create mode 100644 include/libtorrent/disk_io_thread.hpp create mode 100644 include/libtorrent/disk_job_pool.hpp create mode 100644 include/libtorrent/disk_observer.hpp create mode 100644 include/libtorrent/ed25519.hpp create mode 100644 include/libtorrent/entry.hpp create mode 100644 include/libtorrent/enum_net.hpp create mode 100644 include/libtorrent/error.hpp create mode 100644 include/libtorrent/error_code.hpp create mode 100644 include/libtorrent/export.hpp create mode 100644 include/libtorrent/extensions.hpp create mode 100644 include/libtorrent/extensions/lt_trackers.hpp create mode 100644 include/libtorrent/extensions/metadata_transfer.hpp create mode 100644 include/libtorrent/extensions/smart_ban.hpp create mode 100644 include/libtorrent/extensions/ut_metadata.hpp create mode 100644 include/libtorrent/extensions/ut_pex.hpp create mode 100644 include/libtorrent/file.hpp create mode 100644 include/libtorrent/file_pool.hpp create mode 100644 include/libtorrent/file_storage.hpp create mode 100644 include/libtorrent/fingerprint.hpp create mode 100644 include/libtorrent/gzip.hpp create mode 100644 include/libtorrent/hasher.hpp create mode 100644 include/libtorrent/heterogeneous_queue.hpp create mode 100644 include/libtorrent/hex.hpp create mode 100644 include/libtorrent/http_connection.hpp create mode 100644 include/libtorrent/http_parser.hpp create mode 100644 include/libtorrent/http_seed_connection.hpp create mode 100644 include/libtorrent/http_stream.hpp create mode 100644 include/libtorrent/http_tracker_connection.hpp create mode 100644 include/libtorrent/i2p_stream.hpp create mode 100644 include/libtorrent/identify_client.hpp create mode 100644 include/libtorrent/instantiate_connection.hpp create mode 100644 include/libtorrent/invariant_check.hpp create mode 100644 include/libtorrent/io.hpp create mode 100644 include/libtorrent/io_service.hpp create mode 100644 include/libtorrent/io_service_fwd.hpp create mode 100644 include/libtorrent/ip_filter.hpp create mode 100644 include/libtorrent/ip_voter.hpp create mode 100644 include/libtorrent/kademlia/dht_observer.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/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/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/traversal_algorithm.hpp create mode 100644 include/libtorrent/lazy_entry.hpp create mode 100644 include/libtorrent/link.hpp create mode 100644 include/libtorrent/linked_list.hpp create mode 100644 include/libtorrent/lsd.hpp create mode 100644 include/libtorrent/magnet_uri.hpp create mode 100644 include/libtorrent/max.hpp create mode 100644 include/libtorrent/natpmp.hpp create mode 100644 include/libtorrent/network_thread_pool.hpp create mode 100644 include/libtorrent/operations.hpp create mode 100644 include/libtorrent/packet_buffer.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/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/proxy_base.hpp create mode 100644 include/libtorrent/puff.hpp create mode 100644 include/libtorrent/random.hpp create mode 100644 include/libtorrent/receive_buffer.hpp create mode 100644 include/libtorrent/request_blocks.hpp create mode 100644 include/libtorrent/resolve_links.hpp create mode 100644 include/libtorrent/resolver.hpp create mode 100644 include/libtorrent/resolver_interface.hpp create mode 100644 include/libtorrent/rss.hpp create mode 100644 include/libtorrent/session.hpp create mode 100644 include/libtorrent/session_handle.hpp create mode 100644 include/libtorrent/session_settings.hpp create mode 100644 include/libtorrent/session_stats.hpp create mode 100644 include/libtorrent/session_status.hpp create mode 100644 include/libtorrent/settings_pack.hpp create mode 100644 include/libtorrent/sha1.hpp create mode 100644 include/libtorrent/sha1_hash.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/socket_type_fwd.hpp create mode 100644 include/libtorrent/socks5_stream.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/tailqueue.hpp create mode 100644 include/libtorrent/thread.hpp create mode 100644 include/libtorrent/thread_pool.hpp create mode 100644 include/libtorrent/time.hpp create mode 100644 include/libtorrent/timestamp_history.hpp create mode 100644 include/libtorrent/tommath.h create mode 100644 include/libtorrent/tommath_class.h create mode 100755 include/libtorrent/tommath_private.h create mode 100644 include/libtorrent/tommath_superclass.h create mode 100644 include/libtorrent/torrent.hpp create mode 100644 include/libtorrent/torrent_handle.hpp create mode 100644 include/libtorrent/torrent_info.hpp create mode 100644 include/libtorrent/torrent_peer.hpp create mode 100644 include/libtorrent/torrent_peer_allocator.hpp create mode 100644 include/libtorrent/torrent_status.hpp create mode 100644 include/libtorrent/tracker_manager.hpp create mode 100644 include/libtorrent/udp_socket.hpp create mode 100644 include/libtorrent/udp_tracker_connection.hpp create mode 100644 include/libtorrent/uncork_interface.hpp create mode 100644 include/libtorrent/union_endpoint.hpp create mode 100644 include/libtorrent/upnp.hpp create mode 100644 include/libtorrent/utf8.hpp create mode 100644 include/libtorrent/utp_socket_manager.hpp create mode 100644 include/libtorrent/utp_stream.hpp create mode 100644 include/libtorrent/vector_utils.hpp create mode 100644 include/libtorrent/version.hpp create mode 100644 include/libtorrent/web_connection_base.hpp create mode 100644 include/libtorrent/web_peer_connection.hpp create mode 100644 include/libtorrent/xml_parse.hpp create mode 100644 libtorrent-rasterbar-cmake.pc.in create mode 100644 libtorrent-rasterbar.pc.in create mode 100644 m4/._libtool.m4 create mode 100644 m4/._ltoptions.m4 create mode 100644 m4/._ltsugar.m4 create mode 100644 m4/._lt~obsolete.m4 create mode 100644 m4/ax_boost_base.m4 create mode 100644 m4/ax_boost_chrono.m4 create mode 100644 m4/ax_boost_python.m4 create mode 100644 m4/ax_boost_random.m4 create mode 100644 m4/ax_boost_system.m4 create mode 100644 m4/ax_check_openssl.m4 create mode 100644 m4/ax_pthread.m4 create mode 100644 m4/ax_python_devel.m4 create mode 100644 m4/gettext-lib.m4 create mode 100644 m4/iconv.m4 create mode 100644 m4/libtool.m4 create mode 100644 m4/ltoptions.m4 create mode 100644 m4/ltsugar.m4 create mode 100644 m4/ltversion.m4 create mode 100644 m4/lt~obsolete.m4 create mode 100644 m4/pkgconfig.m4 create mode 100755 setup.py create mode 100644 src/ConvertUTF.cpp create mode 100644 src/Makefile.am create mode 100644 src/Makefile.in create mode 100644 src/alert.cpp create mode 100644 src/alert_manager.cpp create mode 100644 src/allocator.cpp create mode 100644 src/announce_entry.cpp create mode 100644 src/assert.cpp create mode 100644 src/bandwidth_limit.cpp create mode 100644 src/bandwidth_manager.cpp create mode 100644 src/bandwidth_queue_entry.cpp create mode 100644 src/bdecode.cpp create mode 100644 src/bitfield.cpp create mode 100644 src/block_cache.cpp create mode 100644 src/bloom_filter.cpp create mode 100644 src/broadcast_socket.cpp create mode 100644 src/bt_peer_connection.cpp create mode 100644 src/chained_buffer.cpp create mode 100644 src/choker.cpp create mode 100644 src/close_reason.cpp create mode 100644 src/cpuid.cpp create mode 100644 src/crc32c.cpp create mode 100644 src/create_torrent.cpp create mode 100644 src/disk_buffer_holder.cpp create mode 100644 src/disk_buffer_pool.cpp create mode 100644 src/disk_io_job.cpp create mode 100644 src/disk_io_thread.cpp create mode 100644 src/disk_job_pool.cpp create mode 100644 src/entry.cpp create mode 100644 src/enum_net.cpp create mode 100644 src/error_code.cpp create mode 100644 src/escape_string.cpp create mode 100644 src/file.cpp create mode 100644 src/file_pool.cpp create mode 100644 src/file_progress.cpp create mode 100644 src/file_storage.cpp create mode 100644 src/gzip.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_stream.cpp create mode 100644 src/http_tracker_connection.cpp create mode 100644 src/i2p_stream.cpp create mode 100644 src/identify_client.cpp create mode 100644 src/instantiate_connection.cpp create mode 100644 src/ip_filter.cpp create mode 100644 src/ip_voter.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/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/traversal_algorithm.cpp create mode 100644 src/lazy_bdecode.cpp create mode 100644 src/lsd.cpp create mode 100644 src/lt_trackers.cpp create mode 100644 src/magnet_uri.cpp create mode 100644 src/merkle.cpp create mode 100644 src/metadata_transfer.cpp create mode 100644 src/mpi.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/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_list.cpp create mode 100644 src/performance_counters.cpp create mode 100644 src/piece_picker.cpp create mode 100644 src/platform_util.cpp create mode 100644 src/proxy_base.cpp create mode 100644 src/proxy_settings.cpp create mode 100644 src/puff.cpp create mode 100644 src/random.cpp create mode 100644 src/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/rss.cpp create mode 100644 src/session.cpp create mode 100644 src/session_call.cpp create mode 100644 src/session_handle.cpp create mode 100644 src/session_impl.cpp create mode 100644 src/session_settings.cpp create mode 100644 src/session_stats.cpp create mode 100644 src/settings_pack.cpp create mode 100644 src/sha1.cpp create mode 100644 src/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/stat.cpp create mode 100644 src/stat_cache.cpp create mode 100644 src/storage.cpp create mode 100644 src/string_util.cpp create mode 100644 src/thread.cpp create mode 100644 src/time.cpp create mode 100644 src/timestamp_history.cpp create mode 100644 src/torrent.cpp create mode 100644 src/torrent_handle.cpp create mode 100644 src/torrent_info.cpp create mode 100644 src/torrent_peer.cpp create mode 100644 src/torrent_peer_allocator.cpp create mode 100644 src/torrent_status.cpp create mode 100644 src/tracker_manager.cpp create mode 100644 src/udp_socket.cpp create mode 100644 src/udp_tracker_connection.cpp create mode 100644 src/upnp.cpp create mode 100644 src/ut_metadata.cpp create mode 100644 src/ut_pex.cpp create mode 100644 src/utf8.cpp create mode 100644 src/utp_socket_manager.cpp create mode 100644 src/utp_stream.cpp create mode 100644 src/version.cpp create mode 100644 src/web_connection_base.cpp create mode 100644 src/web_peer_connection.cpp create mode 100644 src/xml_parse.cpp create mode 100644 test/Jamfile create mode 100644 test/Makefile.am create mode 100644 test/Makefile.in create mode 100644 test/bdecode_benchmark.cpp create mode 100644 test/bittorrent_peer.cpp create mode 100644 test/bittorrent_peer.hpp create mode 100644 test/corrupt.gz create mode 100644 test/dht_server.cpp create mode 100644 test/dht_server.hpp create mode 100644 test/enum_if.cpp create mode 100755 test/http.py 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/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/swarm_suite.cpp create mode 100644 test/swarm_suite.hpp create mode 100644 test/test.cpp create mode 100644 test/test.hpp create mode 100644 test/test_alert_manager.cpp create mode 100644 test/test_auto_unchoke.cpp create mode 100644 test/test_bandwidth_limiter.cpp create mode 100644 test/test_bdecode.cpp create mode 100644 test/test_bencoding.cpp create mode 100644 test/test_bitfield.cpp create mode 100644 test/test_block_cache.cpp create mode 100644 test/test_bloom_filter.cpp create mode 100644 test/test_buffer.cpp create mode 100644 test/test_checking.cpp create mode 100644 test/test_crc32.cpp create mode 100644 test/test_create_torrent.cpp create mode 100644 test/test_dht.cpp create mode 100644 test/test_dht_storage.cpp create mode 100644 test/test_direct_dht.cpp create mode 100644 test/test_dos_blocker.cpp create mode 100644 test/test_enum_net.cpp create mode 100644 test/test_fast_extension.cpp create mode 100644 test/test_fence.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_gzip.cpp create mode 100644 test/test_hasher.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_ip_filter.cpp create mode 100644 test/test_ip_voter.cpp create mode 100644 test/test_linked_list.cpp create mode 100644 test/test_lsd.cpp create mode 100644 test/test_magnet.cpp create mode 100644 test/test_merkle.cpp create mode 100644 test/test_packet_buffer.cpp create mode 100644 test/test_part_file.cpp create mode 100644 test/test_pe_crypto.cpp create mode 100644 test/test_peer_classes.cpp create mode 100644 test/test_peer_list.cpp create mode 100644 test/test_peer_priority.cpp create mode 100644 test/test_pex.cpp create mode 100644 test/test_piece_picker.cpp create mode 100644 test/test_primitives.cpp create mode 100644 test/test_priority.cpp create mode 100644 test/test_privacy.cpp create mode 100644 test/test_random.cpp create mode 100644 test/test_read_piece.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_resolve_links.cpp create mode 100644 test/test_resume.cpp create mode 100644 test/test_session.cpp create mode 100644 test/test_settings_pack.cpp create mode 100644 test/test_sha1_hash.cpp create mode 100644 test/test_sliding_average.cpp create mode 100644 test/test_socket_io.cpp create mode 100644 test/test_ssl.cpp create mode 100644 test/test_stat_cache.cpp create mode 100644 test/test_storage.cpp create mode 100644 test/test_string.cpp create mode 100644 test/test_tailqueue.cpp create mode 100644 test/test_threads.cpp create mode 100644 test/test_time.cpp create mode 100644 test/test_time_critical.cpp create mode 100644 test/test_timestamp_history.cpp create mode 100644 test/test_torrent.cpp create mode 100644 test/test_torrent_info.cpp create mode 100644 test/test_torrents/backslash_path.torrent create mode 100644 test/test_torrents/base.torrent create mode 100644 test/test_torrents/creation_date.torrent create mode 100644 test/test_torrents/duplicate_files.torrent create mode 100644 test/test_torrents/duplicate_web_seeds.torrent create mode 100644 test/test_torrents/empty_httpseed.torrent create mode 100644 test/test_torrents/empty_path.torrent create mode 100644 test/test_torrents/empty_path_multi.torrent create mode 100644 test/test_torrents/hidden_parent_path.torrent create mode 100644 test/test_torrents/httpseed.torrent create mode 100644 test/test_torrents/invalid_file_size.torrent create mode 100644 test/test_torrents/invalid_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_root_hash.torrent create mode 100644 test/test_torrents/invalid_root_hash2.torrent create mode 100644 test/test_torrents/invalid_symlink.torrent create mode 100644 test/test_torrents/long_name.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_name.torrent create mode 100644 test/test_torrents/pad_file.torrent create mode 100644 test/test_torrents/pad_file_no_path.torrent create mode 100644 test/test_torrents/parent_path.torrent create mode 100644 test/test_torrents/root_hash.torrent create mode 100644 test/test_torrents/sample.torrent create mode 100644 test/test_torrents/single_multi_file.torrent create mode 100644 test/test_torrents/slash_path.torrent create mode 100644 test/test_torrents/slash_path2.torrent create mode 100644 test/test_torrents/slash_path3.torrent create mode 100644 test/test_torrents/string.torrent create mode 100644 test/test_torrents/symlink1.torrent create mode 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_space.torrent create mode 100644 test/test_torrents/url_seed_multi_space_nolist.torrent create mode 100644 test/test_torrents/whitespace_url.torrent create mode 100644 test/test_tracker.cpp create mode 100644 test/test_transfer.cpp create mode 100644 test/test_upnp.cpp create mode 100644 test/test_url_seed.cpp create mode 100644 test/test_utf8.cpp create mode 100644 test/test_utils.cpp create mode 100644 test/test_utils.hpp create mode 100644 test/test_utp.cpp create mode 100644 test/test_web_seed.cpp create mode 100644 test/test_web_seed_ban.cpp create mode 100644 test/test_web_seed_chunked.cpp create mode 100644 test/test_web_seed_http.cpp create mode 100644 test/test_web_seed_http_pw.cpp create mode 100644 test/test_web_seed_redirect.cpp create mode 100644 test/test_web_seed_socks4.cpp create mode 100644 test/test_web_seed_socks5.cpp create mode 100644 test/test_web_seed_socks5_no_peers.cpp create mode 100644 test/test_web_seed_socks5_pw.cpp create mode 100644 test/test_xml.cpp create mode 100644 test/udp_tracker.cpp create mode 100644 test/udp_tracker.hpp create mode 100644 test/utf8_test.txt create mode 100644 test/web_seed_suite.cpp create mode 100644 test/web_seed_suite.hpp create mode 100755 test/web_server.py create mode 100644 test/zeroes.gz create mode 100644 tools/Jamfile create mode 100644 tools/Makefile.am create mode 100644 tools/Makefile.in create mode 100644 tools/fuzz_torrent.cpp create mode 100755 tools/parse_bandwidth_log.py create mode 100755 tools/parse_buffer_log.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_disk_buffer_log.py create mode 100755 tools/parse_disk_log.py create mode 100755 tools/parse_memory_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 diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..a023874 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,26 @@ +Written by Arvid Norberg. Copyright (c) 2003-2016 + +Contributions by: +Andrei Kurushin +Steven Siloti +Thomas Fischer +Massaroddel +Tianhao Qiu. +Shyam +Magnus Jonsson +Daniel Wallin +Cory Nelson +Stas Khirman +Ryan Norton +Andrew Resch + +Building and maintainance of the autotools scripts: +Michael Wojciechowski +Peter Koeleman + +Thanks to Reimond Retz for bugfixes, suggestions and testing + +Thanks to University of UmeŒ for providing development and test hardware. + +Project is hosted by sourceforge. + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..37c2ad2 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,389 @@ +cmake_minimum_required(VERSION 2.6) +project(libtorrent) +set (SOVERSION "8") +set (VERSION "1.1.1") + +set(sources + web_connection_base + alert + alert_manager + allocator + announce_entry + assert + bandwidth_limit + bandwidth_manager + bandwidth_queue_entry + bdecode + bitfield + block_cache + bloom_filter + chained_buffer + choker + close_reason + cpuid + crc32c + create_torrent + disk_buffer_holder + entry + error_code + file_storage + file_progress + lazy_bdecode + escape_string + string_util + file + gzip + hasher + hex + http_connection + http_stream + http_parser + i2p_stream + identify_client + ip_filter + ip_voter + performance_counters + peer_class + peer_class_set + peer_connection + bt_peer_connection + web_peer_connection + http_seed_connection + peer_connection_handle + instantiate_connection + merkle + natpmp + part_file + packet_buffer + piece_picker + platform_util + proxy_base + peer_list + puff + random + receive_buffer + request_blocks + resolve_links + resolver + rss + session + session_call + session_handle + session_impl + session_settings + proxy_settings + session_stats + settings_pack + socket_io + socket_type + socks5_stream + stat + stat_cache + storage + time + timestamp_history + torrent + torrent_handle + torrent_info + torrent_peer + torrent_peer_allocator + torrent_status + tracker_manager + http_tracker_connection + utf8 + udp_tracker_connection + udp_socket + upnp + utp_socket_manager + utp_stream + file_pool + lsd + disk_io_job + disk_job_pool + disk_buffer_pool + disk_io_thread + enum_net + broadcast_socket + magnet_uri + parse_url + ConvertUTF + thread + xml_parse + version + +# -- extensions -- + metadata_transfer + ut_pex + ut_metadata + smart_ban + lt_trackers +) + +# -- kademlia -- +set(kademlia_sources + dht_storage + dos_blocker + dht_tracker + msg + node + node_entry + refresh + rpc_manager + find_data + put_data + node_id + routing_table + traversal_algorithm + item + get_peers + get_item +) + +# -- ed25519 -- +set(ed25519_sources + add_scalar + fe + ge + key_exchange + keypair + sc + seed + sha512 + sign + verify +) + +set(includes include ed25519/src) + +option(shared "build libtorrent as a shared library" ON) +option(static_runtime "build libtorrent with static runtime" OFF) +option(tcmalloc "link against google performance tools tcmalloc" OFF) +option(pool-allocators "Uses a pool allocator for disk and piece buffers" ON) +option(encryption "link against openssl and enable encryption" ON) +option(dht "enable support for Mainline DHT" ON) +option(resolve-countries "enable support for resolving countries from peer IPs" ON) +option(unicode "enable unicode support" ON) +option(deprecated-functions "enable deprecated functions for backwards compatibility" ON) +option(exceptions "build with exception support" ON) +option(logging "build with logging" OFF) +option(build_tests "build tests" OFF) + +set(CMAKE_CONFIGURATION_TYPES Debug Release RelWithDebInfo) + +if (NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release FORCE) +endif() + +# add_definitions() doesn't seem to let you say wich build type to apply it to +set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -DTORRENT_DEBUG") +if(UNIX) + set(CMAKE_C_FLAGS_RELWITHDEBINFO "-Os -g") + set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}") +endif() + +if (build_tests) + # this will make some internal functions available in the + # DLL interface, for the tests to access + add_definitions(-DTORRENT_EXPORT_EXTRA) +endif (build_tests) + +find_package(Threads REQUIRED) + +include_directories(${includes}) + +add_definitions(-DTORRENT_BUILDING_LIBRARY) + +if (encryption) + list(APPEND sources mpi pe_crypto) + if(NOT DEFINED OPENSSL_INCLUDE_DIR OR NOT DEFINED OPENSSL_LIBRARIES) + FIND_PACKAGE(OpenSSL REQUIRED) + endif() + add_definitions(-DTORRENT_USE_OPENSSL) + include_directories(${OPENSSL_INCLUDE_DIR}) +else() + add_definitions(-DTORRENT_DISABLE_ENCRYPTION) + list(APPEND sources sha1) +endif (encryption) + +if (NOT logging) + add_definitions(-DTORRENT_DISABLE_LOGGING) +endif() + +foreach(s ${sources}) + list(APPEND sources2 src/${s}) +endforeach(s) + +if (dht) + foreach(s ${kademlia_sources}) + list(APPEND sources2 src/kademlia/${s}) + endforeach(s) + foreach(s ${ed25519_sources}) + list(APPEND sources2 ed25519/src/${s}) + endforeach(s) +else() + add_definitions(-DTORRENT_DISABLE_DHT) +endif() + +if (shared) + add_definitions(-DTORRENT_BUILDING_SHARED) + add_library(torrent-rasterbar SHARED ${sources2}) + if(NOT MSVC) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden -fvisibility-inlines-hidden") + endif() +else() + if(static_runtime) + # fix /MT flag: + set(CompilerFlags + CMAKE_CXX_FLAGS + CMAKE_CXX_FLAGS_DEBUG + CMAKE_CXX_FLAGS_RELWITHDEBINFO + CMAKE_CXX_FLAGS_RELEASE + CMAKE_C_FLAGS + CMAKE_C_FLAGS_DEBUG + CMAKE_CXX_FLAGS_RELWITHDEBINFO + CMAKE_C_FLAGS_RELEASE + ) + foreach(CompilerFlag ${CompilerFlags}) + string(REPLACE "/MD" "/MT" ${CompilerFlag} "${${CompilerFlag}}") + endforeach() + set(Boost_USE_MULTITHREADED ON) + + set(Boost_USE_STATIC_RUNTIME ON) + endif() + set(Boost_USE_STATIC_LIBS ON) + add_library(torrent-rasterbar STATIC ${sources2}) +endif() + +# Boost +if(NOT DEFINED Boost_INCLUDE_DIR OR NOT DEFINED Boost_LIBRARIES) + FIND_PACKAGE(Boost REQUIRED COMPONENTS system chrono random) +endif() +include_directories(${Boost_INCLUDE_DIRS}) +target_link_libraries(torrent-rasterbar ${Boost_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) + +# this works around a bug in asio in boost-1.39 +#add_definitions(-DBOOST_ASIO_HASH_MAP_BUCKETS=1021 -D__USE_W32_SOCKETS -DWIN32_LEAN_AND_MEAN ) + +if (WIN32) + target_link_libraries(torrent-rasterbar wsock32 ws2_32 Iphlpapi) + add_definitions(-D_WIN32_WINNT=0x0600) + # prevent winsock1 to be included + add_definitions(-DWIN32_LEAN_AND_MEAN) + if (MSVC) + add_definitions(-DBOOST_ALL_NO_LIB) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") # multicore compilation + endif() +endif() + +if (encryption) + target_link_libraries(torrent-rasterbar ${OPENSSL_LIBRARIES}) +endif() + +if (NOT pool-allocators) + add_definitions(-DTORRENT_DISABLE_POOL_ALLOCATOR) +endif() + +if (NOT resolve-countries) + add_definitions(-DTORRENT_DISABLE_RESOLVE_COUNTRIES) +endif() + +if (unicode) + add_definitions(-DUNICODE -D_UNICODE) +endif() + +if (NOT deprecated-functions) + add_definitions(-DTORRENT_NO_DEPRECATE) +endif() + +if (exceptions) + if (MSVC) + add_definitions(/EHsc) + else (MSVC) + add_definitions(-fexceptions) + endif (MSVC) +else() + if (MSVC) + add_definitions(-D_HAS_EXCEPTIONS=0) + else (MSVC) + add_definitions(-fno-exceptions) + endif (MSVC) +endif() + +if (MSVC) +# disable bogus deprecation warnings on msvc8 + add_definitions(-D_SCL_SECURE_NO_DEPRECATE -D_CRT_SECURE_NO_DEPRECATE) +# these compiler settings just makes the compiler standard conforming + add_definitions(/Zc:wchar_t /Zc:forScope) +# for multi-core compilation + add_definitions(/MP) + +#$(SolutionDir)msvc,release:/OPT:ICF=5 +#$(SolutionDir)msvc,release:/OPT:REF +elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + add_definitions(-Wno-c++11-extensions) + add_definitions(-fcolor-diagnostics) +endif() + +add_definitions(-D_FILE_OFFSET_BITS=64) +add_definitions(-DBOOST_EXCEPTION_DISABLE) +add_definitions(-DBOOST_ASIO_ENABLE_CANCELIO) + +if (tcmalloc) + target_link_libraries(torrent-rasterbar tcmalloc) +endif() + +set_target_properties(torrent-rasterbar PROPERTIES + SOVERSION ${SOVERSION}) + +get_property (COMPILETIME_OPTIONS_LIST + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIRECTORY} + PROPERTY COMPILE_DEFINITIONS + ) +foreach (s ${COMPILETIME_OPTIONS_LIST}) + set (COMPILETIME_OPTIONS "${COMPILETIME_OPTIONS} -D${s}") +endforeach (s) + +configure_file(libtorrent-rasterbar-cmake.pc.in libtorrent-rasterbar.pc) + +string (COMPARE EQUAL "${CMAKE_SIZEOF_VOID_P}" "8" IS64BITS) + +if (IS64BITS AND RESPECTLIB64) + set (LIBDIR "lib64") +else() + set (LIBDIR "lib") +endif() + +install(TARGETS torrent-rasterbar DESTINATION ${LIBDIR}) +install(DIRECTORY include/libtorrent + DESTINATION include + PATTERN ".svn" EXCLUDE) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libtorrent-rasterbar.pc DESTINATION ${LIBDIR}/pkgconfig) + +# === set up examples directory as an independent project === +file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/examples) +configure_file(examples/run_cmake.sh.in examples/run_cmake.sh) +# to build the examples, run examples/run_cmake.sh after building libtorrent + +# === build tests === +if(build_tests) + file(GLOB tests RELATIVE "${PROJECT_SOURCE_DIR}" "test/test_*.cpp") + list(REMOVE_ITEM tests "test/test_natpmp.cpp") # doesn't build at time of writing + list(REMOVE_ITEM tests "test/test_utils.cpp") # helper file, not a test + + add_library(test_common OBJECT test/main.cpp test/test.cpp + test/setup_transfer.cpp test/dht_server.cpp test/udp_tracker.cpp + test/peer_server.cpp test/web_seed_suite.cpp test/swarm_suite.cpp + test/test_utils.cpp) + enable_testing() + + foreach(s ${tests}) + get_filename_component (sn ${s} NAME_WE) + add_executable(${sn} ${s} $) + target_link_libraries(${sn} torrent-rasterbar) + add_test(${sn} ${s}) + endforeach(s) + + add_executable(bdecode_benchmark test/bdecode_benchmark.cpp) + target_link_libraries(bdecode_benchmark torrent-rasterbar) +endif() diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..d20e569 --- /dev/null +++ b/COPYING @@ -0,0 +1,28 @@ +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of 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..e4c6f2f --- /dev/null +++ b/ChangeLog @@ -0,0 +1,1491 @@ +1.1.1 release + + * update puff.c for gzip inflation + * 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 + * 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 division by zero in super-seeding logic + +1.0.9 release + + * fix issue in checking outgoing interfaces (when that option is enabled) + * python binding fix for boost-1.60.0 + * optimize enumeration of network interfaces on windows + * improve reliability of binding listen sockets + * support SNI in https web seeds and trackers + * fix unhandled exception in DHT when receiving a DHT packet over IPv6 + +1.0.8 release + + * fix bug where web seeds were not used for torrents added by URL + * fix support for symlinks on windows + * fix long filename issue (on unixes) + * fixed performance bug in DHT torrent eviction + * fixed win64 build (GetFileAttributesEx) + * fixed bug when deleting files for magnet links before they had metadata + +1.0.7 release + + * fix bug where loading settings via load_state() would not trigger all + appropriate actions + * fix bug where 32 bit builds could use more disk cache than the virtual + address space (when set to automatic) + * fix support for torrents with > 500'000 pieces + * fix ip filter bug when banning peers + * fix IPv6 IP address resolution in URLs + * introduce run-time check for torrent info-sections beeing too large + * fix web seed bug when using proxy and proxy-peer-connections=false + * fix bug in magnet link parser + * introduce add_torrent_params flags to merge web seeds with resume data + (similar to trackers) + * fix bug where dont_count_slow_torrents could not be disabled + * fix fallocate hack on linux (fixes corruption on some architectures) + * fix auto-manage bug with announce to tracker/lsd/dht limits + * improve DHT routing table to not create an unbalanced tree + * fix bug in uTP that would cause any connection taking more than one second + to connect be timed out (introduced in the vulnerability path) + * fixed falling back to sending UDP packets direct when socks proxy fails + * fixed total_wanted bug (when setting file priorities in add_torrent_params) + * fix python3 compatibility with sha1_hash + +1.0.6 release + + * fixed uTP vulnerability + * make utf8 conversions more lenient + * fix loading of piece priorities from resume data + * improved seed-mode handling (seed-mode will now automatically be left when + performing operations implying it's not a seed) + * fixed issue with file priorities and override resume data + * fix request queue size performance issue + * slightly improve UDP tracker performance + * fix http scrape + * add missing port mapping functions to python binding + * fix bound-checking issue in bdecoder + * expose missing dht_settings fields to python + * add function to query the DHT settings + * fix bug in 'dont_count_slow_torrents' feature, which would start too many + torrents + +1.0.5 release + + * improve ip_voter to avoid flapping + * fixed bug when max_peerlist_size was set to 0 + * fix issues with missing exported symbols when building dll + * fix division by zero bug in edge case while connecting peers + +1.0.4 release + + * fix bug in python binding for file_progress on torrents with no metadata + * fix assert when removing a connected web seed + * fix bug in tracker timeout logic + * switch UPnP post back to HTTP 1.1 + * support conditional DHT get + * OpenSSL build fixes + * fix DHT scrape bug + +1.0.3 release + + * python binding build fix for boost-1.57.0 + * add --enable-export-all option to configure script, to export all symbols + from libtorrent + * fix if_nametoindex build error on windows + * handle overlong utf-8 sequences + * fix link order bug in makefile for python binding + * fix bug in interest calculation, causing premature disconnects + * tweak flag_override_resume_data semantics to make more sense (breaks + backwards compatibility of edge-cases) + * improve DHT bootstrapping and periodic refresh + * improve DHT maintanence performance (by pinging instead of full lookups) + * fix bug in DHT routing table node-id prefix optimization + * fix incorrect behavior of flag_use_resume_save_path + * fix protocol race-condition in super seeding mode + * support read-only DHT nodes + * remove unused partial hash DHT lookups + * remove potentially privacy leaking extension (non-anonymous mode) + * peer-id connection ordering fix in anonymous mode + * mingw fixes + +1.0.2 release + + * added missing force_proxy to python binding + * anonymous_mode defaults to false + * make DHT DOS detection more forgiving to bursts + * support IPv6 multicast in local service discovery + * simplify CAS function in DHT put + * support IPv6 traffic class (via the TOS setting) + * made uTP re-enter slow-start after time-out + * fixed uTP upload performance issue + * fix missing support for DHT put salt + +1.0.1 release + + * fix alignment issue in bitfield + * improved error handling of gzip + * fixed crash when web seeds redirect + * fix compiler warnings + +1.0 release + + * fix bugs in convert_to/from_native() on windows + * fix support for web servers not supporting keepalive + * support storing save_path in resume data + * don't use full allocation on network drives (on windows) + * added clear_piece_deadlines() to remove all piece deadlines + * improve queuing logic of inactive torrents (dont_count_slow_torrents) + * expose optimistic unchoke logic to plugins + * fix issue with large UDP packets on windows + * remove set_ratio() feature + * improve piece_deadline/streaming + * honor pieces with priority 7 in sequential download mode + * simplified building python bindings + * make ignore_non_routers more forgiving in the case there are no UPnP + devices at a known router. Should improve UPnP compatibility. + * include reason in peer_blocked_alert + * support magnet links wrapped in .torrent files + * rate limiter optimization + * rate limiter overflow fix (for very high limits) + * non-auto-managed torrents no longer count against the torrent limits + * handle DHT error responses correctly + * allow force_announce to only affect a single tracker + * add moving_storage field to torrent_status + * expose UPnP and NAT-PMP mapping in session object + * DHT refactoring and support for storing arbitrary data with put and get + * support building on android + * improved support for web seeds that don't support keep-alive + * improve DHT routing table to return better nodes (lower RTT and closer + to target) + * don't use pointers to resume_data and file_priorities in + add_torrent_params + * allow moving files to absolute paths, out of the download directory + * make move_storage more generic to allow both overwriting files as well + as taking existing ones + * fix choking issue at high upload rates + * optimized rate limiter + * make disk cache pool allocator configurable + * fix library ABI to not depend on logging being enabled + * use hex encoding instead of base32 in create_magnet_uri + * include name, save_path and torrent_file in torrent_status, for + improved performance + * separate anonymous mode and force-proxy mode, and tighten it up a bit + * add per-tracker scrape information to announce_entry + * report errors in read_piece_alert + * DHT memory optimization + * improve DHT lookup speed + * improve support for windows XP and earlier + * introduce global connection priority for improved swarm performance + * make files deleted alert non-discardable + * make built-in sha functions not conflict with libcrypto + * improve web seed hash failure case + * improve DHT lookup times + * uTP path MTU discovery improvements + * optimized the torrent creator optimizer to scale significantly better + with more files + * fix uTP edge case where udp socket buffer fills up + * fix nagle implementation in uTP + + * fix bug in error handling in protocol encryption + +0.16.18 release + + * fix uninitialized values in DHT DOS mitigation + * fix error handling in file::phys_offset + * fix bug in HTTP scrape response parsing + * enable TCP keepalive for socks5 connection for UDP associate + * fix python3 support + * fix bug in lt_donthave extension + * expose i2p_alert to python. cleaning up of i2p connection code + * fixed overflow and download performance issue when downloading at high rates + * fixed bug in add_torrent_alert::message for magnet links + * disable optimistic disconnects when connection limit is low + * improved error handling of session::listen_on + * suppress initial 'completed' announce to trackers added with replace_trackers + after becoming a seed + * SOCKS4 fix for trying to connect over IPv6 + * fix saving resume data when removing all trackers + * fix bug in udp_socket when changing socks5 proxy quickly + +0.16.17 release + + * don't fall back on wildcard port in UPnP + * fix local service discovery for magnet links + * fix bitfield issue in file_storage + * added work-around for MingW issue in file I/O + * fixed sparse file detection on windows + * fixed bug in gunzip + * fix to use proxy settings when adding .torrent file from URL + * fix resume file issue related to daylight savings time on windows + * improve error checking in lazy_bdecode + +0.16.16 release + + * add missing add_files overload to the python bindings + * improve error handling in http gunzip + * fix debug logging for banning web seeds + * improve support for de-selected files in full allocation mode + * fix dht_bootstrap_alert being posted + * SetFileValidData fix on windows (prevents zero-fill) + * fix minor lock_files issue on unix + +0.16.15 release + + * fix mingw time_t 64 bit issue + * fix use of SetFileValidData on windows + * fix crash when using full allocation storage mode + * improve error_code and error_category support in python bindings + * fix python binding for external_ip_alert + +0.16.14 release + + * make lt_tex more robust against bugs and malicious behavior + * HTTP chunked encoding fix + * expose file_granularity flag to python bindings + * fix DHT memory error + * change semantics of storage allocation to allocate on first write rather + than on startup (behaves better with changing file priorities) + * fix resend logic in response to uTP SACK messages + * only act on uTP RST packets with correct ack_nr + * make uTP errors log in normal log mode (not require verbose) + * deduplicate web seed entries from torrent files + * improve error reporting from lazy_decode() + +0.16.13 release + + * fix auto-manage issue when pausing session + * fix bug in non-sparse mode on windows, causing incorrect file errors to + be generated + * fix set_name() on file_storage actually affecting save paths + * fix large file support issue on mingw + * add some error handling to set_piece_hashes() + * fix completed-on timestamp to not be clobbered on each startup + * fix deadlock caused by some UDP tracker failures + * fix potential integer overflow issue in timers on windows + * minor fix to peer_proportional mixed_mode algorithm (TCP limit could go + too low) + * graceful pause fix + * i2p fixes + * fix issue when loading certain malformed .torrent files + * pass along host header with http proxy requests and possible + http_connection shutdown hang + +0.16.12 release + + * fix building with C++11 + * fix IPv6 support in UDP socket (uTP) + * fix mingw build issues + * increase max allowed outstanding piece requests from peers + * uTP performance improvement. only fast retransmit one packet at a time + * improve error message for 'file too short' + * fix piece-picker stat bug when only selecting some files for download + * fix bug in async_add_torrent when settings file_priorities + * fix boost-1.42 support for python bindings + * fix memory allocation issue (virtual addres space waste) on windows + +0.16.11 release + + * fix web seed URL double escape issue + * fix string encoding issue in alert messages + * fix SSL authentication issue + * deprecate std::wstring overloads. long live utf-8 + * improve time-critical pieces feature (streaming) + * introduce bandwidth exhaustion attack-mitigation in allowed-fast pieces + * python binding fix issue where torrent_info objects where destructing when + their torrents were deleted + * added missing field to scrape_failed_alert in python bindings + * GCC 4.8 fix + * fix proxy failure semantics with regards to anonymous mode + * fix round-robin seed-unchoke algorithm + * add bootstrap.sh to generage configure script and run configure + * fix bug in SOCK5 UDP support + * fix issue where torrents added by URL would not be started immediately + +0.16.10 release + + * fix encryption level handle invalid values + * add a number of missing functions to the python binding + * fix typo in Jamfile for building shared libraries + * prevent tracker exchange for magnet links before metadata is received + * fix crash in make_magnet_uri when generating links longer than 1024 + characters + * fix hanging issue when closing files on windows (completing a download) + * fix piece picking edge case that could cause torrents to get stuck at + hash failure + * try unencrypted connections first, and fall back to encryption if it + fails (performance improvement) + * add missing functions to python binding (flush_cache(), remap_files() + and orig_files()) + * improve handling of filenames that are invalid on windows + * support 'implied_port' in DHT announce_peer + * don't use pool allocator for disk blocks (cache may now return pages + to the kernel) + +0.16.9 release + + * fix long filename truncation on windows + * distinguish file open mode when checking files and downloading/seeding + with bittorrent. updates storage interface + * improve file_storage::map_file when dealing with invalid input + * improve handling of invalid utf-8 sequences in strings in torrent files + * handle more cases of broken .torrent files + * fix bug filename collision resolver + * fix bug in filename utf-8 verification + * make need_save_resume() a bit more robust + * fixed sparse flag manipulation on windows + * fixed streaming piece picking issue + +0.16.8 release + + * make rename_file create missing directories for new filename + * added missing python function: parse_magnet_uri + * fix alerts.all_categories in python binding + * fix torrent-abort issue which would cancel name lookups of other torrents + * make torrent file parser reject invalid path elements earlier + * fixed piece picker bug when using pad-files + * fix read-piece response for cancelled deadline-pieces + * fixed file priority vector-overrun + * fix potential packet allocation alignment issue in utp + * make 'close_redudnant_connections' cover more cases + * set_piece_deadline() also unfilters the piece (if its priority is 0) + * add work-around for bug in windows vista and earlier in + GetOverlappedResult + * fix traversal algorithm leak in DHT + * fix string encoding conversions on windows + * take torrent_handle::query_pieces into account in torrent_handle::statue() + * honor trackers responding with 410 + * fixed merkle tree torrent creation bug + * fixed crash with empty url-lists in torrent files + * added missing max_connections() function to python bindings + +0.16.7 release + + * fix string encoding in error messages + * handle error in read_piece and set_piece_deadline when torrent is removed + * DHT performance improvement + * attempt to handle ERROR_CANT_WAIT disk error on windows + * improve peers exchanged over PEX + * fixed rare crash in ut_metadata extension + * fixed files checking issue + * added missing pop_alerts() to python bindings + * fixed typos in configure script, inversing some feature-enable/disable flags + * added missing flag_update_subscribe to python bindings + * active_dht_limit, active_tracker_limit and active_lsd_limit now + interpret -1 as infinite + +0.16.6 release + + * fixed verbose log error for NAT holepunching + * fix a bunch of typos in python bindings + * make get_settings available in the python binding regardless of + deprecated functions + * fix typo in python settings binding + * fix possible dangling pointer use in peer list + * fix support for storing arbitrary data in the DHT + * fixed bug in uTP packet circle buffer + * fix potential crash when using torrent_handle::add_piece + * added missing add_torrent_alert to python binding + +0.16.5 release + + * udp socket refcounter fix + * added missing async_add_torrent to python bindings + * raised the limit for bottled http downloads to 2 MiB + * add support for magnet links and URLs in python example client + * fixed typo in python bindings' add_torrent_params + * introduce a way to add built-in plugins from python + * consistently disconnect the same peer when two peers simultaneously connect + * fix local endpoint queries for uTP connections + * small optimization to local peer discovery to ignore our own broadcasts + * try harder to bind the udp socket (uTP, DHT, UDP-trackers, LSD) to the + same port as TCP + * relax file timestamp requirements for accepting resume data + * fix performance issue in web seed downloader (coalescing of blocks + sometimes wouldn't work) + * web seed fixes (better support for torrents without trailing / in + web seeds) + * fix some issues with SSL over uTP connections + * fix UDP trackers trying all endpoints behind the hostname + +0.16.4 release + + * raise the default number of torrents allowed to announce to trackers + to 1600 + * improve uTP slow start behavior + * fixed UDP socket error causing it to fail on Win7 + * update use of boost.system to not use deprecated functions + * fix GIL issue in python bindings. Deprecated extension support in python + * fixed bug where setting upload slots to -1 would not mean infinite + * extend the UDP tracker protocol to include the request string from the + tracker URL + * fix mingw build for linux crosscompiler + +0.16.3 release + + * fix python binding backwards compatibility in replace_trackers + * fix possible starvation in metadata extension + * fix crash when creating torrents and optimizing file order with pad files + * disable support for large MTUs in uTP until it is more reliable + * expose post_torrent_updates and state_update_alert to python bindings + * fix incorrect SSL error messages + * fix windows build of shared library with openssl + * fix race condition causing shutdown hang + +0.16.2 release + + * fix permissions issue on linux with noatime enabled for non-owned files + * use random peer IDs in anonymous mode + * fix move_storage bugs + * fix unnecessary dependency on boost.date_time when building boost.asio as separate compilation + * always use SO_REUSEADDR and deprecate the flag to turn it on + * add python bindings for SSL support + * minor uTP tweaks + * fix end-game mode issue when some files are selected to not be downloaded + * improve uTP slow start + * make uTP less aggressive resetting cwnd when idle + +0.16.1 release + + * fixed crash when providing corrupt resume data + * fixed support for boost-1.44 + * fixed reversed semantics of queue_up() and queue_down() + * added missing functions to python bindings (file_priority(), set_dht_settings()) + * fixed low_prio_disk support on linux + * fixed time critical piece accounting in the request queue + * fixed semantics of rate_limit_utp to also ignore per-torrent limits + * fixed piece sorting bug of deadline pieces + * fixed python binding build on Mac OS and BSD + * fixed UNC path normalization (on windows, unless UNC paths are disabled) + * fixed possible crash when enabling multiple connections per IP + * fixed typo in win vista specific code, breaking the build + * change default of rate_limit_utp to true + * fixed DLL export issue on windows (when building a shared library linking statically against boost) + * fixed FreeBSD build + * fixed web seed performance issue with pieces > 1 MiB + * fixed unchoke logic when using web seeds + * fixed compatibility with older versions of boost (down to boost 1.40) + +0.16 release + + * support torrents with more than 262000 pieces + * make tracker back-off configurable + * don't restart the swarm after downloading metadata from magnet links + * lower the default tracker retry intervals + * support banning web seeds sending corrupt data + * don't let hung outgoing connection attempts block incoming connections + * improve SSL torrent support by using SNI and a single SSL listen socket + * improved peer exchange performance by sharing incoming connections which advertize listen port + * deprecate set_ratio(), and per-peer rate limits + * add web seed support for torrents with pad files + * introduced a more scalable API for torrent status updates (post_torrent_updates()) and updated client_test to use it + * updated the API to add_torrent_params turning all bools into flags of a flags field + * added async_add_torrent() function to significantly improve performance when + adding many torrents + * change peer_states to be a bitmask (bw_limit, bw_network, bw_disk) + * changed semantics of send_buffer_watermark_factor to be specified as a percentage + * add incoming_connection_alert for logging all successful incoming connections + * feature to encrypt peer connections with a secret AES-256 key stored in .torrent file + * deprecated compact storage allocation + * close files in separate thread on systems where close() may block (Mac OS X for instance) + * don't create all directories up front when adding torrents + * support DHT scrape + * added support for fadvise/F_RDADVISE for improved disk read performance + * introduced pop_alerts() which pops the entire alert queue in a single call + * support saving metadata in resume file, enable it by default for magnet links + * support for receiving multi announce messages for local peer discovery + * added session::listen_no_system_port flag to prevent libtorrent from ever binding the listen socket to port 0 + * added option to not recheck on missing or incomplete resume data + * extended stats logging with statistics=on builds + * added new session functions to more efficiently query torrent status + * added alerts for added and removed torrents + * expanded plugin interface to support session wide states + * made the metadata block requesting algorithm more robust against hash check failures + * support a separate option to use proxies for peers or not + * pausing the session now also pauses checking torrents + * moved alert queue size limit into session_settings + * added support for DHT rss feeds (storing only) + * added support for RSS feeds + * fixed up some edge cases in DHT routing table and improved unit test of it + * added error category and error codes for HTTP errors + * made the DHT implementation slightly more robust against routing table poisoning and node ID spoofing + * support chunked encoding in http downloads (http_connection) + * support adding torrents by url to the .torrent file + * support CDATA tags in xml parser + * use a python python dictionary for settings instead of session_settings object (in python bindings) + * optimized metadata transfer (magnet link) startup time (shaved off about 1 second) + * optimized swarm startup time (shaved off about 1 second) + * support DHT name lookup + * optimized memory usage of torrent_info and file_storage, forcing some API changes + around file_storage and file_entry + * support trackerid tracker extension + * graceful peer disconnect mode which finishes transactions before disconnecting peers + * support chunked encoding for web seeds + * uTP protocol support + * resistance towards certain flood attacks + * support chunked encoding for web seeds (only for BEP 19, web seeds) + * optimized session startup time + * support SSL for web seeds, through all proxies + * support extending web seeds with custom authorization and extra headers + * settings that are not changed from the default values are not saved + in the session state + * made seeding choking algorithm configurable + * deprecated setters for max connections, max half-open, upload and download + rates and unchoke slots. These are now set through session_settings + * added functions to query an individual peer's upload and download limit + * full support for BEP 21 (event=paused) + * added share-mode feature for improving share ratios + * merged all proxy settings into a single one + * improved SOCKS5 support by proxying hostname lookups + * improved support for multi-homed clients + * added feature to not count downloaded bytes from web seeds in stats + * added alert for incoming local service discovery messages + * added option to set file priorities when adding torrents + * removed the session mutex for improved performance + * added upload and download activity timer stats for torrents + * made the reuse-address flag configurable on the listen socket + * moved UDP trackers over to use a single socket + * added feature to make asserts log to a file instead of breaking the process + (production asserts) + * optimized disk I/O cache clearing + * added feature to ask a torrent if it needs to save its resume data or not + * added setting to ignore file modification time when loading resume files + * support more fine-grained torrent states between which peer sources it + announces to + * supports calculating sha1 file-hashes when creating torrents + * made the send_buffer_watermark performance warning more meaningful + * supports complete_ago extension + * dropped zlib as a dependency and builds using puff.c instead + * made the default cache size depend on available physical RAM + * added flags to torrent::status() that can filter which values are calculated + * support 'explicit read cache' which keeps a specific set of pieces + in the read cache, without implicitly caching other pieces + * support sending suggest messages based on what's in the read cache + * clear sparse flag on files that complete on windows + * support retry-after header for web seeds + * replaced boost.filesystem with custom functions + * replaced dependency on boost.thread by asio's internal thread primitives + * added support for i2p torrents + * cleaned up usage of MAX_PATH and related macros + * made it possible to build libtorrent without RTTI support + * added support to build with libgcrypt and a shipped version of libtommath + * optimized DHT routing table memory usage + * optimized disk cache to work with large caches + * support variable number of optimistic unchoke slots and to dynamically + adjust based on the total number of unchoke slots + * support for BitTyrant choker algorithm + * support for automatically start torrents when they receive an + incoming connection + * added more detailed instrumentation of the disk I/O thread + +0.15.11 release + + * fixed web seed bug, sometimes causing infinite loops + * fixed race condition when setting session_settings immediately after creating session + * give up immediately when failing to open a listen socket (report the actual error) + * restored ABI compatibility with 0.15.9 + * added missing python bindings for create_torrent and torrent_info + +0.15.10 release + + * fix 'parameter incorrect' issue when using unbuffered IO on windows + * fixed UDP socket error handling on windows + * fixed peer_tos (type of service) setting + * fixed crash when loading resume file with more files than the torrent in it + * fix invalid-parameter error on windows when disabling filesystem disk cache + * fix connection queue issue causing shutdown delays + * fixed mingw build + * fix overflow bug in progress_ppm field + * don't filter local peers received from a non-local tracker + * fix python deadlock when using python extensions + * fixed small memory leak in DHT + +0.15.9 release + + * added some functions missing from the python binding + * fixed rare piece picker bug + * fixed invalid torrent_status::finished_time + * fixed bugs in dont-have and upload-only extension messages + * don't open files in random-access mode (speeds up hashing) + +0.15.8 release + + * allow NULL to be passed to create_torrent::set_comment and create_torrent::set_creator + * fix UPnP issue for routers with multiple PPPoE connections + * fix issue where event=stopped announces wouldn't be sent when closing session + * fix possible hang in file::readv() on windows + * fix CPU busy loop issue in tracker announce logic + * honor IOV_MAX when using writev and readv + * don't post 'operation aborted' UDP errors when changing listen port + * fix tracker retry logic, where in some configurations the next tier would not be tried + * fixed bug in http seeding logic (introduced in 0.15.7) + * add support for dont-have extension message + * fix for set_piece_deadline + * add reset_piece_deadline function + * fix merkle tree torrent assert + +0.15.7 release + + * exposed set_peer_id to python binding + * improve support for merkle tree torrent creation + * exposed comparison operators on torrent_handle to python + * exposed alert error_codes to python + * fixed bug in announce_entry::next_announce_in and min_announce_in + * fixed sign issue in set_alert_mask signature + * fixed unaligned disk access for unbuffered I/O in windows + * support torrents whose name is empty + * fixed connection limit to take web seeds into account as well + * fixed bug when receiving a have message before having the metadata + * fixed python bindings build with disabled DHT support + * fixed BSD file allocation issue + * fixed bug in session::delete_files option to remove_torrent + +0.15.6 release + + * fixed crash in udp trackers when using SOCKS5 proxy + * fixed reconnect delay when leaving upload only mode + * fixed default values being set incorrectly in add_torrent_params through add_magnet_uri in python bindings + * implemented unaligned write (for unbuffered I/O) + * fixed broadcast_lsd option + * fixed udp-socket race condition when using a proxy + * end-game mode optimizations + * fixed bug in udp_socket causing it to issue two simultaneous async. read operations + * fixed mingw build + * fixed minor bug in metadata block requester (for magnet links) + * fixed race condition in iconv string converter + * fixed error handling in torrent_info constructor + * fixed bug in torrent_info::remap_files + * fix python binding for wait_for_alert + * only apply privileged port filter to DHT-only peers + +0.15.5 release + + * support DHT extension to report external IPs + * fixed rare crash in http_connection's error handling + * avoid connecting to peers listening on ports < 1024 + * optimized piece picking to not cause busy loops in some end-game modes + * fixed python bindings for tcp::endpoint + * fixed edge case of pad file support + * limit number of torrents tracked by DHT + * fixed bug when allow_multiple_connections_per_ip was enabled + * potential WOW64 fix for unbuffered I/O (windows) + * expose set_alert_queue_size_limit to python binding + * support dht nodes in magnet links + * support 100 Continue HTTP responses + * changed default choker behavior to use 8 unchoke slots (instead of being rate based) + * fixed error reporting issue in disk I/O thread + * fixed file allocation issues on linux + * fixed filename encoding and decoding issue on platforms using iconv + * reports redundant downloads to tracker, fixed downloaded calculation to + be more stable when not including redundant. Improved redundant data accounting + to be more accurate + * fixed bugs in http seed connection and added unit test for it + * fixed error reporting when fallocate fails + * deprecate support for separate proxies for separate kinds of connections + +0.15.4 release + + * fixed piece picker issue triggered by hash failure and timed out requests to the piece + * fixed optimistic unchoke issue when setting per torrent unchoke limits + * fixed UPnP shutdown issue + * fixed UPnP DeletePortmapping issue + * fixed NAT-PMP issue when adding the same mapping multiple times + * no peers from tracker when stopping is no longer an error + * improved web seed retry behavior + * fixed announce issue + +0.15.3 release + + * fixed announce bug where event=completed would not be sent if it violated the + min-announce of the tracker + * fixed limitation in rate limiter + * fixed build error with boost 1.44 + +0.15.2 release + + * updated compiler to msvc 2008 for python binding + * restored default fail_limit to unlimited on all trackers + * fixed rate limit bug for DHT + * fixed SOCKS5 bug for routing UDP packets + * fixed bug on windows when verifying resume data for a torrent where + one of its directories had been removed + * fixed race condition in peer-list with DHT + * fix force-reannounce and tracker retry issue + +0.15.1 release + + * fixed rare crash when purging the peer list + * fixed race condition around m_abort in session_impl + * fixed bug in web_peer_connection which could cause a hang when downloading + from web servers + * fixed bug in metadata extensions combined with encryption + * refactored socket reading code to not use async. operations unnecessarily + * some timer optimizations + * removed the reuse-address flag on the listen socket + * fixed bug where local peer discovery and DHT wouldn't be announced to without trackers + * fixed bug in bdecoder when decoding invalid messages + * added build warning when building with UNICODE but the standard library + doesn't provide std::wstring + * fixed add_node python binding + * fixed issue where trackers wouldn't tried immediately when the previous one failed + * fixed synchronization issue between download queue and piece picker + * fixed bug in udp tracker scrape response parsing + * fixed bug in the disk thread that could get triggered under heavy load + * fixed bug in add_piece() that would trigger asserts + * fixed vs 2010 build + * recognizes more clients in identify_client() + * fixed bug where trackers wouldn't be retried if they failed + * slight performance fix in disk elevator algorithm + * fixed potential issue where a piece could be checked twice + * fixed build issue on windows related to GetCompressedSize() + * fixed deadlock when starting torrents with certain invalid tracker URLs + * fixed iterator bug in disk I/O thread + * fixed FIEMAP support on linux + * fixed strict aliasing warning on gcc + * fixed inconsistency when creating torrents with symlinks + * properly detect windows version to initialize half-open connection limit + * fixed bug in url encoder where $ would not be encoded + +0.15 release + + * introduced a session state save mechanism. load_state() and save_state(). + this saves all session settings and state (except torrents) + * deprecated dht_state functions and merged it with the session state + * added support for multiple trackers in magnet links + * added support for explicitly flushing the disk cache + * added torrent priority to affect bandwidth allocation for its peers + * reduced the number of floating point operations (to better support + systems without FPU) + * added new alert when individual files complete + * added support for storing symbolic links in .torrent files + * added support for uTorrent interpretation of multi-tracker torrents + * handle torrents with duplicate filenames + * piece timeouts are adjusted to download rate limits + * encodes urls in torrent files that needs to be encoded + * fixed not passing &supportcrypto=1 when encryption is disabled + * introduced an upload mode, which torrents are switched into when + it hits a disk write error, instead of stopping the torrent. + this lets libtorrent keep uploading the parts it has when it + encounters a disk-full error for instance + * improved disk error handling and expanded use of error_code in + error reporting. added a bandwidth state, bw_disk, when waiting + for the disk io thread to catch up writing buffers + * improved read cache memory efficiency + * added another cache flush algorithm to write the largest + contiguous blocks instead of the least recently used + * introduced a mechanism to be lighter on the disk when checking torrents + * applied temporary memory storage optimization to when checking + a torrent as well + * removed hash_for_slot() from storage_interface. It is now implemented + by using the readv() function from the storage implementation + * improved IPv6 support by announcing twice when necessary + * added feature to set a separate global rate limit for local peers + * added preset settings for low memory environments and seed machines + min_memory_usage() and high_performance_seeder() + * optimized overall memory usage for DHT nodes and requests, peer + entries and disk buffers + * change in API for block_info in partial_piece_info, instead of + accessing 'peer', call 'peer()' + * added support for fully automatic unchoker (no need to specify + number of upload slots). This is on by default + * added support for changing socket buffer sizes through + session_settings + * added support for merkle hash tree torrents (.merkle.torrent) + * added 'seed mode', which assumes that all files are complete + and checks hashes lazily, as blocks are requested + * added new extension for file attributes (executable and hidden) + * added support for unbuffered I/O for aligned files + * added workaround for sparse file issue on Windows Vista + * added new lt_trackers extension to exchange trackers between + peers + * added support for BEP 17 http seeds + * added read_piece() to read pieces from torrent storage + * added option for udp tracker preference + * added super seeding + * added add_piece() function to inject data from external sources + * add_tracker() function added to torrent_handle + * if there is no working tracker, current_tracker is the + tracker that is currently being tried + * torrents that are checking can now be paused, which will + pause the checking + * introduced another torrent state, checking_resume_data, which + the torrent is in when it's first added, and is comparing + the files on disk with the resume data + * DHT bandwidth usage optimizations + * rate limited DHT send socket + * tracker connections are now also subject to IP filtering + * improved optimistic unchoke logic + * added monitoring of the DHT lookups + * added bandwidth reports for estimated TCP/IP overhead and DHT + * includes DHT traffic in the rate limiter + * added support for bitcomet padding files + * improved support for sparse files on windows + * added ability to give seeding torrents preference to active slots + * added torrent_status::finished_time + * automatically caps files and connections by default to rlimit + * added session::is_dht_running() function + * added torrent_handle::force_dht_announce() + * added torrent_info::remap_files() + * support min_interval tracker extension + * added session saving and loading functions + * added support for min-interval in tracker responses + * only keeps one outstanding duplicate request per peer + reduces waste download, specifically when streaming + * added support for storing per-peer rate limits across reconnects + * improved fallocate support + * fixed magnet link issue when using resume data + * support disk I/O priority settings + * added info_hash to torrent_deleted_alert + * improved LSD performance and made the interval configurable + * improved UDP tracker support by caching connect tokens + * fast piece optimization + +release 0.14.10 + + * fixed udp tracker race condition + * added support for torrents with odd piece sizes + * fixed issue with disk read cache not being cleared when removing torrents + * made the DHT socket bind to the same interface as the session + * fixed issue where an http proxy would not be used on redirects + * Solaris build fixes + * disabled buggy disconnect_peers feature + +release 0.14.9 + + * disabled feature to drop requests after having been skipped too many times + * fixed range request bug for files larger than 2 GB in web seeds + * don't crash when trying to create torrents with 0 files + * fixed big_number __init__ in python bindings + * fixed optimistic unchoke timer + * fixed bug where torrents with incorrectly formatted web seed URLs would be + connected multiple times + * fixed MinGW support + * fixed DHT bootstrapping issue + * fixed UDP over SOCKS5 issue + * added support for "corrupt" tracker announce + * made end-game mode less aggressive + +release 0.14.8 + + * ignore unkown metadata messages + * fixed typo that would sometimes prevent queued torrents to be checked + * fixed bug in auto-manager where active_downloads and active_seeds would + sometimes be used incorrectly + * force_recheck() no longer crashes on torrents with no metadata + * fixed broadcast socket regression from 0.14.7 + * fixed hang in NATPMP when shut down while waiting for a response + * fixed some more error handling in bdecode + +release 0.14.7 + + * fixed deadlock in natpmp + * resume data alerts are always posted, regardless of alert mask + * added wait_for_alert to python binding + * improved invalid filename character replacement + * improved forward compatibility in DHT + * added set_piece_hashes that takes a callback to the python binding + * fixed division by zero in get_peer_info() + * fixed bug where pieces may have been requested before the metadata + was received + * fixed incorrect error when deleting files from a torrent where + not all files have been created + * announces torrents immediately to the DHT when it's started + * fixed bug in add_files that would fail to recurse if the path + ended with a / + * fixed bug in error handling when parsing torrent files + * fixed file checking bug when renaming a file before checking the torrent + * fixed race conditon when receiving metadata from swarm + * fixed assert in ut_metadata plugin + * back-ported some fixes for building with no exceptions + * fixed create_torrent when passing in a path ending with / + * fixed move_storage when source doesn't exist + * fixed DHT state save bug for node-id + * fixed typo in python binding session_status struct + * broadcast sockets now join every network interface (used for UPnP and + local peer discovery) + +release 0.14.6 + + * various missing include fixes to be buildable with boost 1.40 + * added missing functions to python binding related to torrent creation + * fixed to add filename on web seed urls that lack it + * fixed BOOST_ASIO_HASH_MAP_BUCKETS define for boost 1.39 + * fixed checking of fast and suggest messages when used with magnet links + * fixed bug where web seeds would not disconnect if being resolved when + the torrent was paused + * fixed download piece performance bug in piece picker + * fixed bug in connect candidate counter + * replaces invalid filename characters with . + * added --with-libgeoip option to configure script to allow building and + linking against system wide library + * fixed potential pure virtual function call in extensions on shutdown + * fixed disk buffer leak in smart_ban extension + +release 0.14.5 + + * fixed bug when handling malformed webseed urls and an http proxy + * fixed bug when setting unlimited upload or download rates for torrents + * fix to make torrent_status::list_peers more accurate. + * fixed memory leak in disk io thread when not using the cache + * fixed bug in connect candidate counter + * allow 0 upload slots + * fixed bug in rename_file(). The new name would not always be saved in + the resume data + * fixed resume data compatibility with 0.13 + * fixed rare piece-picker bug + * fixed bug where one allowed-fast message would be sent even when + disabled + * fixed race condition in UPnP which could lead to crash + * fixed inversed seed_time ratio logic + * added get_ip_filter() to session + +release 0.14.4 + + * connect candidate calculation fix + * tightened up disk cache memory usage + * fixed magnet link parser to accept hex-encoded info-hashes + * fixed inverted logic when picking which peers to connect to + (should mean a slight performance improvement) + * fixed a bug where a failed rename_file() would leave the storage + in an error state which would pause the torrent + * fixed case when move_storage() would fail. Added a new alert + to be posted when it does + * fixed crash bug when shutting down while checking a torrent + * fixed handling of web seed urls that didn't end with a + slash for multi-file torrents + * lowered the default connection speed to 10 connection attempts + per second + * optimized memory usage when checking files fails + * fixed bug when checking a torrent twice + * improved handling of out-of-memory conditions in disk I/O thread + * fixed bug when force-checking a torrent with partial pieces + * fixed memory leak in disk cache + * fixed torrent file path vulnerability + * fixed upnp + * fixed bug when dealing with clients that drop requests (i.e. BitComet) + fixes assert as well + +release 0.14.3 + + * added python binding for create_torrent + * fixed boost-1.38 build + * fixed bug where web seeds would be connected before the files + were checked + * fixed filename bug when using wide characters + * fixed rare crash in peer banning code + * fixed potential HTTP compatibility issue + * fixed UPnP crash + * fixed UPnP issue where the control url contained the base url + * fixed a replace_trackers bug + * fixed bug where the DHT port mapping would not be removed when + changing DHT port + * fixed move_storage bug when files were renamed to be moved out + of the root directory + * added error handling for set_piece_hashes + * fixed missing include in enum_if.cpp + * fixed dual IP stack issue + * fixed issue where renamed files were sometimes not saved in resume data + * accepts tracker responses with no 'peers' field, as long as 'peers6' + is present + * fixed CIDR-distance calculation in the precense of IPv6 peers + * save partial resume data for torrents that are queued for checking + or checking, to maintain stats and renamed files + * Don't try IPv6 on windows if it's not installed + * move_storage fix + * fixed potential crash on shutdown + * fixed leaking exception from bdecode on malformed input + * fixed bug where connection would hang when receiving a keepalive + * fixed bug where an asio exception could be thrown when resolving + peer countries + * fixed crash when shutting down while checking a torrent + * fixed potential crash in connection_queue when a peer_connection + fail to open its socket + +release 0.14.2 + + * added missing functions to the python bindings torrent_info::map_file, + torrent_info::map_block and torrent_info::file_at_offset. + * removed support for boost-1.33 and earlier (probably didn't work) + * fixed potential freezes issues at shutdown + * improved error message for python setup script + * fixed bug when torrent file included announce-list, but no valid + tracker urls + * fixed bug where the files requested from web seeds would be the + renamed file names instead of the original file names in the torrent. + * documentation fix of queing section + * fixed potential issue in udp_socket (affected udp tracker support) + * made name, comment and created by also be subject to utf-8 error + correction (filenames already were) + * fixed dead-lock when settings DHT proxy + * added missing export directives to lazy_entry + * fixed disk cache expiry settings bug (if changed, it would be set + to the cache size) + * fixed bug in http_connection when binding to a particular IP + * fixed typo in python binding (torrent_handle::piece_prioritize should + be torrent_handle::piece_priorities) + * fixed race condition when saving DHT state + * fixed bugs related to lexical_cast being locale dependent + * added support for SunPro C++ compiler + * fixed bug where messeges sometimes could be encrypted in the + wrong order, for encrypted connections. + * fixed race condition where torrents could get stuck waiting to + get checked + * fixed mapped files bug where it wouldn't be properly restored + from resume data properly + * removed locale dependency in xml parser (caused asserts on windows) + * fixed bug when talking to https 1.0 servers + * fixed UPnP bug that could cause stack overflow + +release 0.14.1 + + * added converter for python unicode strings to utf-8 paths + * fixed bug in http downloader where the host field did not + include the port number + * fixed headers to not depend on NDEBUG, which would prohibit + linking a release build of libtorrent against a debug application + * fixed bug in disk I/O thread that would make the thread + sometimes quit when an error occurred + * fixed DHT bug + * fixed potential shutdown crash in disk_io_thread + * fixed usage of deprecated boost.filsystem functions + * fixed http_connection unit test + * fixed bug in DHT when a DHT state was loaded + * made rate limiter change in 0.14 optional (to take estimated + TCP/IP overhead into account) + * made the python plugin buildable through the makefile + * fixed UPnP bug when url base ended with a slash and + path started with a slash + * fixed various potentially leaking exceptions + * fixed problem with removing torrents that are checking + * fixed documentation bug regarding save_resume_data() + * added missing documentation on torrent creation + * fixed bugs in python client examples + * fixed missing dependency in package-config file + * fixed shared geoip linking in Jamfile + * fixed python bindings build on windows and made it possible + to generate a windows installer + * fixed bug in NAT-PMP implementation + +release 0.14 + + * deprecated add_torrent() in favor of a new add_torrent() + that takes a struct with parameters instead. Torrents + are paused and auto managed by default. + * removed 'connecting_to_tracker' torrent state. This changes + the enum values for the other states. + * Improved seeding and choking behavior. + * Fixed rare buffer overrun bug when calling get_download_queue + * Fixed rare bug where torrent could be put back into downloading + state even though it was finished, after checking files. + * Fixed rename_file to work before the file on disk has been + created. + * Fixed bug in tracker connections in case of errors caused + in the connection constructor. + * Updated alert system to be filtered by category instead of + severity level. Alerts can generate a message through + alert::message(). + * Session constructor will now start dht, upnp, natpmp, lsd by + default. Flags can be passed in to the constructor to not + do this, if these features are to be enabled and disabled + at a later point. + * Removed 'connecting_to_tracker' torrent state + * Fix bug where FAST pieces were cancelled on choke + * Fixed problems with restoring piece states when hash failed. + * Minimum peer reconnect time fix. Peers with no failures would + reconnect immediately. + * Improved web seed error handling + * DHT announce fixes and off-by-one loop fix + * Fixed UPnP xml parse bug where it would ignore the port number + for the control url. + * Fixed bug in torrent writer where the private flag was added + outside of the info dictionary + * Made the torrent file parser less strict of what goes in the + announce-list entry + * Fixed type overflow bug where some statistics was incorrectly + reported for file larger than 2 GB + * boost-1.35 support + * Fixed bug in statistics from web server peers where it sometimes + could report too many bytes downloaded. + * Fixed bug where statistics from the last second was lost when + disconnecting a peer. + * receive buffer optimizations (memcpy savings and memory savings) + * Support for specifying the TOS byte for peer traffic. + * Basic support for queueing of torrents. + * Better bias to give connections to downloading torrents + with fewer peers. + * Optimized resource usage (removed the checking thread) + * Support to bind outgoing connections to specific ports + * Disk cache support. + * New, more memory efficient, piece picker with sequential download + support (instead of the more complicated sequential download threshold). + * Auto Upload slots. Automtically opens up more slots if + upload limit is not met. + * Improved NAT-PMP support by querying the default gateway + * Improved UPnP support by ignoring routers not on the clients subnet. + +release 0.13 + + * Added scrape support + * Added add_extension() to torrent_handle. Can instantiate + extensions for torrents while downloading + * Added support for remove_torrent to delete the files as well + * Fixed issue with failing async_accept on windows + * DHT improvements, proper error messages are now returned when + nodes sends bad packets + * Optimized the country table used to resolve country of peers + * Copying optimization for sending data. Data is no longer copied from + the disk I/O buffer to the send buffer. + * Buffer optimization to use a raw buffer instead of std::vector + * Improved file storage to use sparse files + * Updated python bindings + * Added more clients to the identifiable clients list. + * Torrents can now be started in paused state (to better support queuing) + * Improved IPv6 support (support for IPv6 extension to trackers and + listens on both IPv6 and IPv4 interfaces). + * Improved asserts used. Generates a stacktrace on linux + * Piece picker optimizations and improvements + * Improved unchoker, connection limit and rate limiter + * Support for FAST extension + * Fixed invalid calculation in DHT node distance + * Fixed bug in URL parser that failed to parse IPv6 addresses + * added peer download rate approximation + * added port filter for outgoing connection (to prevent + triggering firewalls) + * made most parameters configurable via session_settings + * added encryption support + * added parole mode for peers whose data fails the hash check. + * optimized heap usage in piece-picker and web seed downloader. + * fixed bug in DHT where older write tokens weren't accepted. + * added support for sparse files. + * introduced speed categories for peers and pieces, to separate + slow and fast peers. + * added a half-open tcp connection limit that takes all connections + in to account, not just peer connections. + * added alerts for filtered IPs. + * added support for SOCKS4 and 5 proxies and HTTP CONNECT proxies. + * fixed proper distributed copies calculation. + * added option to use openssl for sha-1 calculations. + * optimized the piece picker in the case where a peer is a seed. + * added support for local peer discovery + * removed the dependency on the compiled boost.date_time library + * deprecated torrent_info::print() + * added UPnP support + * fixed problem where peer interested flags were not updated correctly + when pieces were filtered + * improvements to ut_pex messages, including support for seed flag + * prioritizes upload bandwidth to peers that might send back data + * the following functions have been deprecated: + void torrent_handle::filter_piece(int index, bool filter) const; + void torrent_handle::filter_pieces(std::vector const& pieces) const; + bool torrent_handle::is_piece_filtered(int index) const; + std::vector torrent_handle::filtered_pieces() const; + void torrent_handle::filter_files(std::vector const& files) const; + + instead, use the piece_priority functions. + + * added support for NAT-PMP + * added support for piece priorities. Piece filtering is now set as + a priority + * Fixed crash when last piece was smaller than one block and reading + fastresume data for that piece + * Makefiles should do a better job detecting boost + * Fixed crash when all tracker urls are removed + * Log files can now be created at user supplied path + * Log files failing to create is no longer fatal + * Fixed dead-lock in torrent_handle + * Made it build with boost 1.34 on windows + * Fixed bug in URL parser that failed to parse IPv6 addresses + * Fixed bug in DHT, related to IPv6 nodes + * DHT accepts transaction IDs that have garbage appended to them + * DHT logs messages that it fails to decode + +release 0.12 + + * fixes to make the DHT more compatible + * http seed improvements including error reporting and url encoding issues. + * fixed bug where directories would be left behind when moving storage + in some cases. + * fixed crashing bug when restarting or stopping the DHT. + * added python binding, using boost.python + * improved character conversion on windows when strings are not utf-8. + * metadata extension now respects the private flag in the torrent. + * made the DHT to only be used as a fallback to trackers by default. + * added support for HTTP redirection support for web seeds. + * fixed race condition when accessing a torrent that was checking its + fast resume data. + * fixed a bug in the DHT which could be triggered if the network was + dropped or extremely rare cases. + * if the download rate is limited, web seeds will now only use left-over + bandwidth after all bt peers have used up as much bandwidth as they can. + * added the possibility to have libtorrent resolve the countries of + the peers in torrents. + * improved the bandwidth limiter (it now implements a leaky bucket/node bucket). + * improved the HTTP seed downloader to report accurate progress. + * added more client peer-id signatures to be recognized. + * added support for HTTP servers that skip the CR before the NL at line breaks. + * fixed bug in the HTTP code that only accepted headers case sensitive. + * fixed bug where one of the session constructors didn't initialize boost.filesystem. + * fixed bug when the initial checking of a torrent fails with an exception. + * fixed bug in DHT code which would send incorrect announce messages. + * fixed bug where the http header parser was case sensitive to the header + names. + * Implemented an optmization which frees the piece_picker once a torrent + turns into a seed. + * Added support for uT peer exchange extension, implemented by Massaroddel. + * Modified the quota management to offer better bandwidth balancing + between peers. + * logging now supports multiple sessions (different sessions now log + to different directories). + * fixed random number generator seed problem, generating the same + peer-id for sessions constructed the same second. + * added an option to accept multiple connections from the same IP. + * improved tracker logging. + * moved the file_pool into session. The number of open files is now + limited per session. + * fixed uninitialized private flag in torrent_info + * fixed long standing issue with file.cpp on windows. Replaced the low level + io functions used on windows. + * made it possible to associate a name with torrents without metadata. + * improved http-downloading performance by requesting entire pieces via + http. + * added plugin interface for extensions. And changed the interface for + enabling extensions. + +release 0.11 + + * added support for incorrectly encoded paths in torrent files + (assumes Latin-1 encoding and converts to UTF-8). + * added support for destructing session objects asynchronously. + * fixed bug with file_progress() with files = 0 bytes + * fixed a race condition bug in udp_tracker_connection that could + cause a crash. + * fixed bug occuring when increasing the sequenced download threshold + with max availability lower than previous threshold. + * fixed an integer overflow bug occuring when built with gcc 4.1.x + * fixed crasing bug when closing while checking a torrent + * fixed bug causing a crash with a torrent with piece length 0 + * added an extension to the DHT network protocol to support the + exchange of nodes with IPv6 addresses. + * modified the ip_filter api slightly to support IPv6 + * modified the api slightly to make sequenced download threshold + a per torrent-setting. + * changed the address type to support IPv6 + * fixed bug in piece picker which would not behave as + expected with regard to sequenced download threshold. + * fixed bug with file_progress() with files > 2 GB. + * added --enable-examples option to configure script. + * fixed problem with the resource distribution algorithm + (controlling e.g upload/download rates). + * fixed incorrect asserts in storage related to torrents with + zero-sized files. + * added support for trackerless torrents (with kademlia DHT). + * support for torrents with the private flag set. + * support for torrents containing bootstrap nodes for the + DHT network. + * fixed problem with the configure script on FreeBSD. + * limits the pipelining used on url-seeds. + * fixed problem where the shutdown always would delay for + session_settings::stop_tracker_timeout seconds. + * session::listen_on() won't reopen the socket in case the port and + interface is the same as the one currently in use. + * added http proxy support for web seeds. + * fixed problem where upload and download stats could become incorrect + in case of high cpu load. + * added more clients to the identifiable list. + * fixed fingerprint parser to cope with latest Mainline versions. + +release 0.10 + + * fixed a bug where the requested number of peers in a tracker request could + be too big. + * fixed a bug where empty files were not created in full allocation mode. + * fixed a bug in storage that would, in rare cases, fail to do a + complete check. + * exposed more settings for tweaking parameters in the piece-picker, + downloader and uploader (http_settings replaced by session_settings). + * tweaked default settings to improve high bandwidth transfers. + * improved the piece picker performance and made it possible to download + popular pieces in sequence to improve disk performance. + * added the possibility to control upload and download limits per peer. + * fixed problem with re-requesting skipped pieces when peer was sending pieces + out of fifo-order. + * added support for http seeding (the GetRight protocol) + * renamed identifiers called 'id' in the public interface to support linking + with Objective.C++ + * changed the extensions protocol to use the new one, which is also + implemented by uTorrent. + * factorized the peer_connection and added web_peer_connection which is + able to download from http-sources. + * converted the network code to use asio (resulted in slight api changes + dealing with network addresses). + * made libtorrent build in vc7 (patches from Allen Zhao) + * fixed bug caused when binding outgoing connections to a non-local interface. + * add_torrent() will now throw if called while the session object is + being closed. + * added the ability to limit the number of simultaneous half-open + TCP connections. Flags in peer_info has been added. + +release 0.9.1 + + * made the session disable file name checks within the boost.filsystem library + * fixed race condition in the sockets + * strings that are invalid utf-8 strings are now decoded with the + local codepage on windows + * added the ability to build libtorrent both as a shared library + * client_test can now monitor a directory for torrent files and automatically + start and stop downloads while running + * fixed problem with file_size() when building on windows with unicode support + * added a new torrent state, allocating + * added a new alert, metadata_failed_alert + * changed the interface to session::add_torrent for some speed optimizations. + * greatly improved the command line control of the example client_test. + * fixed bug where upload rate limit was not being applied. + * files that are being checked will no longer stall files that don't need + checking. + * changed the way libtorrent identifies support for its excentions + to look for 'ext' at the end of the peer-id. + * improved performance by adding a circle buffer for the send buffer. + * fixed bugs in the http tracker connection when using an http proxy. + * fixed problem with storage's file pool when creating torrents and then + starting to seed them. + * hard limit on remote request queue and timeout on requests (a timeout + triggers rerequests). This makes libtorrent work much better with + "broken" clients like BitComet which may ignore requests. + +Initial release 0.9 + + * multitracker support + * serves multiple torrents on a single port and a single thread + * supports http proxies and proxy authentication + * gzipped tracker-responses + * block level piece picker + * queues torrents for file check, instead of checking all of them in parallel + * uses separate threads for checking files and for main downloader + * upload and download rate limits + * piece-wise, unordered, incremental file allocation + * fast resume support + * supports files > 2 gigabytes + * supports the no_peer_id=1 extension + * support for udp-tracker protocol + * number of connections limit + * delays sending have messages + * can resume pieces downloaded in any order + * adjusts the length of the request queue depending on download rate + * supports compact=1 + * selective downloading + * ip filter + diff --git a/Jamfile b/Jamfile new file mode 100644 index 0000000..490d80d --- /dev/null +++ b/Jamfile @@ -0,0 +1,779 @@ +# 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 ; + +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_chrono : /boost/chrono//boost_chrono : : : $(BOOST_ROOT) ; + alias boost_system : /boost/system//boost_system : : : $(BOOST_ROOT) ; + alias boost_random : /boost/random//boost_random : : : $(BOOST_ROOT) ; +} +else +{ + local boost-lib-search-path = + /opt/local/lib + /usr/lib + /usr/local/lib + /sw/lib + /usr/g++/lib + ; + + local boost-include-path = + /opt/local/include + /usr/local/include + /usr/sfw/include + ; + + # the names are decorated in MacPorts + lib boost_chrono : : darwin boost_chrono-mt $(boost-lib-search-path) + : : $(boost-include-path) ; + lib boost_system : : darwin boost_system-mt $(boost-lib-search-path) + : : $(boost-include-path) ; + lib boost_random : : darwin boost_random-mt $(boost-lib-search-path) + : : $(boost-include-path) ; + + lib boost_chrono : : boost_chrono $(boost-lib-search-path) + : : $(boost-include-path) ; + lib boost_system : : boost_system $(boost-lib-search-path) + : : $(boost-include-path) ; + lib boost_random : : boost_random $(boost-lib-search-path) + : : $(boost-include-path) ; +} + +VERSION = 1.1.1 ; + +# rule for linking the correct libraries depending +# on features and target-os +rule linking ( properties * ) +{ + local result ; + + # openssl libraries, if enabled + if openssl in $(properties) + { + # exclude gcc from a regular windows build to make mingw + # link against the regular unix library name + if windows in $(properties) + && ! gcc in $(properties) + { + result += ssleay32 + libeay32 + advapi32 + user32 + shell32 + gdi32 + ; + } + else + { + result += crypto ssl ; + } + } + + if on in $(properties) + { + result += /libsimulator//simulator ; + } + + # dbghelp doesn't appear to exist in mingw + if windows in $(properties) + && ! gcc in $(properties) + && ( debug in $(properties) + || on in $(properties) + || production in $(properties) + || debug in $(properties) + || debug in $(properties) + || on in $(properties) ) + { + result += dbghelp ; + } + + # gcrypt libraries, if enabled + if gcrypt in $(properties) + { + # on mac os x, adding the /opt/local/include path + # would include openssl headers incompatible with + # the system library. Only add this include path + # if we're not using openssl (which we're most + # likely not if we're using libgcrypt) + result += gcrypt /opt/local/include ; + } + + # socket functions on windows require winsock libraries + if windows in $(properties) + || cygwin in $(properties) + { + result += ws2_32 + wsock32 + iphlpapi + WIN32_LEAN_AND_MEAN + __USE_W32_SOCKETS + WIN32 + _WIN32 + ; + + # when DHT is enabled, we need ed25519 which in turn + # needs entropy + if ! off in $(properties) + { + result += advapi32 ; + } + } + + if beos in $(properties) + { + result += netkit gcc ; + } + + if solaris in $(properties) + { + result += libsocket libnsl ; + } + + if on in $(properties) + { + result += libiconv ; + } + + if ( gcc in $(properties) + || clang in $(properties) ) + && linux in $(properties) + && ( debug in $(properties) + || on in $(properties) + || production in $(properties) + || debug in $(properties) + || debug in $(properties) + || on in $(properties) ) + { + # for backtraces in assertion failures + # which only works on ELF targets with gcc + result += -export-dynamic -rdynamic ; + } + + if static in $(properties) + { + if shared in $(properties) + { + # if libtorrent is being built as a shared library + # but we're linking against boost statically, we still + # need to make boost think it's being built as a shared + # library, so that it properly exports its symbols + result += BOOST_ALL_DYN_LINK ; + result += boost_system/static/BOOST_ALL_DYN_LINK ; + result += boost_chrono/static/BOOST_ALL_DYN_LINK ; + result += boost_random/static/BOOST_ALL_DYN_LINK ; + } + else + { + result += boost_system/static ; + result += boost_chrono/static ; + result += boost_random/static ; + } + + if gcc in $(properties) + && ! windows in $(properties) + && shared in $(properties) + { + result += on ; + } + + } + else if shared in $(properties) + { + result += boost_system/shared ; + result += boost_chrono/shared ; + result += boost_random/shared ; + } + else + { + result += boost_system ; + result += boost_chrono ; + result += boost_random ; + } + + result += BOOST_ALL_NO_LIB + BOOST_MULTI_INDEX_DISABLE_SERIALIZATION + ; + + return $(result) ; +} + +rule warnings ( properties * ) +{ + local result ; + + if off in $(properties) + { + return $(result) ; + } + + if clang in $(properties) + || darwin in $(properties) + { + result += -Weverything ; + result += -Wno-documentation ; + result += -Wno-c++98-compat-pedantic ; + result += -Wno-padded ; + result += -Wno-global-constructors ; + result += -Wno-c++98-compat ; + result += -Wno-exit-time-destructors ; + result += -Wno-documentation-unknown-command ; + result += -Wno-disabled-macro-expansion ; + result += -Wno-unused-command-line-argument ; + result += -Wno-error=implicit-fallthrough ; + result += -Wno-c++11-long-long ; + result += -Wno-variadic-macros ; + +# in C++98 mode there's no way to silence this warning +# in the code (without final) + result += -Wno-non-virtual-dtor ; + +# enable these warnings again, once the other ones are dealt with + result += -Wno-weak-vtables ; + result += -Wno-sign-compare ; + result += -Wno-sign-conversion ; + result += -Wno-conversion ; + } + + if gcc in $(properties) + { + result += -Wall ; + result += -Wextra ; + result += -Wpedantic ; +# result += -Wmisleading-indentation ; + result += -Wparentheses ; + result += -Wvla ; + result += -Wc++11-compat ; + result += -Wno-format-zero-length ; + result += -Wno-long-long ; + +# enable these warnings again, once the other ones are dealt with + result += -Wno-sign-compare ; + result += -Wno-unused-variable ; + } + + if msvc in $(properties) + { +# disable warning C4503: decorated name length exceeded, name was truncated + result += /wd4503 ; + result += all ; + +# enable these warnings again, once the other ones are dealt with + +# disable warning C4389: signed/unsigned mismatch + result += /wd4389 ; + result += /wd4245 ; + result += /wd4018 ; +# disable warning C4244: 'argument' : conversion from 'int' to 'unsigned short', possible loss of data + result += /wd4244 ; +# disable warning C4512: assignment operator could not be generated + result += /wd4512 ; + } + + return $(result) ; +} + +# rule for adding the right source files +# depending on target-os and features +rule building ( properties * ) +{ + local result ; + + if shared in $(properties) && on in $(properties) + { + # 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 + result += TORRENT_EXPORT_EXTRA ; + } + + if msvc in $(properties) + { + # allow larger .obj files (with more sections) + result += /bigobj ; + } + + if ( debug in $(properties) + && ( clang in $(properties) + || gcc in $(properties) + || darwin in $(properties) ) ) + { + result += -ftrapv ; + } + + if ( debug in $(properties) + || on in $(properties) ) + { + result += src/assert.cpp ; + } + + if on in $(properties) + { + result += src/mpi.cpp ; + result += src/pe_crypto.cpp ; + } + + if built-in in $(properties) + { + result += src/sha1.cpp ; + } + + if ( darwin in $(properties) + || gcc in $(properties) + || clang in $(propertoes) + || clang-darwin in $(propertoes) ) + && shared in $(properties) + # on GCC, enabling debugging in libstdc++ + # breaks the ABI and its ability to appear + # in shared object interfaces, so when it's + # enabled, just export everything (since we're) + # probably not a production build anyway + && ! on in $(properties) + { + # hide non-external symbols + result += -fvisibility=hidden ; + result += -fvisibility-inlines-hidden ; + + if ( gcc in $(properties) ) + { + result += -Wl,-Bsymbolic ; + } + } + + return $(result) ; +} + +rule tag ( name : type ? : property-set ) +{ + name = [ virtual-target.add-prefix-and-suffix $(name) : $(type) : $(property-set) ] ; + + if $(type) = SHARED_LIB && + ( ! ( [ $(property-set).get ] in windows cygwin ) ) + { + name = $(name).$(VERSION) ; + } + + return $(name) ; +} + +feature ipv6 : on off : composite propagated link-incompatible ; +feature.compose off : TORRENT_USE_IPV6=0 ; + +feature sanitize : off address bounds undefined thread rtc : composite propagated link-incompatible ; +# sanitize is a clang and GCC feature +feature.compose bounds : -fsanitize=bounds -fsanitize-undefined-trap-on-error -fsanitize=bounds -fsanitize-undefined-trap-on-error ; +feature.compose undefined : -fsanitize=undefined -fsanitize=undefined ; +feature.compose thread : -fsanitize=thread -fsanitize=thread ; +feature.compose address : -fsanitize=address -fsanitize=address ; +# RTC (runtime check) is an msvc feature +feature.compose rtc : /RTCc /RTCsu ; + +feature fiemap : off on : composite propagated ; +feature.compose on : HAVE_LINUX_FIEMAP_H ; + +feature file-leak-logging : off on : composite propagated ; +feature.compose on : TORRENT_DEBUG_FILE_LEAKS=1 ; + +feature i2p : on off : composite propagated ; +feature.compose on : TORRENT_USE_I2P=1 ; +feature.compose off : TORRENT_USE_I2P=0 ; + +feature iconv : auto on off : composite propagated ; +feature.compose on : TORRENT_USE_ICONV=1 ; +feature.compose off : TORRENT_USE_ICONV=0 ; + +feature use-valgrind : off on : composite propagated link-incompatible ; +feature.compose on : TORRENT_USE_VALGRIND=1 ; + +feature memory-optimization : off on : composite propagated link-incompatible ; +feature.compose on : TORRENT_OPTIMIZE_MEMORY_USAGE ; + +feature asserts : auto on off production system : composite propagated ; +feature.compose on : TORRENT_RELEASE_ASSERTS=1 ; +feature.compose production : TORRENT_PRODUCTION_ASSERTS=1 TORRENT_RELEASE_ASSERTS=1 ; +feature.compose off : TORRENT_USE_ASSERTS=0 ; +feature.compose system : TORRENT_USE_SYSTEM_ASSERTS=1 ; + +feature windows-version : vista win7 xp : composite propagated link-incompatible ; +feature.compose vista : _WIN32_WINNT=0x0600 ; +feature.compose win7 : _WIN32_WINNT=0x0601 ; +feature.compose xp : _WIN32_WINNT=0x0501 ; + +feature extensions : on off : composite propagated link-incompatible ; +feature.compose off : TORRENT_DISABLE_EXTENSIONS ; + +feature asio-debugging : off on : composite propagated link-incompatible ; +feature.compose on : TORRENT_ASIO_DEBUGGING ; + +feature picker-debugging : off on : composite propagated link-incompatible ; +feature.compose on : TORRENT_DEBUG_REFCOUNTS ; + +feature simulator : off on : composite propagated link-incompatible ; +feature.compose on : TORRENT_BUILD_SIMULATOR ; + +# deprecated use allocator=pool instead +feature pool-allocators : on off debug : composite propagated link-incompatible ; +feature.compose off : TORRENT_DISABLE_POOL_ALLOCATOR ; +feature.compose debug : TORRENT_DISABLE_POOL_ALLOCATOR TORRENT_DEBUG_BUFFERS ; + +feature allocator : pool system debug : composite propagated ; +feature.compose system : TORRENT_DISABLE_POOL_ALLOCATOR ; +feature.compose debug : TORRENT_DISABLE_POOL_ALLOCATOR TORRENT_DEBUG_BUFFERS ; + +feature piece-allocator : valloc memalign posix_memalign : composite propagated ; +feature.compose memalign : TORRENT_USE_MEMALIGN=1 ; +feature.compose posix_memalign : TORRENT_USE_POSIX_MEMALIGN=1 ; + +feature invariant-checks : on off full : composite propagated link-incompatible ; +feature.compose off : TORRENT_DISABLE_INVARIANT_CHECKS ; +feature.compose full : TORRENT_EXPENSIVE_INVARIANT_CHECKS ; + +feature disk-stats : off on : composite propagated link-incompatible ; +feature.compose on : TORRENT_DISK_STATS ; + +feature utp-log : off on : composite propagated link-incompatible ; +feature.compose on : TORRENT_UTP_LOG_ENABLE ; + +feature simulate-slow-read : off on : composite propagated ; +feature.compose on : TORRENT_SIMULATE_SLOW_READ ; + +feature logging : on off : composite propagated link-incompatible ; +feature.compose off : TORRENT_DISABLE_LOGGING ; + +feature dht : on off : composite propagated link-incompatible ; +feature.compose off : TORRENT_DISABLE_DHT ; + +feature encryption : on off : composite propagated link-incompatible ; +feature.compose off : TORRENT_DISABLE_ENCRYPTION ; + +feature mutable-torrents : on off : composite propagated link-incompatible ; +feature.compose off : TORRENT_DISABLE_MUTABLE_TORRENTS ; + +feature crypto : built-in openssl gcrypt : composite propagated ; +feature.compose openssl : TORRENT_USE_OPENSSL ; +feature.compose gcrypt : TORRENT_USE_GCRYPT ; + +feature resolve-countries : on off : composite propagated link-incompatible ; +feature.compose off : TORRENT_DISABLE_RESOLVE_COUNTRIES ; + +feature character-set : unicode ansi : composite propagated link-incompatible ; +feature.compose unicode : _UNICODE UNICODE ; + +feature deprecated-functions : on off : composite propagated link-incompatible ; +feature.compose off : TORRENT_NO_DEPRECATE ; + +feature boost-link : default static shared : propagated composite ; + +feature debug-iterators : off on : composite propagated link-incompatible ; +feature.compose on : _SCL_SECURE=1 _GLIBCXX_DEBUG ; + +feature fpic : off on : composite propagated link-incompatible ; +feature.compose on : -fPIC ; +feature.compose off : darwin:-mdynamic-no-pic ; + +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 ; + + +# this is a trick to get filename paths to targets to become shorter +# making it possible to build on windows, especially mingw seems particular +# for release builds, disable optimizations as they bump GCC over the edge of +# allowed memory usage on travis-ci +variant test_release : release + : production on + full shared off + on on multi + ; +variant test_debug : debug + : openssl on on + debug on + full shared + on on multi on + ; +variant test_barebones : debug + : off off off off shared + off off + on on multi on + ; + +# required for openssl on windows +lib ssleay32 : : ssleay32 ; +lib libeay32 : : libeay32 ; +lib advapi32 : : advapi32 ; +lib user32 : : user32 ; +lib shell32 : : shell32 ; +lib gdi32 : : gdi32 ; +lib dbghelp : : dbghelp ; + +# required for networking on beos +lib netkit : : net /boot/system/lib shared ; +lib gcc : : gcc static ; + +# when using iconv +lib libiconv : : iconv shared /usr/local/lib ; + +# openssl on linux/bsd/macos etc. +lib gcrypt : : gcrypt shared /opt/local/lib ; +lib z : : shared z /usr/lib ; +lib crypto : : crypto shared /usr/lib z : : /opt/local/include ; +lib ssl : : ssl shared crypto /opt/local/lib : : /opt/local/include ; +lib dl : : shared dl ; + +# time functions used on linux require librt +lib librt : : rt shared ; + +lib libsocket : : libnsl socket shared /usr/sfw/lib shared ; +lib libnsl : : nsl shared /usr/sfw/lib shared ; + +# socket libraries on windows +lib wsock32 : : wsock32 shared ; +lib ws2_32 : : ws2_32 shared ; +lib iphlpapi : : iphlpapi shared ; + +SOURCES = + alert + alert_manager + allocator + announce_entry + assert + bandwidth_limit + bandwidth_manager + bandwidth_queue_entry + bdecode + bitfield + block_cache + bloom_filter + chained_buffer + choker + close_reason + cpuid + crc32c + create_torrent + disk_buffer_holder + disk_buffer_pool + disk_io_job + disk_job_pool + entry + error_code + file_storage + lazy_bdecode + escape_string + string_util + file + gzip + hasher + hex + http_connection + http_stream + http_parser + identify_client + ip_filter + ip_voter + merkle + 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 + receive_buffer + resolve_links + rss + session + session_handle + session_impl + session_call + settings_pack + socket_io + socket_type + socks5_stream + stat + storage + torrent + torrent_handle + torrent_info + torrent_peer + torrent_peer_allocator + torrent_status + time + tracker_manager + http_tracker_connection + udp_tracker_connection + sha1 + timestamp_history + udp_socket + upnp + utf8 + utp_socket_manager + utp_stream + file_pool + lsd + disk_buffer_pool + disk_io_thread + enum_net + broadcast_socket + magnet_uri + parse_url + ConvertUTF + thread + 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 + +# -- extensions -- + metadata_transfer + ut_pex + ut_metadata + lt_trackers + smart_ban + ; + +KADEMLIA_SOURCES = + 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_SOURCES = + add_scalar + fe + ge + key_exchange + keypair + sc + seed + sha512 + sign + verify + ; + +local usage-requirements = + ./include + ./include/libtorrent + /usr/sfw/include +# freebsd doesn't seem to include this automatically +# and iconv.h is installed there + /usr/local/include + release:NDEBUG + debug:TORRENT_DEBUG + _FILE_OFFSET_BITS=64 + BOOST_EXCEPTION_DISABLE +# enable cancel support in asio + BOOST_ASIO_ENABLE_CANCELIO + @linking +# these compiler settings just makes the compiler standard conforming + msvc:/Zc:wchar_t + msvc:/Zc:forScope +# msvc optimizations + msvc,release:/OPT:ICF=5 + msvc,release:/OPT:REF + + $(CXXFLAGS) + $(LDFLAGS) +# this works around a bug in asio in boost-1.39 + BOOST_ASIO_HASH_MAP_BUCKETS=1021 + ; + +project torrent ; + +lib torrent + + : # sources + src/$(SOURCES).cpp + + : # requirements + ./ed25519/src + multi + TORRENT_BUILDING_LIBRARY + shared:TORRENT_BUILDING_SHARED + BOOST_NO_DEPRECATED + shared:BOOST_SYSTEM_SOURCE + + on:src/kademlia/$(KADEMLIA_SOURCES).cpp + on:ed25519/src/$(ED25519_SOURCES).cpp + + @building + @warnings + $(CXXFLAGS) + + # disable bogus deprecation warnings on msvc8 + msvc:_SCL_SECURE_NO_DEPRECATE + msvc:_CRT_SECURE_NO_DEPRECATE + + @tag + + $(usage-requirements) + + : # default build + multi + + : # usage requirements + $(usage-requirements) + shared:TORRENT_LINKING_SHARED + ; + +headers = [ path.glob-tree include/libtorrent : *.hpp ] ; + +package.install install + : libtorrent + : + : torrent + : $(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..80aa2da --- /dev/null +++ b/LICENSE @@ -0,0 +1,83 @@ +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +------------------------------------------------------------------------------ + +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 to conserve memory. The code +works whether integers are stored big endian or little endian. + +In the comments below are "Format notes" that describe the inflate process +and document some of the less obvious aspects of the format. This source +code is meant to supplement RFC 1951, which formally describes the deflate +format: + + http://www.zlib.org/rfc-deflate.html + +------------------------------------------------------------------------------ + +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..b5b424d --- /dev/null +++ b/Makefile.am @@ -0,0 +1,130 @@ +ACLOCAL_AMFLAGS = -I m4 + +AM_DISTCHECK_CONFIGURE_FLAGS = --enable-tests=yes + +SUBDIRS = include/libtorrent src examples test bindings tools + +DOCS_IMAGES = \ + docs/client_test.png \ + docs/cwnd.png \ + docs/cwnd_thumb.png \ + docs/delays.png \ + docs/delays_thumb.png \ + docs/disk_io.png \ + docs/hacking.html \ + docs/im_thumb.jpg \ + docs/merkle_tree.png \ + docs/our_delay_base.png \ + docs/our_delay_base_thumb.png \ + docs/read_disk_buffers.png \ + docs/read_disk_buffers.diagram \ + docs/session_stats_peers.png \ + docs/storage.png \ + docs/todo.html \ + docs/write_disk_buffers.png \ + docs/write_disk_buffers.diagram \ + docs/ziptorrent_thumb.gif \ + docs/ip_id_v4.png \ + docs/ip_id_v6.png \ + docs/hash_distribution.png \ + docs/complete_bit_prefixes.png \ + docs/troubleshooting.dot \ + docs/troubleshooting.png \ + docs/troubleshooting_thumb.png \ + docs/hacking.diagram \ + docs/hacking.png \ + docs/disk_cache.diagram \ + docs/disk_cache.png \ + docs/utp_stack.diagram \ + docs/utp_stack.png \ + docs/style.css \ + docs/rst.css \ + docs/img/bg.png \ + docs/img/blue_bottom.png \ + docs/img/blue_top.png \ + docs/img/dotline.gif \ + docs/img/minus.gif \ + docs/img/orange.png + +DOCS_PAGES = \ + docs/building.html \ + docs/client_test.html \ + docs/contributing.html \ + docs/dht_extensions.html \ + docs/dht_rss.html \ + docs/dht_sec.html \ + docs/dht_store.html \ + docs/examples.html \ + docs/extension_protocol.html \ + docs/features.html \ + docs/index.html \ + docs/manual-ref.html \ + docs/projects.html \ + docs/python_binding.html \ + docs/tuning.html \ + docs/settings.rst \ + docs/stats_counters.rst \ + docs/troubleshooting.html \ + docs/udp_tracker_protocol.html \ + docs/utp.html \ + docs/streaming.html \ + docs/building.rst \ + docs/client_test.rst \ + docs/contributing.rst \ + docs/dht_extensions.rst \ + docs/dht_rss.rst \ + docs/dht_sec.rst \ + docs/dht_store.rst \ + docs/examples.rst \ + docs/extension_protocol.rst \ + docs/features.rst \ + docs/index.rst \ + docs/manual-ref.rst \ + docs/projects.rst \ + docs/python_binding.rst \ + docs/tuning.rst \ + docs/troubleshooting.rst \ + docs/udp_tracker_protocol.rst \ + docs/utp.rst \ + docs/streaming.rst \ + docs/tutorial.rst \ + docs/tutorial.html \ + docs/reference-Alerts.html \ + docs/reference-Bdecoding.html \ + docs/reference-Bencoding.html \ + docs/reference-Core.html \ + docs/reference-Create_Torrents.html \ + docs/reference-Custom_Storage.html \ + docs/reference-ed25519.html \ + docs/reference-Error_Codes.html \ + docs/reference-Filter.html \ + docs/reference-Plugins.html \ + docs/reference-Settings.html \ + docs/reference-Storage.html \ + docs/reference-Utility.html \ + docs/reference.html + +ED25519_SOURCE = \ + ed25519/readme.md \ + ed25519/test.c \ + ed25519/src/fe.h \ + ed25519/src/fixedint.h \ + ed25519/src/ge.h \ + ed25519/src/precomp_data.h \ + ed25519/src/sc.h \ + ed25519/src/sha512.h + +EXTRA_DIST = \ + Jamfile \ + Jamroot.jam \ + CMakeLists.txt \ + setup.py \ + LICENSE \ + README.rst \ + $(DOCS_PAGES) \ + $(DOCS_IMAGES) \ + $(ED25519_SOURCE) + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = libtorrent-rasterbar.pc + diff --git a/Makefile.in b/Makefile.in new file mode 100644 index 0000000..0c81dff --- /dev/null +++ b/Makefile.in @@ -0,0 +1,1039 @@ +# Makefile.in generated by automake 1.15 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2014 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +target_triplet = @target@ +subdir = . +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_boost_base.m4 \ + $(top_srcdir)/m4/ax_boost_chrono.m4 \ + $(top_srcdir)/m4/ax_boost_python.m4 \ + $(top_srcdir)/m4/ax_boost_random.m4 \ + $(top_srcdir)/m4/ax_boost_system.m4 \ + $(top_srcdir)/m4/ax_check_openssl.m4 \ + $(top_srcdir)/m4/ax_pthread.m4 \ + $(top_srcdir)/m4/ax_python_devel.m4 \ + $(top_srcdir)/m4/gettext-lib.m4 $(top_srcdir)/m4/iconv.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/pkgconfig.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(top_srcdir)/configure \ + $(am__configure_deps) $(am__DIST_COMMON) +am__CONFIG_DISTCLEAN_FILES = config.status config.cache config.log \ + configure.lineno config.status.lineno +mkinstalldirs = $(install_sh) -d +CONFIG_CLEAN_FILES = libtorrent-rasterbar.pc \ + libtorrent-rasterbar-cmake.pc +CONFIG_CLEAN_VPATH_FILES = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +SOURCES = +DIST_SOURCES = +RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \ + ctags-recursive dvi-recursive html-recursive info-recursive \ + install-data-recursive install-dvi-recursive \ + install-exec-recursive install-html-recursive \ + install-info-recursive install-pdf-recursive \ + install-ps-recursive install-recursive installcheck-recursive \ + installdirs-recursive pdf-recursive ps-recursive \ + tags-recursive uninstall-recursive +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(pkgconfigdir)" +DATA = $(pkgconfig_DATA) +RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \ + distclean-recursive maintainer-clean-recursive +am__recursive_targets = \ + $(RECURSIVE_TARGETS) \ + $(RECURSIVE_CLEAN_TARGETS) \ + $(am__extra_recursive_targets) +AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \ + cscope distdir dist dist-all distcheck +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +CSCOPE = cscope +DIST_SUBDIRS = $(SUBDIRS) +am__DIST_COMMON = $(srcdir)/Makefile.in \ + $(srcdir)/libtorrent-rasterbar-cmake.pc.in \ + $(srcdir)/libtorrent-rasterbar.pc.in \ + $(top_srcdir)/build-aux/compile \ + $(top_srcdir)/build-aux/config.guess \ + $(top_srcdir)/build-aux/config.rpath \ + $(top_srcdir)/build-aux/config.sub \ + $(top_srcdir)/build-aux/install-sh \ + $(top_srcdir)/build-aux/ltmain.sh \ + $(top_srcdir)/build-aux/missing AUTHORS COPYING ChangeLog NEWS \ + build-aux/compile build-aux/config.guess \ + build-aux/config.rpath build-aux/config.sub \ + build-aux/install-sh build-aux/ltmain.sh build-aux/missing +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +distdir = $(PACKAGE)-$(VERSION) +top_distdir = $(distdir) +am__remove_distdir = \ + if test -d "$(distdir)"; then \ + find "$(distdir)" -type d ! -perm -200 -exec chmod u+w {} ';' \ + && rm -rf "$(distdir)" \ + || { sleep 5 && rm -rf "$(distdir)"; }; \ + else :; fi +am__post_remove_distdir = $(am__remove_distdir) +am__relativize = \ + dir0=`pwd`; \ + sed_first='s,^\([^/]*\)/.*$$,\1,'; \ + sed_rest='s,^[^/]*/*,,'; \ + sed_last='s,^.*/\([^/]*\)$$,\1,'; \ + sed_butlast='s,/*[^/]*$$,,'; \ + while test -n "$$dir1"; do \ + first=`echo "$$dir1" | sed -e "$$sed_first"`; \ + if test "$$first" != "."; then \ + if test "$$first" = ".."; then \ + dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \ + dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \ + else \ + first2=`echo "$$dir2" | sed -e "$$sed_first"`; \ + if test "$$first2" = "$$first"; then \ + dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \ + else \ + dir2="../$$dir2"; \ + fi; \ + dir0="$$dir0"/"$$first"; \ + fi; \ + fi; \ + dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \ + done; \ + reldir="$$dir2" +DIST_ARCHIVES = $(distdir).tar.gz +GZIP_ENV = --best +DIST_TARGETS = dist-gzip +distuninstallcheck_listfiles = find . -type f -print +am__distuninstallcheck_listfiles = $(distuninstallcheck_listfiles) \ + | sed 's|^\./|$(prefix)/|' | grep -v '$(infodir)/dir$$' +distcleancheck_listfiles = find . -type f -print +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BOOST_CHRONO_LIB = @BOOST_CHRONO_LIB@ +BOOST_CPPFLAGS = @BOOST_CPPFLAGS@ +BOOST_LDFLAGS = @BOOST_LDFLAGS@ +BOOST_PYTHON_LIB = @BOOST_PYTHON_LIB@ +BOOST_RANDOM_LIB = @BOOST_RANDOM_LIB@ +BOOST_SYSTEM_LIB = @BOOST_SYSTEM_LIB@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +COMPILETIME_OPTIONS = @COMPILETIME_OPTIONS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEBUGFLAGS = @DEBUGFLAGS@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GREP = @GREP@ +ICONV_LIBS = @ICONV_LIBS@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INTERFACE_VERSION_INFO = @INTERFACE_VERSION_INFO@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBICONV = @LIBICONV@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OPENSSL_INCLUDES = @OPENSSL_INCLUDES@ +OPENSSL_LDFLAGS = @OPENSSL_LDFLAGS@ +OPENSSL_LIBS = @OPENSSL_LIBS@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PTHREAD_CC = @PTHREAD_CC@ +PTHREAD_CFLAGS = @PTHREAD_CFLAGS@ +PTHREAD_LIBS = @PTHREAD_LIBS@ +PYTHON = @PYTHON@ +PYTHON_CPPFLAGS = @PYTHON_CPPFLAGS@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_EXTRA_LDFLAGS = @PYTHON_EXTRA_LDFLAGS@ +PYTHON_EXTRA_LIBS = @PYTHON_EXTRA_LIBS@ +PYTHON_INSTALL_PARAMS = @PYTHON_INSTALL_PARAMS@ +PYTHON_LIBS = @PYTHON_LIBS@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_SITE_PKG = @PYTHON_SITE_PKG@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +ax_pthread_config = @ax_pthread_config@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target = @target@ +target_alias = @target_alias@ +target_cpu = @target_cpu@ +target_os = @target_os@ +target_vendor = @target_vendor@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +ACLOCAL_AMFLAGS = -I m4 +AM_DISTCHECK_CONFIGURE_FLAGS = --enable-tests=yes +SUBDIRS = include/libtorrent src examples test bindings tools +DOCS_IMAGES = \ + docs/client_test.png \ + docs/cwnd.png \ + docs/cwnd_thumb.png \ + docs/delays.png \ + docs/delays_thumb.png \ + docs/disk_io.png \ + docs/hacking.html \ + docs/im_thumb.jpg \ + docs/merkle_tree.png \ + docs/our_delay_base.png \ + docs/our_delay_base_thumb.png \ + docs/read_disk_buffers.png \ + docs/read_disk_buffers.diagram \ + docs/session_stats_peers.png \ + docs/storage.png \ + docs/todo.html \ + docs/write_disk_buffers.png \ + docs/write_disk_buffers.diagram \ + docs/ziptorrent_thumb.gif \ + docs/ip_id_v4.png \ + docs/ip_id_v6.png \ + docs/hash_distribution.png \ + docs/complete_bit_prefixes.png \ + docs/troubleshooting.dot \ + docs/troubleshooting.png \ + docs/troubleshooting_thumb.png \ + docs/hacking.diagram \ + docs/hacking.png \ + docs/disk_cache.diagram \ + docs/disk_cache.png \ + docs/utp_stack.diagram \ + docs/utp_stack.png \ + docs/style.css \ + docs/rst.css \ + docs/img/bg.png \ + docs/img/blue_bottom.png \ + docs/img/blue_top.png \ + docs/img/dotline.gif \ + docs/img/minus.gif \ + docs/img/orange.png + +DOCS_PAGES = \ + docs/building.html \ + docs/client_test.html \ + docs/contributing.html \ + docs/dht_extensions.html \ + docs/dht_rss.html \ + docs/dht_sec.html \ + docs/dht_store.html \ + docs/examples.html \ + docs/extension_protocol.html \ + docs/features.html \ + docs/index.html \ + docs/manual-ref.html \ + docs/projects.html \ + docs/python_binding.html \ + docs/tuning.html \ + docs/settings.rst \ + docs/stats_counters.rst \ + docs/troubleshooting.html \ + docs/udp_tracker_protocol.html \ + docs/utp.html \ + docs/streaming.html \ + docs/building.rst \ + docs/client_test.rst \ + docs/contributing.rst \ + docs/dht_extensions.rst \ + docs/dht_rss.rst \ + docs/dht_sec.rst \ + docs/dht_store.rst \ + docs/examples.rst \ + docs/extension_protocol.rst \ + docs/features.rst \ + docs/index.rst \ + docs/manual-ref.rst \ + docs/projects.rst \ + docs/python_binding.rst \ + docs/tuning.rst \ + docs/troubleshooting.rst \ + docs/udp_tracker_protocol.rst \ + docs/utp.rst \ + docs/streaming.rst \ + docs/tutorial.rst \ + docs/tutorial.html \ + docs/reference-Alerts.html \ + docs/reference-Bdecoding.html \ + docs/reference-Bencoding.html \ + docs/reference-Core.html \ + docs/reference-Create_Torrents.html \ + docs/reference-Custom_Storage.html \ + docs/reference-ed25519.html \ + docs/reference-Error_Codes.html \ + docs/reference-Filter.html \ + docs/reference-Plugins.html \ + docs/reference-Settings.html \ + docs/reference-Storage.html \ + docs/reference-Utility.html \ + docs/reference.html + +ED25519_SOURCE = \ + ed25519/readme.md \ + ed25519/test.c \ + ed25519/src/fe.h \ + ed25519/src/fixedint.h \ + ed25519/src/ge.h \ + ed25519/src/precomp_data.h \ + ed25519/src/sc.h \ + ed25519/src/sha512.h + +EXTRA_DIST = \ + Jamfile \ + Jamroot.jam \ + CMakeLists.txt \ + setup.py \ + LICENSE \ + README.rst \ + $(DOCS_PAGES) \ + $(DOCS_IMAGES) \ + $(ED25519_SOURCE) + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = libtorrent-rasterbar.pc +all: all-recursive + +.SUFFIXES: +am--refresh: Makefile + @: +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + echo ' cd $(srcdir) && $(AUTOMAKE) --foreign'; \ + $(am__cd) $(srcdir) && $(AUTOMAKE) --foreign \ + && exit 0; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + echo ' $(SHELL) ./config.status'; \ + $(SHELL) ./config.status;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + $(SHELL) ./config.status --recheck + +$(top_srcdir)/configure: $(am__configure_deps) + $(am__cd) $(srcdir) && $(AUTOCONF) +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + $(am__cd) $(srcdir) && $(ACLOCAL) $(ACLOCAL_AMFLAGS) +$(am__aclocal_m4_deps): +libtorrent-rasterbar.pc: $(top_builddir)/config.status $(srcdir)/libtorrent-rasterbar.pc.in + cd $(top_builddir) && $(SHELL) ./config.status $@ +libtorrent-rasterbar-cmake.pc: $(top_builddir)/config.status $(srcdir)/libtorrent-rasterbar-cmake.pc.in + cd $(top_builddir) && $(SHELL) ./config.status $@ + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +distclean-libtool: + -rm -f libtool config.lt +install-pkgconfigDATA: $(pkgconfig_DATA) + @$(NORMAL_INSTALL) + @list='$(pkgconfig_DATA)'; test -n "$(pkgconfigdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(pkgconfigdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(pkgconfigdir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(pkgconfigdir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(pkgconfigdir)" || exit $$?; \ + done + +uninstall-pkgconfigDATA: + @$(NORMAL_UNINSTALL) + @list='$(pkgconfig_DATA)'; test -n "$(pkgconfigdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(pkgconfigdir)'; $(am__uninstall_files_from_dir) + +# This directory's subdirectories are mostly independent; you can cd +# into them and run 'make' without going through this Makefile. +# To change the values of 'make' variables: instead of editing Makefiles, +# (1) if the variable is set in 'config.status', edit 'config.status' +# (which will cause the Makefiles to be regenerated when you run 'make'); +# (2) otherwise, pass the desired values on the 'make' command line. +$(am__recursive_targets): + @fail=; \ + if $(am__make_keepgoing); then \ + failcom='fail=yes'; \ + else \ + failcom='exit 1'; \ + fi; \ + dot_seen=no; \ + target=`echo $@ | sed s/-recursive//`; \ + case "$@" in \ + distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \ + *) list='$(SUBDIRS)' ;; \ + esac; \ + for subdir in $$list; do \ + echo "Making $$target in $$subdir"; \ + if test "$$subdir" = "."; then \ + dot_seen=yes; \ + local_target="$$target-am"; \ + else \ + local_target="$$target"; \ + fi; \ + ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ + || eval $$failcom; \ + done; \ + if test "$$dot_seen" = "no"; then \ + $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \ + fi; test -z "$$fail" + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-recursive +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \ + include_option=--etags-include; \ + empty_fix=.; \ + else \ + include_option=--include; \ + empty_fix=; \ + fi; \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + test ! -f $$subdir/TAGS || \ + set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \ + fi; \ + done; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-recursive + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscope: cscope.files + test ! -s cscope.files \ + || $(CSCOPE) -b -q $(AM_CSCOPEFLAGS) $(CSCOPEFLAGS) -i cscope.files $(CSCOPE_ARGS) +clean-cscope: + -rm -f cscope.files +cscope.files: clean-cscope cscopelist +cscopelist: cscopelist-recursive + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + -rm -f cscope.out cscope.in.out cscope.po.out cscope.files + +distdir: $(DISTFILES) + $(am__remove_distdir) + test -d "$(distdir)" || mkdir "$(distdir)" + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done + @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + $(am__make_dryrun) \ + || test -d "$(distdir)/$$subdir" \ + || $(MKDIR_P) "$(distdir)/$$subdir" \ + || exit 1; \ + dir1=$$subdir; dir2="$(distdir)/$$subdir"; \ + $(am__relativize); \ + new_distdir=$$reldir; \ + dir1=$$subdir; dir2="$(top_distdir)"; \ + $(am__relativize); \ + new_top_distdir=$$reldir; \ + echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \ + echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \ + ($(am__cd) $$subdir && \ + $(MAKE) $(AM_MAKEFLAGS) \ + top_distdir="$$new_top_distdir" \ + distdir="$$new_distdir" \ + am__remove_distdir=: \ + am__skip_length_check=: \ + am__skip_mode_fix=: \ + distdir) \ + || exit 1; \ + fi; \ + done + -test -n "$(am__skip_mode_fix)" \ + || find "$(distdir)" -type d ! -perm -755 \ + -exec chmod u+rwx,go+rx {} \; -o \ + ! -type d ! -perm -444 -links 1 -exec chmod a+r {} \; -o \ + ! -type d ! -perm -400 -exec chmod a+r {} \; -o \ + ! -type d ! -perm -444 -exec $(install_sh) -c -m a+r {} {} \; \ + || chmod -R a+r "$(distdir)" +dist-gzip: distdir + tardir=$(distdir) && $(am__tar) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).tar.gz + $(am__post_remove_distdir) + +dist-bzip2: distdir + tardir=$(distdir) && $(am__tar) | BZIP2=$${BZIP2--9} bzip2 -c >$(distdir).tar.bz2 + $(am__post_remove_distdir) + +dist-lzip: distdir + tardir=$(distdir) && $(am__tar) | lzip -c $${LZIP_OPT--9} >$(distdir).tar.lz + $(am__post_remove_distdir) + +dist-xz: distdir + tardir=$(distdir) && $(am__tar) | XZ_OPT=$${XZ_OPT--e} xz -c >$(distdir).tar.xz + $(am__post_remove_distdir) + +dist-tarZ: distdir + @echo WARNING: "Support for distribution archives compressed with" \ + "legacy program 'compress' is deprecated." >&2 + @echo WARNING: "It will be removed altogether in Automake 2.0" >&2 + tardir=$(distdir) && $(am__tar) | compress -c >$(distdir).tar.Z + $(am__post_remove_distdir) + +dist-shar: distdir + @echo WARNING: "Support for shar distribution archives is" \ + "deprecated." >&2 + @echo WARNING: "It will be removed altogether in Automake 2.0" >&2 + shar $(distdir) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).shar.gz + $(am__post_remove_distdir) + +dist-zip: distdir + -rm -f $(distdir).zip + zip -rq $(distdir).zip $(distdir) + $(am__post_remove_distdir) + +dist dist-all: + $(MAKE) $(AM_MAKEFLAGS) $(DIST_TARGETS) am__post_remove_distdir='@:' + $(am__post_remove_distdir) + +# This target untars the dist file and tries a VPATH configuration. Then +# it guarantees that the distribution is self-contained by making another +# tarfile. +distcheck: dist + case '$(DIST_ARCHIVES)' in \ + *.tar.gz*) \ + GZIP=$(GZIP_ENV) gzip -dc $(distdir).tar.gz | $(am__untar) ;;\ + *.tar.bz2*) \ + bzip2 -dc $(distdir).tar.bz2 | $(am__untar) ;;\ + *.tar.lz*) \ + lzip -dc $(distdir).tar.lz | $(am__untar) ;;\ + *.tar.xz*) \ + xz -dc $(distdir).tar.xz | $(am__untar) ;;\ + *.tar.Z*) \ + uncompress -c $(distdir).tar.Z | $(am__untar) ;;\ + *.shar.gz*) \ + GZIP=$(GZIP_ENV) gzip -dc $(distdir).shar.gz | unshar ;;\ + *.zip*) \ + unzip $(distdir).zip ;;\ + esac + chmod -R a-w $(distdir) + chmod u+w $(distdir) + mkdir $(distdir)/_build $(distdir)/_build/sub $(distdir)/_inst + chmod a-w $(distdir) + test -d $(distdir)/_build || exit 0; \ + dc_install_base=`$(am__cd) $(distdir)/_inst && pwd | sed -e 's,^[^:\\/]:[\\/],/,'` \ + && dc_destdir="$${TMPDIR-/tmp}/am-dc-$$$$/" \ + && am__cwd=`pwd` \ + && $(am__cd) $(distdir)/_build/sub \ + && ../../configure \ + $(AM_DISTCHECK_CONFIGURE_FLAGS) \ + $(DISTCHECK_CONFIGURE_FLAGS) \ + --srcdir=../.. --prefix="$$dc_install_base" \ + && $(MAKE) $(AM_MAKEFLAGS) \ + && $(MAKE) $(AM_MAKEFLAGS) dvi \ + && $(MAKE) $(AM_MAKEFLAGS) check \ + && $(MAKE) $(AM_MAKEFLAGS) install \ + && $(MAKE) $(AM_MAKEFLAGS) installcheck \ + && $(MAKE) $(AM_MAKEFLAGS) uninstall \ + && $(MAKE) $(AM_MAKEFLAGS) distuninstallcheck_dir="$$dc_install_base" \ + distuninstallcheck \ + && chmod -R a-w "$$dc_install_base" \ + && ({ \ + (cd ../.. && umask 077 && mkdir "$$dc_destdir") \ + && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" install \ + && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" uninstall \ + && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" \ + distuninstallcheck_dir="$$dc_destdir" distuninstallcheck; \ + } || { rm -rf "$$dc_destdir"; exit 1; }) \ + && rm -rf "$$dc_destdir" \ + && $(MAKE) $(AM_MAKEFLAGS) dist \ + && rm -rf $(DIST_ARCHIVES) \ + && $(MAKE) $(AM_MAKEFLAGS) distcleancheck \ + && cd "$$am__cwd" \ + || exit 1 + $(am__post_remove_distdir) + @(echo "$(distdir) archives ready for distribution: "; \ + list='$(DIST_ARCHIVES)'; for i in $$list; do echo $$i; done) | \ + sed -e 1h -e 1s/./=/g -e 1p -e 1x -e '$$p' -e '$$x' +distuninstallcheck: + @test -n '$(distuninstallcheck_dir)' || { \ + echo 'ERROR: trying to run $@ with an empty' \ + '$$(distuninstallcheck_dir)' >&2; \ + exit 1; \ + }; \ + $(am__cd) '$(distuninstallcheck_dir)' || { \ + echo 'ERROR: cannot chdir into $(distuninstallcheck_dir)' >&2; \ + exit 1; \ + }; \ + test `$(am__distuninstallcheck_listfiles) | wc -l` -eq 0 \ + || { echo "ERROR: files left after uninstall:" ; \ + if test -n "$(DESTDIR)"; then \ + echo " (check DESTDIR support)"; \ + fi ; \ + $(distuninstallcheck_listfiles) ; \ + exit 1; } >&2 +distcleancheck: distclean + @if test '$(srcdir)' = . ; then \ + echo "ERROR: distcleancheck can only run from a VPATH build" ; \ + exit 1 ; \ + fi + @test `$(distcleancheck_listfiles) | wc -l` -eq 0 \ + || { echo "ERROR: files left in build directory after distclean:" ; \ + $(distcleancheck_listfiles) ; \ + exit 1; } >&2 +check-am: all-am +check: check-recursive +all-am: Makefile $(DATA) +installdirs: installdirs-recursive +installdirs-am: + for dir in "$(DESTDIR)$(pkgconfigdir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-recursive +install-exec: install-exec-recursive +install-data: install-data-recursive +uninstall: uninstall-recursive + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-recursive +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-recursive + +clean-am: clean-generic clean-libtool mostlyclean-am + +distclean: distclean-recursive + -rm -f $(am__CONFIG_DISTCLEAN_FILES) + -rm -f Makefile +distclean-am: clean-am distclean-generic distclean-libtool \ + distclean-tags + +dvi: dvi-recursive + +dvi-am: + +html: html-recursive + +html-am: + +info: info-recursive + +info-am: + +install-data-am: install-pkgconfigDATA + +install-dvi: install-dvi-recursive + +install-dvi-am: + +install-exec-am: + +install-html: install-html-recursive + +install-html-am: + +install-info: install-info-recursive + +install-info-am: + +install-man: + +install-pdf: install-pdf-recursive + +install-pdf-am: + +install-ps: install-ps-recursive + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-recursive + -rm -f $(am__CONFIG_DISTCLEAN_FILES) + -rm -rf $(top_srcdir)/autom4te.cache + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-recursive + +mostlyclean-am: mostlyclean-generic mostlyclean-libtool + +pdf: pdf-recursive + +pdf-am: + +ps: ps-recursive + +ps-am: + +uninstall-am: uninstall-pkgconfigDATA + +.MAKE: $(am__recursive_targets) install-am install-strip + +.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \ + am--refresh check check-am clean clean-cscope clean-generic \ + clean-libtool cscope cscopelist-am ctags ctags-am dist \ + dist-all dist-bzip2 dist-gzip dist-lzip dist-shar dist-tarZ \ + dist-xz dist-zip distcheck distclean distclean-generic \ + distclean-libtool distclean-tags distcleancheck distdir \ + distuninstallcheck dvi dvi-am html html-am info info-am \ + install install-am install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-pkgconfigDATA install-ps \ + install-ps-am install-strip installcheck installcheck-am \ + installdirs installdirs-am maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-generic \ + mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \ + uninstall-am uninstall-pkgconfigDATA + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..58378c1 --- /dev/null +++ b/NEWS @@ -0,0 +1 @@ +See ChangeLog diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..90bad7e --- /dev/null +++ b/README.rst @@ -0,0 +1,59 @@ +libtorrent +---------- + +.. image:: https://travis-ci.org/arvidn/libtorrent.svg?branch=master + :target: https://travis-ci.org/arvidn/libtorrent + +.. image:: https://ci.appveyor.com/api/projects/status/w7teauvub5813mew/branch/master?svg=true + :target: https://ci.appveyor.com/project/arvidn/libtorrent/branch/master + +.. image:: https://codecov.io/github/arvidn/libtorrent/coverage.svg?branch=master + :target: https://codecov.io/github/arvidn/libtorrent?branch=master&view=all#sort=missing&dir=desc + +.. image:: https://www.openhub.net/p/rasterbar-libtorrent/widgets/project_thin_badge.gif + :target: https://www.openhub.net/p/rasterbar-libtorrent?ref=sample + +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`__. + +.. __: docs/building.rst +.. __: docs/python_binding.rst + +pull request checklist +...................... + +When creating a pull request, please consider the following checklist: + +* make sure both travis-CI and appveyor builds are green. 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) +* If adding a user-facing feature, please add brief entry to ``ChangeLog`` +* Add a unit test to confirm the new behavior or feature. Don't forget negative + tests (i.e. failure cases) and please pay as much care to tests as you would + production code. +* rebase on top of master periodically +* if your patch is against the current stable release branch, please also + forward-port the patch to master (at the time of this writing, automatic + merge in git does not work, possibly because the branch was created in svn) +* if your patch adds a new .cpp file, please make sure it's added to the + appropriate ``Jamfile``, ``Makefile.am`` and ``CMakeList.txt``. If it's adding + a header file, make sure it's added to ``include/libtorrent/Makefile.am``. diff --git a/aclocal.m4 b/aclocal.m4 new file mode 100644 index 0000000..826fb0b --- /dev/null +++ b/aclocal.m4 @@ -0,0 +1,1403 @@ +# generated automatically by aclocal 1.15 -*- Autoconf -*- + +# Copyright (C) 1996-2014 Free Software Foundation, Inc. + +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +m4_ifndef([AC_CONFIG_MACRO_DIRS], [m4_defun([_AM_CONFIG_MACRO_DIRS], [])m4_defun([AC_CONFIG_MACRO_DIRS], [_AM_CONFIG_MACRO_DIRS($@)])]) +m4_ifndef([AC_AUTOCONF_VERSION], + [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl +m4_if(m4_defn([AC_AUTOCONF_VERSION]), [2.69],, +[m4_warning([this file was generated for autoconf 2.69. +You have another version of autoconf. It may work, but is not guaranteed to. +If you have problems, you may need to regenerate the build system entirely. +To do so, use the procedure documented by the package, typically 'autoreconf'.])]) + +# Copyright (C) 2002-2014 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_AUTOMAKE_VERSION(VERSION) +# ---------------------------- +# Automake X.Y traces this macro to ensure aclocal.m4 has been +# generated from the m4 files accompanying Automake X.Y. +# (This private macro should not be called outside this file.) +AC_DEFUN([AM_AUTOMAKE_VERSION], +[am__api_version='1.15' +dnl Some users find AM_AUTOMAKE_VERSION and mistake it for a way to +dnl require some minimum version. Point them to the right macro. +m4_if([$1], [1.15], [], + [AC_FATAL([Do not call $0, use AM_INIT_AUTOMAKE([$1]).])])dnl +]) + +# _AM_AUTOCONF_VERSION(VERSION) +# ----------------------------- +# aclocal traces this macro to find the Autoconf version. +# This is a private macro too. Using m4_define simplifies +# the logic in aclocal, which can simply ignore this definition. +m4_define([_AM_AUTOCONF_VERSION], []) + +# AM_SET_CURRENT_AUTOMAKE_VERSION +# ------------------------------- +# Call AM_AUTOMAKE_VERSION and AM_AUTOMAKE_VERSION so they can be traced. +# This function is AC_REQUIREd by AM_INIT_AUTOMAKE. +AC_DEFUN([AM_SET_CURRENT_AUTOMAKE_VERSION], +[AM_AUTOMAKE_VERSION([1.15])dnl +m4_ifndef([AC_AUTOCONF_VERSION], + [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl +_AM_AUTOCONF_VERSION(m4_defn([AC_AUTOCONF_VERSION]))]) + +# AM_AUX_DIR_EXPAND -*- Autoconf -*- + +# Copyright (C) 2001-2014 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# For projects using AC_CONFIG_AUX_DIR([foo]), Autoconf sets +# $ac_aux_dir to '$srcdir/foo'. In other projects, it is set to +# '$srcdir', '$srcdir/..', or '$srcdir/../..'. +# +# Of course, Automake must honor this variable whenever it calls a +# tool from the auxiliary directory. The problem is that $srcdir (and +# therefore $ac_aux_dir as well) can be either absolute or relative, +# depending on how configure is run. This is pretty annoying, since +# it makes $ac_aux_dir quite unusable in subdirectories: in the top +# source directory, any form will work fine, but in subdirectories a +# relative path needs to be adjusted first. +# +# $ac_aux_dir/missing +# fails when called from a subdirectory if $ac_aux_dir is relative +# $top_srcdir/$ac_aux_dir/missing +# fails if $ac_aux_dir is absolute, +# fails when called from a subdirectory in a VPATH build with +# a relative $ac_aux_dir +# +# The reason of the latter failure is that $top_srcdir and $ac_aux_dir +# are both prefixed by $srcdir. In an in-source build this is usually +# harmless because $srcdir is '.', but things will broke when you +# start a VPATH build or use an absolute $srcdir. +# +# So we could use something similar to $top_srcdir/$ac_aux_dir/missing, +# iff we strip the leading $srcdir from $ac_aux_dir. That would be: +# am_aux_dir='\$(top_srcdir)/'`expr "$ac_aux_dir" : "$srcdir//*\(.*\)"` +# and then we would define $MISSING as +# MISSING="\${SHELL} $am_aux_dir/missing" +# This will work as long as MISSING is not called from configure, because +# unfortunately $(top_srcdir) has no meaning in configure. +# However there are other variables, like CC, which are often used in +# configure, and could therefore not use this "fixed" $ac_aux_dir. +# +# Another solution, used here, is to always expand $ac_aux_dir to an +# absolute PATH. The drawback is that using absolute paths prevent a +# configured tree to be moved without reconfiguration. + +AC_DEFUN([AM_AUX_DIR_EXPAND], +[AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT])dnl +# Expand $ac_aux_dir to an absolute path. +am_aux_dir=`cd "$ac_aux_dir" && pwd` +]) + +# AM_CONDITIONAL -*- Autoconf -*- + +# Copyright (C) 1997-2014 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_CONDITIONAL(NAME, SHELL-CONDITION) +# ------------------------------------- +# Define a conditional. +AC_DEFUN([AM_CONDITIONAL], +[AC_PREREQ([2.52])dnl + m4_if([$1], [TRUE], [AC_FATAL([$0: invalid condition: $1])], + [$1], [FALSE], [AC_FATAL([$0: invalid condition: $1])])dnl +AC_SUBST([$1_TRUE])dnl +AC_SUBST([$1_FALSE])dnl +_AM_SUBST_NOTMAKE([$1_TRUE])dnl +_AM_SUBST_NOTMAKE([$1_FALSE])dnl +m4_define([_AM_COND_VALUE_$1], [$2])dnl +if $2; then + $1_TRUE= + $1_FALSE='#' +else + $1_TRUE='#' + $1_FALSE= +fi +AC_CONFIG_COMMANDS_PRE( +[if test -z "${$1_TRUE}" && test -z "${$1_FALSE}"; then + AC_MSG_ERROR([[conditional "$1" was never defined. +Usually this means the macro was only invoked conditionally.]]) +fi])]) + +# Copyright (C) 1999-2014 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + + +# There are a few dirty hacks below to avoid letting 'AC_PROG_CC' be +# written in clear, in which case automake, when reading aclocal.m4, +# will think it sees a *use*, and therefore will trigger all it's +# C support machinery. Also note that it means that autoscan, seeing +# CC etc. in the Makefile, will ask for an AC_PROG_CC use... + + +# _AM_DEPENDENCIES(NAME) +# ---------------------- +# See how the compiler implements dependency checking. +# NAME is "CC", "CXX", "OBJC", "OBJCXX", "UPC", or "GJC". +# We try a few techniques and use that to set a single cache variable. +# +# We don't AC_REQUIRE the corresponding AC_PROG_CC since the latter was +# modified to invoke _AM_DEPENDENCIES(CC); we would have a circular +# dependency, and given that the user is not expected to run this macro, +# just rely on AC_PROG_CC. +AC_DEFUN([_AM_DEPENDENCIES], +[AC_REQUIRE([AM_SET_DEPDIR])dnl +AC_REQUIRE([AM_OUTPUT_DEPENDENCY_COMMANDS])dnl +AC_REQUIRE([AM_MAKE_INCLUDE])dnl +AC_REQUIRE([AM_DEP_TRACK])dnl + +m4_if([$1], [CC], [depcc="$CC" am_compiler_list=], + [$1], [CXX], [depcc="$CXX" am_compiler_list=], + [$1], [OBJC], [depcc="$OBJC" am_compiler_list='gcc3 gcc'], + [$1], [OBJCXX], [depcc="$OBJCXX" am_compiler_list='gcc3 gcc'], + [$1], [UPC], [depcc="$UPC" am_compiler_list=], + [$1], [GCJ], [depcc="$GCJ" am_compiler_list='gcc3 gcc'], + [depcc="$$1" am_compiler_list=]) + +AC_CACHE_CHECK([dependency style of $depcc], + [am_cv_$1_dependencies_compiler_type], +[if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then + # We make a subdir and do the tests there. Otherwise we can end up + # making bogus files that we don't know about and never remove. For + # instance it was reported that on HP-UX the gcc test will end up + # making a dummy file named 'D' -- because '-MD' means "put the output + # in D". + rm -rf conftest.dir + mkdir conftest.dir + # Copy depcomp to subdir because otherwise we won't find it if we're + # using a relative directory. + cp "$am_depcomp" conftest.dir + cd conftest.dir + # We will build objects and dependencies in a subdirectory because + # it helps to detect inapplicable dependency modes. For instance + # both Tru64's cc and ICC support -MD to output dependencies as a + # side effect of compilation, but ICC will put the dependencies in + # the current directory while Tru64 will put them in the object + # directory. + mkdir sub + + am_cv_$1_dependencies_compiler_type=none + if test "$am_compiler_list" = ""; then + am_compiler_list=`sed -n ['s/^#*\([a-zA-Z0-9]*\))$/\1/p'] < ./depcomp` + fi + am__universal=false + m4_case([$1], [CC], + [case " $depcc " in #( + *\ -arch\ *\ -arch\ *) am__universal=true ;; + esac], + [CXX], + [case " $depcc " in #( + *\ -arch\ *\ -arch\ *) am__universal=true ;; + esac]) + + for depmode in $am_compiler_list; do + # Setup a source with many dependencies, because some compilers + # like to wrap large dependency lists on column 80 (with \), and + # we should not choose a depcomp mode which is confused by this. + # + # We need to recreate these files for each test, as the compiler may + # overwrite some of them when testing with obscure command lines. + # This happens at least with the AIX C compiler. + : > sub/conftest.c + for i in 1 2 3 4 5 6; do + echo '#include "conftst'$i'.h"' >> sub/conftest.c + # Using ": > sub/conftst$i.h" creates only sub/conftst1.h with + # Solaris 10 /bin/sh. + echo '/* dummy */' > sub/conftst$i.h + done + echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf + + # We check with '-c' and '-o' for the sake of the "dashmstdout" + # mode. It turns out that the SunPro C++ compiler does not properly + # handle '-M -o', and we need to detect this. Also, some Intel + # versions had trouble with output in subdirs. + am__obj=sub/conftest.${OBJEXT-o} + am__minus_obj="-o $am__obj" + case $depmode in + gcc) + # This depmode causes a compiler race in universal mode. + test "$am__universal" = false || continue + ;; + nosideeffect) + # After this tag, mechanisms are not by side-effect, so they'll + # only be used when explicitly requested. + if test "x$enable_dependency_tracking" = xyes; then + continue + else + break + fi + ;; + msvc7 | msvc7msys | msvisualcpp | msvcmsys) + # This compiler won't grok '-c -o', but also, the minuso test has + # not run yet. These depmodes are late enough in the game, and + # so weak that their functioning should not be impacted. + am__obj=conftest.${OBJEXT-o} + am__minus_obj= + ;; + none) break ;; + esac + if depmode=$depmode \ + source=sub/conftest.c object=$am__obj \ + depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \ + $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \ + >/dev/null 2>conftest.err && + grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 && + grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 && + grep $am__obj sub/conftest.Po > /dev/null 2>&1 && + ${MAKE-make} -s -f confmf > /dev/null 2>&1; then + # icc doesn't choke on unknown options, it will just issue warnings + # or remarks (even with -Werror). So we grep stderr for any message + # that says an option was ignored or not supported. + # When given -MP, icc 7.0 and 7.1 complain thusly: + # icc: Command line warning: ignoring option '-M'; no argument required + # The diagnosis changed in icc 8.0: + # icc: Command line remark: option '-MP' not supported + if (grep 'ignoring option' conftest.err || + grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else + am_cv_$1_dependencies_compiler_type=$depmode + break + fi + fi + done + + cd .. + rm -rf conftest.dir +else + am_cv_$1_dependencies_compiler_type=none +fi +]) +AC_SUBST([$1DEPMODE], [depmode=$am_cv_$1_dependencies_compiler_type]) +AM_CONDITIONAL([am__fastdep$1], [ + test "x$enable_dependency_tracking" != xno \ + && test "$am_cv_$1_dependencies_compiler_type" = gcc3]) +]) + + +# AM_SET_DEPDIR +# ------------- +# Choose a directory name for dependency files. +# This macro is AC_REQUIREd in _AM_DEPENDENCIES. +AC_DEFUN([AM_SET_DEPDIR], +[AC_REQUIRE([AM_SET_LEADING_DOT])dnl +AC_SUBST([DEPDIR], ["${am__leading_dot}deps"])dnl +]) + + +# AM_DEP_TRACK +# ------------ +AC_DEFUN([AM_DEP_TRACK], +[AC_ARG_ENABLE([dependency-tracking], [dnl +AS_HELP_STRING( + [--enable-dependency-tracking], + [do not reject slow dependency extractors]) +AS_HELP_STRING( + [--disable-dependency-tracking], + [speeds up one-time build])]) +if test "x$enable_dependency_tracking" != xno; then + am_depcomp="$ac_aux_dir/depcomp" + AMDEPBACKSLASH='\' + am__nodep='_no' +fi +AM_CONDITIONAL([AMDEP], [test "x$enable_dependency_tracking" != xno]) +AC_SUBST([AMDEPBACKSLASH])dnl +_AM_SUBST_NOTMAKE([AMDEPBACKSLASH])dnl +AC_SUBST([am__nodep])dnl +_AM_SUBST_NOTMAKE([am__nodep])dnl +]) + +# Generate code to set up dependency tracking. -*- Autoconf -*- + +# Copyright (C) 1999-2014 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + + +# _AM_OUTPUT_DEPENDENCY_COMMANDS +# ------------------------------ +AC_DEFUN([_AM_OUTPUT_DEPENDENCY_COMMANDS], +[{ + # Older Autoconf quotes --file arguments for eval, but not when files + # are listed without --file. Let's play safe and only enable the eval + # if we detect the quoting. + case $CONFIG_FILES in + *\'*) eval set x "$CONFIG_FILES" ;; + *) set x $CONFIG_FILES ;; + esac + shift + for mf + do + # Strip MF so we end up with the name of the file. + mf=`echo "$mf" | sed -e 's/:.*$//'` + # Check whether this is an Automake generated Makefile or not. + # We used to match only the files named 'Makefile.in', but + # some people rename them; so instead we look at the file content. + # Grep'ing the first line is not enough: some people post-process + # each Makefile.in and add a new line on top of each file to say so. + # Grep'ing the whole file is not good either: AIX grep has a line + # limit of 2048, but all sed's we know have understand at least 4000. + if sed -n 's,^#.*generated by automake.*,X,p' "$mf" | grep X >/dev/null 2>&1; then + dirpart=`AS_DIRNAME("$mf")` + else + continue + fi + # Extract the definition of DEPDIR, am__include, and am__quote + # from the Makefile without running 'make'. + DEPDIR=`sed -n 's/^DEPDIR = //p' < "$mf"` + test -z "$DEPDIR" && continue + am__include=`sed -n 's/^am__include = //p' < "$mf"` + test -z "$am__include" && continue + am__quote=`sed -n 's/^am__quote = //p' < "$mf"` + # Find all dependency output files, they are included files with + # $(DEPDIR) in their names. We invoke sed twice because it is the + # simplest approach to changing $(DEPDIR) to its actual value in the + # expansion. + for file in `sed -n " + s/^$am__include $am__quote\(.*(DEPDIR).*\)$am__quote"'$/\1/p' <"$mf" | \ + sed -e 's/\$(DEPDIR)/'"$DEPDIR"'/g'`; do + # Make sure the directory exists. + test -f "$dirpart/$file" && continue + fdir=`AS_DIRNAME(["$file"])` + AS_MKDIR_P([$dirpart/$fdir]) + # echo "creating $dirpart/$file" + echo '# dummy' > "$dirpart/$file" + done + done +} +])# _AM_OUTPUT_DEPENDENCY_COMMANDS + + +# AM_OUTPUT_DEPENDENCY_COMMANDS +# ----------------------------- +# This macro should only be invoked once -- use via AC_REQUIRE. +# +# This code is only required when automatic dependency tracking +# is enabled. FIXME. This creates each '.P' file that we will +# need in order to bootstrap the dependency handling code. +AC_DEFUN([AM_OUTPUT_DEPENDENCY_COMMANDS], +[AC_CONFIG_COMMANDS([depfiles], + [test x"$AMDEP_TRUE" != x"" || _AM_OUTPUT_DEPENDENCY_COMMANDS], + [AMDEP_TRUE="$AMDEP_TRUE" ac_aux_dir="$ac_aux_dir"]) +]) + +# Do all the work for Automake. -*- Autoconf -*- + +# Copyright (C) 1996-2014 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This macro actually does too much. Some checks are only needed if +# your package does certain things. But this isn't really a big deal. + +dnl Redefine AC_PROG_CC to automatically invoke _AM_PROG_CC_C_O. +m4_define([AC_PROG_CC], +m4_defn([AC_PROG_CC]) +[_AM_PROG_CC_C_O +]) + +# AM_INIT_AUTOMAKE(PACKAGE, VERSION, [NO-DEFINE]) +# AM_INIT_AUTOMAKE([OPTIONS]) +# ----------------------------------------------- +# The call with PACKAGE and VERSION arguments is the old style +# call (pre autoconf-2.50), which is being phased out. PACKAGE +# and VERSION should now be passed to AC_INIT and removed from +# the call to AM_INIT_AUTOMAKE. +# We support both call styles for the transition. After +# the next Automake release, Autoconf can make the AC_INIT +# arguments mandatory, and then we can depend on a new Autoconf +# release and drop the old call support. +AC_DEFUN([AM_INIT_AUTOMAKE], +[AC_PREREQ([2.65])dnl +dnl Autoconf wants to disallow AM_ names. We explicitly allow +dnl the ones we care about. +m4_pattern_allow([^AM_[A-Z]+FLAGS$])dnl +AC_REQUIRE([AM_SET_CURRENT_AUTOMAKE_VERSION])dnl +AC_REQUIRE([AC_PROG_INSTALL])dnl +if test "`cd $srcdir && pwd`" != "`pwd`"; then + # Use -I$(srcdir) only when $(srcdir) != ., so that make's output + # is not polluted with repeated "-I." + AC_SUBST([am__isrc], [' -I$(srcdir)'])_AM_SUBST_NOTMAKE([am__isrc])dnl + # test to see if srcdir already configured + if test -f $srcdir/config.status; then + AC_MSG_ERROR([source directory already configured; run "make distclean" there first]) + fi +fi + +# test whether we have cygpath +if test -z "$CYGPATH_W"; then + if (cygpath --version) >/dev/null 2>/dev/null; then + CYGPATH_W='cygpath -w' + else + CYGPATH_W=echo + fi +fi +AC_SUBST([CYGPATH_W]) + +# Define the identity of the package. +dnl Distinguish between old-style and new-style calls. +m4_ifval([$2], +[AC_DIAGNOSE([obsolete], + [$0: two- and three-arguments forms are deprecated.]) +m4_ifval([$3], [_AM_SET_OPTION([no-define])])dnl + AC_SUBST([PACKAGE], [$1])dnl + AC_SUBST([VERSION], [$2])], +[_AM_SET_OPTIONS([$1])dnl +dnl Diagnose old-style AC_INIT with new-style AM_AUTOMAKE_INIT. +m4_if( + m4_ifdef([AC_PACKAGE_NAME], [ok]):m4_ifdef([AC_PACKAGE_VERSION], [ok]), + [ok:ok],, + [m4_fatal([AC_INIT should be called with package and version arguments])])dnl + AC_SUBST([PACKAGE], ['AC_PACKAGE_TARNAME'])dnl + AC_SUBST([VERSION], ['AC_PACKAGE_VERSION'])])dnl + +_AM_IF_OPTION([no-define],, +[AC_DEFINE_UNQUOTED([PACKAGE], ["$PACKAGE"], [Name of package]) + AC_DEFINE_UNQUOTED([VERSION], ["$VERSION"], [Version number of package])])dnl + +# Some tools Automake needs. +AC_REQUIRE([AM_SANITY_CHECK])dnl +AC_REQUIRE([AC_ARG_PROGRAM])dnl +AM_MISSING_PROG([ACLOCAL], [aclocal-${am__api_version}]) +AM_MISSING_PROG([AUTOCONF], [autoconf]) +AM_MISSING_PROG([AUTOMAKE], [automake-${am__api_version}]) +AM_MISSING_PROG([AUTOHEADER], [autoheader]) +AM_MISSING_PROG([MAKEINFO], [makeinfo]) +AC_REQUIRE([AM_PROG_INSTALL_SH])dnl +AC_REQUIRE([AM_PROG_INSTALL_STRIP])dnl +AC_REQUIRE([AC_PROG_MKDIR_P])dnl +# For better backward compatibility. To be removed once Automake 1.9.x +# dies out for good. For more background, see: +# +# +AC_SUBST([mkdir_p], ['$(MKDIR_P)']) +# We need awk for the "check" target (and possibly the TAP driver). The +# system "awk" is bad on some platforms. +AC_REQUIRE([AC_PROG_AWK])dnl +AC_REQUIRE([AC_PROG_MAKE_SET])dnl +AC_REQUIRE([AM_SET_LEADING_DOT])dnl +_AM_IF_OPTION([tar-ustar], [_AM_PROG_TAR([ustar])], + [_AM_IF_OPTION([tar-pax], [_AM_PROG_TAR([pax])], + [_AM_PROG_TAR([v7])])]) +_AM_IF_OPTION([no-dependencies],, +[AC_PROVIDE_IFELSE([AC_PROG_CC], + [_AM_DEPENDENCIES([CC])], + [m4_define([AC_PROG_CC], + m4_defn([AC_PROG_CC])[_AM_DEPENDENCIES([CC])])])dnl +AC_PROVIDE_IFELSE([AC_PROG_CXX], + [_AM_DEPENDENCIES([CXX])], + [m4_define([AC_PROG_CXX], + m4_defn([AC_PROG_CXX])[_AM_DEPENDENCIES([CXX])])])dnl +AC_PROVIDE_IFELSE([AC_PROG_OBJC], + [_AM_DEPENDENCIES([OBJC])], + [m4_define([AC_PROG_OBJC], + m4_defn([AC_PROG_OBJC])[_AM_DEPENDENCIES([OBJC])])])dnl +AC_PROVIDE_IFELSE([AC_PROG_OBJCXX], + [_AM_DEPENDENCIES([OBJCXX])], + [m4_define([AC_PROG_OBJCXX], + m4_defn([AC_PROG_OBJCXX])[_AM_DEPENDENCIES([OBJCXX])])])dnl +]) +AC_REQUIRE([AM_SILENT_RULES])dnl +dnl The testsuite driver may need to know about EXEEXT, so add the +dnl 'am__EXEEXT' conditional if _AM_COMPILER_EXEEXT was seen. This +dnl macro is hooked onto _AC_COMPILER_EXEEXT early, see below. +AC_CONFIG_COMMANDS_PRE(dnl +[m4_provide_if([_AM_COMPILER_EXEEXT], + [AM_CONDITIONAL([am__EXEEXT], [test -n "$EXEEXT"])])])dnl + +# POSIX will say in a future version that running "rm -f" with no argument +# is OK; and we want to be able to make that assumption in our Makefile +# recipes. So use an aggressive probe to check that the usage we want is +# actually supported "in the wild" to an acceptable degree. +# See automake bug#10828. +# To make any issue more visible, cause the running configure to be aborted +# by default if the 'rm' program in use doesn't match our expectations; the +# user can still override this though. +if rm -f && rm -fr && rm -rf; then : OK; else + cat >&2 <<'END' +Oops! + +Your 'rm' program seems unable to run without file operands specified +on the command line, even when the '-f' option is present. This is contrary +to the behaviour of most rm programs out there, and not conforming with +the upcoming POSIX standard: + +Please tell bug-automake@gnu.org about your system, including the value +of your $PATH and any error possibly output before this message. This +can help us improve future automake versions. + +END + if test x"$ACCEPT_INFERIOR_RM_PROGRAM" = x"yes"; then + echo 'Configuration will proceed anyway, since you have set the' >&2 + echo 'ACCEPT_INFERIOR_RM_PROGRAM variable to "yes"' >&2 + echo >&2 + else + cat >&2 <<'END' +Aborting the configuration process, to ensure you take notice of the issue. + +You can download and install GNU coreutils to get an 'rm' implementation +that behaves properly: . + +If you want to complete the configuration process using your problematic +'rm' anyway, export the environment variable ACCEPT_INFERIOR_RM_PROGRAM +to "yes", and re-run configure. + +END + AC_MSG_ERROR([Your 'rm' program is bad, sorry.]) + fi +fi +dnl The trailing newline in this macro's definition is deliberate, for +dnl backward compatibility and to allow trailing 'dnl'-style comments +dnl after the AM_INIT_AUTOMAKE invocation. See automake bug#16841. +]) + +dnl Hook into '_AC_COMPILER_EXEEXT' early to learn its expansion. Do not +dnl add the conditional right here, as _AC_COMPILER_EXEEXT may be further +dnl mangled by Autoconf and run in a shell conditional statement. +m4_define([_AC_COMPILER_EXEEXT], +m4_defn([_AC_COMPILER_EXEEXT])[m4_provide([_AM_COMPILER_EXEEXT])]) + +# When config.status generates a header, we must update the stamp-h file. +# This file resides in the same directory as the config header +# that is generated. The stamp files are numbered to have different names. + +# Autoconf calls _AC_AM_CONFIG_HEADER_HOOK (when defined) in the +# loop where config.status creates the headers, so we can generate +# our stamp files there. +AC_DEFUN([_AC_AM_CONFIG_HEADER_HOOK], +[# Compute $1's index in $config_headers. +_am_arg=$1 +_am_stamp_count=1 +for _am_header in $config_headers :; do + case $_am_header in + $_am_arg | $_am_arg:* ) + break ;; + * ) + _am_stamp_count=`expr $_am_stamp_count + 1` ;; + esac +done +echo "timestamp for $_am_arg" >`AS_DIRNAME(["$_am_arg"])`/stamp-h[]$_am_stamp_count]) + +# Copyright (C) 2001-2014 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_PROG_INSTALL_SH +# ------------------ +# Define $install_sh. +AC_DEFUN([AM_PROG_INSTALL_SH], +[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl +if test x"${install_sh+set}" != xset; then + case $am_aux_dir in + *\ * | *\ *) + install_sh="\${SHELL} '$am_aux_dir/install-sh'" ;; + *) + install_sh="\${SHELL} $am_aux_dir/install-sh" + esac +fi +AC_SUBST([install_sh])]) + +# Copyright (C) 2003-2014 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# Check whether the underlying file-system supports filenames +# with a leading dot. For instance MS-DOS doesn't. +AC_DEFUN([AM_SET_LEADING_DOT], +[rm -rf .tst 2>/dev/null +mkdir .tst 2>/dev/null +if test -d .tst; then + am__leading_dot=. +else + am__leading_dot=_ +fi +rmdir .tst 2>/dev/null +AC_SUBST([am__leading_dot])]) + +# Check to see how 'make' treats includes. -*- Autoconf -*- + +# Copyright (C) 2001-2014 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_MAKE_INCLUDE() +# ----------------- +# Check to see how make treats includes. +AC_DEFUN([AM_MAKE_INCLUDE], +[am_make=${MAKE-make} +cat > confinc << 'END' +am__doit: + @echo this is the am__doit target +.PHONY: am__doit +END +# If we don't find an include directive, just comment out the code. +AC_MSG_CHECKING([for style of include used by $am_make]) +am__include="#" +am__quote= +_am_result=none +# First try GNU make style include. +echo "include confinc" > confmf +# Ignore all kinds of additional output from 'make'. +case `$am_make -s -f confmf 2> /dev/null` in #( +*the\ am__doit\ target*) + am__include=include + am__quote= + _am_result=GNU + ;; +esac +# Now try BSD make style include. +if test "$am__include" = "#"; then + echo '.include "confinc"' > confmf + case `$am_make -s -f confmf 2> /dev/null` in #( + *the\ am__doit\ target*) + am__include=.include + am__quote="\"" + _am_result=BSD + ;; + esac +fi +AC_SUBST([am__include]) +AC_SUBST([am__quote]) +AC_MSG_RESULT([$_am_result]) +rm -f confinc confmf +]) + +# Fake the existence of programs that GNU maintainers use. -*- Autoconf -*- + +# Copyright (C) 1997-2014 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_MISSING_PROG(NAME, PROGRAM) +# ------------------------------ +AC_DEFUN([AM_MISSING_PROG], +[AC_REQUIRE([AM_MISSING_HAS_RUN]) +$1=${$1-"${am_missing_run}$2"} +AC_SUBST($1)]) + +# AM_MISSING_HAS_RUN +# ------------------ +# Define MISSING if not defined so far and test if it is modern enough. +# If it is, set am_missing_run to use it, otherwise, to nothing. +AC_DEFUN([AM_MISSING_HAS_RUN], +[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl +AC_REQUIRE_AUX_FILE([missing])dnl +if test x"${MISSING+set}" != xset; then + case $am_aux_dir in + *\ * | *\ *) + MISSING="\${SHELL} \"$am_aux_dir/missing\"" ;; + *) + MISSING="\${SHELL} $am_aux_dir/missing" ;; + esac +fi +# Use eval to expand $SHELL +if eval "$MISSING --is-lightweight"; then + am_missing_run="$MISSING " +else + am_missing_run= + AC_MSG_WARN(['missing' script is too old or missing]) +fi +]) + +# Helper functions for option handling. -*- Autoconf -*- + +# Copyright (C) 2001-2014 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# _AM_MANGLE_OPTION(NAME) +# ----------------------- +AC_DEFUN([_AM_MANGLE_OPTION], +[[_AM_OPTION_]m4_bpatsubst($1, [[^a-zA-Z0-9_]], [_])]) + +# _AM_SET_OPTION(NAME) +# -------------------- +# Set option NAME. Presently that only means defining a flag for this option. +AC_DEFUN([_AM_SET_OPTION], +[m4_define(_AM_MANGLE_OPTION([$1]), [1])]) + +# _AM_SET_OPTIONS(OPTIONS) +# ------------------------ +# OPTIONS is a space-separated list of Automake options. +AC_DEFUN([_AM_SET_OPTIONS], +[m4_foreach_w([_AM_Option], [$1], [_AM_SET_OPTION(_AM_Option)])]) + +# _AM_IF_OPTION(OPTION, IF-SET, [IF-NOT-SET]) +# ------------------------------------------- +# Execute IF-SET if OPTION is set, IF-NOT-SET otherwise. +AC_DEFUN([_AM_IF_OPTION], +[m4_ifset(_AM_MANGLE_OPTION([$1]), [$2], [$3])]) + +# Copyright (C) 1999-2014 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# _AM_PROG_CC_C_O +# --------------- +# Like AC_PROG_CC_C_O, but changed for automake. We rewrite AC_PROG_CC +# to automatically call this. +AC_DEFUN([_AM_PROG_CC_C_O], +[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl +AC_REQUIRE_AUX_FILE([compile])dnl +AC_LANG_PUSH([C])dnl +AC_CACHE_CHECK( + [whether $CC understands -c and -o together], + [am_cv_prog_cc_c_o], + [AC_LANG_CONFTEST([AC_LANG_PROGRAM([])]) + # Make sure it works both with $CC and with simple cc. + # Following AC_PROG_CC_C_O, we do the test twice because some + # compilers refuse to overwrite an existing .o file with -o, + # though they will create one. + am_cv_prog_cc_c_o=yes + for am_i in 1 2; do + if AM_RUN_LOG([$CC -c conftest.$ac_ext -o conftest2.$ac_objext]) \ + && test -f conftest2.$ac_objext; then + : OK + else + am_cv_prog_cc_c_o=no + break + fi + done + rm -f core conftest* + unset am_i]) +if test "$am_cv_prog_cc_c_o" != yes; then + # Losing compiler, so override with the script. + # FIXME: It is wrong to rewrite CC. + # But if we don't then we get into trouble of one sort or another. + # A longer-term fix would be to have automake use am__CC in this case, + # and then we could set am__CC="\$(top_srcdir)/compile \$(CC)" + CC="$am_aux_dir/compile $CC" +fi +AC_LANG_POP([C])]) + +# For backward compatibility. +AC_DEFUN_ONCE([AM_PROG_CC_C_O], [AC_REQUIRE([AC_PROG_CC])]) + +# Copyright (C) 1999-2014 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + + +# AM_PATH_PYTHON([MINIMUM-VERSION], [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +# --------------------------------------------------------------------------- +# Adds support for distributing Python modules and packages. To +# install modules, copy them to $(pythondir), using the python_PYTHON +# automake variable. To install a package with the same name as the +# automake package, install to $(pkgpythondir), or use the +# pkgpython_PYTHON automake variable. +# +# The variables $(pyexecdir) and $(pkgpyexecdir) are provided as +# locations to install python extension modules (shared libraries). +# Another macro is required to find the appropriate flags to compile +# extension modules. +# +# If your package is configured with a different prefix to python, +# users will have to add the install directory to the PYTHONPATH +# environment variable, or create a .pth file (see the python +# documentation for details). +# +# If the MINIMUM-VERSION argument is passed, AM_PATH_PYTHON will +# cause an error if the version of python installed on the system +# doesn't meet the requirement. MINIMUM-VERSION should consist of +# numbers and dots only. +AC_DEFUN([AM_PATH_PYTHON], + [ + dnl Find a Python interpreter. Python versions prior to 2.0 are not + dnl supported. (2.0 was released on October 16, 2000). + m4_define_default([_AM_PYTHON_INTERPRETER_LIST], +[python python2 python3 python3.3 python3.2 python3.1 python3.0 python2.7 dnl + python2.6 python2.5 python2.4 python2.3 python2.2 python2.1 python2.0]) + + AC_ARG_VAR([PYTHON], [the Python interpreter]) + + m4_if([$1],[],[ + dnl No version check is needed. + # Find any Python interpreter. + if test -z "$PYTHON"; then + AC_PATH_PROGS([PYTHON], _AM_PYTHON_INTERPRETER_LIST, :) + fi + am_display_PYTHON=python + ], [ + dnl A version check is needed. + if test -n "$PYTHON"; then + # If the user set $PYTHON, use it and don't search something else. + AC_MSG_CHECKING([whether $PYTHON version is >= $1]) + AM_PYTHON_CHECK_VERSION([$PYTHON], [$1], + [AC_MSG_RESULT([yes])], + [AC_MSG_RESULT([no]) + AC_MSG_ERROR([Python interpreter is too old])]) + am_display_PYTHON=$PYTHON + else + # Otherwise, try each interpreter until we find one that satisfies + # VERSION. + AC_CACHE_CHECK([for a Python interpreter with version >= $1], + [am_cv_pathless_PYTHON],[ + for am_cv_pathless_PYTHON in _AM_PYTHON_INTERPRETER_LIST none; do + test "$am_cv_pathless_PYTHON" = none && break + AM_PYTHON_CHECK_VERSION([$am_cv_pathless_PYTHON], [$1], [break]) + done]) + # Set $PYTHON to the absolute path of $am_cv_pathless_PYTHON. + if test "$am_cv_pathless_PYTHON" = none; then + PYTHON=: + else + AC_PATH_PROG([PYTHON], [$am_cv_pathless_PYTHON]) + fi + am_display_PYTHON=$am_cv_pathless_PYTHON + fi + ]) + + if test "$PYTHON" = :; then + dnl Run any user-specified action, or abort. + m4_default([$3], [AC_MSG_ERROR([no suitable Python interpreter found])]) + else + + dnl Query Python for its version number. Getting [:3] seems to be + dnl the best way to do this; it's what "site.py" does in the standard + dnl library. + + AC_CACHE_CHECK([for $am_display_PYTHON version], [am_cv_python_version], + [am_cv_python_version=`$PYTHON -c "import sys; sys.stdout.write(sys.version[[:3]])"`]) + AC_SUBST([PYTHON_VERSION], [$am_cv_python_version]) + + dnl Use the values of $prefix and $exec_prefix for the corresponding + dnl values of PYTHON_PREFIX and PYTHON_EXEC_PREFIX. These are made + dnl distinct variables so they can be overridden if need be. However, + dnl general consensus is that you shouldn't need this ability. + + AC_SUBST([PYTHON_PREFIX], ['${prefix}']) + AC_SUBST([PYTHON_EXEC_PREFIX], ['${exec_prefix}']) + + dnl At times (like when building shared libraries) you may want + dnl to know which OS platform Python thinks this is. + + AC_CACHE_CHECK([for $am_display_PYTHON platform], [am_cv_python_platform], + [am_cv_python_platform=`$PYTHON -c "import sys; sys.stdout.write(sys.platform)"`]) + AC_SUBST([PYTHON_PLATFORM], [$am_cv_python_platform]) + + # Just factor out some code duplication. + am_python_setup_sysconfig="\ +import sys +# Prefer sysconfig over distutils.sysconfig, for better compatibility +# with python 3.x. See automake bug#10227. +try: + import sysconfig +except ImportError: + can_use_sysconfig = 0 +else: + can_use_sysconfig = 1 +# Can't use sysconfig in CPython 2.7, since it's broken in virtualenvs: +# +try: + from platform import python_implementation + if python_implementation() == 'CPython' and sys.version[[:3]] == '2.7': + can_use_sysconfig = 0 +except ImportError: + pass" + + dnl Set up 4 directories: + + dnl pythondir -- where to install python scripts. This is the + dnl site-packages directory, not the python standard library + dnl directory like in previous automake betas. This behavior + dnl is more consistent with lispdir.m4 for example. + dnl Query distutils for this directory. + AC_CACHE_CHECK([for $am_display_PYTHON script directory], + [am_cv_python_pythondir], + [if test "x$prefix" = xNONE + then + am_py_prefix=$ac_default_prefix + else + am_py_prefix=$prefix + fi + am_cv_python_pythondir=`$PYTHON -c " +$am_python_setup_sysconfig +if can_use_sysconfig: + sitedir = sysconfig.get_path('purelib', vars={'base':'$am_py_prefix'}) +else: + from distutils import sysconfig + sitedir = sysconfig.get_python_lib(0, 0, prefix='$am_py_prefix') +sys.stdout.write(sitedir)"` + case $am_cv_python_pythondir in + $am_py_prefix*) + am__strip_prefix=`echo "$am_py_prefix" | sed 's|.|.|g'` + am_cv_python_pythondir=`echo "$am_cv_python_pythondir" | sed "s,^$am__strip_prefix,$PYTHON_PREFIX,"` + ;; + *) + case $am_py_prefix in + /usr|/System*) ;; + *) + am_cv_python_pythondir=$PYTHON_PREFIX/lib/python$PYTHON_VERSION/site-packages + ;; + esac + ;; + esac + ]) + AC_SUBST([pythondir], [$am_cv_python_pythondir]) + + dnl pkgpythondir -- $PACKAGE directory under pythondir. Was + dnl PYTHON_SITE_PACKAGE in previous betas, but this naming is + dnl more consistent with the rest of automake. + + AC_SUBST([pkgpythondir], [\${pythondir}/$PACKAGE]) + + dnl pyexecdir -- directory for installing python extension modules + dnl (shared libraries) + dnl Query distutils for this directory. + AC_CACHE_CHECK([for $am_display_PYTHON extension module directory], + [am_cv_python_pyexecdir], + [if test "x$exec_prefix" = xNONE + then + am_py_exec_prefix=$am_py_prefix + else + am_py_exec_prefix=$exec_prefix + fi + am_cv_python_pyexecdir=`$PYTHON -c " +$am_python_setup_sysconfig +if can_use_sysconfig: + sitedir = sysconfig.get_path('platlib', vars={'platbase':'$am_py_prefix'}) +else: + from distutils import sysconfig + sitedir = sysconfig.get_python_lib(1, 0, prefix='$am_py_prefix') +sys.stdout.write(sitedir)"` + case $am_cv_python_pyexecdir in + $am_py_exec_prefix*) + am__strip_prefix=`echo "$am_py_exec_prefix" | sed 's|.|.|g'` + am_cv_python_pyexecdir=`echo "$am_cv_python_pyexecdir" | sed "s,^$am__strip_prefix,$PYTHON_EXEC_PREFIX,"` + ;; + *) + case $am_py_exec_prefix in + /usr|/System*) ;; + *) + am_cv_python_pyexecdir=$PYTHON_EXEC_PREFIX/lib/python$PYTHON_VERSION/site-packages + ;; + esac + ;; + esac + ]) + AC_SUBST([pyexecdir], [$am_cv_python_pyexecdir]) + + dnl pkgpyexecdir -- $(pyexecdir)/$(PACKAGE) + + AC_SUBST([pkgpyexecdir], [\${pyexecdir}/$PACKAGE]) + + dnl Run any user-specified action. + $2 + fi + +]) + + +# AM_PYTHON_CHECK_VERSION(PROG, VERSION, [ACTION-IF-TRUE], [ACTION-IF-FALSE]) +# --------------------------------------------------------------------------- +# Run ACTION-IF-TRUE if the Python interpreter PROG has version >= VERSION. +# Run ACTION-IF-FALSE otherwise. +# This test uses sys.hexversion instead of the string equivalent (first +# word of sys.version), in order to cope with versions such as 2.2c1. +# This supports Python 2.0 or higher. (2.0 was released on October 16, 2000). +AC_DEFUN([AM_PYTHON_CHECK_VERSION], + [prog="import sys +# split strings by '.' and convert to numeric. Append some zeros +# because we need at least 4 digits for the hex conversion. +# map returns an iterator in Python 3.0 and a list in 2.x +minver = list(map(int, '$2'.split('.'))) + [[0, 0, 0]] +minverhex = 0 +# xrange is not present in Python 3.0 and range returns an iterator +for i in list(range(0, 4)): minverhex = (minverhex << 8) + minver[[i]] +sys.exit(sys.hexversion < minverhex)" + AS_IF([AM_RUN_LOG([$1 -c "$prog"])], [$3], [$4])]) + +# Copyright (C) 2001-2014 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_RUN_LOG(COMMAND) +# ------------------- +# Run COMMAND, save the exit status in ac_status, and log it. +# (This has been adapted from Autoconf's _AC_RUN_LOG macro.) +AC_DEFUN([AM_RUN_LOG], +[{ echo "$as_me:$LINENO: $1" >&AS_MESSAGE_LOG_FD + ($1) >&AS_MESSAGE_LOG_FD 2>&AS_MESSAGE_LOG_FD + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&AS_MESSAGE_LOG_FD + (exit $ac_status); }]) + +# Check to make sure that the build environment is sane. -*- Autoconf -*- + +# Copyright (C) 1996-2014 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_SANITY_CHECK +# --------------- +AC_DEFUN([AM_SANITY_CHECK], +[AC_MSG_CHECKING([whether build environment is sane]) +# Reject unsafe characters in $srcdir or the absolute working directory +# name. Accept space and tab only in the latter. +am_lf=' +' +case `pwd` in + *[[\\\"\#\$\&\'\`$am_lf]]*) + AC_MSG_ERROR([unsafe absolute working directory name]);; +esac +case $srcdir in + *[[\\\"\#\$\&\'\`$am_lf\ \ ]]*) + AC_MSG_ERROR([unsafe srcdir value: '$srcdir']);; +esac + +# Do 'set' in a subshell so we don't clobber the current shell's +# arguments. Must try -L first in case configure is actually a +# symlink; some systems play weird games with the mod time of symlinks +# (eg FreeBSD returns the mod time of the symlink's containing +# directory). +if ( + am_has_slept=no + for am_try in 1 2; do + echo "timestamp, slept: $am_has_slept" > conftest.file + set X `ls -Lt "$srcdir/configure" conftest.file 2> /dev/null` + if test "$[*]" = "X"; then + # -L didn't work. + set X `ls -t "$srcdir/configure" conftest.file` + fi + if test "$[*]" != "X $srcdir/configure conftest.file" \ + && test "$[*]" != "X conftest.file $srcdir/configure"; then + + # If neither matched, then we have a broken ls. This can happen + # if, for instance, CONFIG_SHELL is bash and it inherits a + # broken ls alias from the environment. This has actually + # happened. Such a system could not be considered "sane". + AC_MSG_ERROR([ls -t appears to fail. Make sure there is not a broken + alias in your environment]) + fi + if test "$[2]" = conftest.file || test $am_try -eq 2; then + break + fi + # Just in case. + sleep 1 + am_has_slept=yes + done + test "$[2]" = conftest.file + ) +then + # Ok. + : +else + AC_MSG_ERROR([newly created file is older than distributed files! +Check your system clock]) +fi +AC_MSG_RESULT([yes]) +# If we didn't sleep, we still need to ensure time stamps of config.status and +# generated files are strictly newer. +am_sleep_pid= +if grep 'slept: no' conftest.file >/dev/null 2>&1; then + ( sleep 1 ) & + am_sleep_pid=$! +fi +AC_CONFIG_COMMANDS_PRE( + [AC_MSG_CHECKING([that generated files are newer than configure]) + if test -n "$am_sleep_pid"; then + # Hide warnings about reused PIDs. + wait $am_sleep_pid 2>/dev/null + fi + AC_MSG_RESULT([done])]) +rm -f conftest.file +]) + +# Copyright (C) 2009-2014 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_SILENT_RULES([DEFAULT]) +# -------------------------- +# Enable less verbose build rules; with the default set to DEFAULT +# ("yes" being less verbose, "no" or empty being verbose). +AC_DEFUN([AM_SILENT_RULES], +[AC_ARG_ENABLE([silent-rules], [dnl +AS_HELP_STRING( + [--enable-silent-rules], + [less verbose build output (undo: "make V=1")]) +AS_HELP_STRING( + [--disable-silent-rules], + [verbose build output (undo: "make V=0")])dnl +]) +case $enable_silent_rules in @%:@ ((( + yes) AM_DEFAULT_VERBOSITY=0;; + no) AM_DEFAULT_VERBOSITY=1;; + *) AM_DEFAULT_VERBOSITY=m4_if([$1], [yes], [0], [1]);; +esac +dnl +dnl A few 'make' implementations (e.g., NonStop OS and NextStep) +dnl do not support nested variable expansions. +dnl See automake bug#9928 and bug#10237. +am_make=${MAKE-make} +AC_CACHE_CHECK([whether $am_make supports nested variables], + [am_cv_make_support_nested_variables], + [if AS_ECHO([['TRUE=$(BAR$(V)) +BAR0=false +BAR1=true +V=1 +am__doit: + @$(TRUE) +.PHONY: am__doit']]) | $am_make -f - >/dev/null 2>&1; then + am_cv_make_support_nested_variables=yes +else + am_cv_make_support_nested_variables=no +fi]) +if test $am_cv_make_support_nested_variables = yes; then + dnl Using '$V' instead of '$(V)' breaks IRIX make. + AM_V='$(V)' + AM_DEFAULT_V='$(AM_DEFAULT_VERBOSITY)' +else + AM_V=$AM_DEFAULT_VERBOSITY + AM_DEFAULT_V=$AM_DEFAULT_VERBOSITY +fi +AC_SUBST([AM_V])dnl +AM_SUBST_NOTMAKE([AM_V])dnl +AC_SUBST([AM_DEFAULT_V])dnl +AM_SUBST_NOTMAKE([AM_DEFAULT_V])dnl +AC_SUBST([AM_DEFAULT_VERBOSITY])dnl +AM_BACKSLASH='\' +AC_SUBST([AM_BACKSLASH])dnl +_AM_SUBST_NOTMAKE([AM_BACKSLASH])dnl +]) + +# Copyright (C) 2001-2014 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_PROG_INSTALL_STRIP +# --------------------- +# One issue with vendor 'install' (even GNU) is that you can't +# specify the program used to strip binaries. This is especially +# annoying in cross-compiling environments, where the build's strip +# is unlikely to handle the host's binaries. +# Fortunately install-sh will honor a STRIPPROG variable, so we +# always use install-sh in "make install-strip", and initialize +# STRIPPROG with the value of the STRIP variable (set by the user). +AC_DEFUN([AM_PROG_INSTALL_STRIP], +[AC_REQUIRE([AM_PROG_INSTALL_SH])dnl +# Installed binaries are usually stripped using 'strip' when the user +# run "make install-strip". However 'strip' might not be the right +# tool to use in cross-compilation environments, therefore Automake +# will honor the 'STRIP' environment variable to overrule this program. +dnl Don't test for $cross_compiling = yes, because it might be 'maybe'. +if test "$cross_compiling" != no; then + AC_CHECK_TOOL([STRIP], [strip], :) +fi +INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s" +AC_SUBST([INSTALL_STRIP_PROGRAM])]) + +# Copyright (C) 2006-2014 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# _AM_SUBST_NOTMAKE(VARIABLE) +# --------------------------- +# Prevent Automake from outputting VARIABLE = @VARIABLE@ in Makefile.in. +# This macro is traced by Automake. +AC_DEFUN([_AM_SUBST_NOTMAKE]) + +# AM_SUBST_NOTMAKE(VARIABLE) +# -------------------------- +# Public sister of _AM_SUBST_NOTMAKE. +AC_DEFUN([AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE($@)]) + +# Check how to create a tarball. -*- Autoconf -*- + +# Copyright (C) 2004-2014 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# _AM_PROG_TAR(FORMAT) +# -------------------- +# Check how to create a tarball in format FORMAT. +# FORMAT should be one of 'v7', 'ustar', or 'pax'. +# +# Substitute a variable $(am__tar) that is a command +# writing to stdout a FORMAT-tarball containing the directory +# $tardir. +# tardir=directory && $(am__tar) > result.tar +# +# Substitute a variable $(am__untar) that extract such +# a tarball read from stdin. +# $(am__untar) < result.tar +# +AC_DEFUN([_AM_PROG_TAR], +[# Always define AMTAR for backward compatibility. Yes, it's still used +# in the wild :-( We should find a proper way to deprecate it ... +AC_SUBST([AMTAR], ['$${TAR-tar}']) + +# We'll loop over all known methods to create a tar archive until one works. +_am_tools='gnutar m4_if([$1], [ustar], [plaintar]) pax cpio none' + +m4_if([$1], [v7], + [am__tar='$${TAR-tar} chof - "$$tardir"' am__untar='$${TAR-tar} xf -'], + + [m4_case([$1], + [ustar], + [# The POSIX 1988 'ustar' format is defined with fixed-size fields. + # There is notably a 21 bits limit for the UID and the GID. In fact, + # the 'pax' utility can hang on bigger UID/GID (see automake bug#8343 + # and bug#13588). + am_max_uid=2097151 # 2^21 - 1 + am_max_gid=$am_max_uid + # The $UID and $GID variables are not portable, so we need to resort + # to the POSIX-mandated id(1) utility. Errors in the 'id' calls + # below are definitely unexpected, so allow the users to see them + # (that is, avoid stderr redirection). + am_uid=`id -u || echo unknown` + am_gid=`id -g || echo unknown` + AC_MSG_CHECKING([whether UID '$am_uid' is supported by ustar format]) + if test $am_uid -le $am_max_uid; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + _am_tools=none + fi + AC_MSG_CHECKING([whether GID '$am_gid' is supported by ustar format]) + if test $am_gid -le $am_max_gid; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + _am_tools=none + fi], + + [pax], + [], + + [m4_fatal([Unknown tar format])]) + + AC_MSG_CHECKING([how to create a $1 tar archive]) + + # Go ahead even if we have the value already cached. We do so because we + # need to set the values for the 'am__tar' and 'am__untar' variables. + _am_tools=${am_cv_prog_tar_$1-$_am_tools} + + for _am_tool in $_am_tools; do + case $_am_tool in + gnutar) + for _am_tar in tar gnutar gtar; do + AM_RUN_LOG([$_am_tar --version]) && break + done + am__tar="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$$tardir"' + am__tar_="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$tardir"' + am__untar="$_am_tar -xf -" + ;; + plaintar) + # Must skip GNU tar: if it does not support --format= it doesn't create + # ustar tarball either. + (tar --version) >/dev/null 2>&1 && continue + am__tar='tar chf - "$$tardir"' + am__tar_='tar chf - "$tardir"' + am__untar='tar xf -' + ;; + pax) + am__tar='pax -L -x $1 -w "$$tardir"' + am__tar_='pax -L -x $1 -w "$tardir"' + am__untar='pax -r' + ;; + cpio) + am__tar='find "$$tardir" -print | cpio -o -H $1 -L' + am__tar_='find "$tardir" -print | cpio -o -H $1 -L' + am__untar='cpio -i -H $1 -d' + ;; + none) + am__tar=false + am__tar_=false + am__untar=false + ;; + esac + + # If the value was cached, stop now. We just wanted to have am__tar + # and am__untar set. + test -n "${am_cv_prog_tar_$1}" && break + + # tar/untar a dummy directory, and stop if the command works. + rm -rf conftest.dir + mkdir conftest.dir + echo GrepMe > conftest.dir/file + AM_RUN_LOG([tardir=conftest.dir && eval $am__tar_ >conftest.tar]) + rm -rf conftest.dir + if test -s conftest.tar; then + AM_RUN_LOG([$am__untar /dev/null 2>&1 && break + fi + done + rm -rf conftest.dir + + AC_CACHE_VAL([am_cv_prog_tar_$1], [am_cv_prog_tar_$1=$_am_tool]) + AC_MSG_RESULT([$am_cv_prog_tar_$1])]) + +AC_SUBST([am__tar]) +AC_SUBST([am__untar]) +]) # _AM_PROG_TAR + +m4_include([m4/ax_boost_base.m4]) +m4_include([m4/ax_boost_chrono.m4]) +m4_include([m4/ax_boost_python.m4]) +m4_include([m4/ax_boost_random.m4]) +m4_include([m4/ax_boost_system.m4]) +m4_include([m4/ax_check_openssl.m4]) +m4_include([m4/ax_pthread.m4]) +m4_include([m4/ax_python_devel.m4]) +m4_include([m4/gettext-lib.m4]) +m4_include([m4/iconv.m4]) +m4_include([m4/libtool.m4]) +m4_include([m4/ltoptions.m4]) +m4_include([m4/ltsugar.m4]) +m4_include([m4/ltversion.m4]) +m4_include([m4/lt~obsolete.m4]) +m4_include([m4/pkgconfig.m4]) diff --git a/bindings/Makefile.am b/bindings/Makefile.am new file mode 100644 index 0000000..929b41f --- /dev/null +++ b/bindings/Makefile.am @@ -0,0 +1,4 @@ + +SUBDIRS = python + +EXTRA_DIST = README.txt diff --git a/bindings/Makefile.in b/bindings/Makefile.in new file mode 100644 index 0000000..f382d40 --- /dev/null +++ b/bindings/Makefile.in @@ -0,0 +1,669 @@ +# Makefile.in generated by automake 1.15 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2014 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +target_triplet = @target@ +subdir = bindings +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_boost_base.m4 \ + $(top_srcdir)/m4/ax_boost_chrono.m4 \ + $(top_srcdir)/m4/ax_boost_python.m4 \ + $(top_srcdir)/m4/ax_boost_random.m4 \ + $(top_srcdir)/m4/ax_boost_system.m4 \ + $(top_srcdir)/m4/ax_check_openssl.m4 \ + $(top_srcdir)/m4/ax_pthread.m4 \ + $(top_srcdir)/m4/ax_python_devel.m4 \ + $(top_srcdir)/m4/gettext-lib.m4 $(top_srcdir)/m4/iconv.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/pkgconfig.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +SOURCES = +DIST_SOURCES = +RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \ + ctags-recursive dvi-recursive html-recursive info-recursive \ + install-data-recursive install-dvi-recursive \ + install-exec-recursive install-html-recursive \ + install-info-recursive install-pdf-recursive \ + install-ps-recursive install-recursive installcheck-recursive \ + installdirs-recursive pdf-recursive ps-recursive \ + tags-recursive uninstall-recursive +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \ + distclean-recursive maintainer-clean-recursive +am__recursive_targets = \ + $(RECURSIVE_TARGETS) \ + $(RECURSIVE_CLEAN_TARGETS) \ + $(am__extra_recursive_targets) +AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \ + distdir +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +DIST_SUBDIRS = $(SUBDIRS) +am__DIST_COMMON = $(srcdir)/Makefile.in +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +am__relativize = \ + dir0=`pwd`; \ + sed_first='s,^\([^/]*\)/.*$$,\1,'; \ + sed_rest='s,^[^/]*/*,,'; \ + sed_last='s,^.*/\([^/]*\)$$,\1,'; \ + sed_butlast='s,/*[^/]*$$,,'; \ + while test -n "$$dir1"; do \ + first=`echo "$$dir1" | sed -e "$$sed_first"`; \ + if test "$$first" != "."; then \ + if test "$$first" = ".."; then \ + dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \ + dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \ + else \ + first2=`echo "$$dir2" | sed -e "$$sed_first"`; \ + if test "$$first2" = "$$first"; then \ + dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \ + else \ + dir2="../$$dir2"; \ + fi; \ + dir0="$$dir0"/"$$first"; \ + fi; \ + fi; \ + dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \ + done; \ + reldir="$$dir2" +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BOOST_CHRONO_LIB = @BOOST_CHRONO_LIB@ +BOOST_CPPFLAGS = @BOOST_CPPFLAGS@ +BOOST_LDFLAGS = @BOOST_LDFLAGS@ +BOOST_PYTHON_LIB = @BOOST_PYTHON_LIB@ +BOOST_RANDOM_LIB = @BOOST_RANDOM_LIB@ +BOOST_SYSTEM_LIB = @BOOST_SYSTEM_LIB@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +COMPILETIME_OPTIONS = @COMPILETIME_OPTIONS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEBUGFLAGS = @DEBUGFLAGS@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GREP = @GREP@ +ICONV_LIBS = @ICONV_LIBS@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INTERFACE_VERSION_INFO = @INTERFACE_VERSION_INFO@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBICONV = @LIBICONV@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OPENSSL_INCLUDES = @OPENSSL_INCLUDES@ +OPENSSL_LDFLAGS = @OPENSSL_LDFLAGS@ +OPENSSL_LIBS = @OPENSSL_LIBS@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PTHREAD_CC = @PTHREAD_CC@ +PTHREAD_CFLAGS = @PTHREAD_CFLAGS@ +PTHREAD_LIBS = @PTHREAD_LIBS@ +PYTHON = @PYTHON@ +PYTHON_CPPFLAGS = @PYTHON_CPPFLAGS@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_EXTRA_LDFLAGS = @PYTHON_EXTRA_LDFLAGS@ +PYTHON_EXTRA_LIBS = @PYTHON_EXTRA_LIBS@ +PYTHON_INSTALL_PARAMS = @PYTHON_INSTALL_PARAMS@ +PYTHON_LIBS = @PYTHON_LIBS@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_SITE_PKG = @PYTHON_SITE_PKG@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +ax_pthread_config = @ax_pthread_config@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target = @target@ +target_alias = @target_alias@ +target_cpu = @target_cpu@ +target_os = @target_os@ +target_vendor = @target_vendor@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +SUBDIRS = python +EXTRA_DIST = README.txt +all: all-recursive + +.SUFFIXES: +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign bindings/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign bindings/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +# This directory's subdirectories are mostly independent; you can cd +# into them and run 'make' without going through this Makefile. +# To change the values of 'make' variables: instead of editing Makefiles, +# (1) if the variable is set in 'config.status', edit 'config.status' +# (which will cause the Makefiles to be regenerated when you run 'make'); +# (2) otherwise, pass the desired values on the 'make' command line. +$(am__recursive_targets): + @fail=; \ + if $(am__make_keepgoing); then \ + failcom='fail=yes'; \ + else \ + failcom='exit 1'; \ + fi; \ + dot_seen=no; \ + target=`echo $@ | sed s/-recursive//`; \ + case "$@" in \ + distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \ + *) list='$(SUBDIRS)' ;; \ + esac; \ + for subdir in $$list; do \ + echo "Making $$target in $$subdir"; \ + if test "$$subdir" = "."; then \ + dot_seen=yes; \ + local_target="$$target-am"; \ + else \ + local_target="$$target"; \ + fi; \ + ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ + || eval $$failcom; \ + done; \ + if test "$$dot_seen" = "no"; then \ + $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \ + fi; test -z "$$fail" + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-recursive +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \ + include_option=--etags-include; \ + empty_fix=.; \ + else \ + include_option=--include; \ + empty_fix=; \ + fi; \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + test ! -f $$subdir/TAGS || \ + set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \ + fi; \ + done; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-recursive + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-recursive + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done + @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + $(am__make_dryrun) \ + || test -d "$(distdir)/$$subdir" \ + || $(MKDIR_P) "$(distdir)/$$subdir" \ + || exit 1; \ + dir1=$$subdir; dir2="$(distdir)/$$subdir"; \ + $(am__relativize); \ + new_distdir=$$reldir; \ + dir1=$$subdir; dir2="$(top_distdir)"; \ + $(am__relativize); \ + new_top_distdir=$$reldir; \ + echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \ + echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \ + ($(am__cd) $$subdir && \ + $(MAKE) $(AM_MAKEFLAGS) \ + top_distdir="$$new_top_distdir" \ + distdir="$$new_distdir" \ + am__remove_distdir=: \ + am__skip_length_check=: \ + am__skip_mode_fix=: \ + distdir) \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-recursive +all-am: Makefile +installdirs: installdirs-recursive +installdirs-am: +install: install-recursive +install-exec: install-exec-recursive +install-data: install-data-recursive +uninstall: uninstall-recursive + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-recursive +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-recursive + +clean-am: clean-generic clean-libtool mostlyclean-am + +distclean: distclean-recursive + -rm -f Makefile +distclean-am: clean-am distclean-generic distclean-tags + +dvi: dvi-recursive + +dvi-am: + +html: html-recursive + +html-am: + +info: info-recursive + +info-am: + +install-data-am: + +install-dvi: install-dvi-recursive + +install-dvi-am: + +install-exec-am: + +install-html: install-html-recursive + +install-html-am: + +install-info: install-info-recursive + +install-info-am: + +install-man: + +install-pdf: install-pdf-recursive + +install-pdf-am: + +install-ps: install-ps-recursive + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-recursive + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-recursive + +mostlyclean-am: mostlyclean-generic mostlyclean-libtool + +pdf: pdf-recursive + +pdf-am: + +ps: ps-recursive + +ps-am: + +uninstall-am: + +.MAKE: $(am__recursive_targets) install-am install-strip + +.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \ + check-am clean clean-generic clean-libtool cscopelist-am ctags \ + ctags-am distclean distclean-generic distclean-libtool \ + distclean-tags distdir dvi dvi-am html html-am info info-am \ + install install-am install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + installdirs-am maintainer-clean maintainer-clean-generic \ + mostlyclean mostlyclean-generic mostlyclean-libtool pdf pdf-am \ + ps ps-am tags tags-am uninstall uninstall-am + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/bindings/README.txt b/bindings/README.txt new file mode 100644 index 0000000..977ca8c --- /dev/null +++ b/bindings/README.txt @@ -0,0 +1,3 @@ +Documentation covering building and using the python binding for libtorrent +is located in the main doc directory. See docs/python_binding.html + diff --git a/bindings/python/Jamfile b/bindings/python/Jamfile new file mode 100644 index 0000000..c650bd1 --- /dev/null +++ b/bindings/python/Jamfile @@ -0,0 +1,166 @@ +import python ; +import feature : feature ; +import project ; +import targets ; +import "class" : new ; +import modules ; + +use-project /torrent : ../.. ; + +BOOST_ROOT = [ modules.peek : BOOST_ROOT ] ; + +feature visibility : default hidden : composite ; +feature.compose hidden : -fvisibility=hidden -fvisibility-inlines-hidden ; + +feature libtorrent-link : shared static : composite propagated ; +feature libtorrent-python-pic : off on : composite propagated link-incompatible ; +feature.compose on : -fPIC ; + +if $(BOOST_ROOT) +{ + use-project /boost : $(BOOST_ROOT) ; + alias boost_python : /boost/python//boost_python : : : $(BOOST_ROOT) ; +} +else +{ + local boost-lib-search-path = + /opt/local/lib + /usr/lib + /usr/local/lib + /sw/lib + /usr/g++/lib + ; + + local boost-include-path = + /opt/local/include + /usr/local/include + /usr/sfw/include + ; + + # the names are decorated in MacPorts + lib boost_python : : darwin boost_python-mt + : : $(boost-include-path) ; + + lib boost_python : : boost_python + : : $(boost-include-path) ; +} + + +rule libtorrent_linking ( properties * ) +{ + local result ; + + if ! windows in $(properties) + && gcc in $(properties) + { + result += on ; + } + + if gcc in $(properties) + || darwin in $(properties) + || clang in $(properties) + || clang-darwin in $(properties) + { + result += hidden ; + + if ( gcc in $(properties) ) + { + result += -Wl,-Bsymbolic ; + } + } + + if static in $(properties) + { + ECHO "WARNING: you probably want to specify libtorrent-link=static rather than link=static" ; + } + + if static in $(properties) && linux in $(properties) + { + ECHO "WARNING: you cannot link statically against boost-python on linux, because it links against pthread statically in that case, which is not allowed" ; + } + + # linux must link dynamically against boost python because it pulls + # in libpthread, which must be linked dynamically since we're building a .so + # (the static build of libpthread is not position independent) + if shared in $(properties) || linux in $(properties) + { + result += boost_python/shared ; + } + else + { + result += boost_python/static ; + } + + if shared in $(properties) + { + result += /torrent//torrent/shared ; + } + else + { + result += /torrent//torrent/static ; + } + + return $(result) ; +} + +# this is a copy of the rule from boost-build's python-extension, but without +# specifying no as a mandatory property. That property +# would otherwise cause build failures because it suppresses linking against the +# runtime library and kernel32 on windows + +rule my-python-extension ( name : sources * : requirements * : default-build * : + usage-requirements * ) +{ + requirements += /python//python_for_extensions ; + + local project = [ project.current ] ; + + targets.main-target-alternative + [ new typed-target $(name) : $(project) : PYTHON_EXTENSION + : [ targets.main-target-sources $(sources) : $(name) ] + : [ targets.main-target-requirements $(requirements) : $(project) ] + : [ targets.main-target-default-build $(default-build) : $(project) ] + ] ; +} + +my-python-extension libtorrent + : # sources + src/module.cpp + src/big_number.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 + : # usage-requirements + @libtorrent_linking + false + ; + +install stage_module + : libtorrent + : . + LIB + ; + +explicit stage_module ; + diff --git a/bindings/python/Makefile.am b/bindings/python/Makefile.am new file mode 100644 index 0000000..da853e1 --- /dev/null +++ b/bindings/python/Makefile.am @@ -0,0 +1,48 @@ + +EXTRA_DIST = \ + Jamfile \ + setup.py \ + client.py \ + rss_reader.py \ + simple_client.py \ + make_torrent.py \ + src/alert.cpp \ + src/bytes.hpp \ + src/big_number.cpp \ + src/converters.cpp \ + src/create_torrent.cpp \ + src/datetime.cpp \ + src/entry.cpp \ + src/error_code.cpp \ + src/fingerprint.cpp \ + src/gil.hpp \ + src/ip_filter.cpp \ + src/magnet_uri.cpp \ + src/module.cpp \ + src/optional.hpp \ + src/peer_info.cpp \ + src/boost_python.hpp \ + src/session.cpp \ + src/session_settings.cpp \ + src/string.cpp \ + src/torrent_handle.cpp \ + src/torrent_info.cpp \ + src/torrent_status.cpp \ + src/utility.cpp \ + src/version.cpp + +if ENABLE_PYTHON_BINDING + +all-local: + $(PYTHON) $(srcdir)/setup.py build + +install-exec-local: + $(PYTHON) $(srcdir)/setup.py install @PYTHON_INSTALL_PARAMS@ + +uninstall-local: + rm -rf $(DESTDIR)$(libdir)/python*/*-packages/*libtorrent* + +clean-local: + $(PYTHON) $(srcdir)/setup.py clean --all + +endif diff --git a/bindings/python/Makefile.in b/bindings/python/Makefile.in new file mode 100644 index 0000000..1a4b948 --- /dev/null +++ b/bindings/python/Makefile.in @@ -0,0 +1,541 @@ +# Makefile.in generated by automake 1.15 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2014 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +target_triplet = @target@ +subdir = bindings/python +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_boost_base.m4 \ + $(top_srcdir)/m4/ax_boost_chrono.m4 \ + $(top_srcdir)/m4/ax_boost_python.m4 \ + $(top_srcdir)/m4/ax_boost_random.m4 \ + $(top_srcdir)/m4/ax_boost_system.m4 \ + $(top_srcdir)/m4/ax_check_openssl.m4 \ + $(top_srcdir)/m4/ax_pthread.m4 \ + $(top_srcdir)/m4/ax_python_devel.m4 \ + $(top_srcdir)/m4/gettext-lib.m4 $(top_srcdir)/m4/iconv.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/pkgconfig.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_CLEAN_FILES = link_flags compile_flags +CONFIG_CLEAN_VPATH_FILES = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +SOURCES = +DIST_SOURCES = +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/compile_flags.in \ + $(srcdir)/link_flags.in +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BOOST_CHRONO_LIB = @BOOST_CHRONO_LIB@ +BOOST_CPPFLAGS = @BOOST_CPPFLAGS@ +BOOST_LDFLAGS = @BOOST_LDFLAGS@ +BOOST_PYTHON_LIB = @BOOST_PYTHON_LIB@ +BOOST_RANDOM_LIB = @BOOST_RANDOM_LIB@ +BOOST_SYSTEM_LIB = @BOOST_SYSTEM_LIB@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +COMPILETIME_OPTIONS = @COMPILETIME_OPTIONS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEBUGFLAGS = @DEBUGFLAGS@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GREP = @GREP@ +ICONV_LIBS = @ICONV_LIBS@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INTERFACE_VERSION_INFO = @INTERFACE_VERSION_INFO@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBICONV = @LIBICONV@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OPENSSL_INCLUDES = @OPENSSL_INCLUDES@ +OPENSSL_LDFLAGS = @OPENSSL_LDFLAGS@ +OPENSSL_LIBS = @OPENSSL_LIBS@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PTHREAD_CC = @PTHREAD_CC@ +PTHREAD_CFLAGS = @PTHREAD_CFLAGS@ +PTHREAD_LIBS = @PTHREAD_LIBS@ +PYTHON = @PYTHON@ +PYTHON_CPPFLAGS = @PYTHON_CPPFLAGS@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_EXTRA_LDFLAGS = @PYTHON_EXTRA_LDFLAGS@ +PYTHON_EXTRA_LIBS = @PYTHON_EXTRA_LIBS@ +PYTHON_INSTALL_PARAMS = @PYTHON_INSTALL_PARAMS@ +PYTHON_LIBS = @PYTHON_LIBS@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_SITE_PKG = @PYTHON_SITE_PKG@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +ax_pthread_config = @ax_pthread_config@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target = @target@ +target_alias = @target_alias@ +target_cpu = @target_cpu@ +target_os = @target_os@ +target_vendor = @target_vendor@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +EXTRA_DIST = \ + Jamfile \ + setup.py \ + client.py \ + rss_reader.py \ + simple_client.py \ + make_torrent.py \ + src/alert.cpp \ + src/bytes.hpp \ + src/big_number.cpp \ + src/converters.cpp \ + src/create_torrent.cpp \ + src/datetime.cpp \ + src/entry.cpp \ + src/error_code.cpp \ + src/fingerprint.cpp \ + src/gil.hpp \ + src/ip_filter.cpp \ + src/magnet_uri.cpp \ + src/module.cpp \ + src/optional.hpp \ + src/peer_info.cpp \ + src/boost_python.hpp \ + src/session.cpp \ + src/session_settings.cpp \ + src/string.cpp \ + src/torrent_handle.cpp \ + src/torrent_info.cpp \ + src/torrent_status.cpp \ + src/utility.cpp \ + src/version.cpp + +all: all-am + +.SUFFIXES: +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign bindings/python/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign bindings/python/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +link_flags: $(top_builddir)/config.status $(srcdir)/link_flags.in + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ +compile_flags: $(top_builddir)/config.status $(srcdir)/compile_flags.in + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +tags TAGS: + +ctags CTAGS: + +cscope cscopelist: + + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +@ENABLE_PYTHON_BINDING_FALSE@all-local: +all-am: Makefile all-local +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +@ENABLE_PYTHON_BINDING_FALSE@uninstall-local: +@ENABLE_PYTHON_BINDING_FALSE@install-exec-local: +@ENABLE_PYTHON_BINDING_FALSE@clean-local: +clean: clean-am + +clean-am: clean-generic clean-libtool clean-local mostlyclean-am + +distclean: distclean-am + -rm -f Makefile +distclean-am: clean-am distclean-generic + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-exec-local + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-generic mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-local + +.MAKE: install-am install-strip + +.PHONY: all all-am all-local check check-am clean clean-generic \ + clean-libtool clean-local cscopelist-am ctags-am distclean \ + distclean-generic distclean-libtool distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-exec-local install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags-am uninstall uninstall-am uninstall-local + +.PRECIOUS: Makefile + + +@ENABLE_PYTHON_BINDING_TRUE@all-local: +@ENABLE_PYTHON_BINDING_TRUE@ $(PYTHON) $(srcdir)/setup.py build + +@ENABLE_PYTHON_BINDING_TRUE@install-exec-local: +@ENABLE_PYTHON_BINDING_TRUE@ $(PYTHON) $(srcdir)/setup.py install @PYTHON_INSTALL_PARAMS@ + +@ENABLE_PYTHON_BINDING_TRUE@uninstall-local: +@ENABLE_PYTHON_BINDING_TRUE@ rm -rf $(DESTDIR)$(libdir)/python*/*-packages/*libtorrent* + +@ENABLE_PYTHON_BINDING_TRUE@clean-local: +@ENABLE_PYTHON_BINDING_TRUE@ $(PYTHON) $(srcdir)/setup.py clean --all + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/bindings/python/client.py b/bindings/python/client.py new file mode 100755 index 0000000..10cd91d --- /dev/null +++ b/bindings/python/client.py @@ -0,0 +1,345 @@ +#!/usr/bin/env python + +# 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 +import sys + +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 + + if p.flags & lt.peer_info.interesting: out += 'I' + else: out += '.' + if p.flags & lt.peer_info.choked: out += 'C' + else: out += '.' + if p.flags & lt.peer_info.remote_interested: out += 'i' + else: out += '.' + if p.flags & lt.peer_info.remote_choked: out += 'c' + else: out += '.' + if p.flags & lt.peer_info.supports_extensions: out += 'e' + else: out += '.' + if p.flags & lt.peer_info.local_connection: out += 'l' + else: out += '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' + elif p.flags & lt.peer_info.queued: + id = 'queued' + 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 main(): + from optparse import OptionParser + + parser = OptionParser() + + parser.add_option('-p', '--port', + type='int', help='set listening port') + + 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 + , 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 = lt.session_settings() + settings.user_agent = 'python_client/' + lt.version + + ses = lt.session() + ses.set_download_rate_limit(int(options.max_download_rate)) + ses.set_upload_rate_limit(int(options.max_upload_rate)) + ses.listen_on(options.port, options.port + 10) + ses.set_settings(settings) + ses.set_alert_mask(0xfffffff) + + if options.proxy_host != '': + ps = lt.proxy_settings() + ps.type = lt.proxy_type.http + ps.hostname = options.proxy_host.split(':')[0] + ps.port = int(options.proxy_host.split(':')[1]) + ses.set_proxy(ps) + + handles = [] + alerts = [] + + for f in args: + + atp = {} + atp["save_path"] = options.save_path + atp["storage_mode"] = lt.storage_mode_t.storage_mode_sparse + atp["paused"] = False + atp["auto_managed"] = True + atp["duplicate_is_error"] = True + if f.startswith('magnet:') or f.startswith('http://') or f.startswith('https://'): + atp["url"] = f + else: + info = lt.torrent_info(f) + print('Adding \'%s\'...' % info.name()) + + try: + atp["resume_data"] = open(os.path.join(options.save_path, info.name() + '.fastresume'), 'rb').read() + except: + pass + + atp["ti"] = info + + h = ses.add_torrent(atp) + + handles.append(h) + + h.set_max_connections(60) + h.set_max_uploads(-1) + + if os.name == 'nt': + console = WindowsConsole() + else: + console = UnixConsole() + + alive = True + while alive: + console.clear() + + out = '' + + for h in handles: + if h.has_metadata(): + name = h.get_torrent_info().name()[:40] + else: + name = '-' + out += 'name: %-40s\n' % name + + s = h.status() + + if s.state != lt.torrent_status.seeding: + state_str = ['queued', 'checking', 'downloading metadata', \ + 'downloading', 'finished', 'seeding', \ + 'allocating', 'checking fastresume'] + out += state_str[s.state] + ' ' + + out += '%5.4f%% ' % (s.progress*100) + out += progress_bar(s.progress, 49) + out += '\n' + + out += 'total downloaded: %d Bytes\n' % s.total_done + out += 'peers: %d seeds: %d distributed copies: %d\n' % \ + (s.num_peers, s.num_seeds, s.distributed_copies) + out += '\n' + + out += 'download: %s/s (%s) ' \ + % (add_suffix(s.download_rate), add_suffix(s.total_download)) + out += 'upload: %s/s (%s) ' \ + % (add_suffix(s.upload_rate), add_suffix(s.total_upload)) + + if s.state != lt.torrent_status.seeding: + out += 'info-hash: %s\n' % h.info_hash() + out += 'next announce: %s\n' % s.next_announce + out += 'tracker: %s\n' % s.current_tracker + + write_line(console, out) + + print_peer_info(console, h.get_peer_info()) + print_download_queue(console, h.get_download_queue()) + + if s.state != lt.torrent_status.seeding: + try: + out = '\n' + fp = h.file_progress() + ti = h.get_torrent_info() + for f,p in zip(ti.files(), fp): + out += progress_bar(p / float(f.size), 20) + out += ' ' + f.path + '\n' + write_line(console, out) + except: + pass + + write_line(console, 76 * '-' + '\n') + write_line(console, '(q)uit), (p)ause), (u)npause), (r)eannounce\n') + write_line(console, 76 * '-' + '\n') + + while 1: + a = ses.pop_alert() + if not a: break + alerts.append(a) + + if len(alerts) > 8: + del alerts[:len(alerts) - 8] + + for a in alerts: + if type(a) == str: + write_line(console, a + '\n') + else: + write_line(console, a.message() + '\n') + + c = console.sleep_and_input(0.5) + + if not c: + continue + + if c == 'r': + for h in handles: h.force_reannounce() + elif c == 'q': + alive = False + elif c == 'p': + for h in handles: h.pause() + elif c == 'u': + for h in handles: h.resume() + + ses.pause() + for h in handles: + if not h.is_valid() or not h.has_metadata(): + continue + data = lt.bencode(h.write_resume_data()) + open(os.path.join(options.save_path, h.get_torrent_info().name() + '.fastresume'), 'wb').write(data) + +main() + diff --git a/bindings/python/compile_flags.in b/bindings/python/compile_flags.in new file mode 100644 index 0000000..701d898 --- /dev/null +++ b/bindings/python/compile_flags.in @@ -0,0 +1 @@ +-I@top_srcdir@/include @COMPILETIME_OPTIONS@ @CPPFLAGS@ @BOOST_CPPFLAGS@ @OPENSSL_INCLUDES@ diff --git a/bindings/python/link_flags.in b/bindings/python/link_flags.in new file mode 100644 index 0000000..bf78615 --- /dev/null +++ b/bindings/python/link_flags.in @@ -0,0 +1 @@ +-L@top_builddir@/src/.libs @LDFLAGS@ @LIBS@ @BOOST_LDFLAGS@ @BOOST_SYSTEM_LIB@ @BOOST_PYTHON_LIB@ @PTHREAD_LIBS@ @OPENSSL_LIBS@ @OPENSSL_LDFLAGS@ diff --git a/bindings/python/make_torrent.py b/bindings/python/make_torrent.py new file mode 100755 index 0000000..0bf96b2 --- /dev/null +++ b/bindings/python/make_torrent.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python + +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] + +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.stderr.write('.')) +sys.stderr.write('\n') + +f = open('out.torrent', 'wb+') +print >>f, libtorrent.bencode(t.generate()) +f.close() + diff --git a/bindings/python/rss_reader.py b/bindings/python/rss_reader.py new file mode 100755 index 0000000..a8e4b72 --- /dev/null +++ b/bindings/python/rss_reader.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python + +import sys +import libtorrent as lt +import time + +if len(sys.argv) != 2: + print('usage: rss_reader.py rss-feed-url') + sys.exit(1) + +ses = lt.session() + +h = ses.add_feed({'url': sys.argv[1], 'auto_download': False}) +f = h.get_feed_status() +spinner = ['|', '/', '-', '\\'] +i = 0 +while f['updating']: + time.sleep(0.1) + i = (i + 1) % 4 + print('\b%s' % spinner[i]), + sys.stdout.flush() + f = h.get_feed_status() + +print('\n\nFEED: %s' % f['url']) +if len(f['error']) > 0: + print('ERROR: %s' % f['error']) + +print(' %s\n %s\n' % (f['title'], f['description'])) +print(' ttl: %d minutes' % f['ttl']) + +for item in f['items']: + print('\n%s\n------------------------------------------------------' % item['title']) + print(' url: %s\n size: %d\n uuid: %s\n description: %s' % (item['url'], item['size'], item['uuid'], item['description'])) + print(' comment: %s\n category: %s' % (item['comment'], item['category'])) + diff --git a/bindings/python/setup.py b/bindings/python/setup.py new file mode 100644 index 0000000..69de9b9 --- /dev/null +++ b/bindings/python/setup.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python + +from distutils.core import setup, Extension +from distutils.sysconfig import get_config_vars +import os +import platform +import sys +import shutil +import multiprocessing +import subprocess + +class flags_parser: + def __init__(self): + self.include_dirs = [] + self.library_dirs = [] + self.libraries = [] + + def parse(self, args): + """Parse out the -I -L -l directives and return a list of all other arguments""" + ret = [] + for token in args.split(): + prefix = token[:2] + if prefix == '-I': + self.include_dirs.append(token[2:]) + elif prefix == '-L': + self.library_dirs.append(token[2:]) + elif prefix == '-l': + self.libraries.append(token[2:]) + else: + ret.append(token) + return ret + +def arch(): + if platform.system() != 'Darwin': return [] + a = os.uname()[4] + if a == 'Power Macintosh': a = 'ppc' + return ['-arch', a] + +def target_specific(): + + if platform.system() != 'Darwin': return [] + + # on mavericks, clang will fail when unknown arguments are + # passed in. python distutils will pass in arguments it doesn't + # know about + return ['-Wno-error=unused-command-line-argument-hard-error-in-future'] + +try: + with open('compile_flags') as _file: + extra_cmd = _file.read() + +except: + extra_cmd = None + +try: + with open('link_flags') as _file: + ldflags = _file.read() + +except: + ldflags = None + +ext = None +packages = None + +if '--bjam' in sys.argv: + + if '--bjam' in sys.argv: + del sys.argv[sys.argv.index('--bjam')] + + if not '--help' in sys.argv \ + and not '--help-commands' in sys.argv: + + toolset = '' + file_ext = '.so' + + if platform.system() == 'Windows': + # msvc 9.0 (2008) is the official windows compiler for python 2.6 + # http://docs.python.org/whatsnew/2.6.html#build-and-c-api-changes + toolset = ' msvc-9.0' + file_ext = '.pyd' + + parallel_builds = ' -j%d' % multiprocessing.cpu_count() + + # build libtorrent using bjam and build the installer with distutils + cmdline = 'b2 libtorrent-link=static boost-link=static release optimization=space stage_module --abbreviate-paths' + toolset + parallel_builds + print(cmdline) + if os.system(cmdline) != 0: + print('build failed') + sys.exit(1) + + try: os.mkdir('build') + except: pass + try: shutil.rmtree('build/lib') + except: pass + try: os.mkdir('build/lib') + except: pass + try: os.mkdir('libtorrent') + except: pass + shutil.copyfile('libtorrent' + file_ext, 'build/lib/libtorrent' + file_ext) + + packages = ['libtorrent'] + +else: + # Remove the '-Wstrict-prototypes' compiler option, which isn't valid for C++. + cfg_vars = get_config_vars() + for key, value in cfg_vars.items(): + if isinstance(value, str): + cfg_vars[key] = value.replace('-Wstrict-prototypes', '') + + source_list = os.listdir(os.path.join(os.path.dirname(__file__), "src")) + source_list = [os.path.abspath(os.path.join(os.path.dirname(__file__), "src", s)) for s in source_list if s.endswith(".cpp")] + + if extra_cmd: + flags = flags_parser() + # ldflags must be parsed first to ensure the correct library search path order + extra_link = flags.parse(ldflags) + extra_compile = flags.parse(extra_cmd) + + ext = [Extension('libtorrent', + sources = source_list, + language='c++', + include_dirs = flags.include_dirs, + library_dirs = flags.library_dirs, + extra_link_args = extra_link + arch(), + extra_compile_args = extra_compile + arch() + target_specific(), + libraries = ['torrent-rasterbar'] + flags.libraries)] + +setup(name = 'python-libtorrent', + version = '1.1.1', + author = 'Arvid Norberg', + author_email = 'arvid@libtorrent.org', + description = 'Python bindings for libtorrent-rasterbar', + long_description = 'Python bindings for libtorrent-rasterbar', + url = 'http://libtorrent.org', + platforms = [platform.system() + '-' + platform.machine()], + license = 'BSD', + packages = packages, + ext_modules = ext +) diff --git a/bindings/python/simple_client.py b/bindings/python/simple_client.py new file mode 100755 index 0000000..1b5edfd --- /dev/null +++ b/bindings/python/simple_client.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +# 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() +ses.listen_on(6881, 6891) + +info = lt.torrent_info(sys.argv[1]) +h = ses.add_torrent({'ti': info, 'save_path': '.'}) +print('starting', h.name()) + +while (not h.is_seed()): + s = h.status() + + state_str = ['queued', 'checking', 'downloading metadata', \ + 'downloading', 'finished', 'seeding', 'allocating', 'checking fastresume'] + 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, state_str[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.name(), 'complete') diff --git a/bindings/python/src/alert.cpp b/bindings/python/src/alert.cpp new file mode 100644 index 0000000..cde20e0 --- /dev/null +++ b/bindings/python/src/alert.cpp @@ -0,0 +1,776 @@ +// 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 "bytes.hpp" + +using namespace boost::python; +using namespace libtorrent; + +bytes get_buffer(read_piece_alert const& rpa) +{ + return rpa.buffer ? bytes(rpa.buffer.get(), rpa.size) + : bytes(); +} + +tuple endpoint_to_tuple(tcp::endpoint const& ep) +{ + return boost::python::make_tuple(ep.address().to_string(), ep.port()); +} + +tuple endpoint_to_tuple(udp::endpoint const& ep) +{ + return boost::python::make_tuple(ep.address().to_string(), ep.port()); +} + + +tuple peer_alert_ip(peer_alert const& pa) +{ + return endpoint_to_tuple(pa.ip); +} + +std::string peer_blocked_alert_ip(peer_blocked_alert const& pa) +{ + error_code ec; + return pa.ip.to_string(ec); +} + +std::string dht_announce_alert_ip(dht_announce_alert const& pa) +{ + error_code ec; + return pa.ip.to_string(ec); +} + +tuple incoming_connection_alert_ip(incoming_connection_alert const& ica) +{ + return endpoint_to_tuple(ica.ip); +} + +tuple dht_outgoing_get_peers_alert_ip(dht_outgoing_get_peers_alert const& a) +{ + return endpoint_to_tuple(a.ip); +} + +std::string external_ip_alert_ip(external_ip_alert const& eia) +{ + return eia.external_address.to_string(); +} + +list stats_alert_transferred(stats_alert const& alert) +{ + list result; + for (int i = 0; i < alert.num_channels; ++i) { + result.append(alert.transferred[i]); + } + return result; +} + +list get_status_from_update_alert(state_update_alert const& alert) +{ + list result; + + for (std::vector::const_iterator i = alert.status.begin(); i != alert.status.end(); ++i) + { + result.append(*i); + } + return result; +} + +dict get_params(add_torrent_alert const& alert) +{ + add_torrent_params const& p = alert.params; + dict ret; + ret["ti"] = p.ti; + ret["info_hash"] = p.info_hash; + ret["name"] = p.name; + ret["save_path"] = p.save_path; + ret["storage_mode"] = p.storage_mode; + list trackers; + for (std::vector::const_iterator i = p.trackers.begin(); + i != p.trackers.end(); ++i) + { + trackers.append(*i); + } + ret["trackers"] = trackers; + // TODO: dht_nodes + ret["flags"] = p.flags; + ret["trackerid"] = p.trackerid; + ret["url"] = p.url; + ret["source_feed_url"] = p.source_feed_url; + ret["uuid"] = p.uuid; + return ret; +} + +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.to_string(); + d["value"] = alert.item.to_string(); + return d; +} + +dict dht_mutable_item(dht_mutable_item_alert const& alert) +{ + dict d; + d["key"] = std::string(alert.key.data(), alert.key.size()); + d["value"] = alert.item.to_string(); + d["signature"] = std::string(alert.signature.data(), alert.signature.size()); + d["seq"] = alert.seq; + d["salt"] = 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"] = std::string(alert.public_key.data(), alert.public_key.size()); + d["signature"] = std::string(alert.signature.data(), alert.signature.size()); + d["seq"] = alert.seq; + d["salt"] = alert.salt; + } else { + d["target"] = alert.target.to_string(); + } + return d; +} + +dict session_stats_values(session_stats_alert const& alert) +{ + std::vector map = session_stats_metrics(); + dict d; + + for (std::vector::const_iterator i = map.begin(); + i != map.end(); ++i) + { + d[i->name] = alert.values[i->value_index]; + } + return d; +} + +list dht_get_peers_reply_alert_peers(dht_get_peers_reply_alert const& a) +{ + list result; + + std::vector v(a.peers()); + + for (std::vector::const_iterator i = v.begin(); + i != v.end(); ++i) + { + result.append(endpoint_to_tuple(*i)); + } + + return result; +} + +void bind_alert() +{ + using boost::noncopyable; +#ifndef TORRENT_NO_DEPRECATE + typedef boost::shared_ptr alert_holder; +#if BOOST_VERSION >= 106000 + register_ptr_to_python >(); +#endif +#else + typedef alert alert_holder; +#endif + + { + scope alert_scope = class_("alert", no_init) + .def("message", &alert::message) + .def("what", &alert::what) + .def("category", &alert::category) +#ifndef TORRENT_NO_DEPRECATE + .def("severity", &alert::severity) +#endif + .def("__str__", &alert::message) + ; + +#ifndef TORRENT_NO_DEPRECATE + enum_("severity_levels") + .value("debug", alert::debug) + .value("info", alert::info) + .value("warning", alert::warning) + .value("critical", alert::critical) + .value("fatal", alert::fatal) + .value("none", alert::none) + ; +#endif + + enum_("category_t") + .value("error_notification", alert::error_notification) + .value("peer_notification", alert::peer_notification) + .value("port_mapping_notification", alert::port_mapping_notification) + .value("storage_notification", alert::storage_notification) + .value("tracker_notification", alert::tracker_notification) + .value("debug_notification", alert::debug_notification) + .value("status_notification", alert::status_notification) + .value("progress_notification", alert::progress_notification) + .value("ip_block_notification", alert::ip_block_notification) + .value("performance_warning", alert::performance_warning) + .value("dht_notification", alert::dht_notification) + .value("stats_notification", alert::stats_notification) + .value("session_log_notification", alert::session_log_notification) + .value("torrent_log_notification", alert::torrent_log_notification) + .value("peer_log_notification", alert::peer_log_notification) + .value("incoming_request_notification", alert::incoming_request_notification) + .value("dht_log_notification", alert::dht_log_notification) + .value("dht_operation_notification", alert::dht_operation_notification) + .value("port_mapping_log_notification", alert::port_mapping_log_notification) + .value("picker_log_notification", alert::picker_log_notification) + // deliberately not INT_MAX. Arch linux crash while throwing an exception + .value("all_categories", (alert::category_t)0xfffffff) + ; + + } + + class_, noncopyable>( + "torrent_alert", no_init) + .def_readonly("handle", &torrent_alert::handle) + ; + + class_, noncopyable>( + "tracker_alert", no_init) +#ifndef TORRENT_NO_DEPRECATE + .def_readonly("url", &tracker_alert::url) +#endif + .def("tracker_url", &tracker_alert::tracker_url) + ; + + class_, noncopyable>( + "torrent_added_alert", no_init) + ; + + class_, noncopyable>( + "torrent_removed_alert", no_init) + .def_readonly("info_hash", &torrent_removed_alert::info_hash) + ; + + class_, noncopyable>( + "read_piece_alert", 0, no_init) + .add_property("buffer", get_buffer) + .def_readonly("piece", &read_piece_alert::piece) + .def_readonly("size", &read_piece_alert::size) + ; + + class_, noncopyable>( + "peer_alert", no_init) + .add_property("ip", &peer_alert_ip) + .def_readonly("pid", &peer_alert::pid) + ; + class_, noncopyable>( + "tracker_error_alert", no_init) +#ifndef TORRENT_NO_DEPRECATE + .def_readonly("msg", &tracker_error_alert::msg) +#endif + .def("error_message", &tracker_error_alert::error_message) + .def_readonly("times_in_row", &tracker_error_alert::times_in_row) + .def_readonly("status_code", &tracker_error_alert::status_code) + .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) + .def_readonly("piece_index", &hash_failed_alert::piece_index) + ; + + class_, noncopyable>( + "peer_ban_alert", no_init); + + class_, noncopyable>( + "peer_error_alert", no_init) + .def_readonly("error", &peer_error_alert::error) + ; + + class_, noncopyable>( + "invalid_request_alert", no_init) + .def_readonly("request", &invalid_request_alert::request) + ; + + class_("peer_request") + .def_readonly("piece", &peer_request::piece) + .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) + .def_readonly("piece_index", &piece_finished_alert::piece_index) + ; + + class_, noncopyable>( + "block_finished_alert", no_init) + .def_readonly("block_index", &block_finished_alert::block_index) + .def_readonly("piece_index", &block_finished_alert::piece_index) + ; + + class_, noncopyable>( + "block_downloading_alert", no_init) +#ifndef TORRENT_NO_DEPRECATE + .def_readonly("peer_speedmsg", &block_downloading_alert::peer_speedmsg) +#endif + .def_readonly("block_index", &block_downloading_alert::block_index) + .def_readonly("piece_index", &block_downloading_alert::piece_index) + ; + + class_, noncopyable>( + "storage_moved_alert", no_init) +#ifndef TORRENT_NO_DEPRECATE + .def_readonly("path", &storage_moved_alert::path) +#endif + .def("storage_path", &storage_moved_alert::storage_path) + ; + + class_, noncopyable>( + "storage_moved_failed_alert", no_init) + .def_readonly("error", &storage_moved_failed_alert::error) + ; + + class_, noncopyable>( + "torrent_deleted_alert", no_init) + .def_readonly("info_hash", &torrent_deleted_alert::info_hash) + ; + + class_, noncopyable>( + "torrent_paused_alert", no_init); + + class_, noncopyable>( + "torrent_checked_alert", no_init); + + class_, noncopyable>( + "url_seed_alert", no_init) +#ifndef TORRENT_NO_DEPRECATE + .def_readonly("url", &url_seed_alert::url) + .def_readonly("msg", &url_seed_alert::msg) +#endif + .def("server_url", &url_seed_alert::server_url) + ; + + class_, noncopyable>( + "file_error_alert", no_init) + .def_readonly("error", &file_error_alert::error) + .def("filename", &file_error_alert::filename) +#ifndef TORRENT_NO_DEPRECATE + .def_readonly("file", &file_error_alert::file) + .def_readonly("msg", &file_error_alert::msg) +#endif + ; + + class_, noncopyable>( + "metadata_failed_alert", no_init); + + class_, noncopyable>( + "metadata_received_alert", no_init); + + class_, noncopyable>( + "listen_failed_alert", no_init) + .def_readonly("endpoint", &listen_failed_alert::endpoint) + .def("listen_interface", &listen_failed_alert::listen_interface) + .def_readonly("error", &listen_failed_alert::error) + .def_readonly("operation", &listen_failed_alert::operation) + .def_readonly("sock_type", &listen_failed_alert::sock_type) + ; + + class_, noncopyable>( + "listen_succeeded_alert", no_init) + .def_readonly("endpoint", &listen_succeeded_alert::endpoint) + ; + + class_, noncopyable>( + "portmap_error_alert", no_init) + .def_readonly("mapping", &portmap_error_alert::mapping) + .def_readonly("map_type", &portmap_error_alert::map_type) + .def_readonly("error", &portmap_error_alert::error) +#ifndef TORRENT_NO_DEPRECATE + .def_readonly("type", &portmap_error_alert::map_type) + .def_readonly("msg", &portmap_error_alert::msg) +#endif + ; + + class_, noncopyable>( + "portmap_alert", no_init) + .def_readonly("mapping", &portmap_alert::mapping) + .def_readonly("external_port", &portmap_alert::external_port) +#ifndef TORRENT_NO_DEPRECATE + .def_readonly("type", &portmap_alert::map_type) +#endif + .def_readonly("map_type", &portmap_alert::map_type) + ; + +#ifndef TORRENT_DISABLE_LOGGING + + class_, noncopyable>( + "portmap_log_alert", no_init) + .def_readonly("map_type", &portmap_log_alert::map_type) +#ifndef TORRENT_NO_DEPRECATE + .def_readonly("type", &portmap_log_alert::map_type) + .def_readonly("msg", &portmap_log_alert::msg) +#endif + ; + +#endif // TORRENT_DISABLE_LOGGING + + class_, noncopyable>( + "fastresume_rejected_alert", no_init) + .def_readonly("error", &fastresume_rejected_alert::error) +#ifndef TORRENT_NO_DEPRECATE + .def_readonly("msg", &fastresume_rejected_alert::msg) +#endif + ; + + class_, noncopyable>( + "peer_blocked_alert", no_init) + .add_property("ip", &peer_blocked_alert_ip) + ; + + 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) +#ifndef TORRENT_NO_DEPRECATE + .def_readonly("msg", &scrape_failed_alert::msg) +#endif + .def("error_message", &scrape_failed_alert::error_message) + ; + + class_, noncopyable>( + "udp_error_alert", no_init) + .def_readonly("endpoint", &udp_error_alert::endpoint) + .def_readonly("error", &udp_error_alert::error) + ; + + class_, noncopyable>( + "external_ip_alert", no_init) + .add_property("external_address", &external_ip_alert_ip) + ; + + class_, noncopyable>( + "save_resume_data_alert", no_init) + .def_readonly("resume_data", &save_resume_data_alert::resume_data) + ; + + class_, noncopyable>( + "file_completed_alert", no_init) + .def_readonly("index", &file_completed_alert::index) + ; + + class_, noncopyable>( + "file_renamed_alert", no_init) + .def_readonly("index", &file_renamed_alert::index) +#ifndef TORRENT_NO_DEPRECATE + .def_readonly("name", &file_renamed_alert::name) +#endif + .def("new_name", &file_renamed_alert::new_name) + ; + + class_, noncopyable>( + "file_rename_failed_alert", no_init) + .def_readonly("index", &file_rename_failed_alert::index) + .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", &dht_announce_alert_ip) + .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("error", &peer_disconnected_alert::error) +#ifndef TORRENT_NO_DEPRECATE + .def_readonly("msg", &peer_disconnected_alert::msg) +#endif + ; + + class_, noncopyable>( + "request_dropped_alert", no_init) + .def_readonly("block_index", &request_dropped_alert::block_index) + .def_readonly("piece_index", &request_dropped_alert::piece_index) + ; + + class_, noncopyable>( + "block_timeout_alert", no_init) + .def_readonly("block_index", &block_timeout_alert::block_index) + .def_readonly("piece_index", &block_timeout_alert::piece_index) + ; + + class_, noncopyable>( + "unwanted_block_alert", no_init) + .def_readonly("block_index", &unwanted_block_alert::block_index) + .def_readonly("piece_index", &unwanted_block_alert::piece_index) + ; + + class_, noncopyable>( + "torrent_delete_failed_alert", no_init) +#ifndef TORRENT_NO_DEPRECATE + .def_readonly("msg", &torrent_delete_failed_alert::msg) +#endif + .def_readonly("error", &torrent_delete_failed_alert::error) + ; + + class_, noncopyable>( + "save_resume_data_failed_alert", no_init) +#ifndef TORRENT_NO_DEPRECATE + .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) + .value("bittyrant_with_no_uplimit", performance_alert::bittyrant_with_no_uplimit) + .value("too_high_disk_queue_limit", performance_alert::too_high_disk_queue_limit) + .value("too_few_outgoing_ports", performance_alert::too_few_outgoing_ports) + .value("too_few_file_descriptors", performance_alert::too_few_file_descriptors) + ; + + class_, noncopyable>( + "stats_alert", no_init) + .add_property("transferred", &stats_alert_transferred) + .def_readonly("interval", &stats_alert::interval) + ; + + enum_("stats_channel") + .value("upload_payload", stats_alert::upload_payload) + .value("upload_protocol", stats_alert::upload_protocol) + .value("upload_ip_protocol", stats_alert::upload_ip_protocol) +#ifndef TORRENT_NO_DEPRECATE + .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) +#ifndef TORRENT_NO_DEPRECATE + .value("download_dht_protocol", stats_alert::download_dht_protocol) + .value("download_tracker_protocol", stats_alert::download_tracker_protocol) +#endif + ; + + 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) + ; + + class_, noncopyable>( + "incoming_connection_alert", no_init) + .def_readonly("socket_type", &incoming_connection_alert::socket_type) + .add_property("ip", &incoming_connection_alert_ip) + ; + class_, noncopyable>( + "torrent_need_cert_alert", no_init) + .def_readonly("error", &torrent_need_cert_alert::error) + ; + + class_, noncopyable>( + "add_torrent_alert", no_init) + .def_readonly("error", &add_torrent_alert::error) + .add_property("params", &get_params) + ; + + class_, noncopyable>( + "torrent_update_alert", no_init) + .def_readonly("old_ih", &torrent_update_alert::old_ih) + .def_readonly("new_ih", &torrent_update_alert::new_ih) + ; + + 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) + .add_property("ip", &dht_outgoing_get_peers_alert_ip) + ; + +#ifndef TORRENT_DISABLE_LOGGING + + class_, noncopyable>( + "log_alert", no_init) + .def("msg", &log_alert::msg) + ; + + class_, noncopyable>( + "torrent_log_alert", no_init) + .def("msg", &torrent_log_alert::msg) + ; + + class_, noncopyable>( + "peer_log_alert", no_init) + .def("msg", &peer_log_alert::msg) + ; + + class_, noncopyable>( + "picker_log_alert", no_init) + .add_property("picker_flags", &picker_log_alert::picker_flags) + .def("blocks", &picker_log_alert::blocks) + ; + +#endif // TORRENT_DISABLE_LOGGING + + 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_immutable_item_alert", no_init) + .add_property("item", &dht_immutable_item) + ; + + class_, noncopyable>( + "dht_mutable_item_alert", no_init) + .add_property("item", &dht_mutable_item) + ; + + class_, noncopyable>( + "dht_put_alert", no_init) + .add_property("item", &dht_put_item) + ; + class_, noncopyable>( + "session_stats_alert", no_init) + .add_property("values", &session_stats_values) + ; + + 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", &dht_get_peers_reply_alert_peers) + ; +} diff --git a/bindings/python/src/big_number.cpp b/bindings/python/src/big_number.cpp new file mode 100644 index 0000000..10e896d --- /dev/null +++ b/bindings/python/src/big_number.cpp @@ -0,0 +1,42 @@ +// 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 +#include "boost_python.hpp" +#include "bytes.hpp" + +long get_hash(boost::python::object o) +{ + using namespace boost::python; + return PyObject_Hash(str(o).ptr()); +} + +using namespace libtorrent; + +bytes sha1_hash_bytes(const sha1_hash& bn) { + return bytes(bn.to_string()); +} + +void bind_sha1_hash() +{ + using namespace boost::python; + using namespace libtorrent; + + class_("sha1_hash") + .def(self == self) + .def(self != self) + .def(self < self) + .def(self_ns::str(self)) + .def(init()) + .def("clear", &sha1_hash::clear) + .def("is_all_zeros", &sha1_hash::is_all_zeros) + .def("to_string", &sha1_hash::to_string) + .def("__hash__", get_hash) + .def("to_bytes", sha1_hash_bytes) + ; + + scope().attr("big_number") = scope().attr("sha1_hash"); + scope().attr("peer_id") = scope().attr("sha1_hash"); +} + diff --git a/bindings/python/src/boost_python.hpp b/bindings/python/src/boost_python.hpp new file mode 100644 index 0000000..12c1f3e --- /dev/null +++ b/bindings/python/src/boost_python.hpp @@ -0,0 +1,13 @@ +// 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 + +#endif + diff --git a/bindings/python/src/bytes.hpp b/bindings/python/src/bytes.hpp new file mode 100644 index 0000000..02943b9 --- /dev/null +++ b/bindings/python/src/bytes.hpp @@ -0,0 +1,25 @@ +// 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, int len): arr(s, len) {} + bytes(std::string const& s): arr(s) {} +#if __cplusplus >= 201103L + bytes(std::string&& s): arr(std::move(s)) {} + bytes(bytes const&) = default; + bytes(bytes&&) = default; + bytes& operator=(bytes&&) = default; +#endif + 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..b7981ce --- /dev/null +++ b/bindings/python/src/converters.cpp @@ -0,0 +1,51 @@ +// 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" + +using namespace boost::python; + +template +struct pair_to_tuple +{ + static PyObject* convert(const std::pair& p) + { + return incref(make_tuple(p.first, p.second).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) ? x: 0; + } + + 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]); + new (storage) std::pair(p); + data->convertible = storage; + } +}; + +void bind_converters() +{ + to_python_converter, pair_to_tuple >(); + tuple_to_pair(); +} diff --git a/bindings/python/src/create_torrent.cpp b/bindings/python/src/create_torrent.cpp new file mode 100644 index 0000000..95f512d --- /dev/null +++ b/bindings/python/src/create_torrent.cpp @@ -0,0 +1,238 @@ +// 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 "bytes.hpp" + +using namespace boost::python; +using namespace libtorrent; + +namespace +{ + void set_hash(create_torrent& c, int p, bytes const& b) + { + c.set_hash(p, sha1_hash(b.arr)); + } + + void set_file_hash(create_torrent& c, int f, bytes const& b) + { + c.set_file_hash(f, sha1_hash(b.arr)); + } + + void call_python_object(boost::python::object const& obj, int i) + { + obj(i); + } + +#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, boost::bind(call_python_object, cb, _1)); + } +#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, boost::bind(call_python_object, cb, _1), 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 !defined TORRENT_NO_DEPRECATE + void add_file(file_storage& ct, file_entry const& fe) + { + ct.add_file(fe); + } + + struct FileIter + { + typedef libtorrent::file_entry value_type; + typedef libtorrent::file_entry reference; + typedef libtorrent::file_entry* pointer; + typedef int difference_type; + typedef std::forward_iterator_tag iterator_category; + + FileIter(file_storage const& fs, int i) : m_fs(&fs), m_i(i) {} + FileIter(FileIter const& fi) : m_fs(fi.m_fs), m_i(fi.m_i) {} + FileIter() : m_fs(NULL), m_i(0) {} + libtorrent::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& rhs) + { + m_fs = rhs.m_fs; + m_i = rhs.m_i; + return *this; + } + + file_storage const* m_fs; + int m_i; + }; + + FileIter begin_files(file_storage const& self) + { return FileIter(self, 0); } + + FileIter end_files(file_storage const& self) + { return FileIter(self, self.num_files()); } +#endif + + bool call_python_object2(boost::python::object& obj, std::string const& i) + { + return obj(i); + } + + void add_files_callback(file_storage& fs, std::string const& file + , boost::python::object cb, boost::uint32_t flags) + { + add_files(fs, file, boost::bind(&call_python_object2, cb, _1), flags); + } + +} + +void bind_create_torrent() +{ + void (file_storage::*add_file0)(std::string const&, boost::int64_t + , int, std::time_t, std::string const&) = &file_storage::add_file; +#if !defined TORRENT_NO_DEPRECATE +#if TORRENT_USE_WSTRING + void (file_storage::*add_file1)(std::wstring const&, boost::int64_t + , int, std::time_t, std::string const&) = &file_storage::add_file; +#endif // TORRENT_USE_WSTRING +#endif // TORRENT_NO_DEPRECATE + + void (file_storage::*set_name0)(std::string const&) = &file_storage::set_name; + void (file_storage::*rename_file0)(int, std::string const&) = &file_storage::rename_file; +#if TORRENT_USE_WSTRING && !defined TORRENT_NO_DEPRECATE + void (file_storage::*set_name1)(std::wstring const&) = &file_storage::set_name; + void (file_storage::*rename_file1)(int, std::wstring const&) = &file_storage::rename_file; +#endif + +#ifndef BOOST_NO_EXCEPTIONS + void (*set_piece_hashes0)(create_torrent&, std::string const&) = &set_piece_hashes; +#endif + void (*add_files0)(file_storage&, std::string const&, boost::uint32_t) = add_files; + + std::string const& (file_storage::*file_storage_symlink)(int) const = &file_storage::symlink; + sha1_hash (file_storage::*file_storage_hash)(int) const = &file_storage::hash; + std::string (file_storage::*file_storage_file_path)(int, std::string const&) const = &file_storage::file_path; + boost::int64_t (file_storage::*file_storage_file_size)(int) const = &file_storage::file_size; + boost::int64_t (file_storage::*file_storage_file_offset)(int) const = &file_storage::file_offset; + int (file_storage::*file_storage_file_flags)(int) const = &file_storage::file_flags; + +#if !defined TORRENT_NO_DEPRECATE + file_entry (file_storage::*at)(int) const = &file_storage::at; +#endif + + // TODO: 3 move this to its own file + class_("file_storage") + .def("is_valid", &file_storage::is_valid) + .def("add_file", add_file0, (arg("path"), arg("size"), arg("flags") = 0, arg("mtime") = 0, arg("linkpath") = "")) +#if TORRENT_USE_WSTRING && !defined TORRENT_NO_DEPRECATE + .def("add_file", add_file1, (arg("path"), arg("size"), arg("flags") = 0, arg("mtime") = 0, arg("linkpath") = "")) +#endif + .def("num_files", &file_storage::num_files) +#if !defined TORRENT_NO_DEPRECATE + .def("at", at) + .def("add_file", add_file, arg("entry")) + .def("__iter__", boost::python::range(&begin_files, &end_files)) + .def("__len__", &file_storage::num_files) +#endif + .def("hash", file_storage_hash) + .def("symlink", file_storage_symlink, return_value_policy()) + .def("file_path", file_storage_file_path, (arg("idx"), arg("save_path") = "")) + .def("file_size", file_storage_file_size) + .def("file_offset", file_storage_file_offset) + .def("file_flags", file_storage_file_flags) + + .def("total_size", &file_storage::total_size) + .def("set_num_pieces", &file_storage::set_num_pieces) + .def("num_pieces", &file_storage::num_pieces) + .def("set_piece_length", &file_storage::set_piece_length) + .def("piece_length", &file_storage::piece_length) + .def("piece_size", &file_storage::piece_size) + .def("set_name", set_name0) + .def("rename_file", rename_file0) +#if TORRENT_USE_WSTRING && !defined TORRENT_NO_DEPRECATE + .def("set_name", set_name1) + .def("rename_file", rename_file1) +#endif + .def("name", &file_storage::name, return_value_policy()) + ; + + enum_("file_flags_t") + .value("flag_pad_file", file_storage::flag_pad_file) + .value("flag_hidden", file_storage::flag_hidden) + .value("flag_executable", file_storage::flag_executable) + .value("flag_symlink", file_storage::flag_symlink) + ; + + class_("create_torrent", no_init) + .def(init()) + .def(init(arg("ti"))) + .def(init((arg("storage"), arg("piece_size") = 0 + , arg("pad_file_limit") = -1, arg("flags") = int(libtorrent::create_torrent::optimize_alignment)))) + + .def("generate", &create_torrent::generate) + + .def("files", &create_torrent::files, return_internal_reference<>()) + .def("set_comment", &create_torrent::set_comment) + .def("set_creator", &create_torrent::set_creator) + .def("set_hash", &set_hash) + .def("set_file_hash", &set_file_hash) + .def("add_url_seed", &create_torrent::add_url_seed) + .def("add_http_seed", &create_torrent::add_http_seed) + .def("add_node", &add_node) + .def("add_tracker", &create_torrent::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"))) + ; + + enum_("create_torrent_flags_t") +#ifndef TORRENT_NO_DEPRECATE + .value("optimize", create_torrent::optimize) +#endif + .value("optimize_alignment", create_torrent::optimize_alignment) + .value("merkle", create_torrent::merkle) + .value("modification_time", create_torrent::modification_time) + .value("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); + +} diff --git a/bindings/python/src/datetime.cpp b/bindings/python/src/datetime.cpp new file mode 100644 index 0000000..f1e91d7 --- /dev/null +++ b/bindings/python/src/datetime.cpp @@ -0,0 +1,102 @@ +// 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" + +using namespace boost::python; +namespace lt = libtorrent; + +#if BOOST_VERSION < 103400 + +// From Boost 1.34 +object import(str name) +{ + // should be 'char const *' but older python versions don't use 'const' yet. + char *n = extract(name); + handle<> module(borrowed(PyImport_ImportModule(n))); + return object(module); +} + +#endif + +object datetime_timedelta; +object datetime_datetime; + +struct chrono_time_duration_to_python +{ + static PyObject* convert(lt::time_duration const& d) + { + object result = datetime_timedelta( + 0 // days + , 0 // seconds + , lt::total_microseconds(d) + ); + + 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()); + } +}; + +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< + boost::posix_time::time_duration + , time_duration_to_python + >(); + + to_python_converter< + boost::posix_time::ptime + , ptime_to_python + >(); + + to_python_converter< + lt::time_duration + , chrono_time_duration_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..db43b5b --- /dev/null +++ b/bindings/python/src/entry.cpp @@ -0,0 +1,179 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include +#include "bytes.hpp" + +using namespace boost::python; +using namespace libtorrent; + +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 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 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(boost::shared_ptr const& e) + { + if (!e) + return incref(Py_None); + return convert(*e); + } + + static PyObject* convert(entry const& e) + { + return incref(convert0(e).ptr()); + } +}; + +struct entry_from_python +{ + entry_from_python() + { + converter::registry::push_back( + &convertible, &construct, type_id() + ); + } + + static void* convertible(PyObject* e) + { + return e; + } + + static entry construct0(object e) + { + if (extract(e).check()) + { + dict d = extract(e); + list items(d.items()); + std::size_t length = extract(items.attr("__len__")()); + entry result(entry::dictionary_t); + + for (std::size_t i = 0; i < length; ++i) + { + if (extract(items[i][0]).check()) + { + result.dict().insert( + std::make_pair( + extract(items[i][0])().arr, + construct0(items[i][1]) + ) + ); + } + else + { + result.dict().insert( + std::make_pair( + extract(items[i][0])(), + construct0(items[i][1]) + ) + ); + } + } + + return result; + } + else if (extract(e).check()) + { + list l = extract(e); + + std::size_t length = extract(l.attr("__len__")()); + entry result(entry::list_t); + + for (std::size_t i = 0; i < length; ++i) + { + result.list().push_back(construct0(l[i])); + } + + return result; + } + else if (extract(e).check()) + { + return entry(extract(e)().arr); + } + else if (extract(e).check()) + { + return entry(extract(e)()); + } + else if (extract(e).check()) + { + return entry(extract(e)()); + } + else if (extract(e).check()) + { + tuple t = extract(e); + + std::size_t const length = extract(t.attr("__len__")()); + std::vector preformatted(length); + for (std::size_t i = 0; i < length; ++i) + { + preformatted[i] = char(extract(t[i])); + } + + return entry(preformatted); + } + + return entry(); + } + + static void construct(PyObject* e, converter::rvalue_from_python_stage1_data* data) + { + void* storage = ((converter::rvalue_from_python_storage*)data)->storage.bytes; + new (storage) entry(construct0(object(borrowed(e)))); + data->convertible = storage; + } +}; + +void bind_entry() +{ + to_python_converter, entry_to_python>(); + to_python_converter(); + entry_from_python(); +} + diff --git a/bindings/python/src/error_code.cpp b/bindings/python/src/error_code.cpp new file mode 100644 index 0000000..caea76b --- /dev/null +++ b/bindings/python/src/error_code.cpp @@ -0,0 +1,89 @@ +/* + +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 +#include +#include +#include +#include "boost_python.hpp" + +using namespace boost::python; +using namespace libtorrent; +using boost::system::error_category; + +void bind_error_code() +{ + class_("error_category", no_init) + .def("name", &error_category::name) + .def("message", &error_category::message) + .def(self == self) + .def(self < self) + .def(self != self) + ; + + class_("error_code") + .def(init<>()) + .def("message", &error_code::message) + .def("value", &error_code::value) + .def("clear", &error_code::clear) + .def("category", &error_code::category + , return_internal_reference<>()) + .def("assign", &error_code::assign) + ; + + def("get_libtorrent_category", &get_libtorrent_category + , return_internal_reference<>()); + + def("get_upnp_category", &get_upnp_category + , return_internal_reference<>()); + + def("get_http_category", &get_http_category + , return_internal_reference<>()); + + def("get_socks_category", &get_socks_category + , return_internal_reference<>()); + +#if TORRENT_USE_I2P + def("get_i2p_category", &get_i2p_category + , return_internal_reference<>()); +#endif + + def("get_bdecode_category", &get_bdecode_category + , return_internal_reference<>()); + + def("generic_category", &boost::system::generic_category + , return_internal_reference<>()); + + def("system_category", &boost::system::system_category + , return_internal_reference<>()); +} + diff --git a/bindings/python/src/fingerprint.cpp b/bindings/python/src/fingerprint.cpp new file mode 100644 index 0000000..27215fa --- /dev/null +++ b/bindings/python/src/fingerprint.cpp @@ -0,0 +1,27 @@ +// 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 +#include "boost_python.hpp" + +void bind_fingerprint() +{ + using namespace boost::python; + using namespace libtorrent; + + class_("fingerprint", no_init) + .def( + init( + (arg("id"), "major", "minor", "revision", "tag") + ) + ) + .def("__str__", &fingerprint::to_string) + .def_readonly("name", &fingerprint::name) + .def_readonly("major_version", &fingerprint::major_version) + .def_readonly("minor_version", &fingerprint::minor_version) + .def_readonly("revision_version", &fingerprint::revision_version) + .def_readonly("tag_version", &fingerprint::tag_version) + ; +} + diff --git a/bindings/python/src/gil.hpp b/bindings/python/src/gil.hpp new file mode 100644 index 0000000..9ce9c90 --- /dev/null +++ b/bindings/python/src/gil.hpp @@ -0,0 +1,148 @@ +// Copyright Daniel Wallin 2007. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef GIL_070107_HPP +# define GIL_070107_HPP + +#include + +# include +# include +# include +# include + +#include + +//namespace libtorrent { namespace python { + +// RAII helper to release GIL. +struct allow_threading_guard +{ + allow_threading_guard() + : save(PyEval_SaveThread()) + {} + + ~allow_threading_guard() + { + PyEval_RestoreThread(save); + } + + PyThreadState* save; +}; + +struct lock_gil +{ + lock_gil() + : state(PyGILState_Ensure()) + {} + + ~lock_gil() + { + PyGILState_Release(state); + } + + PyGILState_STATE state; +}; + +template +struct allow_threading +{ + allow_threading(F fn) + : fn(fn) + {} + + template + R operator()(A0& a0) + { + allow_threading_guard guard; + return (a0.*fn)(); + } + + template + R operator()(A0& a0, A1& a1) + { + allow_threading_guard guard; + return (a0.*fn)(a1); + } + + template + R operator()(A0& a0, A1& a1, A2& a2) + { + allow_threading_guard guard; + return (a0.*fn)(a1,a2); + } + + template + R operator()(A0& a0, A1& a1, A2& a2, A3& a3) + { + allow_threading_guard guard; + return (a0.*fn)(a1,a2,a3); + } + + template + R operator()(A0& a0, A1& a1, A2& a2, A3& a3, A4& a4) + { + allow_threading_guard guard; + return (a0.*fn)(a1,a2,a3,a4); + } + + template + R operator()(A0& a0, A1& a1, A2& a2, A3& a3, A4& a4, A5& a5) + { + allow_threading_guard guard; + return (a0.*fn)(a1,a2,a3,a4,a5); + } + + F fn; +}; + +template +struct visitor : boost::python::def_visitor > +{ + visitor(F fn) + : fn(fn) + {} + + template + void visit_aux( + Class& cl, char const* name + , Options const& options, Signature const& signature) const + { + typedef typename boost::mpl::at_c::type return_type; + + cl.def( + name + , boost::python::make_function( + allow_threading(fn) + , options.policies() + , options.keywords() + , signature + ) + ); + } + + template + void visit(Class& cl, char const* name, Options const& options) const + { + this->visit_aux( + cl, name, options + , boost::python::detail::get_signature(fn, (typename Class::wrapped_type*)0) + ); + } + + F fn; +}; + +// Member function adaptor that releases and aqcuires the GIL +// around the function call. +template +visitor allow_threads(F fn) +{ + return visitor(fn); +} + +//}} // namespace libtorrent::python + +#endif // GIL_070107_HPP + diff --git a/bindings/python/src/ip_filter.cpp b/bindings/python/src/ip_filter.cpp new file mode 100644 index 0000000..8fea188 --- /dev/null +++ b/bindings/python/src/ip_filter.cpp @@ -0,0 +1,32 @@ +// Copyright Andrew Resch 2008. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include "boost_python.hpp" +#include "gil.hpp" + +using namespace boost::python; +using namespace libtorrent; + +namespace +{ + void add_rule(ip_filter& filter, std::string start, std::string end, int flags) + { + return filter.add_rule(address::from_string(start), address::from_string(end), flags); + } + + int access0(ip_filter& filter, std::string addr) + { + return filter.access(address::from_string(addr)); + } +} + +void bind_ip_filter() +{ + class_("ip_filter") + .def("add_rule", add_rule) + .def("access", access0) + .def("export_filter", allow_threads(&ip_filter::export_filter)) + ; +} diff --git a/bindings/python/src/magnet_uri.cpp b/bindings/python/src/magnet_uri.cpp new file mode 100644 index 0000000..f3f087f --- /dev/null +++ b/bindings/python/src/magnet_uri.cpp @@ -0,0 +1,85 @@ +// 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 +#include +#include "gil.hpp" + +using namespace boost::python; +using namespace libtorrent; +namespace lt = libtorrent; + +extern void dict_to_add_torrent_params(dict params, add_torrent_params& p); + +namespace { + +#ifndef TORRENT_NO_DEPRECATE + torrent_handle _add_magnet_uri(lt::session& s, std::string uri, dict params) + { + add_torrent_params p; + + dict_to_add_torrent_params(params, p); + + allow_threading_guard guard; + + p.url = uri; + +#ifndef BOOST_NO_EXCEPTIONS + return s.add_torrent(p); +#else + error_code ec; + return s.add_torrent(p, ec); +#endif + } +#endif + + dict parse_magnet_uri_wrap(std::string const& uri) + { + add_torrent_params p; + error_code ec; + parse_magnet_uri(uri, p, ec); + + if (ec) throw libtorrent_exception(ec); + + dict ret; + + 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 (std::vector >::const_iterator i = p.dht_nodes.begin() + , end(p.dht_nodes.end()); i != end; ++i) + tracker_list.append(boost::python::make_tuple(i->first, i->second)); + ret["dht_nodes"] = nodes_list; + ret["info_hash"] = p.info_hash; + ret["name"] = p.name; + ret["save_path"] = p.save_path; + ret["storage_mode"] = p.storage_mode; + ret["url"] = p.url; + ret["uuid"] = p.uuid; + ret["source_feed_url"] = p.source_feed_url; + ret["flags"] = p.flags; + return ret; + } + + 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() +{ +#ifndef TORRENT_NO_DEPRECATE + 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); +} + diff --git a/bindings/python/src/module.cpp b/bindings/python/src/module.cpp new file mode 100644 index 0000000..d64fae6 --- /dev/null +++ b/bindings/python/src/module.cpp @@ -0,0 +1,56 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifdef __GNUC__ +#define BOOST_PYTHON_USE_GCC_SYMBOL_VISIBILITY 1 +#endif + +#include "libtorrent/config.hpp" +#include + +void bind_utility(); +void bind_fingerprint(); +void bind_sha1_hash(); +void bind_session(); +void bind_entry(); +void bind_torrent_info(); +void bind_unicode_string_conversion(); +void bind_torrent_handle(); +void bind_torrent_status(); +void bind_session_settings(); +void bind_version(); +void bind_alert(); +void bind_datetime(); +void bind_peer_info(); +void bind_ip_filter(); +void bind_magnet_uri(); +void bind_converters(); +void bind_create_torrent(); +void bind_error_code(); + +BOOST_PYTHON_MODULE(libtorrent) +{ + Py_Initialize(); + PyEval_InitThreads(); + + bind_error_code(); + bind_utility(); + bind_fingerprint(); + bind_sha1_hash(); + bind_entry(); + bind_torrent_handle(); + bind_session(); + bind_torrent_info(); + bind_unicode_string_conversion(); + bind_torrent_status(); + bind_session_settings(); + bind_version(); + bind_alert(); + bind_datetime(); + bind_peer_info(); + bind_ip_filter(); + bind_magnet_uri(); + bind_converters(); + 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..3fd7fd0 --- /dev/null +++ b/bindings/python/src/peer_info.cpp @@ -0,0 +1,163 @@ +// 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 +#include +#include "boost_python.hpp" +#include + +using namespace boost::python; +using namespace libtorrent; + +boost::int64_t get_last_active(peer_info const& pi) +{ + return total_seconds(pi.last_active); +} + +boost::int64_t get_last_request(peer_info const& pi) +{ + return total_seconds(pi.last_request); +} + +boost::int64_t get_download_queue_time(peer_info const& pi) +{ + return total_seconds(pi.download_queue_time); +} + +#ifndef TORRENT_NO_DEPRECATE +#ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES +str get_country(peer_info const& pi) +{ + return str(pi.country, 2); +} +#endif +#endif // TORRENT_NO_DEPRECATE + +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; +} + +void bind_peer_info() +{ + scope pi = class_("peer_info") + .def_readonly("flags", &peer_info::flags) + .def_readonly("source", &peer_info::source) + .def_readonly("read_state", &peer_info::read_state) + .def_readonly("write_state", &peer_info::write_state) + .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) +#ifndef TORRENT_NO_DEPRECATE + .def_readonly("upload_limit", &peer_info::upload_limit) + .def_readonly("download_limit", &peer_info::download_limit) + .def_readonly("load_balancing", &peer_info::load_balancing) +#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) +#ifndef TORRENT_NO_DEPRECATE +#ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES + .add_property("country", get_country) +#endif +#endif // TORRENT_NO_DEPRECATE + .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) + .def_readonly("downloading_piece_index", &peer_info::downloading_piece_index) + .def_readonly("downloading_block_index", &peer_info::downloading_block_index) + .def_readonly("downloading_progress", &peer_info::downloading_progress) + .def_readonly("downloading_total", &peer_info::downloading_total) + .def_readonly("client", &peer_info::client) + .def_readonly("connection_type", &peer_info::connection_type) + .def_readonly("remote_dl_rate", &peer_info::remote_dl_rate) + .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) + .def_readonly("estimated_reciprocation_rate", &peer_info::estimated_reciprocation_rate) + .add_property("local_endpoint", get_local_endpoint) + ; + + // flags + pi.attr("interesting") = (int)peer_info::interesting; + pi.attr("choked") = (int)peer_info::choked; + pi.attr("remote_interested") = (int)peer_info::remote_interested; + pi.attr("remote_choked") = (int)peer_info::remote_choked; + pi.attr("supports_extensions") = (int)peer_info::supports_extensions; + pi.attr("local_connection") = (int)peer_info::local_connection; + pi.attr("handshake") = (int)peer_info::handshake; + pi.attr("connecting") = (int)peer_info::connecting; +#ifndef TORRENT_NO_DEPRECATE + pi.attr("queued") = (int)peer_info::queued; +#endif + pi.attr("on_parole") = (int)peer_info::on_parole; + pi.attr("seed") = (int)peer_info::seed; + pi.attr("optimistic_unchoke") = (int)peer_info::optimistic_unchoke; + pi.attr("snubbed") = (int)peer_info::snubbed; + pi.attr("upload_only") = (int)peer_info::upload_only; + pi.attr("endgame_mode") = (int)peer_info::endgame_mode; + pi.attr("holepunched") = (int)peer_info::holepunched; +#ifndef TORRENT_DISABLE_ENCRYPTION + pi.attr("rc4_encrypted") = (int)peer_info::rc4_encrypted; + pi.attr("plaintext_encrypted") = (int)peer_info::plaintext_encrypted; +#endif + + // connection_type + pi.attr("standard_bittorrent") = (int)peer_info::standard_bittorrent; + pi.attr("web_seed") = (int)peer_info::web_seed; + + // source + pi.attr("tracker") = (int)peer_info::tracker; + pi.attr("dht") = (int)peer_info::dht; + pi.attr("pex") = (int)peer_info::pex; + pi.attr("lsd") = (int)peer_info::lsd; + pi.attr("resume_data") = (int)peer_info::resume_data; + + // read/write state + pi.attr("bw_idle") = (int)peer_info::bw_idle; +#ifndef TORRENT_NO_DEPRECATE + pi.attr("bw_torrent") = (int)peer_info::bw_torrent; + pi.attr("bw_global") = (int)peer_info::bw_global; +#endif + pi.attr("bw_limit") = (int)peer_info::bw_limit; + pi.attr("bw_network") = (int)peer_info::bw_network; + pi.attr("bw_disk") = (int)peer_info::bw_disk; +} + diff --git a/bindings/python/src/session.cpp b/bindings/python/src/session.cpp new file mode 100644 index 0000000..1363f02 --- /dev/null +++ b/bindings/python/src/session.cpp @@ -0,0 +1,956 @@ +// 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // for settings_map() +#include +#include // for sign_mutable_item + +#ifndef TORRENT_NO_DEPRECATE +#include +#include +#endif +#include +#include +#include + +#include "gil.hpp" +#include "bytes.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include "boost_python.hpp" + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +using namespace boost::python; +using namespace libtorrent; +namespace lt = libtorrent; + +namespace +{ +#ifndef TORRENT_NO_DEPRECATE + 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 libtorrent_exception(ec); +#endif + } +#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; + } +#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)); + } + + 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 // 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); +#ifndef TORRENT_NO_DEPRECATE + else if (name == "lt_trackers") + s.add_extension(create_lt_trackers_plugin); + else if (name == "metadata_transfer") + s.add_extension(create_metadata_plugin); +#endif // TORRENT_NO_DEPRECATE + +#endif // TORRENT_DISABLE_EXTENSIONS + } + + void make_settings_pack(lt::settings_pack& p, dict const& sett_dict) + { + list iterkeys = (list)sett_dict.keys(); + int const len = boost::python::len(iterkeys); + for (int i = 0; i < len; i++) + { + std::string const key = extract(iterkeys[i]); + + int sett = setting_by_name(key); + if (sett < 0) continue; + + TORRENT_TRY + { + object const value = sett_dict[key]; + switch (sett & settings_pack::type_mask) + { + case settings_pack::string_type_base: + p.set_str(sett, extract(value)); + break; + case settings_pack::int_type_base: + p.set_int(sett, extract(value)); + break; + case settings_pack::bool_type_base: + p.set_bool(sett, extract(value)); + break; + } + } + TORRENT_CATCH(...) {} + } + } + + boost::shared_ptr make_session(boost::python::dict sett, int flags) + { + settings_pack p; + make_settings_pack(p, sett); + return boost::make_shared(p, flags); + } + +#ifndef TORRENT_NO_DEPRECATE + void session_set_settings(lt::session& ses, object const& sett) + { + extract old_settings(sett); + if (old_settings.check()) + { + allow_threading_guard guard; + ses.set_settings(old_settings); + } + else + { + settings_pack p; + make_settings_pack(p, extract(sett)); + allow_threading_guard guard; + ses.apply_settings(p); + } + } +#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(); + } + dict ret; + for (int i = settings_pack::string_type_base; + i < settings_pack::max_string_setting_internal; ++i) + { + ret[name_for_setting(i)] = sett.get_str(i); + } + + for (int i = settings_pack::int_type_base; + i < settings_pack::max_int_setting_internal; ++i) + { + ret[name_for_setting(i)] = sett.get_int(i); + } + + for (int i = settings_pack::bool_type_base; + i < settings_pack::max_bool_setting_internal; ++i) + { + ret[name_for_setting(i)] = sett.get_bool(i); + } + return ret; + } + +#ifndef BOOST_NO_EXCEPTIONS +#ifndef TORRENT_NO_DEPRECATE + torrent_handle add_torrent_depr(lt::session& s, torrent_info const& ti + , std::string const& save, entry const& resume + , storage_mode_t storage_mode, bool paused) + { + allow_threading_guard guard; + return s.add_torrent(ti, save, resume, storage_mode, paused, default_storage_constructor); + } +#endif +#endif +} + + void dict_to_add_torrent_params(dict params, add_torrent_params& p) + { + // torrent_info objects are always held by a shared_ptr in the python binding + if (params.has_key("ti") && params.get("ti") != 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 = boost::make_shared( + extract(params["ti"])); + } + + + if (params.has_key("info_hash")) + p.info_hash = sha1_hash(bytes(extract(params["info_hash"])).arr); + if (params.has_key("name")) + p.name = extract(params["name"]); + p.save_path = extract(params["save_path"]); + + if (params.has_key("resume_data")) + { + std::string resume = extract(params["resume_data"]); + p.resume_data.assign(resume.begin(), resume.end()); + } + if (params.has_key("storage_mode")) + p.storage_mode = extract(params["storage_mode"]); + + if (params.has_key("trackers")) + { + list l = extract(params["trackers"]); + int n = boost::python::len(l); + for(int i = 0; i < n; i++) + p.trackers.push_back(extract(l[i])); + } + + if (params.has_key("dht_nodes")) + { + list l = extract(params["dht_nodes"]); + int n = boost::python::len(l); + for(int i = 0; i < n; i++) + p.dht_nodes.push_back(extract >(l[i])); + } +#ifndef TORRENT_NO_DEPRECATE + std::string url; + if (params.has_key("tracker_url")) + p.trackers.push_back(extract(params["tracker_url"])); + if (params.has_key("seed_mode")) + p.seed_mode = params["seed_mode"]; + if (params.has_key("upload_mode")) + p.upload_mode = params["upload_mode"]; + if (params.has_key("share_mode")) + p.upload_mode = params["share_mode"]; + if (params.has_key("override_resume_data")) + p.override_resume_data = params["override_resume_data"]; + if (params.has_key("apply_ip_filter")) + p.apply_ip_filter = params["apply_ip_filter"]; + if (params.has_key("paused")) + p.paused = params["paused"]; + if (params.has_key("auto_managed")) + p.auto_managed = params["auto_managed"]; + if (params.has_key("duplicate_is_error")) + p.duplicate_is_error = params["duplicate_is_error"]; + if (params.has_key("merge_resume_trackers")) + p.merge_resume_trackers = params["merge_resume_trackers"]; +#endif + if (params.has_key("flags")) + p.flags = extract(params["flags"]); + + if (params.has_key("trackerid")) + p.trackerid = extract(params["trackerid"]); + if (params.has_key("url")) + p.url = extract(params["url"]); + if (params.has_key("source_feed_url")) + p.source_feed_url = extract(params["source_feed_url"]); + if (params.has_key("uuid")) + p.uuid = extract(params["uuid"]); + + if (params.has_key("file_priorities")) + { + list l = extract(params["file_priorities"]); + int n = boost::python::len(l); + for(int i = 0; i < n; i++) + p.file_priorities.push_back(extract(l[i])); + p.file_priorities.clear(); + } + } + +namespace +{ + + torrent_handle add_torrent(lt::session& s, dict params) + { + add_torrent_params p; + dict_to_add_torrent_params(params, p); + + allow_threading_guard guard; + +#ifndef BOOST_NO_EXCEPTIONS + return s.add_torrent(p); +#else + error_code ec; + return s.add_torrent(p, ec); +#endif + } + + void async_add_torrent(lt::session& s, dict params) + { + add_torrent_params p; + dict_to_add_torrent_params(params, p); + + allow_threading_guard guard; + + s.async_add_torrent(p); + } + +#ifndef TORRENT_NO_DEPRECATE + void dict_to_feed_settings(dict params, feed_settings& feed) + { + if (params.has_key("auto_download")) + feed.auto_download = extract(params["auto_download"]); + if (params.has_key("default_ttl")) + feed.default_ttl = extract(params["default_ttl"]); + if (params.has_key("url")) + feed.url = extract(params["url"]); + if (params.has_key("add_args")) + dict_to_add_torrent_params(dict(params["add_args"]), feed.add_args); + } + + feed_handle add_feed(lt::session& s, dict params) + { + feed_settings feed; + // this static here is a bit of a hack. It will + // probably work for the most part + dict_to_feed_settings(params, feed); + + allow_threading_guard guard; + return s.add_feed(feed); + } + + dict get_feed_status(feed_handle const& h) + { + feed_status s; + { + allow_threading_guard guard; + s = h.get_feed_status(); + } + dict ret; + ret["url"] = s.url; + ret["title"] = s.title; + ret["description"] = s.description; + ret["last_update"] = s.last_update; + ret["next_update"] = s.next_update; + ret["updating"] = s.updating; + ret["error"] = s.error ? s.error.message() : ""; + ret["ttl"] = s.ttl; + + list items; + for (std::vector::iterator i = s.items.begin() + , end(s.items.end()); i != end; ++i) + { + dict item; + item["url"] = i->url; + item["uuid"] = i->uuid; + item["title"] = i->title; + item["description"] = i->description; + item["comment"] = i->comment; + item["category"] = i->category; + item["size"] = i->size; + item["handle"] = i->handle; + item["info_hash"] = i->info_hash.to_string(); + items.append(item); + } + ret["items"] = items; + return ret; + } + + void set_feed_settings(feed_handle& h, dict sett) + { + feed_settings feed; + dict_to_feed_settings(sett, feed); + h.set_settings(feed); + } + + dict get_feed_settings(feed_handle& h) + { + feed_settings s; + { + allow_threading_guard guard; + s = h.settings(); + } + dict ret; + ret["url"] = s.url; + ret["auto_download"] = s.auto_download; + ret["default_ttl"] = s.default_ttl; + return ret; + } + + 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_NO_DEPRECATE + +#ifndef TORRENT_NO_DEPRECATE + boost::shared_ptr +#else + alert const* +#endif + wait_for_alert(lt::session& s, int ms) + { + allow_threading_guard guard; + alert const* a = s.wait_for_alert(milliseconds(ms)); +#ifndef TORRENT_NO_DEPRECATE + if (a == NULL) return boost::shared_ptr(); + return boost::shared_ptr(a->clone().release()); +#else + return a; +#endif + } + + list get_torrents(lt::session& s) + { + list ret; + std::vector torrents; + { + allow_threading_guard guard; + torrents = s.get_torrents(); + } + + for (std::vector::iterator i = torrents.begin(); i != torrents.end(); ++i) + { + ret.append(*i); + } + return ret; + } + + cache_status get_cache_info1(lt::session& s, torrent_handle h, int flags) + { + cache_status ret; + s.get_cache_info(&ret, h, flags); + return ret; + } + +#ifndef TORRENT_NO_DEPRECATE + cache_status get_cache_status(lt::session& s) + { + cache_status ret; + s.get_cache_info(&ret); + return ret; + } + + dict get_utp_stats(session_status const& st) + { + dict ret; + ret["num_idle"] = st.utp_stats.num_idle; + ret["num_syn_sent"] = st.utp_stats.num_syn_sent; + ret["num_connected"] = st.utp_stats.num_connected; + ret["num_fin_sent"] = st.utp_stats.num_fin_sent; + ret["num_close_wait"] = st.utp_stats.num_close_wait; + return ret; + } + + list get_cache_info2(lt::session& ses, sha1_hash ih) + { + std::vector ret; + + { + allow_threading_guard guard; + ses.get_cache_info(ih, ret); + } + + list pieces; + ptime now = time_now(); + for (std::vector::iterator i = ret.begin() + , end(ret.end()); i != end; ++i) + { + dict d; + d["piece"] = i->piece; + d["last_use"] = total_milliseconds(now - i->last_use) / 1000.f; + d["next_to_hash"] = i->next_to_hash; + d["kind"] = i->kind; + pieces.append(d); + } + return pieces; + } +#endif + + entry save_state(lt::session const& s, boost::uint32_t flags) + { + allow_threading_guard guard; + entry e; + s.save_state(e, flags); + return e; + } + +#ifndef TORRENT_NO_DEPRECATE + object pop_alert(lt::session& ses) + { + std::auto_ptr a; + { + allow_threading_guard guard; + a = ses.pop_alert(); + } + + return object(boost::shared_ptr(a.release())); + } + + list pop_alerts(lt::session& ses) + { + std::vector alerts; + { + allow_threading_guard guard; + ses.pop_alerts(&alerts); + } + + list ret; + for (std::vector::iterator i = alerts.begin() + , end(alerts.end()); i != end; ++i) + { + ret.append(boost::shared_ptr((*i)->clone().release())); + } + return ret; + } +#else + list pop_alerts(lt::session& ses) + { + std::vector alerts; + { + allow_threading_guard guard; + ses.pop_alerts(&alerts); + } + + list ret; + for (std::vector::iterator i = alerts.begin() + , end(alerts.end()); i != end; ++i) + { + ret.append(boost::python::ptr(*i)); + } + return ret; + } +#endif + + void load_state(lt::session& ses, entry const& st, boost::uint32_t flags) + { + allow_threading_guard guard; + + std::vector buf; + bencode(std::back_inserter(buf), st); + bdecode_node e; + error_code ec; + bdecode(&buf[0], &buf[0] + buf.size(), e, ec); + TORRENT_ASSERT(!ec); + ses.load_state(e, flags); + } + +#ifndef TORRENT_DISABLE_DHT + void dht_get_mutable_item(lt::session& ses, std::string key, std::string salt) + { + TORRENT_ASSERT(key.size() == 32); + boost::array public_key; + std::copy(key.begin(), key.end(), public_key.begin()); + ses.dht_get_item(public_key, salt); + } + + void put_string(entry& e, boost::array& sig, boost::uint64_t& seq, + std::string const& salt, std::string public_key, std::string private_key, + std::string data) + { + using libtorrent::dht::sign_mutable_item; + + e = data; + std::vector buf; + bencode(std::back_inserter(buf), e); + ++seq; + sign_mutable_item(std::pair(&buf[0], buf.size()) + , std::pair(&salt[0], salt.size()) + , seq, public_key.c_str(), private_key.c_str(), sig.data()); + } + + 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); + boost::array key; + std::copy(public_key.begin(), public_key.end(), key.begin()); + ses.dht_put_item(key, boost::bind(&put_string, _1, _2, _3, _4 + , public_key, private_key, data) + , salt); + } +#endif +} // namespace unnamed + + +void bind_session() +{ +#ifndef TORRENT_DISABLE_DHT + void (lt::session::*dht_get_immutable_item)(sha1_hash const&) = <::session::dht_get_item; + sha1_hash (lt::session::*dht_put_immutable_item)(entry data) = <::session::dht_put_item; +#endif // TORRENT_DISABLE_DHT + +#ifndef TORRENT_NO_DEPRECATE +#ifndef TORRENT_DISABLE_DHT + void (lt::session::*start_dht0)() = <::session::start_dht; + void (lt::session::*start_dht1)(entry const&) = <::session::start_dht; +#endif + + class_("session_status") + .def_readonly("has_incoming_connections", &session_status::has_incoming_connections) + + .def_readonly("upload_rate", &session_status::upload_rate) + .def_readonly("download_rate", &session_status::download_rate) + .def_readonly("total_download", &session_status::total_download) + .def_readonly("total_upload", &session_status::total_upload) + + .def_readonly("payload_upload_rate", &session_status::payload_upload_rate) + .def_readonly("payload_download_rate", &session_status::payload_download_rate) + .def_readonly("total_payload_download", &session_status::total_payload_download) + .def_readonly("total_payload_upload", &session_status::total_payload_upload) + + .def_readonly("ip_overhead_upload_rate", &session_status::ip_overhead_upload_rate) + .def_readonly("ip_overhead_download_rate", &session_status::ip_overhead_download_rate) + .def_readonly("total_ip_overhead_download", &session_status::total_ip_overhead_download) + .def_readonly("total_ip_overhead_upload", &session_status::total_ip_overhead_upload) + + .def_readonly("dht_upload_rate", &session_status::dht_upload_rate) + .def_readonly("dht_download_rate", &session_status::dht_download_rate) + .def_readonly("total_dht_download", &session_status::total_dht_download) + .def_readonly("total_dht_upload", &session_status::total_dht_upload) + + .def_readonly("tracker_upload_rate", &session_status::tracker_upload_rate) + .def_readonly("tracker_download_rate", &session_status::tracker_download_rate) + .def_readonly("total_tracker_download", &session_status::total_tracker_download) + .def_readonly("total_tracker_upload", &session_status::total_tracker_upload) + + .def_readonly("total_redundant_bytes", &session_status::total_redundant_bytes) + .def_readonly("total_failed_bytes", &session_status::total_failed_bytes) + + .def_readonly("num_peers", &session_status::num_peers) + .def_readonly("num_unchoked", &session_status::num_unchoked) + .def_readonly("allowed_upload_slots", &session_status::allowed_upload_slots) + + .def_readonly("up_bandwidth_queue", &session_status::up_bandwidth_queue) + .def_readonly("down_bandwidth_queue", &session_status::down_bandwidth_queue) + + .def_readonly("up_bandwidth_bytes_queue", &session_status::up_bandwidth_bytes_queue) + .def_readonly("down_bandwidth_bytes_queue", &session_status::down_bandwidth_bytes_queue) + + .def_readonly("optimistic_unchoke_counter", &session_status::optimistic_unchoke_counter) + .def_readonly("unchoke_counter", &session_status::unchoke_counter) + +#ifndef TORRENT_DISABLE_DHT + .def_readonly("dht_nodes", &session_status::dht_nodes) + .def_readonly("dht_node_cache", &session_status::dht_node_cache) + .def_readonly("dht_torrents", &session_status::dht_torrents) + .def_readonly("dht_global_nodes", &session_status::dht_global_nodes) + .def_readonly("active_requests", &session_status::active_requests) + .def_readonly("dht_total_allocations", &session_status::dht_total_allocations) +#endif // TORRENT_DISABLE_DHT + .add_property("utp_stats", &get_utp_stats) + ; + +#ifndef TORRENT_DISABLE_DHT + class_("dht_lookup") + .def_readonly("type", &dht_lookup::type) + .def_readonly("outstanding_requests", &dht_lookup::outstanding_requests) + .def_readonly("timeouts", &dht_lookup::timeouts) + .def_readonly("response", &dht_lookup::responses) + .def_readonly("branch_factor", &dht_lookup::branch_factor) + ; +#endif // TORRENT_DISABLE_DHT +#endif // TORRENT_NO_DEPRECATE + + enum_("storage_mode_t") + .value("storage_mode_allocate", storage_mode_allocate) + .value("storage_mode_sparse", storage_mode_sparse) + ; + + enum_("options_t") + .value("delete_files", lt::session::delete_files) + ; + + enum_("session_flags_t") + .value("add_default_plugins", lt::session::add_default_plugins) + .value("start_default_features", lt::session::start_default_features) + ; + + enum_("add_torrent_params_flags_t") + .value("flag_seed_mode", add_torrent_params::flag_seed_mode) + .value("flag_override_resume_data", add_torrent_params::flag_override_resume_data) + .value("flag_upload_mode", add_torrent_params::flag_upload_mode) + .value("flag_share_mode", add_torrent_params::flag_share_mode) + .value("flag_apply_ip_filter", add_torrent_params::flag_apply_ip_filter) + .value("flag_paused", add_torrent_params::flag_paused) + .value("flag_auto_managed", add_torrent_params::flag_auto_managed) + .value("flag_duplicate_is_error", add_torrent_params::flag_duplicate_is_error) + .value("flag_merge_resume_trackers", add_torrent_params::flag_merge_resume_trackers) + .value("flag_update_subscribe", add_torrent_params::flag_update_subscribe) + .value("flag_super_seeding", add_torrent_params::flag_super_seeding) + .value("flag_sequential_download", add_torrent_params::flag_sequential_download) + .value("flag_use_resume_save_path", add_torrent_params::flag_use_resume_save_path) + .value("flag_merge_resume_http_seeds", add_torrent_params::flag_merge_resume_http_seeds) + .value("flag_stop_when_ready", add_torrent_params::flag_stop_when_ready) + ; + class_("cache_status") +#ifndef TORRENT_NO_DEPRECATE + .def_readonly("blocks_written", &cache_status::blocks_written) + .def_readonly("writes", &cache_status::writes) + .def_readonly("blocks_read", &cache_status::blocks_read) + .def_readonly("blocks_read_hit", &cache_status::blocks_read_hit) + .def_readonly("reads", &cache_status::reads) + .def_readonly("queued_bytes", &cache_status::queued_bytes) + .def_readonly("cache_size", &cache_status::cache_size) + .def_readonly("write_cache_size", &cache_status::write_cache_size) + .def_readonly("read_cache_size", &cache_status::read_cache_size) + .def_readonly("pinned_blocks", &cache_status::pinned_blocks) + .def_readonly("total_used_buffers", &cache_status::total_used_buffers) + .def_readonly("average_read_time", &cache_status::average_read_time) + .def_readonly("average_write_time", &cache_status::average_write_time) + .def_readonly("average_hash_time", &cache_status::average_hash_time) + .def_readonly("average_job_time", &cache_status::average_job_time) + .def_readonly("cumulative_job_time", &cache_status::cumulative_job_time) + .def_readonly("cumulative_read_time", &cache_status::cumulative_read_time) + .def_readonly("cumulative_write_time", &cache_status::cumulative_write_time) + .def_readonly("cumulative_hash_time", &cache_status::cumulative_hash_time) + .def_readonly("total_read_back", &cache_status::total_read_back) + .def_readonly("read_queue_size", &cache_status::read_queue_size) + .def_readonly("blocked_jobs", &cache_status::blocked_jobs) + .def_readonly("queued_jobs", &cache_status::queued_jobs) + .def_readonly("peak_queued", &cache_status::peak_queued) + .def_readonly("pending_jobs", &cache_status::pending_jobs) + .def_readonly("num_jobs", &cache_status::num_jobs) + .def_readonly("num_read_jobs", &cache_status::num_read_jobs) + .def_readonly("num_write_jobs", &cache_status::num_write_jobs) + .def_readonly("arc_mru_size", &cache_status::arc_mru_size) + .def_readonly("arc_mru_ghost_size", &cache_status::arc_mru_ghost_size) + .def_readonly("arc_mfu_size", &cache_status::arc_mfu_size) + .def_readonly("arc_mfu_ghost_size", &cache_status::arc_mfu_ghost_size) +#endif + ; + + class_("session", no_init) + .def("__init__", boost::python::make_constructor(&make_session + , default_call_policies() + , (arg("settings") + , arg("flags")=lt::session::start_default_features + | lt::session::add_default_plugins)) + ) +#ifndef TORRENT_NO_DEPRECATE + .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")=int(alert::error_notification))) + ) +#endif + .def("post_torrent_updates", allow_threads(<::session::post_torrent_updates), arg("flags") = 0xffffffff) + .def("post_session_stats", allow_threads(<::session::post_session_stats)) + .def("outgoing_ports", &outgoing_ports) + .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) + .def( + "add_dht_router", &add_dht_router + , (arg("router"), "port") + ) + .def("is_dht_running", allow_threads(<::session::is_dht_running)) + .def("set_dht_settings", allow_threads(<::session::set_dht_settings)) + .def("get_dht_settings", allow_threads(<::session::get_dht_settings)) + .def("dht_get_immutable_item", allow_threads(dht_get_immutable_item)) + .def("dht_get_mutable_item", &dht_get_mutable_item) + .def("dht_put_immutable_item", allow_threads(dht_put_immutable_item)) + .def("dht_put_mutable_item", &dht_put_mutable_item) + .def("dht_get_peers", allow_threads(<::session::dht_get_peers)) + .def("dht_announce", allow_threads(<::session::dht_announce)) +#endif // TORRENT_DISABLE_DHT + .def("add_torrent", &add_torrent) + .def("async_add_torrent", &async_add_torrent) +#ifndef BOOST_NO_EXCEPTIONS +#ifndef TORRENT_NO_DEPRECATE + .def( + "add_torrent", &add_torrent_depr + , ( + arg("resume_data") = entry(), + arg("storage_mode") = storage_mode_sparse, + arg("paused") = false + ) + ) +#endif // TORRENT_NO_DEPRECATE +#endif // BOOST_NO_EXCEPTIONS + .def("remove_torrent", allow_threads(<::session::remove_torrent), arg("option") = 0) +#ifndef TORRENT_NO_DEPRECATE + .def("add_feed", &add_feed) + .def("status", allow_threads(<::session::status)) + .def("settings", <::session::settings) + .def("set_settings", &session_set_settings) +#endif + .def("get_settings", &session_get_settings) + .def("apply_settings", &session_apply_settings) +#ifndef TORRENT_NO_DEPRECATE +#ifndef TORRENT_DISABLE_ENCRYPTION + .def("set_pe_settings", allow_threads(<::session::set_pe_settings)) + .def("get_pe_settings", allow_threads(<::session::get_pe_settings)) +#endif +#endif + .def("load_state", &load_state, (arg("entry"), arg("flags") = 0xffffffff)) + .def("save_state", &save_state, (arg("entry"), arg("flags") = 0xffffffff)) + .def("pop_alerts", &pop_alerts) + .def("wait_for_alert", &wait_for_alert +#ifdef TORRENT_NO_DEPRECATE + , return_internal_reference<>() +#endif + ) + .def("add_extension", &add_extension) +#ifndef TORRENT_NO_DEPRECATE + .def("pop_alert", &pop_alert) +#if TORRENT_USE_I2P + .def("set_i2p_proxy", allow_threads(<::session::set_i2p_proxy)) + .def("i2p_proxy", allow_threads(<::session::i2p_proxy)) +#endif +#endif + .def("set_ip_filter", allow_threads(<::session::set_ip_filter)) + .def("get_ip_filter", allow_threads(<::session::get_ip_filter)) + .def("find_torrent", allow_threads(<::session::find_torrent)) + .def("get_torrents", &get_torrents) + .def("pause", allow_threads(<::session::pause)) + .def("resume", allow_threads(<::session::resume)) + .def("is_paused", allow_threads(<::session::is_paused)) + .def("id", allow_threads(<::session::id)) + .def("get_cache_info", &get_cache_info1, (arg("handle") = torrent_handle(), arg("flags") = 0)) + .def("add_port_mapping", allow_threads(<::session::add_port_mapping)) + .def("delete_port_mapping", allow_threads(<::session::delete_port_mapping)) + +#ifndef TORRENT_NO_DEPRECATE + .def( + "listen_on", &listen_on + , (arg("min"), "max", arg("interface") = (char const*)0, arg("flags") = 0) + ) +#ifndef TORRENT_DISABLE_DHT + .def("start_dht", allow_threads(start_dht0)) + .def("stop_dht", allow_threads(<::session::stop_dht)) + .def("start_dht", allow_threads(start_dht1)) + .def("dht_state", allow_threads(<::session::dht_state)) + .def("set_dht_proxy", allow_threads(<::session::set_dht_proxy)) + .def("dht_proxy", allow_threads(<::session::dht_proxy)) +#endif + .def("set_local_download_rate_limit", allow_threads(<::session::set_local_download_rate_limit)) + .def("local_download_rate_limit", allow_threads(<::session::local_download_rate_limit)) + .def("set_local_upload_rate_limit", allow_threads(<::session::set_local_upload_rate_limit)) + .def("local_upload_rate_limit", allow_threads(<::session::local_upload_rate_limit)) + .def("set_download_rate_limit", allow_threads(<::session::set_download_rate_limit)) + .def("download_rate_limit", allow_threads(<::session::download_rate_limit)) + .def("set_upload_rate_limit", allow_threads(<::session::set_upload_rate_limit)) + .def("upload_rate_limit", allow_threads(<::session::upload_rate_limit)) + .def("set_max_uploads", allow_threads(<::session::set_max_uploads)) + .def("set_max_connections", allow_threads(<::session::set_max_connections)) + .def("max_connections", allow_threads(<::session::max_connections)) + .def("num_connections", allow_threads(<::session::num_connections)) + .def("set_max_half_open_connections", allow_threads(<::session::set_max_half_open_connections)) + .def("set_severity_level", allow_threads(<::session::set_severity_level)) + .def("set_alert_queue_size_limit", allow_threads(<::session::set_alert_queue_size_limit)) + .def("set_alert_mask", allow_threads(<::session::set_alert_mask)) + .def("set_peer_proxy", allow_threads(<::session::set_peer_proxy)) + .def("set_tracker_proxy", allow_threads(<::session::set_tracker_proxy)) + .def("set_web_seed_proxy", allow_threads(<::session::set_web_seed_proxy)) + .def("peer_proxy", allow_threads(<::session::peer_proxy)) + .def("tracker_proxy", allow_threads(<::session::tracker_proxy)) + .def("web_seed_proxy", allow_threads(<::session::web_seed_proxy)) + .def("set_proxy", allow_threads(<::session::set_proxy)) + .def("proxy", allow_threads(<::session::proxy)) + .def("start_upnp", &start_upnp) + .def("stop_upnp", allow_threads(<::session::stop_upnp)) + .def("start_lsd", allow_threads(<::session::start_lsd)) + .def("stop_lsd", allow_threads(<::session::stop_lsd)) + .def("start_natpmp", &start_natpmp) + .def("stop_natpmp", allow_threads(<::session::stop_natpmp)) + .def("get_cache_status", &get_cache_status) + .def("get_cache_info", &get_cache_info2) + .def("set_peer_id", allow_threads(<::session::set_peer_id)) +#endif // TORRENT_NO_DEPRECATE + ; + + enum_("protocol_type") + .value("udp", lt::session::udp) + .value("tcp", lt::session::tcp) + ; + + enum_("save_state_flags_t") + .value("save_settings", lt::session::save_settings) + .value("save_dht_settings", lt::session::save_dht_settings) + .value("save_dht_state", lt::session::save_dht_state) + .value("save_encryption_settings", lt::session:: save_encryption_settings) +#ifndef TORRENT_NO_DEPRECATE + .value("save_as_map", lt::session::save_as_map) + .value("save_i2p_proxy", lt::session::save_i2p_proxy) + .value("save_proxy", lt::session::save_proxy) + .value("save_dht_proxy", lt::session::save_dht_proxy) + .value("save_peer_proxy", lt::session::save_peer_proxy) + .value("save_web_proxy", lt::session::save_web_proxy) + .value("save_tracker_proxy", lt::session::save_tracker_proxy) +#endif + ; + +#ifndef TORRENT_NO_DEPRECATE + 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) + ; + + class_("feed_handle") + .def("update_feed", &feed_handle::update_feed) + .def("get_feed_status", &get_feed_status) + .def("set_settings", &set_feed_settings) + .def("settings", &get_feed_settings) + ; +#endif + + typedef void (*mem_preset2)(settings_pack& s); + typedef void (*perf_preset2)(settings_pack& s); + +#ifndef TORRENT_NO_DEPRECATE + + typedef session_settings (*mem_preset1)(); + typedef session_settings (*perf_preset1)(); + + def("high_performance_seed", (perf_preset1)high_performance_seed); + def("min_memory_usage", (mem_preset1)min_memory_usage); + scope().attr("create_metadata_plugin") = "metadata_transfer"; +#endif + + def("high_performance_seed", (perf_preset2)high_performance_seed); + def("min_memory_usage", (mem_preset2)min_memory_usage); + + scope().attr("create_ut_metadata_plugin") = "ut_metadata"; + scope().attr("create_ut_pex_plugin") = "ut_pex"; + scope().attr("create_smart_ban_plugin") = "smart_ban"; +} + diff --git a/bindings/python/src/session_settings.cpp b/bindings/python/src/session_settings.cpp new file mode 100644 index 0000000..f474183 --- /dev/null +++ b/bindings/python/src/session_settings.cpp @@ -0,0 +1,307 @@ +// 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 namespace libtorrent; + +void bind_session_settings() +{ +#ifndef TORRENT_NO_DEPRECATE + class_("session_settings") + .def_readwrite("user_agent", &session_settings::user_agent) + .def_readwrite("tracker_completion_timeout", &session_settings::tracker_completion_timeout) + .def_readwrite("tracker_receive_timeout", &session_settings::tracker_receive_timeout) + .def_readwrite("stop_tracker_timeout", &session_settings::stop_tracker_timeout) + .def_readwrite("tracker_maximum_response_length", &session_settings::tracker_maximum_response_length) + .def_readwrite("piece_timeout", &session_settings::piece_timeout) + .def_readwrite("request_timeout", &session_settings::request_timeout) + .def_readwrite("request_queue_time", &session_settings::request_queue_time) + .def_readwrite("max_allowed_in_request_queue", &session_settings::max_allowed_in_request_queue) + .def_readwrite("max_out_request_queue", &session_settings::max_out_request_queue) + .def_readwrite("whole_pieces_threshold", &session_settings::whole_pieces_threshold) + .def_readwrite("peer_timeout", &session_settings::peer_timeout) + .def_readwrite("urlseed_timeout", &session_settings::urlseed_timeout) + .def_readwrite("urlseed_pipeline_size", &session_settings::urlseed_pipeline_size) + .def_readwrite("urlseed_wait_retry", &session_settings::urlseed_wait_retry) + .def_readwrite("file_pool_size", &session_settings::file_pool_size) + .def_readwrite("allow_multiple_connections_per_ip", &session_settings::allow_multiple_connections_per_ip) + .def_readwrite("max_failcount", &session_settings::max_failcount) + .def_readwrite("min_reconnect_time", &session_settings::min_reconnect_time) + .def_readwrite("peer_connect_timeout", &session_settings::peer_connect_timeout) + .def_readwrite("ignore_limits_on_local_network", &session_settings::ignore_limits_on_local_network) + .def_readwrite("connection_speed", &session_settings::connection_speed) + .def_readwrite("send_redundant_have", &session_settings::send_redundant_have) + .def_readwrite("lazy_bitfields", &session_settings::lazy_bitfields) + .def_readwrite("inactivity_timeout", &session_settings::inactivity_timeout) + .def_readwrite("unchoke_interval", &session_settings::unchoke_interval) + .def_readwrite("optimistic_unchoke_interval", &session_settings::optimistic_unchoke_interval) + .def_readwrite("announce_ip", &session_settings::announce_ip) + .def_readwrite("num_want", &session_settings::num_want) + .def_readwrite("initial_picker_threshold", &session_settings::initial_picker_threshold) + .def_readwrite("allowed_fast_set_size", &session_settings::allowed_fast_set_size) + // this is no longer used + .def_readwrite("max_queued_disk_bytes", &session_settings::max_queued_disk_bytes) + .def_readwrite("max_queued_disk_bytes_low_watermark", &session_settings::max_queued_disk_bytes_low_watermark) + .def_readwrite("handshake_timeout", &session_settings::handshake_timeout) +#ifndef TORRENT_DISABLE_DHT + .def_readwrite("use_dht_as_fallback", &session_settings::use_dht_as_fallback) +#endif + .def_readwrite("free_torrent_hashes", &session_settings::free_torrent_hashes) + .def_readwrite("upnp_ignore_nonrouters", &session_settings::upnp_ignore_nonrouters) + .def_readwrite("send_buffer_low_watermark", &session_settings::send_buffer_low_watermark) + .def_readwrite("send_buffer_watermark", &session_settings::send_buffer_watermark) + .def_readwrite("send_buffer_watermark_factor", &session_settings::send_buffer_watermark_factor) + .def_readwrite("choking_algorithm", &session_settings::choking_algorithm) + .def_readwrite("seed_choking_algorithm", &session_settings::seed_choking_algorithm) + .def_readwrite("use_parole_mode", &session_settings::use_parole_mode) + .def_readwrite("cache_size", &session_settings::cache_size) + .def_readwrite("cache_buffer_chunk_size", &session_settings::cache_buffer_chunk_size) + .def_readwrite("cache_expiry", &session_settings::cache_expiry) + .def_readwrite("use_read_cache", &session_settings::use_read_cache) + .def_readwrite("explicit_read_cache", &session_settings::explicit_read_cache) + .def_readwrite("explicit_cache_interval", &session_settings::explicit_cache_interval) + .def_readwrite("disk_io_write_mode", &session_settings::disk_io_write_mode) + .def_readwrite("disk_io_read_mode", &session_settings::disk_io_read_mode) + .def_readwrite("coalesce_reads", &session_settings::coalesce_reads) + .def_readwrite("coalesce_writes", &session_settings::coalesce_writes) + .def_readwrite("peer_tos", &session_settings::peer_tos) + .def_readwrite("active_downloads", &session_settings::active_downloads) + .def_readwrite("active_seeds", &session_settings::active_seeds) + .def_readwrite("active_dht_limit", &session_settings::active_dht_limit) + .def_readwrite("active_tracker_limit", &session_settings::active_tracker_limit) + .def_readwrite("active_lsd_limit", &session_settings::active_lsd_limit) + .def_readwrite("active_limit", &session_settings::active_limit) + .def_readwrite("auto_manage_prefer_seeds", &session_settings::auto_manage_prefer_seeds) + .def_readwrite("dont_count_slow_torrents", &session_settings::dont_count_slow_torrents) + .def_readwrite("auto_manage_interval", &session_settings::auto_manage_interval) + .def_readwrite("share_ratio_limit", &session_settings::share_ratio_limit) + .def_readwrite("seed_time_ratio_limit", &session_settings::seed_time_ratio_limit) + .def_readwrite("seed_time_limit", &session_settings::seed_time_limit) + .def_readwrite("peer_turnover_interval", &session_settings::peer_turnover_interval) + .def_readwrite("peer_turnover", &session_settings::peer_turnover) + .def_readwrite("peer_turnover_cutoff", &session_settings::peer_turnover_cutoff) + .def_readwrite("close_redundant_connections", &session_settings::close_redundant_connections) + .def_readwrite("auto_scrape_interval", &session_settings::auto_scrape_interval) + .def_readwrite("auto_scrape_min_interval", &session_settings::auto_scrape_min_interval) + .def_readwrite("max_peerlist_size", &session_settings::max_peerlist_size) + .def_readwrite("max_paused_peerlist_size", &session_settings::max_paused_peerlist_size) + .def_readwrite("min_announce_interval", &session_settings::min_announce_interval) + .def_readwrite("prioritize_partial_pieces", &session_settings::prioritize_partial_pieces) + .def_readwrite("auto_manage_startup", &session_settings::auto_manage_startup) + .def_readwrite("rate_limit_ip_overhead", &session_settings::rate_limit_ip_overhead) + .def_readwrite("announce_to_all_trackers", &session_settings::announce_to_all_trackers) + .def_readwrite("announce_to_all_tiers", &session_settings::announce_to_all_tiers) + .def_readwrite("prefer_udp_trackers", &session_settings::prefer_udp_trackers) + .def_readwrite("strict_super_seeding", &session_settings::strict_super_seeding) + .def_readwrite("seeding_piece_quota", &session_settings::seeding_piece_quota) + .def_readwrite("max_sparse_regions", &session_settings::max_sparse_regions) + .def_readwrite("lock_disk_cache", &session_settings::lock_disk_cache) + .def_readwrite("max_rejects", &session_settings::max_rejects) + .def_readwrite("recv_socket_buffer_size", &session_settings::recv_socket_buffer_size) + .def_readwrite("send_socket_buffer_size", &session_settings::send_socket_buffer_size) + .def_readwrite("optimize_hashing_for_speed", &session_settings::optimize_hashing_for_speed) + .def_readwrite("file_checks_delay_per_block", &session_settings::file_checks_delay_per_block) + .def_readwrite("disk_cache_algorithm", &session_settings::disk_cache_algorithm) + .def_readwrite("read_cache_line_size", &session_settings::read_cache_line_size) + .def_readwrite("write_cache_line_size", &session_settings::write_cache_line_size) + .def_readwrite("optimistic_disk_retry", &session_settings::optimistic_disk_retry) + .def_readwrite("disable_hash_checks", &session_settings::disable_hash_checks) + .def_readwrite("allow_reordered_disk_operations", &session_settings::allow_reordered_disk_operations) + .def_readwrite("allow_i2p_mixed", &session_settings::allow_i2p_mixed) + .def_readwrite("max_suggest_pieces", &session_settings::max_suggest_pieces) + .def_readwrite("drop_skipped_requests", &session_settings::drop_skipped_requests) + .def_readwrite("low_prio_disk", &session_settings::low_prio_disk) + .def_readwrite("local_service_announce_interval", &session_settings::local_service_announce_interval) + .def_readwrite("dht_announce_interval", &session_settings::dht_announce_interval) + .def_readwrite("udp_tracker_token_expiry", &session_settings::udp_tracker_token_expiry) + .def_readwrite("volatile_read_cache", &session_settings::volatile_read_cache) + .def_readwrite("guided_read_cache", &session_settings::guided_read_cache) + .def_readwrite("default_cache_min_age", &session_settings::default_cache_min_age) + .def_readwrite("num_optimistic_unchoke_slots", &session_settings::num_optimistic_unchoke_slots) + .def_readwrite("no_atime_storage", &session_settings::no_atime_storage) + .def_readwrite("default_est_reciprocation_rate", &session_settings::default_est_reciprocation_rate) + .def_readwrite("increase_est_reciprocation_rate", &session_settings::increase_est_reciprocation_rate) + .def_readwrite("decrease_est_reciprocation_rate", &session_settings::decrease_est_reciprocation_rate) + .def_readwrite("incoming_starts_queued_torrents", &session_settings::incoming_starts_queued_torrents) + .def_readwrite("report_true_downloaded", &session_settings::report_true_downloaded) + .def_readwrite("strict_end_game_mode", &session_settings::strict_end_game_mode) + .def_readwrite("broadcast_lsd", &session_settings::broadcast_lsd) + .def_readwrite("ignore_resume_timestamps", &session_settings::ignore_resume_timestamps) + .def_readwrite("no_recheck_incomplete_resume", &session_settings::no_recheck_incomplete_resume) + .def_readwrite("anonymous_mode", &session_settings::anonymous_mode) + .def_readwrite("force_proxy", &session_settings::force_proxy) + .def_readwrite("tick_interval", &session_settings::tick_interval) + .def_readwrite("report_web_seed_downloads", &session_settings::report_web_seed_downloads) + .def_readwrite("share_mode_target", &session_settings::share_mode_target) + .def_readwrite("rate_limit_utp", &session_settings::rate_limit_utp) + .def_readwrite("upload_rate_limit", &session_settings::upload_rate_limit) + .def_readwrite("download_rate_limit", &session_settings::download_rate_limit) + .def_readwrite("local_upload_rate_limit", &session_settings::local_upload_rate_limit) + .def_readwrite("local_download_rate_limit", &session_settings::local_download_rate_limit) + .def_readwrite("dht_upload_rate_limit", &session_settings::dht_upload_rate_limit) + .def_readwrite("unchoke_slots_limit", &session_settings::unchoke_slots_limit) + .def_readwrite("connections_limit", &session_settings::connections_limit) + .def_readwrite("utp_target_delay", &session_settings::utp_target_delay) + .def_readwrite("utp_gain_factor", &session_settings::utp_gain_factor) + .def_readwrite("utp_min_timeout", &session_settings::utp_min_timeout) + .def_readwrite("utp_syn_resends", &session_settings::utp_syn_resends) + .def_readwrite("utp_fin_resends", &session_settings::utp_fin_resends) + .def_readwrite("utp_num_resends", &session_settings::utp_num_resends) + .def_readwrite("utp_connect_timeout", &session_settings::utp_connect_timeout) + .def_readwrite("half_open_limit", &session_settings::half_open_limit) + .def_readwrite("utp_delayed_ack", &session_settings::utp_delayed_ack) + .def_readwrite("utp_dynamic_sock_buf", &session_settings::utp_dynamic_sock_buf) + .def_readwrite("utp_loss_multiplier", &session_settings::utp_loss_multiplier) + .def_readwrite("mixed_mode_algorithm", &session_settings::mixed_mode_algorithm) + .def_readwrite("listen_queue_size", &session_settings::listen_queue_size) + .def_readwrite("announce_double_nat", &session_settings::announce_double_nat) + .def_readwrite("torrent_connect_boost", &session_settings::torrent_connect_boost) + .def_readwrite("seeding_outgoing_connections", &session_settings::seeding_outgoing_connections) + .def_readwrite("no_connect_privileged_ports", &session_settings::no_connect_privileged_ports) + .def_readwrite("alert_queue_size", &session_settings::alert_queue_size) + .def_readwrite("max_metadata_size", &session_settings::max_metadata_size) + .def_readwrite("smooth_connects", &session_settings::smooth_connects) + .def_readwrite("always_send_user_agent", &session_settings::always_send_user_agent) + .def_readwrite("apply_ip_filter_to_trackers", &session_settings::apply_ip_filter_to_trackers) + .def_readwrite("read_job_every", &session_settings::read_job_every) + .def_readwrite("use_disk_read_ahead", &session_settings::use_disk_read_ahead) + .def_readwrite("lock_files", &session_settings::lock_files) + .def_readwrite("enable_outgoing_tcp", &session_settings::enable_outgoing_tcp) + .def_readwrite("enable_incoming_tcp", &session_settings::enable_incoming_tcp) + .def_readwrite("enable_outgoing_utp", &session_settings::enable_outgoing_utp) + .def_readwrite("enable_incoming_utp", &session_settings::enable_incoming_utp) + .def_readwrite("max_pex_peers", &session_settings::max_pex_peers) + .def_readwrite("ssl_listen", &session_settings::ssl_listen) + .def_readwrite("tracker_backoff", &session_settings::tracker_backoff) + .def_readwrite("ban_web_seeds", &session_settings::ban_web_seeds) + .def_readwrite("max_http_recv_buffer_size", &session_settings::max_http_recv_buffer_size) + .def_readwrite("support_share_mode", &session_settings::support_share_mode) + .def_readwrite("support_merkle_torrents", &session_settings::support_merkle_torrents) + .def_readwrite("report_redundant_bytes", &session_settings::report_redundant_bytes) + .def_readwrite("handshake_client_version", &session_settings::handshake_client_version) + .def_readwrite("use_disk_cache_pool", &session_settings::use_disk_cache_pool) + ; + + enum_("disk_cache_algo_t") + .value("lru", session_settings::lru) + .value("largest_contiguous", session_settings::largest_contiguous) + .value("avoid_readback", session_settings::avoid_readback) + ; +#endif // TORRENT_NO_DEPRECATE + + enum_("choking_algorithm_t") + .value("fixed_slots_choker", settings_pack::fixed_slots_choker) +#ifndef TORRENT_NO_DEPRECATE + .value("auto_expand_choker", settings_pack::rate_based_choker) +#endif + .value("rate_based_choker", settings_pack::rate_based_choker) + .value("bittyrant_choker", settings_pack::bittyrant_choker) + ; + + 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) +#ifndef TORRENT_NO_DEPRECATE + .value("disable_os_cache_for_aligned_files", settings_pack::disable_os_cache_for_aligned_files) +#endif + .value("disable_os_cache", settings_pack::disable_os_cache) + ; + + enum_("bandwidth_mixed_algo_t") + .value("prefer_tcp", settings_pack::prefer_tcp) + .value("peer_proportional", settings_pack::peer_proportional) + ; + + enum_("enc_policy") + .value("pe_forced", settings_pack::pe_forced) + .value("pe_enabled", settings_pack::pe_enabled) + .value("pe_disabled", settings_pack::pe_disabled) +#ifndef TORRENT_NO_DEPRECATE + .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) +#ifndef TORRENT_NO_DEPRECATE + .value("rc4", settings_pack::pe_rc4) + .value("plaintext", settings_pack::pe_plaintext) + .value("both", settings_pack::pe_both) +#endif + ; + +#ifndef TORRENT_NO_DEPRECATE + enum_("proxy_type") + .value("none", proxy_settings::none) + .value("socks4", proxy_settings::socks4) + .value("socks5", proxy_settings::socks5) + .value("socks5_pw", proxy_settings::socks5_pw) + .value("http", proxy_settings::http) + .value("http_pw", proxy_settings::http_pw) + .value("i2p_proxy", proxy_settings::i2p_proxy) + ; + + class_("proxy_settings") + .def_readwrite("hostname", &proxy_settings::hostname) + .def_readwrite("port", &proxy_settings::port) + .def_readwrite("password", &proxy_settings::password) + .def_readwrite("username", &proxy_settings::username) + .def_readwrite("type", &proxy_settings::type) + .def_readwrite("proxy_peer_connections", &proxy_settings::proxy_peer_connections) + .def_readwrite("proxy_hostnames", &proxy_settings::proxy_hostnames) + ; +#endif + +#ifndef TORRENT_DISABLE_DHT + class_("dht_settings") + .def_readwrite("max_peers_reply", &dht_settings::max_peers_reply) + .def_readwrite("search_branching", &dht_settings::search_branching) +#ifndef TORRENT_NO_DEPRECATE + .def_readwrite("service_port", &dht_settings::service_port) +#endif + .def_readwrite("max_fail_count", &dht_settings::max_fail_count) + .def_readwrite("max_torrents", &dht_settings::max_torrents) + .def_readwrite("max_dht_items", &dht_settings::max_dht_items) + .def_readwrite("restrict_routing_ips", &dht_settings::restrict_routing_ips) + .def_readwrite("restrict_search_ips", &dht_settings::restrict_search_ips) + .def_readwrite("max_torrent_search_reply", &dht_settings::max_torrent_search_reply) + .def_readwrite("extended_routing_table", &dht_settings::extended_routing_table) + .def_readwrite("aggressive_lookups", &dht_settings::aggressive_lookups) + .def_readwrite("privacy_lookups", &dht_settings::privacy_lookups) + .def_readwrite("enforce_node_id", &dht_settings::enforce_node_id) + .def_readwrite("ignore_dark_internet", &dht_settings::ignore_dark_internet) + .def_readwrite("block_timeout", &dht_settings::block_timeout) + .def_readwrite("block_ratelimit", &dht_settings::block_ratelimit) + .def_readwrite("read_only", &dht_settings::read_only) + .def_readwrite("item_lifetime", &dht_settings::item_lifetime) + ; +#endif + +#ifndef TORRENT_NO_DEPRECATE + 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/string.cpp b/bindings/python/src/string.cpp new file mode 100644 index 0000000..6784f80 --- /dev/null +++ b/bindings/python/src/string.cpp @@ -0,0 +1,69 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include + +using namespace boost::python; + +struct unicode_from_python +{ + unicode_from_python() + { + converter::registry::push_back( + &convertible, &construct, type_id() + ); + } + + static void* convertible(PyObject* x) + { +#if PY_VERSION_HEX >= 0x03020000 + return PyBytes_Check(x) ? x : PyUnicode_Check(x) ? x : 0; +#else + return PyString_Check(x) ? x : PyUnicode_Check(x) ? x : 0; +#endif + } + + static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data) + { + void* storage = ((converter::rvalue_from_python_storage< + std::string>*)data)->storage.bytes; + + if (PyUnicode_Check(x)) + { + PyObject* utf8 = PyUnicode_AsUTF8String(x); + if (utf8 == NULL) + { + new (storage) std::string(); + } + else + { +#if PY_VERSION_HEX >= 0x03000000 + new (storage) std::string(PyBytes_AsString(utf8) + , PyBytes_Size(utf8)); +#else + new (storage) std::string(PyString_AsString(utf8) + , PyString_Size(utf8)); +#endif + Py_DECREF(utf8); + } + } + else + { +#if PY_VERSION_HEX >= 0x03000000 + new (storage) std::string(PyBytes_AsString(x), PyBytes_Size(x)); +#else + new (storage) std::string(PyString_AsString(x) + , PyString_Size(x)); +#endif + } + data->convertible = storage; + } +}; + +void bind_unicode_string_conversion() +{ + unicode_from_python(); +} + diff --git a/bindings/python/src/torrent_handle.cpp b/bindings/python/src/torrent_handle.cpp new file mode 100644 index 0000000..62e0356 --- /dev/null +++ b/bindings/python/src/torrent_handle.cpp @@ -0,0 +1,527 @@ +// 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 +#include +#include +#include +#include "libtorrent/announce_entry.hpp" +#include +#include +#include "gil.hpp" + +using namespace boost::python; +using namespace libtorrent; + +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 (std::vector::iterator i(avail.begin()) + , end(avail.end()); i != end; ++i) + ret.append(*i); + return ret; + } + + list piece_priorities(torrent_handle& handle) + { + list ret; + std::vector prio; + { + allow_threading_guard guard; + prio = handle.piece_priorities(); + } + + for (std::vector::iterator i(prio.begin()) + , end(prio.end()); i != end; ++i) + ret.append(*i); + return ret; + } + +} // namespace unnamed + +list file_progress(torrent_handle& handle, int flags) +{ + std::vector p; + + { + allow_threading_guard guard; + boost::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.file_priorities(); + + for (std::vector::iterator i = priorities.begin(); i != priorities.end(); ++i) + ret.append(*i); + + return ret; +} + +int file_prioritity0(torrent_handle& h, int index) +{ + return h.file_priority(index); +} + +void file_prioritity1(torrent_handle& h, int index, int 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"]); + if (d.has_key("source")) + ae.source = extract(d["source"]); + if (d.has_key("verified")) + ae.verified = extract(d["verified"]); + if (d.has_key("send_stats")) + ae.send_stats = extract(d["send_stats"]); +} + +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); +} + +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["tier"] = i->tier; + d["fail_limit"] = i->fail_limit; + d["fails"] = i->fails; + d["source"] = i->source; + d["verified"] = i->verified; + d["updating"] = i->updating; + d["start_sent"] = i->start_sent; + d["complete_sent"] = i->complete_sent; + d["send_stats"] = i->send_stats; + ret.append(d); + } + return ret; +} + +list get_download_queue(torrent_handle& handle) +{ + list ret; + + std::vector downloading; + + { + allow_threading_guard guard; + handle.get_download_queue(downloading); + } + + for (std::vector::iterator i = downloading.begin() + , end(downloading.end()); i != end; ++i) + { + dict partial_piece; + partial_piece["piece_index"] = i->piece_index; + partial_piece["blocks_in_piece"] = i->blocks_in_piece; + list block_list; + for (int k = 0; k < i->blocks_in_piece; ++k) + { + dict block_info; + block_info["state"] = i->blocks[k].state; + block_info["num_peers"] = i->blocks[k].num_peers; + block_info["bytes_progress"] = i->blocks[k].bytes_progress; + block_info["block_size"] = i->blocks[k].block_size; + block_info["peer"] = boost::python::make_tuple( + i->blocks[k].peer().address().to_string() + , i->blocks[k].peer().port()); + block_list.append(block_info); + } + partial_piece["blocks"] = block_list; + + ret.append(partial_piece); + } + + return ret; +} + +void set_metadata(torrent_handle& handle, std::string const& buf) +{ + handle.set_metadata(buf.c_str(), buf.size()); +} + +namespace +{ + tcp::endpoint tuple_to_endpoint(tuple const& t) + { + return tcp::endpoint(address::from_string(extract(t[0])), extract(t[1])); + } +} + +void connect_peer(torrent_handle& th, tuple ip, int source) +{ + th.connect_peer(tuple_to_endpoint(ip), source); +} + +#ifndef TORRENT_NO_DEPRECATE +#if BOOST_VERSION > 104200 + +boost::shared_ptr get_torrent_info(torrent_handle const& h) +{ + allow_threading_guard guard; + return h.torrent_file(); +} + +#else + +boost::shared_ptr get_torrent_info(torrent_handle const& h) +{ + // I can't figure out how to expose shared_ptr + // as well as supporting mutable instances. So, this hack is better + // than compilation errors. It seems to work on newer versions of boost though + allow_threading_guard guard; + return boost::const_pointer_cast(h.torrent_file()); +} + +#endif + +void set_peer_upload_limit(torrent_handle& th, tuple const& ip, int limit) +{ + th.set_peer_upload_limit(tuple_to_endpoint(ip), limit); +} + +void set_peer_download_limit(torrent_handle& th, tuple const& ip, int limit) +{ + th.set_peer_download_limit(tuple_to_endpoint(ip), limit); +} + +#endif // TORRENT_NO_DEPRECAE + +void add_piece(torrent_handle& th, int piece, char const *data, int flags) +{ + th.add_piece(piece, data, flags); +} + +void bind_torrent_handle() +{ + // arguments are: number of seconds and tracker index + void (torrent_handle::*force_reannounce0)(int, int) const = &torrent_handle::force_reannounce; + +#ifndef TORRENT_NO_DEPRECATE + bool (torrent_handle::*super_seeding0)() const = &torrent_handle::super_seeding; +#endif + void (torrent_handle::*super_seeding1)(bool) const = &torrent_handle::super_seeding; + + int (torrent_handle::*piece_priority0)(int) const = &torrent_handle::piece_priority; + void (torrent_handle::*piece_priority1)(int, int) const = &torrent_handle::piece_priority; + + void (torrent_handle::*move_storage0)(std::string const&, int flags) const = &torrent_handle::move_storage; + void (torrent_handle::*rename_file0)(int, std::string const&) const = &torrent_handle::rename_file; + +#if TORRENT_USE_WSTRING && !defined TORRENT_NO_DEPRECATE + void (torrent_handle::*move_storage1)(std::wstring const&, int flags) const = &torrent_handle::move_storage; + void (torrent_handle::*rename_file1)(int, std::wstring const&) const = &torrent_handle::rename_file; +#endif + +#ifndef TORRENT_NO_DEPRECATE + // deprecated in 1.1 +#ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES + bool (torrent_handle::*resolve_countries0)() const = &torrent_handle::resolve_countries; + void (torrent_handle::*resolve_countries1)(bool) = &torrent_handle::resolve_countries; +#endif +#endif // TORRENT_NO_DEPRECATE + +#define _ allow_threads + + class_("torrent_handle") + .def(self == self) + .def(self != self) + .def(self < self) + .def("get_peer_info", get_peer_info) + .def("status", _(&torrent_handle::status), arg("flags") = 0xffffffff) + .def("get_download_queue", get_download_queue) + .def("file_progress", file_progress, arg("flags") = 0) + .def("trackers", trackers) + .def("replace_trackers", replace_trackers) + .def("add_tracker", add_tracker) + .def("add_url_seed", _(&torrent_handle::add_url_seed)) + .def("remove_url_seed", _(&torrent_handle::remove_url_seed)) + .def("url_seeds", url_seeds) + .def("add_http_seed", _(&torrent_handle::add_http_seed)) + .def("remove_http_seed", _(&torrent_handle::remove_http_seed)) + .def("http_seeds", http_seeds) + .def("torrent_file", _(&torrent_handle::torrent_file)) + .def("set_metadata", set_metadata) + .def("is_valid", _(&torrent_handle::is_valid)) + .def("pause", _(&torrent_handle::pause), arg("flags") = 0) + .def("resume", _(&torrent_handle::resume)) + .def("stop_when_ready", _(&torrent_handle::stop_when_ready)) + .def("clear_error", _(&torrent_handle::clear_error)) + .def("set_priority", _(&torrent_handle::set_priority)) + .def("super_seeding", super_seeding1) + + .def("auto_managed", _(&torrent_handle::auto_managed)) + .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)) + + // deprecated +#ifndef TORRENT_NO_DEPRECATE + // resolve countries deprecated in 1.1 +#ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES + .def("resolve_countries", _(resolve_countries0)) + .def("resolve_countries", _(resolve_countries1)) +#endif + .def("get_torrent_info", &get_torrent_info) + .def("super_seeding", super_seeding0) + .def("filter_piece", _(&torrent_handle::filter_piece)) + .def("is_piece_filtered", _(&torrent_handle::is_piece_filtered)) + .def("write_resume_data", _(&torrent_handle::write_resume_data)) + .def("is_seed", _(&torrent_handle::is_seed)) + .def("is_finished", _(&torrent_handle::is_finished)) + .def("is_paused", _(&torrent_handle::is_paused)) + .def("is_auto_managed", _(&torrent_handle::is_auto_managed)) + .def("has_metadata", _(&torrent_handle::has_metadata)) + .def("use_interface", &torrent_handle::use_interface) + .def("name", _(&torrent_handle::name)) +#endif + .def("add_piece", add_piece) + .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("piece_availability", &piece_availability) + .def("piece_priority", _(piece_priority0)) + .def("piece_priority", _(piece_priority1)) + .def("prioritize_pieces", &prioritize_pieces) + .def("piece_priorities", &piece_priorities) + .def("prioritize_files", &prioritize_files) + .def("file_priorities", &file_priorities) + .def("file_priority", &file_prioritity0) + .def("file_priority", &file_prioritity1) + .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)) +#ifndef TORRENT_DISABLE_DHT + .def("force_dht_announce", _(&torrent_handle::force_dht_announce)) +#endif + .def("scrape_tracker", _(&torrent_handle::scrape_tracker)) + .def("set_upload_mode", _(&torrent_handle::set_upload_mode)) + .def("set_share_mode", _(&torrent_handle::set_share_mode)) + .def("flush_cache", &torrent_handle::flush_cache) + .def("apply_ip_filter", &torrent_handle::apply_ip_filter) + .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("set_sequential_download", _(&torrent_handle::set_sequential_download)) +#ifndef TORRENT_NO_DEPRECATE + .def("set_peer_upload_limit", &set_peer_upload_limit) + .def("set_peer_download_limit", &set_peer_download_limit) + .def("set_ratio", _(&torrent_handle::set_ratio)) + .def("save_path", _(&torrent_handle::save_path)) +#endif + .def("connect_peer", &connect_peer) + .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)) +#ifndef TORRENT_NO_DEPRECATE + .def("set_tracker_login", _(&torrent_handle::set_tracker_login)) +#endif + .def("move_storage", _(move_storage0), (arg("path"), arg("flags") = 0)) + .def("info_hash", _(&torrent_handle::info_hash)) + .def("force_recheck", _(&torrent_handle::force_recheck)) + .def("rename_file", _(rename_file0)) + .def("set_ssl_certificate", &torrent_handle::set_ssl_certificate, (arg("cert"), arg("private_key"), arg("dh_params"), arg("passphrase")="")) +#if TORRENT_USE_WSTRING && !defined TORRENT_NO_DEPRECATE + .def("move_storage", _(move_storage1), (arg("path"), arg("flags") = 0)) + .def("rename_file", _(rename_file1)) +#endif + ; + + enum_("file_progress_flags") + .value("piece_granularity", torrent_handle::piece_granularity) + ; + + enum_("pause_flags_t") + .value("graceful_pause", torrent_handle::graceful_pause) + ; + + enum_("save_resume_flags_t") + .value("flush_disk_cache", torrent_handle::flush_disk_cache) + ; + + enum_("deadline_flags") + .value("alert_when_available", torrent_handle::alert_when_available) + ; + + enum_("status_flags_t") + .value("query_distributed_copies", torrent_handle::query_distributed_copies) + .value("query_accurate_download_counters", torrent_handle::query_accurate_download_counters) + .value("query_last_seen_complete", torrent_handle::query_last_seen_complete) + .value("query_pieces", torrent_handle::query_pieces) + .value("query_verified_pieces", torrent_handle::query_verified_pieces) + ; + + enum_("move_flags_t") + .value("always_replace_files", always_replace_files) + .value("fail_if_exist", fail_if_exist) + .value("dont_replace", dont_replace) + ; +} diff --git a/bindings/python/src/torrent_info.cpp b/bindings/python/src/torrent_info.cpp new file mode 100644 index 0000000..78e3547 --- /dev/null +++ b/bindings/python/src/torrent_info.cpp @@ -0,0 +1,316 @@ +// 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 "libtorrent/aux_/disable_warnings_push.hpp" + +#include "boost_python.hpp" +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/socket_io.hpp" +#include "libtorrent/announce_entry.hpp" +#include "bytes.hpp" + +using namespace boost::python; +using namespace libtorrent; + +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; + + typedef std::vector > list_type; + + for (list_type::const_iterator i = ti.nodes().begin(); i != ti.nodes().end(); ++i) + { + 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; + d["extra_headers"] = i->extra_headers; + ret.append(d); + } + + return ret; + } + + 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 = len(hashes); i < e; ++i) + h.push_back(sha1_hash(bytes(extract(hashes[i])).arr)); + + ti.set_merkle_tree(h); + } + + bytes hash_for_piece(torrent_info const& ti, int i) + { + return bytes(ti.hash_for_piece(i).to_string()); + } + + bytes metadata(torrent_info const& ti) + { + return bytes(ti.metadata().get(), ti.metadata_size()); + } + + list map_block(torrent_info& ti, int piece, boost::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; + } + + int get_tier(announce_entry const& ae) { return ae.tier; } + void set_tier(announce_entry& ae, int v) { ae.tier = v; } + int get_fail_limit(announce_entry const& ae) { return ae.fail_limit; } + void set_fail_limit(announce_entry& ae, int l) { ae.fail_limit = l; } + int get_fails(announce_entry const& ae) { return ae.fails; } + int get_source(announce_entry const& ae) { return ae.source; } + bool get_verified(announce_entry const& ae) { return ae.verified; } + bool get_updating(announce_entry const& ae) { return ae.updating; } + bool get_start_sent(announce_entry const& ae) { return ae.start_sent; } + bool get_complete_sent(announce_entry const& ae) { return ae.complete_sent; } + bool get_send_stats(announce_entry const& ae) { return ae.send_stats; } + +#if !defined TORRENT_NO_DEPRECATE + boost::int64_t get_size(file_entry const& fe) { return fe.size; } + boost::int64_t get_offset(file_entry const& fe) { return fe.offset; } + boost::int64_t get_file_base(file_entry const& fe) { return fe.file_base; } + void set_file_base(file_entry& fe, int b) { fe.file_base = b; } + bool get_pad_file(file_entry const& fe) { return fe.pad_file; } + bool get_executable_attribute(file_entry const& fe) { return fe.executable_attribute; } + bool get_hidden_attribute(file_entry const& fe) { return fe.hidden_attribute; } + bool get_symlink_attribute(file_entry const& fe) { return fe.symlink_attribute; } +#endif + +} // namespace unnamed + +boost::shared_ptr buffer_constructor0(char const* buf, int len, int flags) +{ + error_code ec; + boost::shared_ptr ret(boost::make_shared(buf + , len, boost::ref(ec), flags)); +#ifndef BOOST_NO_EXCEPTIONS + if (ec) throw libtorrent_exception(ec); +#endif + return ret; +} + +boost::shared_ptr buffer_constructor1(char const* buf, int len) +{ + return buffer_constructor0(buf, len, 0); +} + +boost::shared_ptr file_constructor0(std::string const& filename, int flags) +{ + error_code ec; + boost::shared_ptr ret(boost::make_shared(filename + , boost::ref(ec), flags)); +#ifndef BOOST_NO_EXCEPTIONS + if (ec) throw libtorrent_exception(ec); +#endif + return ret; +} + +boost::shared_ptr file_constructor1(std::string const& filename) +{ + return file_constructor0(filename, 0); +} + +boost::shared_ptr bencoded_constructor0(entry const& ent, int flags) +{ + std::vector buf; + bencode(std::back_inserter(buf), ent); + + bdecode_node e; + error_code ec; + if (buf.size() == 0 || bdecode(&buf[0], &buf[0] + buf.size(), e, ec) != 0) + { +#ifndef BOOST_NO_EXCEPTIONS + throw invalid_torrent_file(ec); +#endif + } + + boost::shared_ptr ret(boost::make_shared(e + , boost::ref(ec), flags)); +#ifndef BOOST_NO_EXCEPTIONS + if (ec) throw libtorrent_exception(ec); +#endif + return ret; +} + +boost::shared_ptr bencoded_constructor1(entry const& ent) +{ + return bencoded_constructor0(ent, 0); +} + +void bind_torrent_info() +{ + return_value_policy copy; + + void (torrent_info::*rename_file0)(int, std::string const&) = &torrent_info::rename_file; +#if TORRENT_USE_WSTRING && !defined TORRENT_NO_DEPRECATE + void (torrent_info::*rename_file1)(int, std::wstring const&) = &torrent_info::rename_file; +#endif + + class_("file_slice") + .def_readwrite("file_index", &file_slice::file_index) + .def_readwrite("offset", &file_slice::offset) + .def_readwrite("size", &file_slice::size) + ; + + class_ >("torrent_info", no_init) + .def(init((arg("info_hash"), arg("flags") = 0))) + .def("__init__", make_constructor(&bencoded_constructor0)) + .def("__init__", make_constructor(&bencoded_constructor1)) + .def("__init__", make_constructor(&buffer_constructor0)) + .def("__init__", make_constructor(&buffer_constructor1)) + .def("__init__", make_constructor(&file_constructor0)) + .def("__init__", make_constructor(&file_constructor1)) + .def(init((arg("ti")))) + +#if TORRENT_USE_WSTRING && !defined TORRENT_NO_DEPRECATE + .def(init((arg("file"), arg("flags") = 0))) +#endif + + .def("add_tracker", &torrent_info::add_tracker, arg("url")) + .def("add_url_seed", &torrent_info::add_url_seed) + .def("add_http_seed", &torrent_info::add_http_seed) + .def("web_seeds", get_web_seeds) + + .def("name", &torrent_info::name, copy) + .def("comment", &torrent_info::comment, copy) + .def("creator", &torrent_info::creator, copy) + .def("total_size", &torrent_info::total_size) + .def("piece_length", &torrent_info::piece_length) + .def("num_pieces", &torrent_info::num_pieces) + .def("info_hash", &torrent_info::info_hash, copy) + .def("hash_for_piece", &hash_for_piece) + .def("merkle_tree", get_merkle_tree) + .def("set_merkle_tree", set_merkle_tree) + .def("piece_size", &torrent_info::piece_size) + + .def("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 !defined TORRENT_NO_DEPRECATE + .def("file_at", &torrent_info::file_at) + .def("file_at_offset", &torrent_info::file_at_offset) +#if TORRENT_USE_WSTRING + .def("rename_file", rename_file1) +#endif // TORRENT_USE_WSTRING +#endif // TORRENT_NO_DEPRECATE + + .def("priv", &torrent_info::priv) + .def("trackers", range(begin_trackers, end_trackers)) + + .def("creation_date", &torrent_info::creation_date) + + .def("add_node", &add_node) + .def("nodes", &nodes) + .def("metadata", &metadata) + .def("metadata_size", &torrent_info::metadata_size) + .def("map_block", map_block) + .def("map_file", &torrent_info::map_file) + ; + +#if !defined TORRENT_NO_DEPRECATE + 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) + .add_property("file_base", &get_file_base, &set_file_base) + ; +#endif + + class_("announce_entry", init()) + .def_readwrite("url", &announce_entry::url) + .add_property("tier", &get_tier, &set_tier) + .add_property("fail_limit", &get_fail_limit, &set_fail_limit) + .add_property("fails", &get_fails) + .add_property("source", &get_source) + .add_property("verified", &get_verified) + .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("reset", &announce_entry::reset) + .def("can_announce", &announce_entry::can_announce) + .def("is_working", &announce_entry::is_working) + .def("trim", &announce_entry::trim) + ; + + 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) + ; + +#if BOOST_VERSION > 104200 + implicitly_convertible, boost::shared_ptr >(); + boost::python::register_ptr_to_python >(); +#endif +} + diff --git a/bindings/python/src/torrent_status.cpp b/bindings/python/src/torrent_status.cpp new file mode 100644 index 0000000..fc74edd --- /dev/null +++ b/bindings/python/src/torrent_status.cpp @@ -0,0 +1,138 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include +#include +#include + +using namespace boost::python; +using namespace libtorrent; + +object bitfield_to_list(bitfield const& bf) +{ + list ret; + + for (bitfield::const_iterator i(bf.begin()), e(bf.end()); i != e; ++i) + ret.append(*i); + return ret; +} + +object pieces(torrent_status const& s) { return bitfield_to_list(s.pieces); } +object verified_pieces(torrent_status const& s) { return bitfield_to_list(s.verified_pieces); } + +void bind_torrent_status() +{ + scope status = class_("torrent_status") + .def_readonly("info_hash", &torrent_status::info_hash) + .def_readonly("state", &torrent_status::state) + .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) + .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, return_value_policy() + ) + ) +#ifndef TORRENT_NO_DEPRECATE + .add_property( + "announce_interval" + , make_getter( + &torrent_status::announce_interval, return_value_policy() + ) + ) +#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", &pieces) + .add_property("verified_pieces", &verified_pieces) + .def_readonly("num_pieces", &torrent_status::num_pieces) + .def_readonly("total_done", &torrent_status::total_done) + .def_readonly("total_wanted_done", &torrent_status::total_wanted_done) + .def_readonly("total_wanted", &torrent_status::total_wanted) + .def_readonly("distributed_full_copies", &torrent_status::distributed_full_copies) + .def_readonly("distributed_fraction", &torrent_status::distributed_fraction) + .def_readonly("distributed_copies", &torrent_status::distributed_copies) + .def_readonly("block_size", &torrent_status::block_size) + .def_readonly("num_uploads", &torrent_status::num_uploads) + .def_readonly("num_connections", &torrent_status::num_connections) + .def_readonly("uploads_limit", &torrent_status::uploads_limit) + .def_readonly("connections_limit", &torrent_status::connections_limit) + .def_readonly("storage_mode", &torrent_status::storage_mode) + .def_readonly("up_bandwidth_queue", &torrent_status::up_bandwidth_queue) + .def_readonly("down_bandwidth_queue", &torrent_status::down_bandwidth_queue) + .def_readonly("all_time_upload", &torrent_status::all_time_upload) + .def_readonly("all_time_download", &torrent_status::all_time_download) + .def_readonly("active_time", &torrent_status::active_time) + .def_readonly("finished_time", &torrent_status::finished_time) + .def_readonly("seeding_time", &torrent_status::seeding_time) + .def_readonly("seed_rank", &torrent_status::seed_rank) + .def_readonly("last_scrape", &torrent_status::last_scrape) + .def_readonly("has_incoming", &torrent_status::has_incoming) + .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) +#ifndef TORRENT_NO_DEPRECATE + .def_readonly("error", &torrent_status::error) +#endif + .def_readonly("errc", &torrent_status::errc) + .def_readonly("error_file", &torrent_status::error_file) + .def_readonly("name", &torrent_status::name) + .def_readonly("save_path", &torrent_status::save_path) + .def_readonly("priority", &torrent_status::priority) + .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) + .def_readonly("time_since_upload", &torrent_status::time_since_upload) + .def_readonly("time_since_download", &torrent_status::time_since_download) + .def_readonly("queue_position", &torrent_status::queue_position) + .def_readonly("need_save_resume", &torrent_status::need_save_resume) + .def_readonly("ip_filter_applies", &torrent_status::ip_filter_applies) + .def_readonly("moving_storage", &torrent_status::moving_storage) + .def_readonly("is_loaded", &torrent_status::is_loaded) + .def_readonly("announcing_to_trackers", &torrent_status::announcing_to_trackers) + .def_readonly("announcing_to_lsd", &torrent_status::announcing_to_lsd) + .def_readonly("announcing_to_dht", &torrent_status::announcing_to_dht) + .def_readonly("info_hash", &torrent_status::info_hash) + ; + + enum_("states") +#ifndef TORRENT_NO_DEPRECATE + .value("queued_for_checking", torrent_status::queued_for_checking) +#endif + .value("checking_files", torrent_status::checking_files) + .value("downloading_metadata", torrent_status::downloading_metadata) + .value("downloading", torrent_status::downloading) + .value("finished", torrent_status::finished) + .value("seeding", torrent_status::seeding) + .value("allocating", torrent_status::allocating) + .value("checking_resume_data", torrent_status::checking_resume_data) + .export_values() + ; +} + diff --git a/bindings/python/src/utility.cpp b/bindings/python/src/utility.cpp new file mode 100644 index 0000000..a357d7f --- /dev/null +++ b/bindings/python/src/utility.cpp @@ -0,0 +1,94 @@ +// 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 +#include +#include "boost_python.hpp" +#include "bytes.hpp" + +using namespace boost::python; +using namespace libtorrent; + +struct bytes_to_python +{ + static PyObject* convert(bytes const& p) + { +#if PY_MAJOR_VERSION >= 3 + PyObject *ret = PyBytes_FromStringAndSize(p.arr.c_str(), p.arr.size()); +#else + PyObject *ret = PyString_FromStringAndSize(p.arr.c_str(), p.arr.size()); +#endif + return ret; + } +}; + +struct bytes_from_python +{ + bytes_from_python() + { + converter::registry::push_back( + &convertible, &construct, type_id()); + } + + static void* convertible(PyObject* x) + { +#if PY_MAJOR_VERSION >= 3 + return PyBytes_Check(x) ? x : NULL; +#else + return PyString_Check(x) ? x : NULL; +#endif + } + + static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data) + { +#if PY_MAJOR_VERSION >= 3 + void* storage = ((converter::rvalue_from_python_storage*)data)->storage.bytes; + bytes* ret = new (storage) bytes(); + ret->arr.resize(PyBytes_Size(x)); + memcpy(&ret->arr[0], PyBytes_AsString(x), ret->arr.size()); + data->convertible = storage; +#else + void* storage = ((converter::rvalue_from_python_storage*)data)->storage.bytes; + bytes* ret = new (storage) bytes(); + ret->arr.resize(PyString_Size(x)); + memcpy(&ret->arr[0], PyString_AsString(x), ret->arr.size()); + data->convertible = storage; +#endif + } +}; + +#ifndef TORRENT_NO_DEPRECATE +object client_fingerprint_(peer_id const& id) +{ + boost::optional result = client_fingerprint(id); + return result ? object(*result) : object(); +} +#endif + +entry bdecode_(bytes const& data) +{ + return bdecode(data.arr.begin(), data.arr.end()); +} + +bytes bencode_(entry const& e) +{ + bytes result; + bencode(std::back_inserter(result.arr), e); + return result; +} + +void bind_utility() +{ + // TODO: it would be nice to install converters for sha1_hash as well + to_python_converter(); + bytes_from_python(); + +#ifndef TORRENT_NO_DEPRECATE + def("identify_client", &libtorrent::identify_client); + def("client_fingerprint", &client_fingerprint_); +#endif + def("bdecode", &bdecode_); + def("bencode", &bencode_); +} + diff --git a/bindings/python/src/version.cpp b/bindings/python/src/version.cpp new file mode 100644 index 0000000..e6234cb --- /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 +#include "boost_python.hpp" + +using namespace boost::python; +using libtorrent::version; + +void bind_version() +{ + scope().attr("__version__") = version(); + +#ifndef TORRENT_NO_DEPRECATE + scope().attr("version") = LIBTORRENT_VERSION; + scope().attr("version_major") = LIBTORRENT_VERSION_MAJOR; + scope().attr("version_minor") = LIBTORRENT_VERSION_MINOR; +#endif +} + diff --git a/build-aux/compile b/build-aux/compile new file mode 100755 index 0000000..a85b723 --- /dev/null +++ b/build-aux/compile @@ -0,0 +1,347 @@ +#! /bin/sh +# Wrapper for compilers which do not understand '-c -o'. + +scriptversion=2012-10-14.11; # UTC + +# Copyright (C) 1999-2014 Free Software Foundation, Inc. +# Written by Tom Tromey . +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# This file is maintained in Automake, please report +# bugs to or send patches to +# . + +nl=' +' + +# We need space, tab and new line, in precisely that order. Quoting is +# there to prevent tools from complaining about whitespace usage. +IFS=" "" $nl" + +file_conv= + +# func_file_conv build_file lazy +# Convert a $build file to $host form and store it in $file +# Currently only supports Windows hosts. If the determined conversion +# type is listed in (the comma separated) LAZY, no conversion will +# take place. +func_file_conv () +{ + file=$1 + case $file in + / | /[!/]*) # absolute file, and not a UNC file + if test -z "$file_conv"; then + # lazily determine how to convert abs files + case `uname -s` in + MINGW*) + file_conv=mingw + ;; + CYGWIN*) + file_conv=cygwin + ;; + *) + file_conv=wine + ;; + esac + fi + case $file_conv/,$2, in + *,$file_conv,*) + ;; + mingw/*) + file=`cmd //C echo "$file " | sed -e 's/"\(.*\) " *$/\1/'` + ;; + cygwin/*) + file=`cygpath -m "$file" || echo "$file"` + ;; + wine/*) + file=`winepath -w "$file" || echo "$file"` + ;; + esac + ;; + esac +} + +# func_cl_dashL linkdir +# Make cl look for libraries in LINKDIR +func_cl_dashL () +{ + func_file_conv "$1" + if test -z "$lib_path"; then + lib_path=$file + else + lib_path="$lib_path;$file" + fi + linker_opts="$linker_opts -LIBPATH:$file" +} + +# func_cl_dashl library +# Do a library search-path lookup for cl +func_cl_dashl () +{ + lib=$1 + found=no + save_IFS=$IFS + IFS=';' + for dir in $lib_path $LIB + do + IFS=$save_IFS + if $shared && test -f "$dir/$lib.dll.lib"; then + found=yes + lib=$dir/$lib.dll.lib + break + fi + if test -f "$dir/$lib.lib"; then + found=yes + lib=$dir/$lib.lib + break + fi + if test -f "$dir/lib$lib.a"; then + found=yes + lib=$dir/lib$lib.a + break + fi + done + IFS=$save_IFS + + if test "$found" != yes; then + lib=$lib.lib + fi +} + +# func_cl_wrapper cl arg... +# Adjust compile command to suit cl +func_cl_wrapper () +{ + # Assume a capable shell + lib_path= + shared=: + linker_opts= + for arg + do + if test -n "$eat"; then + eat= + else + case $1 in + -o) + # configure might choose to run compile as 'compile cc -o foo foo.c'. + eat=1 + case $2 in + *.o | *.[oO][bB][jJ]) + func_file_conv "$2" + set x "$@" -Fo"$file" + shift + ;; + *) + func_file_conv "$2" + set x "$@" -Fe"$file" + shift + ;; + esac + ;; + -I) + eat=1 + func_file_conv "$2" mingw + set x "$@" -I"$file" + shift + ;; + -I*) + func_file_conv "${1#-I}" mingw + set x "$@" -I"$file" + shift + ;; + -l) + eat=1 + func_cl_dashl "$2" + set x "$@" "$lib" + shift + ;; + -l*) + func_cl_dashl "${1#-l}" + set x "$@" "$lib" + shift + ;; + -L) + eat=1 + func_cl_dashL "$2" + ;; + -L*) + func_cl_dashL "${1#-L}" + ;; + -static) + shared=false + ;; + -Wl,*) + arg=${1#-Wl,} + save_ifs="$IFS"; IFS=',' + for flag in $arg; do + IFS="$save_ifs" + linker_opts="$linker_opts $flag" + done + IFS="$save_ifs" + ;; + -Xlinker) + eat=1 + linker_opts="$linker_opts $2" + ;; + -*) + set x "$@" "$1" + shift + ;; + *.cc | *.CC | *.cxx | *.CXX | *.[cC]++) + func_file_conv "$1" + set x "$@" -Tp"$file" + shift + ;; + *.c | *.cpp | *.CPP | *.lib | *.LIB | *.Lib | *.OBJ | *.obj | *.[oO]) + func_file_conv "$1" mingw + set x "$@" "$file" + shift + ;; + *) + set x "$@" "$1" + shift + ;; + esac + fi + shift + done + if test -n "$linker_opts"; then + linker_opts="-link$linker_opts" + fi + exec "$@" $linker_opts + exit 1 +} + +eat= + +case $1 in + '') + echo "$0: No command. Try '$0 --help' for more information." 1>&2 + exit 1; + ;; + -h | --h*) + cat <<\EOF +Usage: compile [--help] [--version] PROGRAM [ARGS] + +Wrapper for compilers which do not understand '-c -o'. +Remove '-o dest.o' from ARGS, run PROGRAM with the remaining +arguments, and rename the output as expected. + +If you are trying to build a whole package this is not the +right script to run: please start by reading the file 'INSTALL'. + +Report bugs to . +EOF + exit $? + ;; + -v | --v*) + echo "compile $scriptversion" + exit $? + ;; + cl | *[/\\]cl | cl.exe | *[/\\]cl.exe ) + func_cl_wrapper "$@" # Doesn't return... + ;; +esac + +ofile= +cfile= + +for arg +do + if test -n "$eat"; then + eat= + else + case $1 in + -o) + # configure might choose to run compile as 'compile cc -o foo foo.c'. + # So we strip '-o arg' only if arg is an object. + eat=1 + case $2 in + *.o | *.obj) + ofile=$2 + ;; + *) + set x "$@" -o "$2" + shift + ;; + esac + ;; + *.c) + cfile=$1 + set x "$@" "$1" + shift + ;; + *) + set x "$@" "$1" + shift + ;; + esac + fi + shift +done + +if test -z "$ofile" || test -z "$cfile"; then + # If no '-o' option was seen then we might have been invoked from a + # pattern rule where we don't need one. That is ok -- this is a + # normal compilation that the losing compiler can handle. If no + # '.c' file was seen then we are probably linking. That is also + # ok. + exec "$@" +fi + +# Name of file we expect compiler to create. +cofile=`echo "$cfile" | sed 's|^.*[\\/]||; s|^[a-zA-Z]:||; s/\.c$/.o/'` + +# Create the lock directory. +# Note: use '[/\\:.-]' here to ensure that we don't use the same name +# that we are using for the .o file. Also, base the name on the expected +# object file name, since that is what matters with a parallel build. +lockdir=`echo "$cofile" | sed -e 's|[/\\:.-]|_|g'`.d +while true; do + if mkdir "$lockdir" >/dev/null 2>&1; then + break + fi + sleep 1 +done +# FIXME: race condition here if user kills between mkdir and trap. +trap "rmdir '$lockdir'; exit 1" 1 2 15 + +# Run the compile. +"$@" +ret=$? + +if test -f "$cofile"; then + test "$cofile" = "$ofile" || mv "$cofile" "$ofile" +elif test -f "${cofile}bj"; then + test "${cofile}bj" = "$ofile" || mv "${cofile}bj" "$ofile" +fi + +rmdir "$lockdir" +exit $ret + +# Local Variables: +# mode: shell-script +# sh-indentation: 2 +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC" +# time-stamp-end: "; # UTC" +# End: diff --git a/build-aux/config.guess b/build-aux/config.guess new file mode 100755 index 0000000..6c32c86 --- /dev/null +++ b/build-aux/config.guess @@ -0,0 +1,1421 @@ +#! /bin/sh +# Attempt to guess a canonical system name. +# Copyright 1992-2014 Free Software Foundation, Inc. + +timestamp='2014-11-04' + +# This file is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that +# program. This Exception is an additional permission under section 7 +# of the GNU General Public License, version 3 ("GPLv3"). +# +# Originally written by Per Bothner; maintained since 2000 by Ben Elliston. +# +# You can get the latest version of this script from: +# http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess;hb=HEAD +# +# Please send patches to . + + +me=`echo "$0" | sed -e 's,.*/,,'` + +usage="\ +Usage: $0 [OPTION] + +Output the configuration name of the system \`$me' is run on. + +Operation modes: + -h, --help print this help, then exit + -t, --time-stamp print date of last modification, then exit + -v, --version print version number, then exit + +Report bugs and patches to ." + +version="\ +GNU config.guess ($timestamp) + +Originally written by Per Bothner. +Copyright 1992-2014 Free Software Foundation, Inc. + +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + +help=" +Try \`$me --help' for more information." + +# Parse command line +while test $# -gt 0 ; do + case $1 in + --time-stamp | --time* | -t ) + echo "$timestamp" ; exit ;; + --version | -v ) + echo "$version" ; exit ;; + --help | --h* | -h ) + echo "$usage"; exit ;; + -- ) # Stop option processing + shift; break ;; + - ) # Use stdin as input. + break ;; + -* ) + echo "$me: invalid option $1$help" >&2 + exit 1 ;; + * ) + break ;; + esac +done + +if test $# != 0; then + echo "$me: too many arguments$help" >&2 + exit 1 +fi + +trap 'exit 1' 1 2 15 + +# CC_FOR_BUILD -- compiler used by this script. Note that the use of a +# compiler to aid in system detection is discouraged as it requires +# temporary files to be created and, as you can see below, it is a +# headache to deal with in a portable fashion. + +# Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still +# use `HOST_CC' if defined, but it is deprecated. + +# Portable tmp directory creation inspired by the Autoconf team. + +set_cc_for_build=' +trap "exitcode=\$?; (rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null) && exit \$exitcode" 0 ; +trap "rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null; exit 1" 1 2 13 15 ; +: ${TMPDIR=/tmp} ; + { tmp=`(umask 077 && mktemp -d "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } || + { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir $tmp) ; } || + { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir $tmp) && echo "Warning: creating insecure temp directory" >&2 ; } || + { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; } ; +dummy=$tmp/dummy ; +tmpfiles="$dummy.c $dummy.o $dummy.rel $dummy" ; +case $CC_FOR_BUILD,$HOST_CC,$CC in + ,,) echo "int x;" > $dummy.c ; + for c in cc gcc c89 c99 ; do + if ($c -c -o $dummy.o $dummy.c) >/dev/null 2>&1 ; then + CC_FOR_BUILD="$c"; break ; + fi ; + done ; + if test x"$CC_FOR_BUILD" = x ; then + CC_FOR_BUILD=no_compiler_found ; + fi + ;; + ,,*) CC_FOR_BUILD=$CC ;; + ,*,*) CC_FOR_BUILD=$HOST_CC ;; +esac ; set_cc_for_build= ;' + +# This is needed to find uname on a Pyramid OSx when run in the BSD universe. +# (ghazi@noc.rutgers.edu 1994-08-24) +if (test -f /.attbin/uname) >/dev/null 2>&1 ; then + PATH=$PATH:/.attbin ; export PATH +fi + +UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown +UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown +UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown +UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown + +case "${UNAME_SYSTEM}" in +Linux|GNU|GNU/*) + # If the system lacks a compiler, then just pick glibc. + # We could probably try harder. + LIBC=gnu + + eval $set_cc_for_build + cat <<-EOF > $dummy.c + #include + #if defined(__UCLIBC__) + LIBC=uclibc + #elif defined(__dietlibc__) + LIBC=dietlibc + #else + LIBC=gnu + #endif + EOF + eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep '^LIBC' | sed 's, ,,g'` + ;; +esac + +# Note: order is significant - the case branches are not exclusive. + +case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in + *:NetBSD:*:*) + # NetBSD (nbsd) targets should (where applicable) match one or + # more of the tuples: *-*-netbsdelf*, *-*-netbsdaout*, + # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently + # switched to ELF, *-*-netbsd* would select the old + # object file format. This provides both forward + # compatibility and a consistent mechanism for selecting the + # object file format. + # + # Note: NetBSD doesn't particularly care about the vendor + # portion of the name. We always set it to "unknown". + sysctl="sysctl -n hw.machine_arch" + UNAME_MACHINE_ARCH=`(/sbin/$sysctl 2>/dev/null || \ + /usr/sbin/$sysctl 2>/dev/null || echo unknown)` + case "${UNAME_MACHINE_ARCH}" in + armeb) machine=armeb-unknown ;; + arm*) machine=arm-unknown ;; + sh3el) machine=shl-unknown ;; + sh3eb) machine=sh-unknown ;; + sh5el) machine=sh5le-unknown ;; + *) machine=${UNAME_MACHINE_ARCH}-unknown ;; + esac + # The Operating System including object format, if it has switched + # to ELF recently, or will in the future. + case "${UNAME_MACHINE_ARCH}" in + arm*|i386|m68k|ns32k|sh3*|sparc|vax) + eval $set_cc_for_build + if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep -q __ELF__ + then + # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout). + # Return netbsd for either. FIX? + os=netbsd + else + os=netbsdelf + fi + ;; + *) + os=netbsd + ;; + esac + # The OS release + # Debian GNU/NetBSD machines have a different userland, and + # thus, need a distinct triplet. However, they do not need + # kernel version information, so it can be replaced with a + # suitable tag, in the style of linux-gnu. + case "${UNAME_VERSION}" in + Debian*) + release='-gnu' + ;; + *) + release=`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'` + ;; + esac + # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM: + # contains redundant information, the shorter form: + # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used. + echo "${machine}-${os}${release}" + exit ;; + *:Bitrig:*:*) + UNAME_MACHINE_ARCH=`arch | sed 's/Bitrig.//'` + echo ${UNAME_MACHINE_ARCH}-unknown-bitrig${UNAME_RELEASE} + exit ;; + *:OpenBSD:*:*) + UNAME_MACHINE_ARCH=`arch | sed 's/OpenBSD.//'` + echo ${UNAME_MACHINE_ARCH}-unknown-openbsd${UNAME_RELEASE} + exit ;; + *:ekkoBSD:*:*) + echo ${UNAME_MACHINE}-unknown-ekkobsd${UNAME_RELEASE} + exit ;; + *:SolidBSD:*:*) + echo ${UNAME_MACHINE}-unknown-solidbsd${UNAME_RELEASE} + exit ;; + macppc:MirBSD:*:*) + echo powerpc-unknown-mirbsd${UNAME_RELEASE} + exit ;; + *:MirBSD:*:*) + echo ${UNAME_MACHINE}-unknown-mirbsd${UNAME_RELEASE} + exit ;; + alpha:OSF1:*:*) + case $UNAME_RELEASE in + *4.0) + UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'` + ;; + *5.*) + UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $4}'` + ;; + esac + # According to Compaq, /usr/sbin/psrinfo has been available on + # OSF/1 and Tru64 systems produced since 1995. I hope that + # covers most systems running today. This code pipes the CPU + # types through head -n 1, so we only detect the type of CPU 0. + ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^ The alpha \(.*\) processor.*$/\1/p' | head -n 1` + case "$ALPHA_CPU_TYPE" in + "EV4 (21064)") + UNAME_MACHINE="alpha" ;; + "EV4.5 (21064)") + UNAME_MACHINE="alpha" ;; + "LCA4 (21066/21068)") + UNAME_MACHINE="alpha" ;; + "EV5 (21164)") + UNAME_MACHINE="alphaev5" ;; + "EV5.6 (21164A)") + UNAME_MACHINE="alphaev56" ;; + "EV5.6 (21164PC)") + UNAME_MACHINE="alphapca56" ;; + "EV5.7 (21164PC)") + UNAME_MACHINE="alphapca57" ;; + "EV6 (21264)") + UNAME_MACHINE="alphaev6" ;; + "EV6.7 (21264A)") + UNAME_MACHINE="alphaev67" ;; + "EV6.8CB (21264C)") + UNAME_MACHINE="alphaev68" ;; + "EV6.8AL (21264B)") + UNAME_MACHINE="alphaev68" ;; + "EV6.8CX (21264D)") + UNAME_MACHINE="alphaev68" ;; + "EV6.9A (21264/EV69A)") + UNAME_MACHINE="alphaev69" ;; + "EV7 (21364)") + UNAME_MACHINE="alphaev7" ;; + "EV7.9 (21364A)") + UNAME_MACHINE="alphaev79" ;; + esac + # A Pn.n version is a patched version. + # A Vn.n version is a released version. + # A Tn.n version is a released field test version. + # A Xn.n version is an unreleased experimental baselevel. + # 1.2 uses "1.2" for uname -r. + echo ${UNAME_MACHINE}-dec-osf`echo ${UNAME_RELEASE} | sed -e 's/^[PVTX]//' | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` + # Reset EXIT trap before exiting to avoid spurious non-zero exit code. + exitcode=$? + trap '' 0 + exit $exitcode ;; + Alpha\ *:Windows_NT*:*) + # How do we know it's Interix rather than the generic POSIX subsystem? + # Should we change UNAME_MACHINE based on the output of uname instead + # of the specific Alpha model? + echo alpha-pc-interix + exit ;; + 21064:Windows_NT:50:3) + echo alpha-dec-winnt3.5 + exit ;; + Amiga*:UNIX_System_V:4.0:*) + echo m68k-unknown-sysv4 + exit ;; + *:[Aa]miga[Oo][Ss]:*:*) + echo ${UNAME_MACHINE}-unknown-amigaos + exit ;; + *:[Mm]orph[Oo][Ss]:*:*) + echo ${UNAME_MACHINE}-unknown-morphos + exit ;; + *:OS/390:*:*) + echo i370-ibm-openedition + exit ;; + *:z/VM:*:*) + echo s390-ibm-zvmoe + exit ;; + *:OS400:*:*) + echo powerpc-ibm-os400 + exit ;; + arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) + echo arm-acorn-riscix${UNAME_RELEASE} + exit ;; + arm*:riscos:*:*|arm*:RISCOS:*:*) + echo arm-unknown-riscos + exit ;; + SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*) + echo hppa1.1-hitachi-hiuxmpp + exit ;; + Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*) + # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE. + if test "`(/bin/universe) 2>/dev/null`" = att ; then + echo pyramid-pyramid-sysv3 + else + echo pyramid-pyramid-bsd + fi + exit ;; + NILE*:*:*:dcosx) + echo pyramid-pyramid-svr4 + exit ;; + DRS?6000:unix:4.0:6*) + echo sparc-icl-nx6 + exit ;; + DRS?6000:UNIX_SV:4.2*:7* | DRS?6000:isis:4.2*:7*) + case `/usr/bin/uname -p` in + sparc) echo sparc-icl-nx7; exit ;; + esac ;; + s390x:SunOS:*:*) + echo ${UNAME_MACHINE}-ibm-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + sun4H:SunOS:5.*:*) + echo sparc-hal-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*) + echo sparc-sun-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + i86pc:AuroraUX:5.*:* | i86xen:AuroraUX:5.*:*) + echo i386-pc-auroraux${UNAME_RELEASE} + exit ;; + i86pc:SunOS:5.*:* | i86xen:SunOS:5.*:*) + eval $set_cc_for_build + SUN_ARCH="i386" + # If there is a compiler, see if it is configured for 64-bit objects. + # Note that the Sun cc does not turn __LP64__ into 1 like gcc does. + # This test works for both compilers. + if [ "$CC_FOR_BUILD" != 'no_compiler_found' ]; then + if (echo '#ifdef __amd64'; echo IS_64BIT_ARCH; echo '#endif') | \ + (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) | \ + grep IS_64BIT_ARCH >/dev/null + then + SUN_ARCH="x86_64" + fi + fi + echo ${SUN_ARCH}-pc-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + sun4*:SunOS:6*:*) + # According to config.sub, this is the proper way to canonicalize + # SunOS6. Hard to guess exactly what SunOS6 will be like, but + # it's likely to be more like Solaris than SunOS4. + echo sparc-sun-solaris3`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + sun4*:SunOS:*:*) + case "`/usr/bin/arch -k`" in + Series*|S4*) + UNAME_RELEASE=`uname -v` + ;; + esac + # Japanese Language versions have a version number like `4.1.3-JL'. + echo sparc-sun-sunos`echo ${UNAME_RELEASE}|sed -e 's/-/_/'` + exit ;; + sun3*:SunOS:*:*) + echo m68k-sun-sunos${UNAME_RELEASE} + exit ;; + sun*:*:4.2BSD:*) + UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null` + test "x${UNAME_RELEASE}" = "x" && UNAME_RELEASE=3 + case "`/bin/arch`" in + sun3) + echo m68k-sun-sunos${UNAME_RELEASE} + ;; + sun4) + echo sparc-sun-sunos${UNAME_RELEASE} + ;; + esac + exit ;; + aushp:SunOS:*:*) + echo sparc-auspex-sunos${UNAME_RELEASE} + exit ;; + # The situation for MiNT is a little confusing. The machine name + # can be virtually everything (everything which is not + # "atarist" or "atariste" at least should have a processor + # > m68000). The system name ranges from "MiNT" over "FreeMiNT" + # to the lowercase version "mint" (or "freemint"). Finally + # the system name "TOS" denotes a system which is actually not + # MiNT. But MiNT is downward compatible to TOS, so this should + # be no problem. + atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit ;; + atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit ;; + *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit ;; + milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) + echo m68k-milan-mint${UNAME_RELEASE} + exit ;; + hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) + echo m68k-hades-mint${UNAME_RELEASE} + exit ;; + *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) + echo m68k-unknown-mint${UNAME_RELEASE} + exit ;; + m68k:machten:*:*) + echo m68k-apple-machten${UNAME_RELEASE} + exit ;; + powerpc:machten:*:*) + echo powerpc-apple-machten${UNAME_RELEASE} + exit ;; + RISC*:Mach:*:*) + echo mips-dec-mach_bsd4.3 + exit ;; + RISC*:ULTRIX:*:*) + echo mips-dec-ultrix${UNAME_RELEASE} + exit ;; + VAX*:ULTRIX*:*:*) + echo vax-dec-ultrix${UNAME_RELEASE} + exit ;; + 2020:CLIX:*:* | 2430:CLIX:*:*) + echo clipper-intergraph-clix${UNAME_RELEASE} + exit ;; + mips:*:*:UMIPS | mips:*:*:RISCos) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c +#ifdef __cplusplus +#include /* for printf() prototype */ + int main (int argc, char *argv[]) { +#else + int main (argc, argv) int argc; char *argv[]; { +#endif + #if defined (host_mips) && defined (MIPSEB) + #if defined (SYSTYPE_SYSV) + printf ("mips-mips-riscos%ssysv\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_SVR4) + printf ("mips-mips-riscos%ssvr4\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD) + printf ("mips-mips-riscos%sbsd\n", argv[1]); exit (0); + #endif + #endif + exit (-1); + } +EOF + $CC_FOR_BUILD -o $dummy $dummy.c && + dummyarg=`echo "${UNAME_RELEASE}" | sed -n 's/\([0-9]*\).*/\1/p'` && + SYSTEM_NAME=`$dummy $dummyarg` && + { echo "$SYSTEM_NAME"; exit; } + echo mips-mips-riscos${UNAME_RELEASE} + exit ;; + Motorola:PowerMAX_OS:*:*) + echo powerpc-motorola-powermax + exit ;; + Motorola:*:4.3:PL8-*) + echo powerpc-harris-powermax + exit ;; + Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*) + echo powerpc-harris-powermax + exit ;; + Night_Hawk:Power_UNIX:*:*) + echo powerpc-harris-powerunix + exit ;; + m88k:CX/UX:7*:*) + echo m88k-harris-cxux7 + exit ;; + m88k:*:4*:R4*) + echo m88k-motorola-sysv4 + exit ;; + m88k:*:3*:R3*) + echo m88k-motorola-sysv3 + exit ;; + AViiON:dgux:*:*) + # DG/UX returns AViiON for all architectures + UNAME_PROCESSOR=`/usr/bin/uname -p` + if [ $UNAME_PROCESSOR = mc88100 ] || [ $UNAME_PROCESSOR = mc88110 ] + then + if [ ${TARGET_BINARY_INTERFACE}x = m88kdguxelfx ] || \ + [ ${TARGET_BINARY_INTERFACE}x = x ] + then + echo m88k-dg-dgux${UNAME_RELEASE} + else + echo m88k-dg-dguxbcs${UNAME_RELEASE} + fi + else + echo i586-dg-dgux${UNAME_RELEASE} + fi + exit ;; + M88*:DolphinOS:*:*) # DolphinOS (SVR3) + echo m88k-dolphin-sysv3 + exit ;; + M88*:*:R3*:*) + # Delta 88k system running SVR3 + echo m88k-motorola-sysv3 + exit ;; + XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3) + echo m88k-tektronix-sysv3 + exit ;; + Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD) + echo m68k-tektronix-bsd + exit ;; + *:IRIX*:*:*) + echo mips-sgi-irix`echo ${UNAME_RELEASE}|sed -e 's/-/_/g'` + exit ;; + ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX. + echo romp-ibm-aix # uname -m gives an 8 hex-code CPU id + exit ;; # Note that: echo "'`uname -s`'" gives 'AIX ' + i*86:AIX:*:*) + echo i386-ibm-aix + exit ;; + ia64:AIX:*:*) + if [ -x /usr/bin/oslevel ] ; then + IBM_REV=`/usr/bin/oslevel` + else + IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} + fi + echo ${UNAME_MACHINE}-ibm-aix${IBM_REV} + exit ;; + *:AIX:2:3) + if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #include + + main() + { + if (!__power_pc()) + exit(1); + puts("powerpc-ibm-aix3.2.5"); + exit(0); + } +EOF + if $CC_FOR_BUILD -o $dummy $dummy.c && SYSTEM_NAME=`$dummy` + then + echo "$SYSTEM_NAME" + else + echo rs6000-ibm-aix3.2.5 + fi + elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then + echo rs6000-ibm-aix3.2.4 + else + echo rs6000-ibm-aix3.2 + fi + exit ;; + *:AIX:*:[4567]) + IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'` + if /usr/sbin/lsattr -El ${IBM_CPU_ID} | grep ' POWER' >/dev/null 2>&1; then + IBM_ARCH=rs6000 + else + IBM_ARCH=powerpc + fi + if [ -x /usr/bin/lslpp ] ; then + IBM_REV=`/usr/bin/lslpp -Lqc bos.rte.libc | + awk -F: '{ print $3 }' | sed s/[0-9]*$/0/` + else + IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} + fi + echo ${IBM_ARCH}-ibm-aix${IBM_REV} + exit ;; + *:AIX:*:*) + echo rs6000-ibm-aix + exit ;; + ibmrt:4.4BSD:*|romp-ibm:BSD:*) + echo romp-ibm-bsd4.4 + exit ;; + ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and + echo romp-ibm-bsd${UNAME_RELEASE} # 4.3 with uname added to + exit ;; # report: romp-ibm BSD 4.3 + *:BOSX:*:*) + echo rs6000-bull-bosx + exit ;; + DPX/2?00:B.O.S.:*:*) + echo m68k-bull-sysv3 + exit ;; + 9000/[34]??:4.3bsd:1.*:*) + echo m68k-hp-bsd + exit ;; + hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*) + echo m68k-hp-bsd4.4 + exit ;; + 9000/[34678]??:HP-UX:*:*) + HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` + case "${UNAME_MACHINE}" in + 9000/31? ) HP_ARCH=m68000 ;; + 9000/[34]?? ) HP_ARCH=m68k ;; + 9000/[678][0-9][0-9]) + if [ -x /usr/bin/getconf ]; then + sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null` + sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null` + case "${sc_cpu_version}" in + 523) HP_ARCH="hppa1.0" ;; # CPU_PA_RISC1_0 + 528) HP_ARCH="hppa1.1" ;; # CPU_PA_RISC1_1 + 532) # CPU_PA_RISC2_0 + case "${sc_kernel_bits}" in + 32) HP_ARCH="hppa2.0n" ;; + 64) HP_ARCH="hppa2.0w" ;; + '') HP_ARCH="hppa2.0" ;; # HP-UX 10.20 + esac ;; + esac + fi + if [ "${HP_ARCH}" = "" ]; then + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + + #define _HPUX_SOURCE + #include + #include + + int main () + { + #if defined(_SC_KERNEL_BITS) + long bits = sysconf(_SC_KERNEL_BITS); + #endif + long cpu = sysconf (_SC_CPU_VERSION); + + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1"); break; + case CPU_PA_RISC2_0: + #if defined(_SC_KERNEL_BITS) + switch (bits) + { + case 64: puts ("hppa2.0w"); break; + case 32: puts ("hppa2.0n"); break; + default: puts ("hppa2.0"); break; + } break; + #else /* !defined(_SC_KERNEL_BITS) */ + puts ("hppa2.0"); break; + #endif + default: puts ("hppa1.0"); break; + } + exit (0); + } +EOF + (CCOPTS= $CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null) && HP_ARCH=`$dummy` + test -z "$HP_ARCH" && HP_ARCH=hppa + fi ;; + esac + if [ ${HP_ARCH} = "hppa2.0w" ] + then + eval $set_cc_for_build + + # hppa2.0w-hp-hpux* has a 64-bit kernel and a compiler generating + # 32-bit code. hppa64-hp-hpux* has the same kernel and a compiler + # generating 64-bit code. GNU and HP use different nomenclature: + # + # $ CC_FOR_BUILD=cc ./config.guess + # => hppa2.0w-hp-hpux11.23 + # $ CC_FOR_BUILD="cc +DA2.0w" ./config.guess + # => hppa64-hp-hpux11.23 + + if echo __LP64__ | (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) | + grep -q __LP64__ + then + HP_ARCH="hppa2.0w" + else + HP_ARCH="hppa64" + fi + fi + echo ${HP_ARCH}-hp-hpux${HPUX_REV} + exit ;; + ia64:HP-UX:*:*) + HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` + echo ia64-hp-hpux${HPUX_REV} + exit ;; + 3050*:HI-UX:*:*) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #include + int + main () + { + long cpu = sysconf (_SC_CPU_VERSION); + /* The order matters, because CPU_IS_HP_MC68K erroneously returns + true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct + results, however. */ + if (CPU_IS_PA_RISC (cpu)) + { + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break; + case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break; + default: puts ("hppa-hitachi-hiuxwe2"); break; + } + } + else if (CPU_IS_HP_MC68K (cpu)) + puts ("m68k-hitachi-hiuxwe2"); + else puts ("unknown-hitachi-hiuxwe2"); + exit (0); + } +EOF + $CC_FOR_BUILD -o $dummy $dummy.c && SYSTEM_NAME=`$dummy` && + { echo "$SYSTEM_NAME"; exit; } + echo unknown-hitachi-hiuxwe2 + exit ;; + 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:* ) + echo hppa1.1-hp-bsd + exit ;; + 9000/8??:4.3bsd:*:*) + echo hppa1.0-hp-bsd + exit ;; + *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*) + echo hppa1.0-hp-mpeix + exit ;; + hp7??:OSF1:*:* | hp8?[79]:OSF1:*:* ) + echo hppa1.1-hp-osf + exit ;; + hp8??:OSF1:*:*) + echo hppa1.0-hp-osf + exit ;; + i*86:OSF1:*:*) + if [ -x /usr/sbin/sysversion ] ; then + echo ${UNAME_MACHINE}-unknown-osf1mk + else + echo ${UNAME_MACHINE}-unknown-osf1 + fi + exit ;; + parisc*:Lites*:*:*) + echo hppa1.1-hp-lites + exit ;; + C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) + echo c1-convex-bsd + exit ;; + C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*) + if getsysinfo -f scalar_acc + then echo c32-convex-bsd + else echo c2-convex-bsd + fi + exit ;; + C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*) + echo c34-convex-bsd + exit ;; + C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) + echo c38-convex-bsd + exit ;; + C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) + echo c4-convex-bsd + exit ;; + CRAY*Y-MP:*:*:*) + echo ymp-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*[A-Z]90:*:*:*) + echo ${UNAME_MACHINE}-cray-unicos${UNAME_RELEASE} \ + | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \ + -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \ + -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*TS:*:*:*) + echo t90-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*T3E:*:*:*) + echo alphaev5-cray-unicosmk${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*SV1:*:*:*) + echo sv1-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + *:UNICOS/mp:*:*) + echo craynv-cray-unicosmp${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*) + FUJITSU_PROC=`uname -m | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` + FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` + FUJITSU_REL=`echo ${UNAME_RELEASE} | sed -e 's/ /_/'` + echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" + exit ;; + 5000:UNIX_System_V:4.*:*) + FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` + FUJITSU_REL=`echo ${UNAME_RELEASE} | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/ /_/'` + echo "sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" + exit ;; + i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*) + echo ${UNAME_MACHINE}-pc-bsdi${UNAME_RELEASE} + exit ;; + sparc*:BSD/OS:*:*) + echo sparc-unknown-bsdi${UNAME_RELEASE} + exit ;; + *:BSD/OS:*:*) + echo ${UNAME_MACHINE}-unknown-bsdi${UNAME_RELEASE} + exit ;; + *:FreeBSD:*:*) + UNAME_PROCESSOR=`/usr/bin/uname -p` + case ${UNAME_PROCESSOR} in + amd64) + echo x86_64-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;; + *) + echo ${UNAME_PROCESSOR}-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;; + esac + exit ;; + i*:CYGWIN*:*) + echo ${UNAME_MACHINE}-pc-cygwin + exit ;; + *:MINGW64*:*) + echo ${UNAME_MACHINE}-pc-mingw64 + exit ;; + *:MINGW*:*) + echo ${UNAME_MACHINE}-pc-mingw32 + exit ;; + *:MSYS*:*) + echo ${UNAME_MACHINE}-pc-msys + exit ;; + i*:windows32*:*) + # uname -m includes "-pc" on this system. + echo ${UNAME_MACHINE}-mingw32 + exit ;; + i*:PW*:*) + echo ${UNAME_MACHINE}-pc-pw32 + exit ;; + *:Interix*:*) + case ${UNAME_MACHINE} in + x86) + echo i586-pc-interix${UNAME_RELEASE} + exit ;; + authenticamd | genuineintel | EM64T) + echo x86_64-unknown-interix${UNAME_RELEASE} + exit ;; + IA64) + echo ia64-unknown-interix${UNAME_RELEASE} + exit ;; + esac ;; + [345]86:Windows_95:* | [345]86:Windows_98:* | [345]86:Windows_NT:*) + echo i${UNAME_MACHINE}-pc-mks + exit ;; + 8664:Windows_NT:*) + echo x86_64-pc-mks + exit ;; + i*:Windows_NT*:* | Pentium*:Windows_NT*:*) + # How do we know it's Interix rather than the generic POSIX subsystem? + # It also conflicts with pre-2.0 versions of AT&T UWIN. Should we + # UNAME_MACHINE based on the output of uname instead of i386? + echo i586-pc-interix + exit ;; + i*:UWIN*:*) + echo ${UNAME_MACHINE}-pc-uwin + exit ;; + amd64:CYGWIN*:*:* | x86_64:CYGWIN*:*:*) + echo x86_64-unknown-cygwin + exit ;; + p*:CYGWIN*:*) + echo powerpcle-unknown-cygwin + exit ;; + prep*:SunOS:5.*:*) + echo powerpcle-unknown-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + *:GNU:*:*) + # the GNU system + echo `echo ${UNAME_MACHINE}|sed -e 's,[-/].*$,,'`-unknown-${LIBC}`echo ${UNAME_RELEASE}|sed -e 's,/.*$,,'` + exit ;; + *:GNU/*:*:*) + # other systems with GNU libc and userland + echo ${UNAME_MACHINE}-unknown-`echo ${UNAME_SYSTEM} | sed 's,^[^/]*/,,' | tr '[A-Z]' '[a-z]'``echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`-${LIBC} + exit ;; + i*86:Minix:*:*) + echo ${UNAME_MACHINE}-pc-minix + exit ;; + aarch64:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + aarch64_be:Linux:*:*) + UNAME_MACHINE=aarch64_be + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + alpha:Linux:*:*) + case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in + EV5) UNAME_MACHINE=alphaev5 ;; + EV56) UNAME_MACHINE=alphaev56 ;; + PCA56) UNAME_MACHINE=alphapca56 ;; + PCA57) UNAME_MACHINE=alphapca56 ;; + EV6) UNAME_MACHINE=alphaev6 ;; + EV67) UNAME_MACHINE=alphaev67 ;; + EV68*) UNAME_MACHINE=alphaev68 ;; + esac + objdump --private-headers /bin/sh | grep -q ld.so.1 + if test "$?" = 0 ; then LIBC="gnulibc1" ; fi + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + arc:Linux:*:* | arceb:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + arm*:Linux:*:*) + eval $set_cc_for_build + if echo __ARM_EABI__ | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep -q __ARM_EABI__ + then + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + else + if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep -q __ARM_PCS_VFP + then + echo ${UNAME_MACHINE}-unknown-linux-${LIBC}eabi + else + echo ${UNAME_MACHINE}-unknown-linux-${LIBC}eabihf + fi + fi + exit ;; + avr32*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + cris:Linux:*:*) + echo ${UNAME_MACHINE}-axis-linux-${LIBC} + exit ;; + crisv32:Linux:*:*) + echo ${UNAME_MACHINE}-axis-linux-${LIBC} + exit ;; + frv:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + hexagon:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + i*86:Linux:*:*) + echo ${UNAME_MACHINE}-pc-linux-${LIBC} + exit ;; + ia64:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + m32r*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + m68*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + mips:Linux:*:* | mips64:Linux:*:*) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #undef CPU + #undef ${UNAME_MACHINE} + #undef ${UNAME_MACHINE}el + #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) + CPU=${UNAME_MACHINE}el + #else + #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) + CPU=${UNAME_MACHINE} + #else + CPU= + #endif + #endif +EOF + eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep '^CPU'` + test x"${CPU}" != x && { echo "${CPU}-unknown-linux-${LIBC}"; exit; } + ;; + openrisc*:Linux:*:*) + echo or1k-unknown-linux-${LIBC} + exit ;; + or32:Linux:*:* | or1k*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + padre:Linux:*:*) + echo sparc-unknown-linux-${LIBC} + exit ;; + parisc64:Linux:*:* | hppa64:Linux:*:*) + echo hppa64-unknown-linux-${LIBC} + exit ;; + parisc:Linux:*:* | hppa:Linux:*:*) + # Look for CPU level + case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in + PA7*) echo hppa1.1-unknown-linux-${LIBC} ;; + PA8*) echo hppa2.0-unknown-linux-${LIBC} ;; + *) echo hppa-unknown-linux-${LIBC} ;; + esac + exit ;; + ppc64:Linux:*:*) + echo powerpc64-unknown-linux-${LIBC} + exit ;; + ppc:Linux:*:*) + echo powerpc-unknown-linux-${LIBC} + exit ;; + ppc64le:Linux:*:*) + echo powerpc64le-unknown-linux-${LIBC} + exit ;; + ppcle:Linux:*:*) + echo powerpcle-unknown-linux-${LIBC} + exit ;; + s390:Linux:*:* | s390x:Linux:*:*) + echo ${UNAME_MACHINE}-ibm-linux-${LIBC} + exit ;; + sh64*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + sh*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + sparc:Linux:*:* | sparc64:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + tile*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + vax:Linux:*:*) + echo ${UNAME_MACHINE}-dec-linux-${LIBC} + exit ;; + x86_64:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + xtensa*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + i*86:DYNIX/ptx:4*:*) + # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there. + # earlier versions are messed up and put the nodename in both + # sysname and nodename. + echo i386-sequent-sysv4 + exit ;; + i*86:UNIX_SV:4.2MP:2.*) + # Unixware is an offshoot of SVR4, but it has its own version + # number series starting with 2... + # I am not positive that other SVR4 systems won't match this, + # I just have to hope. -- rms. + # Use sysv4.2uw... so that sysv4* matches it. + echo ${UNAME_MACHINE}-pc-sysv4.2uw${UNAME_VERSION} + exit ;; + i*86:OS/2:*:*) + # If we were able to find `uname', then EMX Unix compatibility + # is probably installed. + echo ${UNAME_MACHINE}-pc-os2-emx + exit ;; + i*86:XTS-300:*:STOP) + echo ${UNAME_MACHINE}-unknown-stop + exit ;; + i*86:atheos:*:*) + echo ${UNAME_MACHINE}-unknown-atheos + exit ;; + i*86:syllable:*:*) + echo ${UNAME_MACHINE}-pc-syllable + exit ;; + i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.[02]*:*) + echo i386-unknown-lynxos${UNAME_RELEASE} + exit ;; + i*86:*DOS:*:*) + echo ${UNAME_MACHINE}-pc-msdosdjgpp + exit ;; + i*86:*:4.*:* | i*86:SYSTEM_V:4.*:*) + UNAME_REL=`echo ${UNAME_RELEASE} | sed 's/\/MP$//'` + if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then + echo ${UNAME_MACHINE}-univel-sysv${UNAME_REL} + else + echo ${UNAME_MACHINE}-pc-sysv${UNAME_REL} + fi + exit ;; + i*86:*:5:[678]*) + # UnixWare 7.x, OpenUNIX and OpenServer 6. + case `/bin/uname -X | grep "^Machine"` in + *486*) UNAME_MACHINE=i486 ;; + *Pentium) UNAME_MACHINE=i586 ;; + *Pent*|*Celeron) UNAME_MACHINE=i686 ;; + esac + echo ${UNAME_MACHINE}-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION} + exit ;; + i*86:*:3.2:*) + if test -f /usr/options/cb.name; then + UNAME_REL=`sed -n 's/.*Version //p' /dev/null >/dev/null ; then + UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')` + (/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486 + (/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \ + && UNAME_MACHINE=i586 + (/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \ + && UNAME_MACHINE=i686 + (/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \ + && UNAME_MACHINE=i686 + echo ${UNAME_MACHINE}-pc-sco$UNAME_REL + else + echo ${UNAME_MACHINE}-pc-sysv32 + fi + exit ;; + pc:*:*:*) + # Left here for compatibility: + # uname -m prints for DJGPP always 'pc', but it prints nothing about + # the processor, so we play safe by assuming i586. + # Note: whatever this is, it MUST be the same as what config.sub + # prints for the "djgpp" host, or else GDB configury will decide that + # this is a cross-build. + echo i586-pc-msdosdjgpp + exit ;; + Intel:Mach:3*:*) + echo i386-pc-mach3 + exit ;; + paragon:*:*:*) + echo i860-intel-osf1 + exit ;; + i860:*:4.*:*) # i860-SVR4 + if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then + echo i860-stardent-sysv${UNAME_RELEASE} # Stardent Vistra i860-SVR4 + else # Add other i860-SVR4 vendors below as they are discovered. + echo i860-unknown-sysv${UNAME_RELEASE} # Unknown i860-SVR4 + fi + exit ;; + mini*:CTIX:SYS*5:*) + # "miniframe" + echo m68010-convergent-sysv + exit ;; + mc68k:UNIX:SYSTEM5:3.51m) + echo m68k-convergent-sysv + exit ;; + M680?0:D-NIX:5.3:*) + echo m68k-diab-dnix + exit ;; + M68*:*:R3V[5678]*:*) + test -r /sysV68 && { echo 'm68k-motorola-sysv'; exit; } ;; + 3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0 | S7501*:*:4.0:3.0) + OS_REL='' + test -r /etc/.relid \ + && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && { echo i486-ncr-sysv4.3${OS_REL}; exit; } + /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ + && { echo i586-ncr-sysv4.3${OS_REL}; exit; } ;; + 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*) + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && { echo i486-ncr-sysv4; exit; } ;; + NCR*:*:4.2:* | MPRAS*:*:4.2:*) + OS_REL='.3' + test -r /etc/.relid \ + && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && { echo i486-ncr-sysv4.3${OS_REL}; exit; } + /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ + && { echo i586-ncr-sysv4.3${OS_REL}; exit; } + /bin/uname -p 2>/dev/null | /bin/grep pteron >/dev/null \ + && { echo i586-ncr-sysv4.3${OS_REL}; exit; } ;; + m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*) + echo m68k-unknown-lynxos${UNAME_RELEASE} + exit ;; + mc68030:UNIX_System_V:4.*:*) + echo m68k-atari-sysv4 + exit ;; + TSUNAMI:LynxOS:2.*:*) + echo sparc-unknown-lynxos${UNAME_RELEASE} + exit ;; + rs6000:LynxOS:2.*:*) + echo rs6000-unknown-lynxos${UNAME_RELEASE} + exit ;; + PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.[02]*:*) + echo powerpc-unknown-lynxos${UNAME_RELEASE} + exit ;; + SM[BE]S:UNIX_SV:*:*) + echo mips-dde-sysv${UNAME_RELEASE} + exit ;; + RM*:ReliantUNIX-*:*:*) + echo mips-sni-sysv4 + exit ;; + RM*:SINIX-*:*:*) + echo mips-sni-sysv4 + exit ;; + *:SINIX-*:*:*) + if uname -p 2>/dev/null >/dev/null ; then + UNAME_MACHINE=`(uname -p) 2>/dev/null` + echo ${UNAME_MACHINE}-sni-sysv4 + else + echo ns32k-sni-sysv + fi + exit ;; + PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort + # says + echo i586-unisys-sysv4 + exit ;; + *:UNIX_System_V:4*:FTX*) + # From Gerald Hewes . + # How about differentiating between stratus architectures? -djm + echo hppa1.1-stratus-sysv4 + exit ;; + *:*:*:FTX*) + # From seanf@swdc.stratus.com. + echo i860-stratus-sysv4 + exit ;; + i*86:VOS:*:*) + # From Paul.Green@stratus.com. + echo ${UNAME_MACHINE}-stratus-vos + exit ;; + *:VOS:*:*) + # From Paul.Green@stratus.com. + echo hppa1.1-stratus-vos + exit ;; + mc68*:A/UX:*:*) + echo m68k-apple-aux${UNAME_RELEASE} + exit ;; + news*:NEWS-OS:6*:*) + echo mips-sony-newsos6 + exit ;; + R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*) + if [ -d /usr/nec ]; then + echo mips-nec-sysv${UNAME_RELEASE} + else + echo mips-unknown-sysv${UNAME_RELEASE} + fi + exit ;; + BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. + echo powerpc-be-beos + exit ;; + BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. + echo powerpc-apple-beos + exit ;; + BePC:BeOS:*:*) # BeOS running on Intel PC compatible. + echo i586-pc-beos + exit ;; + BePC:Haiku:*:*) # Haiku running on Intel PC compatible. + echo i586-pc-haiku + exit ;; + x86_64:Haiku:*:*) + echo x86_64-unknown-haiku + exit ;; + SX-4:SUPER-UX:*:*) + echo sx4-nec-superux${UNAME_RELEASE} + exit ;; + SX-5:SUPER-UX:*:*) + echo sx5-nec-superux${UNAME_RELEASE} + exit ;; + SX-6:SUPER-UX:*:*) + echo sx6-nec-superux${UNAME_RELEASE} + exit ;; + SX-7:SUPER-UX:*:*) + echo sx7-nec-superux${UNAME_RELEASE} + exit ;; + SX-8:SUPER-UX:*:*) + echo sx8-nec-superux${UNAME_RELEASE} + exit ;; + SX-8R:SUPER-UX:*:*) + echo sx8r-nec-superux${UNAME_RELEASE} + exit ;; + Power*:Rhapsody:*:*) + echo powerpc-apple-rhapsody${UNAME_RELEASE} + exit ;; + *:Rhapsody:*:*) + echo ${UNAME_MACHINE}-apple-rhapsody${UNAME_RELEASE} + exit ;; + *:Darwin:*:*) + UNAME_PROCESSOR=`uname -p` || UNAME_PROCESSOR=unknown + eval $set_cc_for_build + if test "$UNAME_PROCESSOR" = unknown ; then + UNAME_PROCESSOR=powerpc + fi + if test `echo "$UNAME_RELEASE" | sed -e 's/\..*//'` -le 10 ; then + if [ "$CC_FOR_BUILD" != 'no_compiler_found' ]; then + if (echo '#ifdef __LP64__'; echo IS_64BIT_ARCH; echo '#endif') | \ + (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) | \ + grep IS_64BIT_ARCH >/dev/null + then + case $UNAME_PROCESSOR in + i386) UNAME_PROCESSOR=x86_64 ;; + powerpc) UNAME_PROCESSOR=powerpc64 ;; + esac + fi + fi + elif test "$UNAME_PROCESSOR" = i386 ; then + # Avoid executing cc on OS X 10.9, as it ships with a stub + # that puts up a graphical alert prompting to install + # developer tools. Any system running Mac OS X 10.7 or + # later (Darwin 11 and later) is required to have a 64-bit + # processor. This is not true of the ARM version of Darwin + # that Apple uses in portable devices. + UNAME_PROCESSOR=x86_64 + fi + echo ${UNAME_PROCESSOR}-apple-darwin${UNAME_RELEASE} + exit ;; + *:procnto*:*:* | *:QNX:[0123456789]*:*) + UNAME_PROCESSOR=`uname -p` + if test "$UNAME_PROCESSOR" = "x86"; then + UNAME_PROCESSOR=i386 + UNAME_MACHINE=pc + fi + echo ${UNAME_PROCESSOR}-${UNAME_MACHINE}-nto-qnx${UNAME_RELEASE} + exit ;; + *:QNX:*:4*) + echo i386-pc-qnx + exit ;; + NEO-?:NONSTOP_KERNEL:*:*) + echo neo-tandem-nsk${UNAME_RELEASE} + exit ;; + NSE-*:NONSTOP_KERNEL:*:*) + echo nse-tandem-nsk${UNAME_RELEASE} + exit ;; + NSR-?:NONSTOP_KERNEL:*:*) + echo nsr-tandem-nsk${UNAME_RELEASE} + exit ;; + *:NonStop-UX:*:*) + echo mips-compaq-nonstopux + exit ;; + BS2000:POSIX*:*:*) + echo bs2000-siemens-sysv + exit ;; + DS/*:UNIX_System_V:*:*) + echo ${UNAME_MACHINE}-${UNAME_SYSTEM}-${UNAME_RELEASE} + exit ;; + *:Plan9:*:*) + # "uname -m" is not consistent, so use $cputype instead. 386 + # is converted to i386 for consistency with other x86 + # operating systems. + if test "$cputype" = "386"; then + UNAME_MACHINE=i386 + else + UNAME_MACHINE="$cputype" + fi + echo ${UNAME_MACHINE}-unknown-plan9 + exit ;; + *:TOPS-10:*:*) + echo pdp10-unknown-tops10 + exit ;; + *:TENEX:*:*) + echo pdp10-unknown-tenex + exit ;; + KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*) + echo pdp10-dec-tops20 + exit ;; + XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*) + echo pdp10-xkl-tops20 + exit ;; + *:TOPS-20:*:*) + echo pdp10-unknown-tops20 + exit ;; + *:ITS:*:*) + echo pdp10-unknown-its + exit ;; + SEI:*:*:SEIUX) + echo mips-sei-seiux${UNAME_RELEASE} + exit ;; + *:DragonFly:*:*) + echo ${UNAME_MACHINE}-unknown-dragonfly`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` + exit ;; + *:*VMS:*:*) + UNAME_MACHINE=`(uname -p) 2>/dev/null` + case "${UNAME_MACHINE}" in + A*) echo alpha-dec-vms ; exit ;; + I*) echo ia64-dec-vms ; exit ;; + V*) echo vax-dec-vms ; exit ;; + esac ;; + *:XENIX:*:SysV) + echo i386-pc-xenix + exit ;; + i*86:skyos:*:*) + echo ${UNAME_MACHINE}-pc-skyos`echo ${UNAME_RELEASE}` | sed -e 's/ .*$//' + exit ;; + i*86:rdos:*:*) + echo ${UNAME_MACHINE}-pc-rdos + exit ;; + i*86:AROS:*:*) + echo ${UNAME_MACHINE}-pc-aros + exit ;; + x86_64:VMkernel:*:*) + echo ${UNAME_MACHINE}-unknown-esx + exit ;; +esac + +cat >&2 < in order to provide the needed +information to handle your system. + +config.guess timestamp = $timestamp + +uname -m = `(uname -m) 2>/dev/null || echo unknown` +uname -r = `(uname -r) 2>/dev/null || echo unknown` +uname -s = `(uname -s) 2>/dev/null || echo unknown` +uname -v = `(uname -v) 2>/dev/null || echo unknown` + +/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null` +/bin/uname -X = `(/bin/uname -X) 2>/dev/null` + +hostinfo = `(hostinfo) 2>/dev/null` +/bin/universe = `(/bin/universe) 2>/dev/null` +/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null` +/bin/arch = `(/bin/arch) 2>/dev/null` +/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null` +/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null` + +UNAME_MACHINE = ${UNAME_MACHINE} +UNAME_RELEASE = ${UNAME_RELEASE} +UNAME_SYSTEM = ${UNAME_SYSTEM} +UNAME_VERSION = ${UNAME_VERSION} +EOF + +exit 1 + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "timestamp='" +# time-stamp-format: "%:y-%02m-%02d" +# time-stamp-end: "'" +# End: diff --git a/build-aux/config.rpath b/build-aux/config.rpath new file mode 100644 index 0000000..4dea759 --- /dev/null +++ b/build-aux/config.rpath @@ -0,0 +1,543 @@ +#! /bin/sh +# Output a system dependent set of variables, describing how to set the +# run time search path of shared libraries in an executable. +# +# Copyright 1996-2003 Free Software Foundation, Inc. +# Taken from GNU libtool, 2001 +# Originally by Gordon Matzigkeit , 1996 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. +# +# The first argument passed to this file is the canonical host specification, +# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM +# or +# CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM +# The environment variables CC, GCC, LDFLAGS, LD, with_gnu_ld +# should be set by the caller. +# +# The set of defined variables is at the end of this script. + +# Known limitations: +# - On IRIX 6.5 with CC="cc", the run time search patch must not be longer +# than 256 bytes, otherwise the compiler driver will dump core. The only +# known workaround is to choose shorter directory names for the build +# directory and/or the installation directory. + +# All known linkers require a `.a' archive for static linking (except M$VC, +# which needs '.lib'). +libext=a +shrext=.so + +host="$1" +host_cpu=`echo "$host" | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\1/'` +host_vendor=`echo "$host" | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\2/'` +host_os=`echo "$host" | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\3/'` + +# Code taken from libtool.m4's AC_LIBTOOL_PROG_COMPILER_PIC. + +wl= +if test "$GCC" = yes; then + wl='-Wl,' +else + case "$host_os" in + aix*) + wl='-Wl,' + ;; + mingw* | pw32* | os2*) + ;; + hpux9* | hpux10* | hpux11*) + wl='-Wl,' + ;; + irix5* | irix6* | nonstopux*) + wl='-Wl,' + ;; + newsos6) + ;; + linux*) + case $CC in + icc|ecc) + wl='-Wl,' + ;; + ccc) + wl='-Wl,' + ;; + esac + ;; + osf3* | osf4* | osf5*) + wl='-Wl,' + ;; + sco3.2v5*) + ;; + solaris*) + wl='-Wl,' + ;; + sunos4*) + wl='-Qoption ld ' + ;; + sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*) + wl='-Wl,' + ;; + sysv4*MP*) + ;; + uts4*) + ;; + esac +fi + +# Code taken from libtool.m4's AC_LIBTOOL_PROG_LD_SHLIBS. + +hardcode_libdir_flag_spec= +hardcode_libdir_separator= +hardcode_direct=no +hardcode_minus_L=no + +case "$host_os" in + cygwin* | mingw* | pw32*) + # FIXME: the MSVC++ port hasn't been tested in a loooong time + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + if test "$GCC" != yes; then + with_gnu_ld=no + fi + ;; + openbsd*) + with_gnu_ld=no + ;; +esac + +ld_shlibs=yes +if test "$with_gnu_ld" = yes; then + case "$host_os" in + aix[3-9]*) + # On AIX/PPC, the GNU linker is very broken + if test "$host_cpu" != ia64; then + ld_shlibs=no + fi + ;; + amigaos*) + hardcode_libdir_flag_spec='-L$libdir' + hardcode_minus_L=yes + # Samuel A. Falvo II reports + # that the semantics of dynamic libraries on AmigaOS, at least up + # to version 4, is to share data among multiple programs linked + # with the same dynamic library. Since this doesn't match the + # behavior of shared libraries on other platforms, we can use + # them. + ld_shlibs=no + ;; + beos*) + if $LD --help 2>&1 | egrep ': supported targets:.* elf' > /dev/null; then + : + else + ld_shlibs=no + fi + ;; + cygwin* | mingw* | pw32*) + # hardcode_libdir_flag_spec is actually meaningless, as there is + # no search path for DLLs. + hardcode_libdir_flag_spec='-L$libdir' + if $LD --help 2>&1 | grep 'auto-import' > /dev/null; then + : + else + ld_shlibs=no + fi + ;; + netbsd*) + ;; + solaris* | sysv5*) + if $LD -v 2>&1 | egrep 'BFD 2\.8' > /dev/null; then + ld_shlibs=no + elif $LD --help 2>&1 | egrep ': supported targets:.* elf' > /dev/null; then + : + else + ld_shlibs=no + fi + ;; + sunos4*) + hardcode_direct=yes + ;; + *) + if $LD --help 2>&1 | egrep ': supported targets:.* elf' > /dev/null; then + : + else + ld_shlibs=no + fi + ;; + esac + if test "$ld_shlibs" = yes; then + # Unlike libtool, we use -rpath here, not --rpath, since the documented + # option of GNU ld is called -rpath, not --rpath. + hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' + fi +else + case "$host_os" in + aix3*) + # Note: this linker hardcodes the directories in LIBPATH if there + # are no directories specified by -L. + hardcode_minus_L=yes + if test "$GCC" = yes; then + # Neither direct hardcoding nor static linking is supported with a + # broken collect2. + hardcode_direct=unsupported + fi + ;; + aix[4-9]*) + if test "$host_cpu" = ia64; then + # On IA64, the linker does run time linking by default, so we don't + # have to do anything special. + aix_use_runtimelinking=no + else + aix_use_runtimelinking=no + # Test if we are trying to use run time linking or normal + # AIX style linking. If -brtl is somewhere in LDFLAGS, we + # need to do runtime linking. + case $host_os in aix4.[23]|aix4.[23].*|aix[5-9]*) + for ld_flag in $LDFLAGS; do + if (test $ld_flag = "-brtl" || test $ld_flag = "-Wl,-brtl"); then + aix_use_runtimelinking=yes + break + fi + done + esac + fi + hardcode_direct=yes + hardcode_libdir_separator=':' + if test "$GCC" = yes; then + case $host_os in aix4.[012]|aix4.[012].*) + collect2name=`${CC} -print-prog-name=collect2` + if test -f "$collect2name" && \ + strings "$collect2name" | grep resolve_lib_name >/dev/null + then + # We have reworked collect2 + hardcode_direct=yes + else + # We have old collect2 + hardcode_direct=unsupported + hardcode_minus_L=yes + hardcode_libdir_flag_spec='-L$libdir' + hardcode_libdir_separator= + fi + esac + fi + # Begin _LT_AC_SYS_LIBPATH_AIX. + echo 'int main () { return 0; }' > conftest.c + ${CC} ${LDFLAGS} conftest.c -o conftest + aix_libpath=`dump -H conftest 2>/dev/null | sed -n -e '/Import File Strings/,/^$/ { /^0/ { s/^0 *\(.*\)$/\1/; p; } +}'` + if test -z "$aix_libpath"; then + aix_libpath=`dump -HX64 conftest 2>/dev/null | sed -n -e '/Import File Strings/,/^$/ { /^0/ { s/^0 *\(.*\)$/\1/; p; } +}'` + fi + if test -z "$aix_libpath"; then + aix_libpath="/usr/lib:/lib" + fi + rm -f conftest.c conftest + # End _LT_AC_SYS_LIBPATH_AIX. + if test "$aix_use_runtimelinking" = yes; then + hardcode_libdir_flag_spec='${wl}-blibpath:$libdir:'"$aix_libpath" + else + if test "$host_cpu" = ia64; then + hardcode_libdir_flag_spec='${wl}-R $libdir:/usr/lib:/lib' + else + hardcode_libdir_flag_spec='${wl}-blibpath:$libdir:'"$aix_libpath" + fi + fi + ;; + amigaos*) + hardcode_libdir_flag_spec='-L$libdir' + hardcode_minus_L=yes + # see comment about different semantics on the GNU ld section + ld_shlibs=no + ;; + bsdi4*) + ;; + cygwin* | mingw* | pw32*) + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + # hardcode_libdir_flag_spec is actually meaningless, as there is + # no search path for DLLs. + hardcode_libdir_flag_spec=' ' + libext=lib + ;; + darwin* | rhapsody*) + if $CC -v 2>&1 | grep 'Apple' >/dev/null ; then + hardcode_direct=no + fi + ;; + dgux*) + hardcode_libdir_flag_spec='-L$libdir' + ;; + freebsd2.2*) + hardcode_libdir_flag_spec='-R$libdir' + hardcode_direct=yes + ;; + freebsd2*) + hardcode_direct=yes + hardcode_minus_L=yes + ;; + freebsd*) + hardcode_libdir_flag_spec='-R$libdir' + hardcode_direct=yes + ;; + hpux9*) + hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir' + hardcode_libdir_separator=: + hardcode_direct=yes + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + hardcode_minus_L=yes + ;; + hpux10* | hpux11*) + if test "$with_gnu_ld" = no; then + case "$host_cpu" in + hppa*64*) + hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir' + hardcode_libdir_separator=: + hardcode_direct=no + ;; + ia64*) + hardcode_libdir_flag_spec='-L$libdir' + hardcode_direct=no + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + hardcode_minus_L=yes + ;; + *) + hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir' + hardcode_libdir_separator=: + hardcode_direct=yes + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + hardcode_minus_L=yes + ;; + esac + fi + ;; + irix5* | irix6* | nonstopux*) + hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' + hardcode_libdir_separator=: + ;; + netbsd*) + hardcode_libdir_flag_spec='-R$libdir' + hardcode_direct=yes + ;; + newsos6) + hardcode_direct=yes + hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' + hardcode_libdir_separator=: + ;; + openbsd*) + hardcode_direct=yes + if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then + hardcode_libdir_flag_spec='${wl}-rpath,$libdir' + else + case "$host_os" in + openbsd[01].* | openbsd2.[0-7] | openbsd2.[0-7].*) + hardcode_libdir_flag_spec='-R$libdir' + ;; + *) + hardcode_libdir_flag_spec='${wl}-rpath,$libdir' + ;; + esac + fi + ;; + os2*) + hardcode_libdir_flag_spec='-L$libdir' + hardcode_minus_L=yes + ;; + osf3*) + hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' + hardcode_libdir_separator=: + ;; + osf4* | osf5*) + if test "$GCC" = yes; then + hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' + else + # Both cc and cxx compiler support -rpath directly + hardcode_libdir_flag_spec='-rpath $libdir' + fi + hardcode_libdir_separator=: + ;; + sco3.2v5*) + ;; + solaris*) + hardcode_libdir_flag_spec='-R$libdir' + ;; + sunos4*) + hardcode_libdir_flag_spec='-L$libdir' + hardcode_direct=yes + hardcode_minus_L=yes + ;; + sysv4) + case $host_vendor in + sni) + hardcode_direct=yes # is this really true??? + ;; + siemens) + hardcode_direct=no + ;; + motorola) + hardcode_direct=no #Motorola manual says yes, but my tests say they lie + ;; + esac + ;; + sysv4.3*) + ;; + sysv4*MP*) + if test -d /usr/nec; then + ld_shlibs=yes + fi + ;; + sysv4.2uw2*) + hardcode_direct=yes + hardcode_minus_L=no + ;; + sysv5OpenUNIX8* | sysv5UnixWare7* | sysv5uw[78]* | unixware7*) + ;; + sysv5*) + hardcode_libdir_flag_spec= + ;; + uts4*) + hardcode_libdir_flag_spec='-L$libdir' + ;; + *) + ld_shlibs=no + ;; + esac +fi + +# Check dynamic linker characteristics +# Code taken from libtool.m4's AC_LIBTOOL_SYS_DYNAMIC_LINKER. +libname_spec='lib$name' +case "$host_os" in + aix3*) + ;; + aix[4-9]*) + ;; + amigaos*) + ;; + beos*) + ;; + bsdi4*) + ;; + cygwin* | mingw* | pw32*) + shrext=.dll + ;; + darwin* | rhapsody*) + shrext=.dylib + ;; + dgux*) + ;; + freebsd*) + ;; + gnu*) + ;; + hpux9* | hpux10* | hpux11*) + case "$host_cpu" in + ia64*) + shrext=.so + ;; + hppa*64*) + shrext=.sl + ;; + *) + shrext=.sl + ;; + esac + ;; + irix5* | irix6* | nonstopux*) + case "$host_os" in + irix5* | nonstopux*) + libsuff= shlibsuff= + ;; + *) + case $LD in + *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ") libsuff= shlibsuff= ;; + *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ") libsuff=32 shlibsuff=N32 ;; + *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ") libsuff=64 shlibsuff=64 ;; + *) libsuff= shlibsuff= ;; + esac + ;; + esac + ;; + linux*oldld* | linux*aout* | linux*coff*) + ;; + linux*) + ;; + netbsd*) + ;; + newsos6) + ;; + nto-qnx) + ;; + openbsd*) + ;; + os2*) + libname_spec='$name' + shrext=.dll + ;; + osf3* | osf4* | osf5*) + ;; + sco3.2v5*) + ;; + solaris*) + ;; + sunos4*) + ;; + sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*) + ;; + sysv4*MP*) + ;; + uts4*) + ;; +esac + +sed_quote_subst='s/\(["`$\\]\)/\\\1/g' +escaped_wl=`echo "X$wl" | sed -e 's/^X//' -e "$sed_quote_subst"` +shlibext=`echo "$shrext" | sed -e 's,^\.,,'` +escaped_hardcode_libdir_flag_spec=`echo "X$hardcode_libdir_flag_spec" | sed -e 's/^X//' -e "$sed_quote_subst"` + +sed -e 's/^\([a-zA-Z0-9_]*\)=/acl_cv_\1=/' <. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that +# program. This Exception is an additional permission under section 7 +# of the GNU General Public License, version 3 ("GPLv3"). + + +# Please send patches to . +# +# Configuration subroutine to validate and canonicalize a configuration type. +# Supply the specified configuration type as an argument. +# If it is invalid, we print an error message on stderr and exit with code 1. +# Otherwise, we print the canonical config type on stdout and succeed. + +# You can get the latest version of this script from: +# http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub;hb=HEAD + +# This file is supposed to be the same for all GNU packages +# and recognize all the CPU types, system types and aliases +# that are meaningful with *any* GNU software. +# Each package is responsible for reporting which valid configurations +# it does not support. The user should be able to distinguish +# a failure to support a valid configuration from a meaningless +# configuration. + +# The goal of this file is to map all the various variations of a given +# machine specification into a single specification in the form: +# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM +# or in some cases, the newer four-part form: +# CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM +# It is wrong to echo any other type of specification. + +me=`echo "$0" | sed -e 's,.*/,,'` + +usage="\ +Usage: $0 [OPTION] CPU-MFR-OPSYS + $0 [OPTION] ALIAS + +Canonicalize a configuration name. + +Operation modes: + -h, --help print this help, then exit + -t, --time-stamp print date of last modification, then exit + -v, --version print version number, then exit + +Report bugs and patches to ." + +version="\ +GNU config.sub ($timestamp) + +Copyright 1992-2014 Free Software Foundation, Inc. + +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + +help=" +Try \`$me --help' for more information." + +# Parse command line +while test $# -gt 0 ; do + case $1 in + --time-stamp | --time* | -t ) + echo "$timestamp" ; exit ;; + --version | -v ) + echo "$version" ; exit ;; + --help | --h* | -h ) + echo "$usage"; exit ;; + -- ) # Stop option processing + shift; break ;; + - ) # Use stdin as input. + break ;; + -* ) + echo "$me: invalid option $1$help" + exit 1 ;; + + *local*) + # First pass through any local machine types. + echo $1 + exit ;; + + * ) + break ;; + esac +done + +case $# in + 0) echo "$me: missing argument$help" >&2 + exit 1;; + 1) ;; + *) echo "$me: too many arguments$help" >&2 + exit 1;; +esac + +# Separate what the user gave into CPU-COMPANY and OS or KERNEL-OS (if any). +# Here we must recognize all the valid KERNEL-OS combinations. +maybe_os=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\2/'` +case $maybe_os in + nto-qnx* | linux-gnu* | linux-android* | linux-dietlibc | linux-newlib* | \ + linux-musl* | linux-uclibc* | uclinux-uclibc* | uclinux-gnu* | kfreebsd*-gnu* | \ + knetbsd*-gnu* | netbsd*-gnu* | \ + kopensolaris*-gnu* | \ + storm-chaos* | os2-emx* | rtmk-nova*) + os=-$maybe_os + basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'` + ;; + android-linux) + os=-linux-android + basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'`-unknown + ;; + *) + basic_machine=`echo $1 | sed 's/-[^-]*$//'` + if [ $basic_machine != $1 ] + then os=`echo $1 | sed 's/.*-/-/'` + else os=; fi + ;; +esac + +### Let's recognize common machines as not being operating systems so +### that things like config.sub decstation-3100 work. We also +### recognize some manufacturers as not being operating systems, so we +### can provide default operating systems below. +case $os in + -sun*os*) + # Prevent following clause from handling this invalid input. + ;; + -dec* | -mips* | -sequent* | -encore* | -pc532* | -sgi* | -sony* | \ + -att* | -7300* | -3300* | -delta* | -motorola* | -sun[234]* | \ + -unicom* | -ibm* | -next | -hp | -isi* | -apollo | -altos* | \ + -convergent* | -ncr* | -news | -32* | -3600* | -3100* | -hitachi* |\ + -c[123]* | -convex* | -sun | -crds | -omron* | -dg | -ultra | -tti* | \ + -harris | -dolphin | -highlevel | -gould | -cbm | -ns | -masscomp | \ + -apple | -axis | -knuth | -cray | -microblaze*) + os= + basic_machine=$1 + ;; + -bluegene*) + os=-cnk + ;; + -sim | -cisco | -oki | -wec | -winbond) + os= + basic_machine=$1 + ;; + -scout) + ;; + -wrs) + os=-vxworks + basic_machine=$1 + ;; + -chorusos*) + os=-chorusos + basic_machine=$1 + ;; + -chorusrdb) + os=-chorusrdb + basic_machine=$1 + ;; + -hiux*) + os=-hiuxwe2 + ;; + -sco6) + os=-sco5v6 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco5) + os=-sco3.2v5 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco4) + os=-sco3.2v4 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco3.2.[4-9]*) + os=`echo $os | sed -e 's/sco3.2./sco3.2v/'` + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco3.2v[4-9]*) + # Don't forget version if it is 3.2v4 or newer. + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco5v6*) + # Don't forget version if it is 3.2v4 or newer. + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco*) + os=-sco3.2v2 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -udk*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -isc) + os=-isc2.2 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -clix*) + basic_machine=clipper-intergraph + ;; + -isc*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -lynx*178) + os=-lynxos178 + ;; + -lynx*5) + os=-lynxos5 + ;; + -lynx*) + os=-lynxos + ;; + -ptx*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-sequent/'` + ;; + -windowsnt*) + os=`echo $os | sed -e 's/windowsnt/winnt/'` + ;; + -psos*) + os=-psos + ;; + -mint | -mint[0-9]*) + basic_machine=m68k-atari + os=-mint + ;; +esac + +# Decode aliases for certain CPU-COMPANY combinations. +case $basic_machine in + # Recognize the basic CPU types without company name. + # Some are omitted here because they have special meanings below. + 1750a | 580 \ + | a29k \ + | aarch64 | aarch64_be \ + | alpha | alphaev[4-8] | alphaev56 | alphaev6[78] | alphapca5[67] \ + | alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] | alpha64pca5[67] \ + | am33_2.0 \ + | arc | arceb \ + | arm | arm[bl]e | arme[lb] | armv[2-8] | armv[3-8][lb] | armv7[arm] \ + | avr | avr32 \ + | be32 | be64 \ + | bfin \ + | c4x | c8051 | clipper \ + | d10v | d30v | dlx | dsp16xx \ + | epiphany \ + | fido | fr30 | frv \ + | h8300 | h8500 | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \ + | hexagon \ + | i370 | i860 | i960 | ia64 \ + | ip2k | iq2000 \ + | k1om \ + | le32 | le64 \ + | lm32 \ + | m32c | m32r | m32rle | m68000 | m68k | m88k \ + | maxq | mb | microblaze | microblazeel | mcore | mep | metag \ + | mips | mipsbe | mipseb | mipsel | mipsle \ + | mips16 \ + | mips64 | mips64el \ + | mips64octeon | mips64octeonel \ + | mips64orion | mips64orionel \ + | mips64r5900 | mips64r5900el \ + | mips64vr | mips64vrel \ + | mips64vr4100 | mips64vr4100el \ + | mips64vr4300 | mips64vr4300el \ + | mips64vr5000 | mips64vr5000el \ + | mips64vr5900 | mips64vr5900el \ + | mipsisa32 | mipsisa32el \ + | mipsisa32r2 | mipsisa32r2el \ + | mipsisa32r6 | mipsisa32r6el \ + | mipsisa64 | mipsisa64el \ + | mipsisa64r2 | mipsisa64r2el \ + | mipsisa64r6 | mipsisa64r6el \ + | mipsisa64sb1 | mipsisa64sb1el \ + | mipsisa64sr71k | mipsisa64sr71kel \ + | mipsr5900 | mipsr5900el \ + | mipstx39 | mipstx39el \ + | mn10200 | mn10300 \ + | moxie \ + | mt \ + | msp430 \ + | nds32 | nds32le | nds32be \ + | nios | nios2 | nios2eb | nios2el \ + | ns16k | ns32k \ + | open8 | or1k | or1knd | or32 \ + | pdp10 | pdp11 | pj | pjl \ + | powerpc | powerpc64 | powerpc64le | powerpcle \ + | pyramid \ + | riscv32 | riscv64 \ + | rl78 | rx \ + | score \ + | sh | sh[1234] | sh[24]a | sh[24]aeb | sh[23]e | sh[34]eb | sheb | shbe | shle | sh[1234]le | sh3ele \ + | sh64 | sh64le \ + | sparc | sparc64 | sparc64b | sparc64v | sparc86x | sparclet | sparclite \ + | sparcv8 | sparcv9 | sparcv9b | sparcv9v \ + | spu \ + | tahoe | tic4x | tic54x | tic55x | tic6x | tic80 | tron \ + | ubicom32 \ + | v850 | v850e | v850e1 | v850e2 | v850es | v850e2v3 \ + | visium \ + | we32k \ + | x86 | xc16x | xstormy16 | xtensa \ + | z8k | z80) + basic_machine=$basic_machine-unknown + ;; + c54x) + basic_machine=tic54x-unknown + ;; + c55x) + basic_machine=tic55x-unknown + ;; + c6x) + basic_machine=tic6x-unknown + ;; + leon|leon[3-9]) + basic_machine=sparc-$basic_machine + ;; + m6811 | m68hc11 | m6812 | m68hc12 | m68hcs12x | nvptx | picochip) + basic_machine=$basic_machine-unknown + os=-none + ;; + m88110 | m680[12346]0 | m683?2 | m68360 | m5200 | v70 | w65 | z8k) + ;; + ms1) + basic_machine=mt-unknown + ;; + + strongarm | thumb | xscale) + basic_machine=arm-unknown + ;; + xgate) + basic_machine=$basic_machine-unknown + os=-none + ;; + xscaleeb) + basic_machine=armeb-unknown + ;; + + xscaleel) + basic_machine=armel-unknown + ;; + + # We use `pc' rather than `unknown' + # because (1) that's what they normally are, and + # (2) the word "unknown" tends to confuse beginning users. + i*86 | x86_64) + basic_machine=$basic_machine-pc + ;; + # Object if more than one company name word. + *-*-*) + echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2 + exit 1 + ;; + # Recognize the basic CPU types with company name. + 580-* \ + | a29k-* \ + | aarch64-* | aarch64_be-* \ + | alpha-* | alphaev[4-8]-* | alphaev56-* | alphaev6[78]-* \ + | alpha64-* | alpha64ev[4-8]-* | alpha64ev56-* | alpha64ev6[78]-* \ + | alphapca5[67]-* | alpha64pca5[67]-* | arc-* | arceb-* \ + | arm-* | armbe-* | armle-* | armeb-* | armv*-* \ + | avr-* | avr32-* \ + | be32-* | be64-* \ + | bfin-* | bs2000-* \ + | c[123]* | c30-* | [cjt]90-* | c4x-* \ + | c8051-* | clipper-* | craynv-* | cydra-* \ + | d10v-* | d30v-* | dlx-* \ + | elxsi-* \ + | f30[01]-* | f700-* | fido-* | fr30-* | frv-* | fx80-* \ + | h8300-* | h8500-* \ + | hppa-* | hppa1.[01]-* | hppa2.0-* | hppa2.0[nw]-* | hppa64-* \ + | hexagon-* \ + | i*86-* | i860-* | i960-* | ia64-* \ + | ip2k-* | iq2000-* \ + | k1om-* \ + | le32-* | le64-* \ + | lm32-* \ + | m32c-* | m32r-* | m32rle-* \ + | m68000-* | m680[012346]0-* | m68360-* | m683?2-* | m68k-* \ + | m88110-* | m88k-* | maxq-* | mcore-* | metag-* \ + | microblaze-* | microblazeel-* \ + | mips-* | mipsbe-* | mipseb-* | mipsel-* | mipsle-* \ + | mips16-* \ + | mips64-* | mips64el-* \ + | mips64octeon-* | mips64octeonel-* \ + | mips64orion-* | mips64orionel-* \ + | mips64r5900-* | mips64r5900el-* \ + | mips64vr-* | mips64vrel-* \ + | mips64vr4100-* | mips64vr4100el-* \ + | mips64vr4300-* | mips64vr4300el-* \ + | mips64vr5000-* | mips64vr5000el-* \ + | mips64vr5900-* | mips64vr5900el-* \ + | mipsisa32-* | mipsisa32el-* \ + | mipsisa32r2-* | mipsisa32r2el-* \ + | mipsisa32r6-* | mipsisa32r6el-* \ + | mipsisa64-* | mipsisa64el-* \ + | mipsisa64r2-* | mipsisa64r2el-* \ + | mipsisa64r6-* | mipsisa64r6el-* \ + | mipsisa64sb1-* | mipsisa64sb1el-* \ + | mipsisa64sr71k-* | mipsisa64sr71kel-* \ + | mipsr5900-* | mipsr5900el-* \ + | mipstx39-* | mipstx39el-* \ + | mmix-* \ + | mt-* \ + | msp430-* \ + | nds32-* | nds32le-* | nds32be-* \ + | nios-* | nios2-* | nios2eb-* | nios2el-* \ + | none-* | np1-* | ns16k-* | ns32k-* \ + | open8-* \ + | or1k*-* \ + | orion-* \ + | pdp10-* | pdp11-* | pj-* | pjl-* | pn-* | power-* \ + | powerpc-* | powerpc64-* | powerpc64le-* | powerpcle-* \ + | pyramid-* \ + | rl78-* | romp-* | rs6000-* | rx-* \ + | sh-* | sh[1234]-* | sh[24]a-* | sh[24]aeb-* | sh[23]e-* | sh[34]eb-* | sheb-* | shbe-* \ + | shle-* | sh[1234]le-* | sh3ele-* | sh64-* | sh64le-* \ + | sparc-* | sparc64-* | sparc64b-* | sparc64v-* | sparc86x-* | sparclet-* \ + | sparclite-* \ + | sparcv8-* | sparcv9-* | sparcv9b-* | sparcv9v-* | sv1-* | sx?-* \ + | tahoe-* \ + | tic30-* | tic4x-* | tic54x-* | tic55x-* | tic6x-* | tic80-* \ + | tile*-* \ + | tron-* \ + | ubicom32-* \ + | v850-* | v850e-* | v850e1-* | v850es-* | v850e2-* | v850e2v3-* \ + | vax-* \ + | visium-* \ + | we32k-* \ + | x86-* | x86_64-* | xc16x-* | xps100-* \ + | xstormy16-* | xtensa*-* \ + | ymp-* \ + | z8k-* | z80-*) + ;; + # Recognize the basic CPU types without company name, with glob match. + xtensa*) + basic_machine=$basic_machine-unknown + ;; + # Recognize the various machine names and aliases which stand + # for a CPU type and a company and sometimes even an OS. + 386bsd) + basic_machine=i386-unknown + os=-bsd + ;; + 3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc) + basic_machine=m68000-att + ;; + 3b*) + basic_machine=we32k-att + ;; + a29khif) + basic_machine=a29k-amd + os=-udi + ;; + abacus) + basic_machine=abacus-unknown + ;; + adobe68k) + basic_machine=m68010-adobe + os=-scout + ;; + alliant | fx80) + basic_machine=fx80-alliant + ;; + altos | altos3068) + basic_machine=m68k-altos + ;; + am29k) + basic_machine=a29k-none + os=-bsd + ;; + amd64) + basic_machine=x86_64-pc + ;; + amd64-*) + basic_machine=x86_64-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + amdahl) + basic_machine=580-amdahl + os=-sysv + ;; + amiga | amiga-*) + basic_machine=m68k-unknown + ;; + amigaos | amigados) + basic_machine=m68k-unknown + os=-amigaos + ;; + amigaunix | amix) + basic_machine=m68k-unknown + os=-sysv4 + ;; + apollo68) + basic_machine=m68k-apollo + os=-sysv + ;; + apollo68bsd) + basic_machine=m68k-apollo + os=-bsd + ;; + aros) + basic_machine=i386-pc + os=-aros + ;; + aux) + basic_machine=m68k-apple + os=-aux + ;; + balance) + basic_machine=ns32k-sequent + os=-dynix + ;; + blackfin) + basic_machine=bfin-unknown + os=-linux + ;; + blackfin-*) + basic_machine=bfin-`echo $basic_machine | sed 's/^[^-]*-//'` + os=-linux + ;; + bluegene*) + basic_machine=powerpc-ibm + os=-cnk + ;; + c54x-*) + basic_machine=tic54x-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + c55x-*) + basic_machine=tic55x-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + c6x-*) + basic_machine=tic6x-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + c90) + basic_machine=c90-cray + os=-unicos + ;; + cegcc) + basic_machine=arm-unknown + os=-cegcc + ;; + convex-c1) + basic_machine=c1-convex + os=-bsd + ;; + convex-c2) + basic_machine=c2-convex + os=-bsd + ;; + convex-c32) + basic_machine=c32-convex + os=-bsd + ;; + convex-c34) + basic_machine=c34-convex + os=-bsd + ;; + convex-c38) + basic_machine=c38-convex + os=-bsd + ;; + cray | j90) + basic_machine=j90-cray + os=-unicos + ;; + craynv) + basic_machine=craynv-cray + os=-unicosmp + ;; + cr16 | cr16-*) + basic_machine=cr16-unknown + os=-elf + ;; + crds | unos) + basic_machine=m68k-crds + ;; + crisv32 | crisv32-* | etraxfs*) + basic_machine=crisv32-axis + ;; + cris | cris-* | etrax*) + basic_machine=cris-axis + ;; + crx) + basic_machine=crx-unknown + os=-elf + ;; + da30 | da30-*) + basic_machine=m68k-da30 + ;; + decstation | decstation-3100 | pmax | pmax-* | pmin | dec3100 | decstatn) + basic_machine=mips-dec + ;; + decsystem10* | dec10*) + basic_machine=pdp10-dec + os=-tops10 + ;; + decsystem20* | dec20*) + basic_machine=pdp10-dec + os=-tops20 + ;; + delta | 3300 | motorola-3300 | motorola-delta \ + | 3300-motorola | delta-motorola) + basic_machine=m68k-motorola + ;; + delta88) + basic_machine=m88k-motorola + os=-sysv3 + ;; + dicos) + basic_machine=i686-pc + os=-dicos + ;; + djgpp) + basic_machine=i586-pc + os=-msdosdjgpp + ;; + dpx20 | dpx20-*) + basic_machine=rs6000-bull + os=-bosx + ;; + dpx2* | dpx2*-bull) + basic_machine=m68k-bull + os=-sysv3 + ;; + ebmon29k) + basic_machine=a29k-amd + os=-ebmon + ;; + elxsi) + basic_machine=elxsi-elxsi + os=-bsd + ;; + encore | umax | mmax) + basic_machine=ns32k-encore + ;; + es1800 | OSE68k | ose68k | ose | OSE) + basic_machine=m68k-ericsson + os=-ose + ;; + fx2800) + basic_machine=i860-alliant + ;; + genix) + basic_machine=ns32k-ns + ;; + gmicro) + basic_machine=tron-gmicro + os=-sysv + ;; + go32) + basic_machine=i386-pc + os=-go32 + ;; + h3050r* | hiux*) + basic_machine=hppa1.1-hitachi + os=-hiuxwe2 + ;; + h8300hms) + basic_machine=h8300-hitachi + os=-hms + ;; + h8300xray) + basic_machine=h8300-hitachi + os=-xray + ;; + h8500hms) + basic_machine=h8500-hitachi + os=-hms + ;; + harris) + basic_machine=m88k-harris + os=-sysv3 + ;; + hp300-*) + basic_machine=m68k-hp + ;; + hp300bsd) + basic_machine=m68k-hp + os=-bsd + ;; + hp300hpux) + basic_machine=m68k-hp + os=-hpux + ;; + hp3k9[0-9][0-9] | hp9[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hp9k2[0-9][0-9] | hp9k31[0-9]) + basic_machine=m68000-hp + ;; + hp9k3[2-9][0-9]) + basic_machine=m68k-hp + ;; + hp9k6[0-9][0-9] | hp6[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hp9k7[0-79][0-9] | hp7[0-79][0-9]) + basic_machine=hppa1.1-hp + ;; + hp9k78[0-9] | hp78[0-9]) + # FIXME: really hppa2.0-hp + basic_machine=hppa1.1-hp + ;; + hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893) + # FIXME: really hppa2.0-hp + basic_machine=hppa1.1-hp + ;; + hp9k8[0-9][13679] | hp8[0-9][13679]) + basic_machine=hppa1.1-hp + ;; + hp9k8[0-9][0-9] | hp8[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hppa-next) + os=-nextstep3 + ;; + hppaosf) + basic_machine=hppa1.1-hp + os=-osf + ;; + hppro) + basic_machine=hppa1.1-hp + os=-proelf + ;; + i370-ibm* | ibm*) + basic_machine=i370-ibm + ;; + i*86v32) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv32 + ;; + i*86v4*) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv4 + ;; + i*86v) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv + ;; + i*86sol2) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-solaris2 + ;; + i386mach) + basic_machine=i386-mach + os=-mach + ;; + i386-vsta | vsta) + basic_machine=i386-unknown + os=-vsta + ;; + iris | iris4d) + basic_machine=mips-sgi + case $os in + -irix*) + ;; + *) + os=-irix4 + ;; + esac + ;; + isi68 | isi) + basic_machine=m68k-isi + os=-sysv + ;; + leon-*|leon[3-9]-*) + basic_machine=sparc-`echo $basic_machine | sed 's/-.*//'` + ;; + m68knommu) + basic_machine=m68k-unknown + os=-linux + ;; + m68knommu-*) + basic_machine=m68k-`echo $basic_machine | sed 's/^[^-]*-//'` + os=-linux + ;; + m88k-omron*) + basic_machine=m88k-omron + ;; + magnum | m3230) + basic_machine=mips-mips + os=-sysv + ;; + merlin) + basic_machine=ns32k-utek + os=-sysv + ;; + microblaze*) + basic_machine=microblaze-xilinx + ;; + mingw64) + basic_machine=x86_64-pc + os=-mingw64 + ;; + mingw32) + basic_machine=i686-pc + os=-mingw32 + ;; + mingw32ce) + basic_machine=arm-unknown + os=-mingw32ce + ;; + miniframe) + basic_machine=m68000-convergent + ;; + *mint | -mint[0-9]* | *MiNT | *MiNT[0-9]*) + basic_machine=m68k-atari + os=-mint + ;; + mips3*-*) + basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'` + ;; + mips3*) + basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'`-unknown + ;; + monitor) + basic_machine=m68k-rom68k + os=-coff + ;; + morphos) + basic_machine=powerpc-unknown + os=-morphos + ;; + moxiebox) + basic_machine=moxie-unknown + os=-moxiebox + ;; + msdos) + basic_machine=i386-pc + os=-msdos + ;; + ms1-*) + basic_machine=`echo $basic_machine | sed -e 's/ms1-/mt-/'` + ;; + msys) + basic_machine=i686-pc + os=-msys + ;; + mvs) + basic_machine=i370-ibm + os=-mvs + ;; + nacl) + basic_machine=le32-unknown + os=-nacl + ;; + ncr3000) + basic_machine=i486-ncr + os=-sysv4 + ;; + netbsd386) + basic_machine=i386-unknown + os=-netbsd + ;; + netwinder) + basic_machine=armv4l-rebel + os=-linux + ;; + news | news700 | news800 | news900) + basic_machine=m68k-sony + os=-newsos + ;; + news1000) + basic_machine=m68030-sony + os=-newsos + ;; + news-3600 | risc-news) + basic_machine=mips-sony + os=-newsos + ;; + necv70) + basic_machine=v70-nec + os=-sysv + ;; + next | m*-next ) + basic_machine=m68k-next + case $os in + -nextstep* ) + ;; + -ns2*) + os=-nextstep2 + ;; + *) + os=-nextstep3 + ;; + esac + ;; + nh3000) + basic_machine=m68k-harris + os=-cxux + ;; + nh[45]000) + basic_machine=m88k-harris + os=-cxux + ;; + nindy960) + basic_machine=i960-intel + os=-nindy + ;; + mon960) + basic_machine=i960-intel + os=-mon960 + ;; + nonstopux) + basic_machine=mips-compaq + os=-nonstopux + ;; + np1) + basic_machine=np1-gould + ;; + neo-tandem) + basic_machine=neo-tandem + ;; + nse-tandem) + basic_machine=nse-tandem + ;; + nsr-tandem) + basic_machine=nsr-tandem + ;; + op50n-* | op60c-*) + basic_machine=hppa1.1-oki + os=-proelf + ;; + openrisc | openrisc-*) + basic_machine=or32-unknown + ;; + os400) + basic_machine=powerpc-ibm + os=-os400 + ;; + OSE68000 | ose68000) + basic_machine=m68000-ericsson + os=-ose + ;; + os68k) + basic_machine=m68k-none + os=-os68k + ;; + pa-hitachi) + basic_machine=hppa1.1-hitachi + os=-hiuxwe2 + ;; + paragon) + basic_machine=i860-intel + os=-osf + ;; + parisc) + basic_machine=hppa-unknown + os=-linux + ;; + parisc-*) + basic_machine=hppa-`echo $basic_machine | sed 's/^[^-]*-//'` + os=-linux + ;; + pbd) + basic_machine=sparc-tti + ;; + pbb) + basic_machine=m68k-tti + ;; + pc532 | pc532-*) + basic_machine=ns32k-pc532 + ;; + pc98) + basic_machine=i386-pc + ;; + pc98-*) + basic_machine=i386-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentium | p5 | k5 | k6 | nexgen | viac3) + basic_machine=i586-pc + ;; + pentiumpro | p6 | 6x86 | athlon | athlon_*) + basic_machine=i686-pc + ;; + pentiumii | pentium2 | pentiumiii | pentium3) + basic_machine=i686-pc + ;; + pentium4) + basic_machine=i786-pc + ;; + pentium-* | p5-* | k5-* | k6-* | nexgen-* | viac3-*) + basic_machine=i586-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentiumpro-* | p6-* | 6x86-* | athlon-*) + basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentiumii-* | pentium2-* | pentiumiii-* | pentium3-*) + basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentium4-*) + basic_machine=i786-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pn) + basic_machine=pn-gould + ;; + power) basic_machine=power-ibm + ;; + ppc | ppcbe) basic_machine=powerpc-unknown + ;; + ppc-* | ppcbe-*) + basic_machine=powerpc-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ppcle | powerpclittle | ppc-le | powerpc-little) + basic_machine=powerpcle-unknown + ;; + ppcle-* | powerpclittle-*) + basic_machine=powerpcle-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ppc64) basic_machine=powerpc64-unknown + ;; + ppc64-*) basic_machine=powerpc64-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ppc64le | powerpc64little | ppc64-le | powerpc64-little) + basic_machine=powerpc64le-unknown + ;; + ppc64le-* | powerpc64little-*) + basic_machine=powerpc64le-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ps2) + basic_machine=i386-ibm + ;; + pw32) + basic_machine=i586-unknown + os=-pw32 + ;; + rdos | rdos64) + basic_machine=x86_64-pc + os=-rdos + ;; + rdos32) + basic_machine=i386-pc + os=-rdos + ;; + rom68k) + basic_machine=m68k-rom68k + os=-coff + ;; + rm[46]00) + basic_machine=mips-siemens + ;; + rtpc | rtpc-*) + basic_machine=romp-ibm + ;; + s390 | s390-*) + basic_machine=s390-ibm + ;; + s390x | s390x-*) + basic_machine=s390x-ibm + ;; + sa29200) + basic_machine=a29k-amd + os=-udi + ;; + sb1) + basic_machine=mipsisa64sb1-unknown + ;; + sb1el) + basic_machine=mipsisa64sb1el-unknown + ;; + sde) + basic_machine=mipsisa32-sde + os=-elf + ;; + sei) + basic_machine=mips-sei + os=-seiux + ;; + sequent) + basic_machine=i386-sequent + ;; + sh) + basic_machine=sh-hitachi + os=-hms + ;; + sh5el) + basic_machine=sh5le-unknown + ;; + sh64) + basic_machine=sh64-unknown + ;; + sparclite-wrs | simso-wrs) + basic_machine=sparclite-wrs + os=-vxworks + ;; + sps7) + basic_machine=m68k-bull + os=-sysv2 + ;; + spur) + basic_machine=spur-unknown + ;; + st2000) + basic_machine=m68k-tandem + ;; + stratus) + basic_machine=i860-stratus + os=-sysv4 + ;; + strongarm-* | thumb-*) + basic_machine=arm-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + sun2) + basic_machine=m68000-sun + ;; + sun2os3) + basic_machine=m68000-sun + os=-sunos3 + ;; + sun2os4) + basic_machine=m68000-sun + os=-sunos4 + ;; + sun3os3) + basic_machine=m68k-sun + os=-sunos3 + ;; + sun3os4) + basic_machine=m68k-sun + os=-sunos4 + ;; + sun4os3) + basic_machine=sparc-sun + os=-sunos3 + ;; + sun4os4) + basic_machine=sparc-sun + os=-sunos4 + ;; + sun4sol2) + basic_machine=sparc-sun + os=-solaris2 + ;; + sun3 | sun3-*) + basic_machine=m68k-sun + ;; + sun4) + basic_machine=sparc-sun + ;; + sun386 | sun386i | roadrunner) + basic_machine=i386-sun + ;; + sv1) + basic_machine=sv1-cray + os=-unicos + ;; + symmetry) + basic_machine=i386-sequent + os=-dynix + ;; + t3e) + basic_machine=alphaev5-cray + os=-unicos + ;; + t90) + basic_machine=t90-cray + os=-unicos + ;; + tile*) + basic_machine=$basic_machine-unknown + os=-linux-gnu + ;; + tx39) + basic_machine=mipstx39-unknown + ;; + tx39el) + basic_machine=mipstx39el-unknown + ;; + toad1) + basic_machine=pdp10-xkl + os=-tops20 + ;; + tower | tower-32) + basic_machine=m68k-ncr + ;; + tpf) + basic_machine=s390x-ibm + os=-tpf + ;; + udi29k) + basic_machine=a29k-amd + os=-udi + ;; + ultra3) + basic_machine=a29k-nyu + os=-sym1 + ;; + v810 | necv810) + basic_machine=v810-nec + os=-none + ;; + vaxv) + basic_machine=vax-dec + os=-sysv + ;; + vms) + basic_machine=vax-dec + os=-vms + ;; + vpp*|vx|vx-*) + basic_machine=f301-fujitsu + ;; + vxworks960) + basic_machine=i960-wrs + os=-vxworks + ;; + vxworks68) + basic_machine=m68k-wrs + os=-vxworks + ;; + vxworks29k) + basic_machine=a29k-wrs + os=-vxworks + ;; + w65*) + basic_machine=w65-wdc + os=-none + ;; + w89k-*) + basic_machine=hppa1.1-winbond + os=-proelf + ;; + xbox) + basic_machine=i686-pc + os=-mingw32 + ;; + xps | xps100) + basic_machine=xps100-honeywell + ;; + xscale-* | xscalee[bl]-*) + basic_machine=`echo $basic_machine | sed 's/^xscale/arm/'` + ;; + ymp) + basic_machine=ymp-cray + os=-unicos + ;; + z8k-*-coff) + basic_machine=z8k-unknown + os=-sim + ;; + z80-*-coff) + basic_machine=z80-unknown + os=-sim + ;; + none) + basic_machine=none-none + os=-none + ;; + +# Here we handle the default manufacturer of certain CPU types. It is in +# some cases the only manufacturer, in others, it is the most popular. + w89k) + basic_machine=hppa1.1-winbond + ;; + op50n) + basic_machine=hppa1.1-oki + ;; + op60c) + basic_machine=hppa1.1-oki + ;; + romp) + basic_machine=romp-ibm + ;; + mmix) + basic_machine=mmix-knuth + ;; + rs6000) + basic_machine=rs6000-ibm + ;; + vax) + basic_machine=vax-dec + ;; + pdp10) + # there are many clones, so DEC is not a safe bet + basic_machine=pdp10-unknown + ;; + pdp11) + basic_machine=pdp11-dec + ;; + we32k) + basic_machine=we32k-att + ;; + sh[1234] | sh[24]a | sh[24]aeb | sh[34]eb | sh[1234]le | sh[23]ele) + basic_machine=sh-unknown + ;; + sparc | sparcv8 | sparcv9 | sparcv9b | sparcv9v) + basic_machine=sparc-sun + ;; + cydra) + basic_machine=cydra-cydrome + ;; + orion) + basic_machine=orion-highlevel + ;; + orion105) + basic_machine=clipper-highlevel + ;; + mac | mpw | mac-mpw) + basic_machine=m68k-apple + ;; + pmac | pmac-mpw) + basic_machine=powerpc-apple + ;; + *-unknown) + # Make sure to match an already-canonicalized machine name. + ;; + *) + echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2 + exit 1 + ;; +esac + +# Here we canonicalize certain aliases for manufacturers. +case $basic_machine in + *-digital*) + basic_machine=`echo $basic_machine | sed 's/digital.*/dec/'` + ;; + *-commodore*) + basic_machine=`echo $basic_machine | sed 's/commodore.*/cbm/'` + ;; + *) + ;; +esac + +# Decode manufacturer-specific aliases for certain operating systems. + +if [ x"$os" != x"" ] +then +case $os in + # First match some system type aliases + # that might get confused with valid system types. + # -solaris* is a basic system type, with this one exception. + -auroraux) + os=-auroraux + ;; + -solaris1 | -solaris1.*) + os=`echo $os | sed -e 's|solaris1|sunos4|'` + ;; + -solaris) + os=-solaris2 + ;; + -svr4*) + os=-sysv4 + ;; + -unixware*) + os=-sysv4.2uw + ;; + -gnu/linux*) + os=`echo $os | sed -e 's|gnu/linux|linux-gnu|'` + ;; + # First accept the basic system types. + # The portable systems comes first. + # Each alternative MUST END IN A *, to match a version number. + # -sysv* is not here because it comes later, after sysvr4. + -gnu* | -bsd* | -mach* | -minix* | -genix* | -ultrix* | -irix* \ + | -*vms* | -sco* | -esix* | -isc* | -aix* | -cnk* | -sunos | -sunos[34]*\ + | -hpux* | -unos* | -osf* | -luna* | -dgux* | -auroraux* | -solaris* \ + | -sym* | -kopensolaris* | -plan9* \ + | -amigaos* | -amigados* | -msdos* | -newsos* | -unicos* | -aof* \ + | -aos* | -aros* \ + | -nindy* | -vxsim* | -vxworks* | -ebmon* | -hms* | -mvs* \ + | -clix* | -riscos* | -uniplus* | -iris* | -rtu* | -xenix* \ + | -hiux* | -386bsd* | -knetbsd* | -mirbsd* | -netbsd* \ + | -bitrig* | -openbsd* | -solidbsd* \ + | -ekkobsd* | -kfreebsd* | -freebsd* | -riscix* | -lynxos* \ + | -bosx* | -nextstep* | -cxux* | -aout* | -elf* | -oabi* \ + | -ptx* | -coff* | -ecoff* | -winnt* | -domain* | -vsta* \ + | -udi* | -eabi* | -lites* | -ieee* | -go32* | -aux* \ + | -chorusos* | -chorusrdb* | -cegcc* \ + | -cygwin* | -msys* | -pe* | -psos* | -moss* | -proelf* | -rtems* \ + | -mingw32* | -mingw64* | -linux-gnu* | -linux-android* \ + | -linux-newlib* | -linux-musl* | -linux-uclibc* \ + | -uxpv* | -beos* | -mpeix* | -udk* | -moxiebox* \ + | -interix* | -uwin* | -mks* | -rhapsody* | -darwin* | -opened* \ + | -openstep* | -oskit* | -conix* | -pw32* | -nonstopux* \ + | -storm-chaos* | -tops10* | -tenex* | -tops20* | -its* \ + | -os2* | -vos* | -palmos* | -uclinux* | -nucleus* \ + | -morphos* | -superux* | -rtmk* | -rtmk-nova* | -windiss* \ + | -powermax* | -dnix* | -nx6 | -nx7 | -sei* | -dragonfly* \ + | -skyos* | -haiku* | -rdos* | -toppers* | -drops* | -es* | -tirtos*) + # Remember, each alternative MUST END IN *, to match a version number. + ;; + -qnx*) + case $basic_machine in + x86-* | i*86-*) + ;; + *) + os=-nto$os + ;; + esac + ;; + -nto-qnx*) + ;; + -nto*) + os=`echo $os | sed -e 's|nto|nto-qnx|'` + ;; + -sim | -es1800* | -hms* | -xray | -os68k* | -none* | -v88r* \ + | -windows* | -osx | -abug | -netware* | -os9* | -beos* | -haiku* \ + | -macos* | -mpw* | -magic* | -mmixware* | -mon960* | -lnews*) + ;; + -mac*) + os=`echo $os | sed -e 's|mac|macos|'` + ;; + -linux-dietlibc) + os=-linux-dietlibc + ;; + -linux*) + os=`echo $os | sed -e 's|linux|linux-gnu|'` + ;; + -sunos5*) + os=`echo $os | sed -e 's|sunos5|solaris2|'` + ;; + -sunos6*) + os=`echo $os | sed -e 's|sunos6|solaris3|'` + ;; + -opened*) + os=-openedition + ;; + -os400*) + os=-os400 + ;; + -wince*) + os=-wince + ;; + -osfrose*) + os=-osfrose + ;; + -osf*) + os=-osf + ;; + -utek*) + os=-bsd + ;; + -dynix*) + os=-bsd + ;; + -acis*) + os=-aos + ;; + -atheos*) + os=-atheos + ;; + -syllable*) + os=-syllable + ;; + -386bsd) + os=-bsd + ;; + -ctix* | -uts*) + os=-sysv + ;; + -nova*) + os=-rtmk-nova + ;; + -ns2 ) + os=-nextstep2 + ;; + -nsk*) + os=-nsk + ;; + # Preserve the version number of sinix5. + -sinix5.*) + os=`echo $os | sed -e 's|sinix|sysv|'` + ;; + -sinix*) + os=-sysv4 + ;; + -tpf*) + os=-tpf + ;; + -triton*) + os=-sysv3 + ;; + -oss*) + os=-sysv3 + ;; + -svr4) + os=-sysv4 + ;; + -svr3) + os=-sysv3 + ;; + -sysvr4) + os=-sysv4 + ;; + # This must come after -sysvr4. + -sysv*) + ;; + -ose*) + os=-ose + ;; + -es1800*) + os=-ose + ;; + -xenix) + os=-xenix + ;; + -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) + os=-mint + ;; + -aros*) + os=-aros + ;; + -zvmoe) + os=-zvmoe + ;; + -dicos*) + os=-dicos + ;; + -nacl*) + ;; + -none) + ;; + *) + # Get rid of the `-' at the beginning of $os. + os=`echo $os | sed 's/[^-]*-//'` + echo Invalid configuration \`$1\': system \`$os\' not recognized 1>&2 + exit 1 + ;; +esac +else + +# Here we handle the default operating systems that come with various machines. +# The value should be what the vendor currently ships out the door with their +# machine or put another way, the most popular os provided with the machine. + +# Note that if you're going to try to match "-MANUFACTURER" here (say, +# "-sun"), then you have to tell the case statement up towards the top +# that MANUFACTURER isn't an operating system. Otherwise, code above +# will signal an error saying that MANUFACTURER isn't an operating +# system, and we'll never get to this point. + +case $basic_machine in + score-*) + os=-elf + ;; + spu-*) + os=-elf + ;; + *-acorn) + os=-riscix1.2 + ;; + arm*-rebel) + os=-linux + ;; + arm*-semi) + os=-aout + ;; + c4x-* | tic4x-*) + os=-coff + ;; + c8051-*) + os=-elf + ;; + hexagon-*) + os=-elf + ;; + tic54x-*) + os=-coff + ;; + tic55x-*) + os=-coff + ;; + tic6x-*) + os=-coff + ;; + # This must come before the *-dec entry. + pdp10-*) + os=-tops20 + ;; + pdp11-*) + os=-none + ;; + *-dec | vax-*) + os=-ultrix4.2 + ;; + m68*-apollo) + os=-domain + ;; + i386-sun) + os=-sunos4.0.2 + ;; + m68000-sun) + os=-sunos3 + ;; + m68*-cisco) + os=-aout + ;; + mep-*) + os=-elf + ;; + mips*-cisco) + os=-elf + ;; + mips*-*) + os=-elf + ;; + or32-*) + os=-coff + ;; + *-tti) # must be before sparc entry or we get the wrong os. + os=-sysv3 + ;; + sparc-* | *-sun) + os=-sunos4.1.1 + ;; + *-be) + os=-beos + ;; + *-haiku) + os=-haiku + ;; + *-ibm) + os=-aix + ;; + *-knuth) + os=-mmixware + ;; + *-wec) + os=-proelf + ;; + *-winbond) + os=-proelf + ;; + *-oki) + os=-proelf + ;; + *-hp) + os=-hpux + ;; + *-hitachi) + os=-hiux + ;; + i860-* | *-att | *-ncr | *-altos | *-motorola | *-convergent) + os=-sysv + ;; + *-cbm) + os=-amigaos + ;; + *-dg) + os=-dgux + ;; + *-dolphin) + os=-sysv3 + ;; + m68k-ccur) + os=-rtu + ;; + m88k-omron*) + os=-luna + ;; + *-next ) + os=-nextstep + ;; + *-sequent) + os=-ptx + ;; + *-crds) + os=-unos + ;; + *-ns) + os=-genix + ;; + i370-*) + os=-mvs + ;; + *-next) + os=-nextstep3 + ;; + *-gould) + os=-sysv + ;; + *-highlevel) + os=-bsd + ;; + *-encore) + os=-bsd + ;; + *-sgi) + os=-irix + ;; + *-siemens) + os=-sysv4 + ;; + *-masscomp) + os=-rtu + ;; + f30[01]-fujitsu | f700-fujitsu) + os=-uxpv + ;; + *-rom68k) + os=-coff + ;; + *-*bug) + os=-coff + ;; + *-apple) + os=-macos + ;; + *-atari*) + os=-mint + ;; + *) + os=-none + ;; +esac +fi + +# Here we handle the case where we know the os, and the CPU type, but not the +# manufacturer. We pick the logical manufacturer. +vendor=unknown +case $basic_machine in + *-unknown) + case $os in + -riscix*) + vendor=acorn + ;; + -sunos*) + vendor=sun + ;; + -cnk*|-aix*) + vendor=ibm + ;; + -beos*) + vendor=be + ;; + -hpux*) + vendor=hp + ;; + -mpeix*) + vendor=hp + ;; + -hiux*) + vendor=hitachi + ;; + -unos*) + vendor=crds + ;; + -dgux*) + vendor=dg + ;; + -luna*) + vendor=omron + ;; + -genix*) + vendor=ns + ;; + -mvs* | -opened*) + vendor=ibm + ;; + -os400*) + vendor=ibm + ;; + -ptx*) + vendor=sequent + ;; + -tpf*) + vendor=ibm + ;; + -vxsim* | -vxworks* | -windiss*) + vendor=wrs + ;; + -aux*) + vendor=apple + ;; + -hms*) + vendor=hitachi + ;; + -mpw* | -macos*) + vendor=apple + ;; + -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) + vendor=atari + ;; + -vos*) + vendor=stratus + ;; + esac + basic_machine=`echo $basic_machine | sed "s/unknown/$vendor/"` + ;; +esac + +echo $basic_machine$os +exit + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "timestamp='" +# time-stamp-format: "%:y-%02m-%02d" +# time-stamp-end: "'" +# End: diff --git a/build-aux/depcomp b/build-aux/depcomp new file mode 100755 index 0000000..fc98710 --- /dev/null +++ b/build-aux/depcomp @@ -0,0 +1,791 @@ +#! /bin/sh +# depcomp - compile a program generating dependencies as side-effects + +scriptversion=2013-05-30.07; # UTC + +# Copyright (C) 1999-2014 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# Originally written by Alexandre Oliva . + +case $1 in + '') + echo "$0: No command. Try '$0 --help' for more information." 1>&2 + exit 1; + ;; + -h | --h*) + cat <<\EOF +Usage: depcomp [--help] [--version] PROGRAM [ARGS] + +Run PROGRAMS ARGS to compile a file, generating dependencies +as side-effects. + +Environment variables: + depmode Dependency tracking mode. + source Source file read by 'PROGRAMS ARGS'. + object Object file output by 'PROGRAMS ARGS'. + DEPDIR directory where to store dependencies. + depfile Dependency file to output. + tmpdepfile Temporary file to use when outputting dependencies. + libtool Whether libtool is used (yes/no). + +Report bugs to . +EOF + exit $? + ;; + -v | --v*) + echo "depcomp $scriptversion" + exit $? + ;; +esac + +# Get the directory component of the given path, and save it in the +# global variables '$dir'. Note that this directory component will +# be either empty or ending with a '/' character. This is deliberate. +set_dir_from () +{ + case $1 in + */*) dir=`echo "$1" | sed -e 's|/[^/]*$|/|'`;; + *) dir=;; + esac +} + +# Get the suffix-stripped basename of the given path, and save it the +# global variable '$base'. +set_base_from () +{ + base=`echo "$1" | sed -e 's|^.*/||' -e 's/\.[^.]*$//'` +} + +# If no dependency file was actually created by the compiler invocation, +# we still have to create a dummy depfile, to avoid errors with the +# Makefile "include basename.Plo" scheme. +make_dummy_depfile () +{ + echo "#dummy" > "$depfile" +} + +# Factor out some common post-processing of the generated depfile. +# Requires the auxiliary global variable '$tmpdepfile' to be set. +aix_post_process_depfile () +{ + # If the compiler actually managed to produce a dependency file, + # post-process it. + if test -f "$tmpdepfile"; then + # Each line is of the form 'foo.o: dependency.h'. + # Do two passes, one to just change these to + # $object: dependency.h + # and one to simply output + # dependency.h: + # which is needed to avoid the deleted-header problem. + { sed -e "s,^.*\.[$lower]*:,$object:," < "$tmpdepfile" + sed -e "s,^.*\.[$lower]*:[$tab ]*,," -e 's,$,:,' < "$tmpdepfile" + } > "$depfile" + rm -f "$tmpdepfile" + else + make_dummy_depfile + fi +} + +# A tabulation character. +tab=' ' +# A newline character. +nl=' +' +# Character ranges might be problematic outside the C locale. +# These definitions help. +upper=ABCDEFGHIJKLMNOPQRSTUVWXYZ +lower=abcdefghijklmnopqrstuvwxyz +digits=0123456789 +alpha=${upper}${lower} + +if test -z "$depmode" || test -z "$source" || test -z "$object"; then + echo "depcomp: Variables source, object and depmode must be set" 1>&2 + exit 1 +fi + +# Dependencies for sub/bar.o or sub/bar.obj go into sub/.deps/bar.Po. +depfile=${depfile-`echo "$object" | + sed 's|[^\\/]*$|'${DEPDIR-.deps}'/&|;s|\.\([^.]*\)$|.P\1|;s|Pobj$|Po|'`} +tmpdepfile=${tmpdepfile-`echo "$depfile" | sed 's/\.\([^.]*\)$/.T\1/'`} + +rm -f "$tmpdepfile" + +# Avoid interferences from the environment. +gccflag= dashmflag= + +# Some modes work just like other modes, but use different flags. We +# parameterize here, but still list the modes in the big case below, +# to make depend.m4 easier to write. Note that we *cannot* use a case +# here, because this file can only contain one case statement. +if test "$depmode" = hp; then + # HP compiler uses -M and no extra arg. + gccflag=-M + depmode=gcc +fi + +if test "$depmode" = dashXmstdout; then + # This is just like dashmstdout with a different argument. + dashmflag=-xM + depmode=dashmstdout +fi + +cygpath_u="cygpath -u -f -" +if test "$depmode" = msvcmsys; then + # This is just like msvisualcpp but w/o cygpath translation. + # Just convert the backslash-escaped backslashes to single forward + # slashes to satisfy depend.m4 + cygpath_u='sed s,\\\\,/,g' + depmode=msvisualcpp +fi + +if test "$depmode" = msvc7msys; then + # This is just like msvc7 but w/o cygpath translation. + # Just convert the backslash-escaped backslashes to single forward + # slashes to satisfy depend.m4 + cygpath_u='sed s,\\\\,/,g' + depmode=msvc7 +fi + +if test "$depmode" = xlc; then + # IBM C/C++ Compilers xlc/xlC can output gcc-like dependency information. + gccflag=-qmakedep=gcc,-MF + depmode=gcc +fi + +case "$depmode" in +gcc3) +## gcc 3 implements dependency tracking that does exactly what +## we want. Yay! Note: for some reason libtool 1.4 doesn't like +## it if -MD -MP comes after the -MF stuff. Hmm. +## Unfortunately, FreeBSD c89 acceptance of flags depends upon +## the command line argument order; so add the flags where they +## appear in depend2.am. Note that the slowdown incurred here +## affects only configure: in makefiles, %FASTDEP% shortcuts this. + for arg + do + case $arg in + -c) set fnord "$@" -MT "$object" -MD -MP -MF "$tmpdepfile" "$arg" ;; + *) set fnord "$@" "$arg" ;; + esac + shift # fnord + shift # $arg + done + "$@" + stat=$? + if test $stat -ne 0; then + rm -f "$tmpdepfile" + exit $stat + fi + mv "$tmpdepfile" "$depfile" + ;; + +gcc) +## Note that this doesn't just cater to obsosete pre-3.x GCC compilers. +## but also to in-use compilers like IMB xlc/xlC and the HP C compiler. +## (see the conditional assignment to $gccflag above). +## There are various ways to get dependency output from gcc. Here's +## why we pick this rather obscure method: +## - Don't want to use -MD because we'd like the dependencies to end +## up in a subdir. Having to rename by hand is ugly. +## (We might end up doing this anyway to support other compilers.) +## - The DEPENDENCIES_OUTPUT environment variable makes gcc act like +## -MM, not -M (despite what the docs say). Also, it might not be +## supported by the other compilers which use the 'gcc' depmode. +## - Using -M directly means running the compiler twice (even worse +## than renaming). + if test -z "$gccflag"; then + gccflag=-MD, + fi + "$@" -Wp,"$gccflag$tmpdepfile" + stat=$? + if test $stat -ne 0; then + rm -f "$tmpdepfile" + exit $stat + fi + rm -f "$depfile" + echo "$object : \\" > "$depfile" + # The second -e expression handles DOS-style file names with drive + # letters. + sed -e 's/^[^:]*: / /' \ + -e 's/^['$alpha']:\/[^:]*: / /' < "$tmpdepfile" >> "$depfile" +## This next piece of magic avoids the "deleted header file" problem. +## The problem is that when a header file which appears in a .P file +## is deleted, the dependency causes make to die (because there is +## typically no way to rebuild the header). We avoid this by adding +## dummy dependencies for each header file. Too bad gcc doesn't do +## this for us directly. +## Some versions of gcc put a space before the ':'. On the theory +## that the space means something, we add a space to the output as +## well. hp depmode also adds that space, but also prefixes the VPATH +## to the object. Take care to not repeat it in the output. +## Some versions of the HPUX 10.20 sed can't process this invocation +## correctly. Breaking it into two sed invocations is a workaround. + tr ' ' "$nl" < "$tmpdepfile" \ + | sed -e 's/^\\$//' -e '/^$/d' -e "s|.*$object$||" -e '/:$/d' \ + | sed -e 's/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +hp) + # This case exists only to let depend.m4 do its work. It works by + # looking at the text of this script. This case will never be run, + # since it is checked for above. + exit 1 + ;; + +sgi) + if test "$libtool" = yes; then + "$@" "-Wp,-MDupdate,$tmpdepfile" + else + "$@" -MDupdate "$tmpdepfile" + fi + stat=$? + if test $stat -ne 0; then + rm -f "$tmpdepfile" + exit $stat + fi + rm -f "$depfile" + + if test -f "$tmpdepfile"; then # yes, the sourcefile depend on other files + echo "$object : \\" > "$depfile" + # Clip off the initial element (the dependent). Don't try to be + # clever and replace this with sed code, as IRIX sed won't handle + # lines with more than a fixed number of characters (4096 in + # IRIX 6.2 sed, 8192 in IRIX 6.5). We also remove comment lines; + # the IRIX cc adds comments like '#:fec' to the end of the + # dependency line. + tr ' ' "$nl" < "$tmpdepfile" \ + | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' \ + | tr "$nl" ' ' >> "$depfile" + echo >> "$depfile" + # The second pass generates a dummy entry for each header file. + tr ' ' "$nl" < "$tmpdepfile" \ + | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' -e 's/$/:/' \ + >> "$depfile" + else + make_dummy_depfile + fi + rm -f "$tmpdepfile" + ;; + +xlc) + # This case exists only to let depend.m4 do its work. It works by + # looking at the text of this script. This case will never be run, + # since it is checked for above. + exit 1 + ;; + +aix) + # The C for AIX Compiler uses -M and outputs the dependencies + # in a .u file. In older versions, this file always lives in the + # current directory. Also, the AIX compiler puts '$object:' at the + # start of each line; $object doesn't have directory information. + # Version 6 uses the directory in both cases. + set_dir_from "$object" + set_base_from "$object" + if test "$libtool" = yes; then + tmpdepfile1=$dir$base.u + tmpdepfile2=$base.u + tmpdepfile3=$dir.libs/$base.u + "$@" -Wc,-M + else + tmpdepfile1=$dir$base.u + tmpdepfile2=$dir$base.u + tmpdepfile3=$dir$base.u + "$@" -M + fi + stat=$? + if test $stat -ne 0; then + rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" + exit $stat + fi + + for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" + do + test -f "$tmpdepfile" && break + done + aix_post_process_depfile + ;; + +tcc) + # tcc (Tiny C Compiler) understand '-MD -MF file' since version 0.9.26 + # FIXME: That version still under development at the moment of writing. + # Make that this statement remains true also for stable, released + # versions. + # It will wrap lines (doesn't matter whether long or short) with a + # trailing '\', as in: + # + # foo.o : \ + # foo.c \ + # foo.h \ + # + # It will put a trailing '\' even on the last line, and will use leading + # spaces rather than leading tabs (at least since its commit 0394caf7 + # "Emit spaces for -MD"). + "$@" -MD -MF "$tmpdepfile" + stat=$? + if test $stat -ne 0; then + rm -f "$tmpdepfile" + exit $stat + fi + rm -f "$depfile" + # Each non-empty line is of the form 'foo.o : \' or ' dep.h \'. + # We have to change lines of the first kind to '$object: \'. + sed -e "s|.*:|$object :|" < "$tmpdepfile" > "$depfile" + # And for each line of the second kind, we have to emit a 'dep.h:' + # dummy dependency, to avoid the deleted-header problem. + sed -n -e 's|^ *\(.*\) *\\$|\1:|p' < "$tmpdepfile" >> "$depfile" + rm -f "$tmpdepfile" + ;; + +## The order of this option in the case statement is important, since the +## shell code in configure will try each of these formats in the order +## listed in this file. A plain '-MD' option would be understood by many +## compilers, so we must ensure this comes after the gcc and icc options. +pgcc) + # Portland's C compiler understands '-MD'. + # Will always output deps to 'file.d' where file is the root name of the + # source file under compilation, even if file resides in a subdirectory. + # The object file name does not affect the name of the '.d' file. + # pgcc 10.2 will output + # foo.o: sub/foo.c sub/foo.h + # and will wrap long lines using '\' : + # foo.o: sub/foo.c ... \ + # sub/foo.h ... \ + # ... + set_dir_from "$object" + # Use the source, not the object, to determine the base name, since + # that's sadly what pgcc will do too. + set_base_from "$source" + tmpdepfile=$base.d + + # For projects that build the same source file twice into different object + # files, the pgcc approach of using the *source* file root name can cause + # problems in parallel builds. Use a locking strategy to avoid stomping on + # the same $tmpdepfile. + lockdir=$base.d-lock + trap " + echo '$0: caught signal, cleaning up...' >&2 + rmdir '$lockdir' + exit 1 + " 1 2 13 15 + numtries=100 + i=$numtries + while test $i -gt 0; do + # mkdir is a portable test-and-set. + if mkdir "$lockdir" 2>/dev/null; then + # This process acquired the lock. + "$@" -MD + stat=$? + # Release the lock. + rmdir "$lockdir" + break + else + # If the lock is being held by a different process, wait + # until the winning process is done or we timeout. + while test -d "$lockdir" && test $i -gt 0; do + sleep 1 + i=`expr $i - 1` + done + fi + i=`expr $i - 1` + done + trap - 1 2 13 15 + if test $i -le 0; then + echo "$0: failed to acquire lock after $numtries attempts" >&2 + echo "$0: check lockdir '$lockdir'" >&2 + exit 1 + fi + + if test $stat -ne 0; then + rm -f "$tmpdepfile" + exit $stat + fi + rm -f "$depfile" + # Each line is of the form `foo.o: dependent.h', + # or `foo.o: dep1.h dep2.h \', or ` dep3.h dep4.h \'. + # Do two passes, one to just change these to + # `$object: dependent.h' and one to simply `dependent.h:'. + sed "s,^[^:]*:,$object :," < "$tmpdepfile" > "$depfile" + # Some versions of the HPUX 10.20 sed can't process this invocation + # correctly. Breaking it into two sed invocations is a workaround. + sed 's,^[^:]*: \(.*\)$,\1,;s/^\\$//;/^$/d;/:$/d' < "$tmpdepfile" \ + | sed -e 's/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +hp2) + # The "hp" stanza above does not work with aCC (C++) and HP's ia64 + # compilers, which have integrated preprocessors. The correct option + # to use with these is +Maked; it writes dependencies to a file named + # 'foo.d', which lands next to the object file, wherever that + # happens to be. + # Much of this is similar to the tru64 case; see comments there. + set_dir_from "$object" + set_base_from "$object" + if test "$libtool" = yes; then + tmpdepfile1=$dir$base.d + tmpdepfile2=$dir.libs/$base.d + "$@" -Wc,+Maked + else + tmpdepfile1=$dir$base.d + tmpdepfile2=$dir$base.d + "$@" +Maked + fi + stat=$? + if test $stat -ne 0; then + rm -f "$tmpdepfile1" "$tmpdepfile2" + exit $stat + fi + + for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" + do + test -f "$tmpdepfile" && break + done + if test -f "$tmpdepfile"; then + sed -e "s,^.*\.[$lower]*:,$object:," "$tmpdepfile" > "$depfile" + # Add 'dependent.h:' lines. + sed -ne '2,${ + s/^ *// + s/ \\*$// + s/$/:/ + p + }' "$tmpdepfile" >> "$depfile" + else + make_dummy_depfile + fi + rm -f "$tmpdepfile" "$tmpdepfile2" + ;; + +tru64) + # The Tru64 compiler uses -MD to generate dependencies as a side + # effect. 'cc -MD -o foo.o ...' puts the dependencies into 'foo.o.d'. + # At least on Alpha/Redhat 6.1, Compaq CCC V6.2-504 seems to put + # dependencies in 'foo.d' instead, so we check for that too. + # Subdirectories are respected. + set_dir_from "$object" + set_base_from "$object" + + if test "$libtool" = yes; then + # Libtool generates 2 separate objects for the 2 libraries. These + # two compilations output dependencies in $dir.libs/$base.o.d and + # in $dir$base.o.d. We have to check for both files, because + # one of the two compilations can be disabled. We should prefer + # $dir$base.o.d over $dir.libs/$base.o.d because the latter is + # automatically cleaned when .libs/ is deleted, while ignoring + # the former would cause a distcleancheck panic. + tmpdepfile1=$dir$base.o.d # libtool 1.5 + tmpdepfile2=$dir.libs/$base.o.d # Likewise. + tmpdepfile3=$dir.libs/$base.d # Compaq CCC V6.2-504 + "$@" -Wc,-MD + else + tmpdepfile1=$dir$base.d + tmpdepfile2=$dir$base.d + tmpdepfile3=$dir$base.d + "$@" -MD + fi + + stat=$? + if test $stat -ne 0; then + rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" + exit $stat + fi + + for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" + do + test -f "$tmpdepfile" && break + done + # Same post-processing that is required for AIX mode. + aix_post_process_depfile + ;; + +msvc7) + if test "$libtool" = yes; then + showIncludes=-Wc,-showIncludes + else + showIncludes=-showIncludes + fi + "$@" $showIncludes > "$tmpdepfile" + stat=$? + grep -v '^Note: including file: ' "$tmpdepfile" + if test $stat -ne 0; then + rm -f "$tmpdepfile" + exit $stat + fi + rm -f "$depfile" + echo "$object : \\" > "$depfile" + # The first sed program below extracts the file names and escapes + # backslashes for cygpath. The second sed program outputs the file + # name when reading, but also accumulates all include files in the + # hold buffer in order to output them again at the end. This only + # works with sed implementations that can handle large buffers. + sed < "$tmpdepfile" -n ' +/^Note: including file: *\(.*\)/ { + s//\1/ + s/\\/\\\\/g + p +}' | $cygpath_u | sort -u | sed -n ' +s/ /\\ /g +s/\(.*\)/'"$tab"'\1 \\/p +s/.\(.*\) \\/\1:/ +H +$ { + s/.*/'"$tab"'/ + G + p +}' >> "$depfile" + echo >> "$depfile" # make sure the fragment doesn't end with a backslash + rm -f "$tmpdepfile" + ;; + +msvc7msys) + # This case exists only to let depend.m4 do its work. It works by + # looking at the text of this script. This case will never be run, + # since it is checked for above. + exit 1 + ;; + +#nosideeffect) + # This comment above is used by automake to tell side-effect + # dependency tracking mechanisms from slower ones. + +dashmstdout) + # Important note: in order to support this mode, a compiler *must* + # always write the preprocessed file to stdout, regardless of -o. + "$@" || exit $? + + # Remove the call to Libtool. + if test "$libtool" = yes; then + while test "X$1" != 'X--mode=compile'; do + shift + done + shift + fi + + # Remove '-o $object'. + IFS=" " + for arg + do + case $arg in + -o) + shift + ;; + $object) + shift + ;; + *) + set fnord "$@" "$arg" + shift # fnord + shift # $arg + ;; + esac + done + + test -z "$dashmflag" && dashmflag=-M + # Require at least two characters before searching for ':' + # in the target name. This is to cope with DOS-style filenames: + # a dependency such as 'c:/foo/bar' could be seen as target 'c' otherwise. + "$@" $dashmflag | + sed "s|^[$tab ]*[^:$tab ][^:][^:]*:[$tab ]*|$object: |" > "$tmpdepfile" + rm -f "$depfile" + cat < "$tmpdepfile" > "$depfile" + # Some versions of the HPUX 10.20 sed can't process this sed invocation + # correctly. Breaking it into two sed invocations is a workaround. + tr ' ' "$nl" < "$tmpdepfile" \ + | sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' \ + | sed -e 's/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +dashXmstdout) + # This case only exists to satisfy depend.m4. It is never actually + # run, as this mode is specially recognized in the preamble. + exit 1 + ;; + +makedepend) + "$@" || exit $? + # Remove any Libtool call + if test "$libtool" = yes; then + while test "X$1" != 'X--mode=compile'; do + shift + done + shift + fi + # X makedepend + shift + cleared=no eat=no + for arg + do + case $cleared in + no) + set ""; shift + cleared=yes ;; + esac + if test $eat = yes; then + eat=no + continue + fi + case "$arg" in + -D*|-I*) + set fnord "$@" "$arg"; shift ;; + # Strip any option that makedepend may not understand. Remove + # the object too, otherwise makedepend will parse it as a source file. + -arch) + eat=yes ;; + -*|$object) + ;; + *) + set fnord "$@" "$arg"; shift ;; + esac + done + obj_suffix=`echo "$object" | sed 's/^.*\././'` + touch "$tmpdepfile" + ${MAKEDEPEND-makedepend} -o"$obj_suffix" -f"$tmpdepfile" "$@" + rm -f "$depfile" + # makedepend may prepend the VPATH from the source file name to the object. + # No need to regex-escape $object, excess matching of '.' is harmless. + sed "s|^.*\($object *:\)|\1|" "$tmpdepfile" > "$depfile" + # Some versions of the HPUX 10.20 sed can't process the last invocation + # correctly. Breaking it into two sed invocations is a workaround. + sed '1,2d' "$tmpdepfile" \ + | tr ' ' "$nl" \ + | sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' \ + | sed -e 's/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" "$tmpdepfile".bak + ;; + +cpp) + # Important note: in order to support this mode, a compiler *must* + # always write the preprocessed file to stdout. + "$@" || exit $? + + # Remove the call to Libtool. + if test "$libtool" = yes; then + while test "X$1" != 'X--mode=compile'; do + shift + done + shift + fi + + # Remove '-o $object'. + IFS=" " + for arg + do + case $arg in + -o) + shift + ;; + $object) + shift + ;; + *) + set fnord "$@" "$arg" + shift # fnord + shift # $arg + ;; + esac + done + + "$@" -E \ + | sed -n -e '/^# [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \ + -e '/^#line [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \ + | sed '$ s: \\$::' > "$tmpdepfile" + rm -f "$depfile" + echo "$object : \\" > "$depfile" + cat < "$tmpdepfile" >> "$depfile" + sed < "$tmpdepfile" '/^$/d;s/^ //;s/ \\$//;s/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +msvisualcpp) + # Important note: in order to support this mode, a compiler *must* + # always write the preprocessed file to stdout. + "$@" || exit $? + + # Remove the call to Libtool. + if test "$libtool" = yes; then + while test "X$1" != 'X--mode=compile'; do + shift + done + shift + fi + + IFS=" " + for arg + do + case "$arg" in + -o) + shift + ;; + $object) + shift + ;; + "-Gm"|"/Gm"|"-Gi"|"/Gi"|"-ZI"|"/ZI") + set fnord "$@" + shift + shift + ;; + *) + set fnord "$@" "$arg" + shift + shift + ;; + esac + done + "$@" -E 2>/dev/null | + sed -n '/^#line [0-9][0-9]* "\([^"]*\)"/ s::\1:p' | $cygpath_u | sort -u > "$tmpdepfile" + rm -f "$depfile" + echo "$object : \\" > "$depfile" + sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::'"$tab"'\1 \\:p' >> "$depfile" + echo "$tab" >> "$depfile" + sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::\1\::p' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +msvcmsys) + # This case exists only to let depend.m4 do its work. It works by + # looking at the text of this script. This case will never be run, + # since it is checked for above. + exit 1 + ;; + +none) + exec "$@" + ;; + +*) + echo "Unknown depmode $depmode" 1>&2 + exit 1 + ;; +esac + +exit 0 + +# Local Variables: +# mode: shell-script +# sh-indentation: 2 +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC" +# time-stamp-end: "; # UTC" +# End: diff --git a/build-aux/install-sh b/build-aux/install-sh new file mode 100755 index 0000000..0b0fdcb --- /dev/null +++ b/build-aux/install-sh @@ -0,0 +1,501 @@ +#!/bin/sh +# install - install a program, script, or datafile + +scriptversion=2013-12-25.23; # UTC + +# This originates from X11R5 (mit/util/scripts/install.sh), which was +# later released in X11R6 (xc/config/util/install.sh) with the +# following copyright and license. +# +# Copyright (C) 1994 X Consortium +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC- +# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Except as contained in this notice, the name of the X Consortium shall not +# be used in advertising or otherwise to promote the sale, use or other deal- +# ings in this Software without prior written authorization from the X Consor- +# tium. +# +# +# FSF changes to this file are in the public domain. +# +# Calling this script install-sh is preferred over install.sh, to prevent +# 'make' implicit rules from creating a file called install from it +# when there is no Makefile. +# +# This script is compatible with the BSD install script, but was written +# from scratch. + +tab=' ' +nl=' +' +IFS=" $tab$nl" + +# Set DOITPROG to "echo" to test this script. + +doit=${DOITPROG-} +doit_exec=${doit:-exec} + +# Put in absolute file names if you don't have them in your path; +# or use environment vars. + +chgrpprog=${CHGRPPROG-chgrp} +chmodprog=${CHMODPROG-chmod} +chownprog=${CHOWNPROG-chown} +cmpprog=${CMPPROG-cmp} +cpprog=${CPPROG-cp} +mkdirprog=${MKDIRPROG-mkdir} +mvprog=${MVPROG-mv} +rmprog=${RMPROG-rm} +stripprog=${STRIPPROG-strip} + +posix_mkdir= + +# Desired mode of installed file. +mode=0755 + +chgrpcmd= +chmodcmd=$chmodprog +chowncmd= +mvcmd=$mvprog +rmcmd="$rmprog -f" +stripcmd= + +src= +dst= +dir_arg= +dst_arg= + +copy_on_change=false +is_target_a_directory=possibly + +usage="\ +Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE + or: $0 [OPTION]... SRCFILES... DIRECTORY + or: $0 [OPTION]... -t DIRECTORY SRCFILES... + or: $0 [OPTION]... -d DIRECTORIES... + +In the 1st form, copy SRCFILE to DSTFILE. +In the 2nd and 3rd, copy all SRCFILES to DIRECTORY. +In the 4th, create DIRECTORIES. + +Options: + --help display this help and exit. + --version display version info and exit. + + -c (ignored) + -C install only if different (preserve the last data modification time) + -d create directories instead of installing files. + -g GROUP $chgrpprog installed files to GROUP. + -m MODE $chmodprog installed files to MODE. + -o USER $chownprog installed files to USER. + -s $stripprog installed files. + -t DIRECTORY install into DIRECTORY. + -T report an error if DSTFILE is a directory. + +Environment variables override the default commands: + CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG + RMPROG STRIPPROG +" + +while test $# -ne 0; do + case $1 in + -c) ;; + + -C) copy_on_change=true;; + + -d) dir_arg=true;; + + -g) chgrpcmd="$chgrpprog $2" + shift;; + + --help) echo "$usage"; exit $?;; + + -m) mode=$2 + case $mode in + *' '* | *"$tab"* | *"$nl"* | *'*'* | *'?'* | *'['*) + echo "$0: invalid mode: $mode" >&2 + exit 1;; + esac + shift;; + + -o) chowncmd="$chownprog $2" + shift;; + + -s) stripcmd=$stripprog;; + + -t) + is_target_a_directory=always + dst_arg=$2 + # Protect names problematic for 'test' and other utilities. + case $dst_arg in + -* | [=\(\)!]) dst_arg=./$dst_arg;; + esac + shift;; + + -T) is_target_a_directory=never;; + + --version) echo "$0 $scriptversion"; exit $?;; + + --) shift + break;; + + -*) echo "$0: invalid option: $1" >&2 + exit 1;; + + *) break;; + esac + shift +done + +# We allow the use of options -d and -T together, by making -d +# take the precedence; this is for compatibility with GNU install. + +if test -n "$dir_arg"; then + if test -n "$dst_arg"; then + echo "$0: target directory not allowed when installing a directory." >&2 + exit 1 + fi +fi + +if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then + # When -d is used, all remaining arguments are directories to create. + # When -t is used, the destination is already specified. + # Otherwise, the last argument is the destination. Remove it from $@. + for arg + do + if test -n "$dst_arg"; then + # $@ is not empty: it contains at least $arg. + set fnord "$@" "$dst_arg" + shift # fnord + fi + shift # arg + dst_arg=$arg + # Protect names problematic for 'test' and other utilities. + case $dst_arg in + -* | [=\(\)!]) dst_arg=./$dst_arg;; + esac + done +fi + +if test $# -eq 0; then + if test -z "$dir_arg"; then + echo "$0: no input file specified." >&2 + exit 1 + fi + # It's OK to call 'install-sh -d' without argument. + # This can happen when creating conditional directories. + exit 0 +fi + +if test -z "$dir_arg"; then + if test $# -gt 1 || test "$is_target_a_directory" = always; then + if test ! -d "$dst_arg"; then + echo "$0: $dst_arg: Is not a directory." >&2 + exit 1 + fi + fi +fi + +if test -z "$dir_arg"; then + do_exit='(exit $ret); exit $ret' + trap "ret=129; $do_exit" 1 + trap "ret=130; $do_exit" 2 + trap "ret=141; $do_exit" 13 + trap "ret=143; $do_exit" 15 + + # Set umask so as not to create temps with too-generous modes. + # However, 'strip' requires both read and write access to temps. + case $mode in + # Optimize common cases. + *644) cp_umask=133;; + *755) cp_umask=22;; + + *[0-7]) + if test -z "$stripcmd"; then + u_plus_rw= + else + u_plus_rw='% 200' + fi + cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;; + *) + if test -z "$stripcmd"; then + u_plus_rw= + else + u_plus_rw=,u+rw + fi + cp_umask=$mode$u_plus_rw;; + esac +fi + +for src +do + # Protect names problematic for 'test' and other utilities. + case $src in + -* | [=\(\)!]) src=./$src;; + esac + + if test -n "$dir_arg"; then + dst=$src + dstdir=$dst + test -d "$dstdir" + dstdir_status=$? + else + + # Waiting for this to be detected by the "$cpprog $src $dsttmp" command + # might cause directories to be created, which would be especially bad + # if $src (and thus $dsttmp) contains '*'. + if test ! -f "$src" && test ! -d "$src"; then + echo "$0: $src does not exist." >&2 + exit 1 + fi + + if test -z "$dst_arg"; then + echo "$0: no destination specified." >&2 + exit 1 + fi + dst=$dst_arg + + # If destination is a directory, append the input filename; won't work + # if double slashes aren't ignored. + if test -d "$dst"; then + if test "$is_target_a_directory" = never; then + echo "$0: $dst_arg: Is a directory" >&2 + exit 1 + fi + dstdir=$dst + dst=$dstdir/`basename "$src"` + dstdir_status=0 + else + dstdir=`dirname "$dst"` + test -d "$dstdir" + dstdir_status=$? + fi + fi + + obsolete_mkdir_used=false + + if test $dstdir_status != 0; then + case $posix_mkdir in + '') + # Create intermediate dirs using mode 755 as modified by the umask. + # This is like FreeBSD 'install' as of 1997-10-28. + umask=`umask` + case $stripcmd.$umask in + # Optimize common cases. + *[2367][2367]) mkdir_umask=$umask;; + .*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;; + + *[0-7]) + mkdir_umask=`expr $umask + 22 \ + - $umask % 100 % 40 + $umask % 20 \ + - $umask % 10 % 4 + $umask % 2 + `;; + *) mkdir_umask=$umask,go-w;; + esac + + # With -d, create the new directory with the user-specified mode. + # Otherwise, rely on $mkdir_umask. + if test -n "$dir_arg"; then + mkdir_mode=-m$mode + else + mkdir_mode= + fi + + posix_mkdir=false + case $umask in + *[123567][0-7][0-7]) + # POSIX mkdir -p sets u+wx bits regardless of umask, which + # is incompatible with FreeBSD 'install' when (umask & 300) != 0. + ;; + *) + tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$ + trap 'ret=$?; rmdir "$tmpdir/d" "$tmpdir" 2>/dev/null; exit $ret' 0 + + if (umask $mkdir_umask && + exec $mkdirprog $mkdir_mode -p -- "$tmpdir/d") >/dev/null 2>&1 + then + if test -z "$dir_arg" || { + # Check for POSIX incompatibilities with -m. + # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or + # other-writable bit of parent directory when it shouldn't. + # FreeBSD 6.1 mkdir -m -p sets mode of existing directory. + ls_ld_tmpdir=`ls -ld "$tmpdir"` + case $ls_ld_tmpdir in + d????-?r-*) different_mode=700;; + d????-?--*) different_mode=755;; + *) false;; + esac && + $mkdirprog -m$different_mode -p -- "$tmpdir" && { + ls_ld_tmpdir_1=`ls -ld "$tmpdir"` + test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1" + } + } + then posix_mkdir=: + fi + rmdir "$tmpdir/d" "$tmpdir" + else + # Remove any dirs left behind by ancient mkdir implementations. + rmdir ./$mkdir_mode ./-p ./-- 2>/dev/null + fi + trap '' 0;; + esac;; + esac + + if + $posix_mkdir && ( + umask $mkdir_umask && + $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir" + ) + then : + else + + # The umask is ridiculous, or mkdir does not conform to POSIX, + # or it failed possibly due to a race condition. Create the + # directory the slow way, step by step, checking for races as we go. + + case $dstdir in + /*) prefix='/';; + [-=\(\)!]*) prefix='./';; + *) prefix='';; + esac + + oIFS=$IFS + IFS=/ + set -f + set fnord $dstdir + shift + set +f + IFS=$oIFS + + prefixes= + + for d + do + test X"$d" = X && continue + + prefix=$prefix$d + if test -d "$prefix"; then + prefixes= + else + if $posix_mkdir; then + (umask=$mkdir_umask && + $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break + # Don't fail if two instances are running concurrently. + test -d "$prefix" || exit 1 + else + case $prefix in + *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;; + *) qprefix=$prefix;; + esac + prefixes="$prefixes '$qprefix'" + fi + fi + prefix=$prefix/ + done + + if test -n "$prefixes"; then + # Don't fail if two instances are running concurrently. + (umask $mkdir_umask && + eval "\$doit_exec \$mkdirprog $prefixes") || + test -d "$dstdir" || exit 1 + obsolete_mkdir_used=true + fi + fi + fi + + if test -n "$dir_arg"; then + { test -z "$chowncmd" || $doit $chowncmd "$dst"; } && + { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } && + { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false || + test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1 + else + + # Make a couple of temp file names in the proper directory. + dsttmp=$dstdir/_inst.$$_ + rmtmp=$dstdir/_rm.$$_ + + # Trap to clean up those temp files at exit. + trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0 + + # Copy the file name to the temp name. + (umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") && + + # and set any options; do chmod last to preserve setuid bits. + # + # If any of these fail, we abort the whole thing. If we want to + # ignore errors from any of these, just make sure not to ignore + # errors from the above "$doit $cpprog $src $dsttmp" command. + # + { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } && + { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } && + { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } && + { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } && + + # If -C, don't bother to copy if it wouldn't change the file. + if $copy_on_change && + old=`LC_ALL=C ls -dlL "$dst" 2>/dev/null` && + new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` && + set -f && + set X $old && old=:$2:$4:$5:$6 && + set X $new && new=:$2:$4:$5:$6 && + set +f && + test "$old" = "$new" && + $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1 + then + rm -f "$dsttmp" + else + # Rename the file to the real destination. + $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null || + + # The rename failed, perhaps because mv can't rename something else + # to itself, or perhaps because mv is so ancient that it does not + # support -f. + { + # Now remove or move aside any old file at destination location. + # We try this two ways since rm can't unlink itself on some + # systems and the destination file might be busy for other + # reasons. In this case, the final cleanup might fail but the new + # file should still install successfully. + { + test ! -f "$dst" || + $doit $rmcmd -f "$dst" 2>/dev/null || + { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null && + { $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; } + } || + { echo "$0: cannot unlink or rename $dst" >&2 + (exit 1); exit 1 + } + } && + + # Now rename the file to the real destination. + $doit $mvcmd "$dsttmp" "$dst" + } + fi || exit 1 + + trap '' 0 + fi +done + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC" +# time-stamp-end: "; # UTC" +# End: diff --git a/build-aux/ltmain.sh b/build-aux/ltmain.sh new file mode 100644 index 0000000..b6f3fcb --- /dev/null +++ b/build-aux/ltmain.sh @@ -0,0 +1,11149 @@ +#! /bin/sh +## DO NOT EDIT - This file generated from ./build-aux/ltmain.in +## by inline-source v2014-01-03.01 + +# libtool (GNU libtool) 2.4.6 +# Provide generalized library-building support services. +# Written by Gordon Matzigkeit , 1996 + +# Copyright (C) 1996-2015 Free Software Foundation, Inc. +# This is free software; see the source for copying conditions. There is NO +# warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +# GNU Libtool is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# As a special exception to the GNU General Public License, +# if you distribute this file as part of a program or library that +# is built using GNU Libtool, you may include this file under the +# same distribution terms that you use for the rest of that program. +# +# GNU Libtool is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +PROGRAM=libtool +PACKAGE=libtool +VERSION=2.4.6 +package_revision=2.4.6 + + +## ------ ## +## Usage. ## +## ------ ## + +# Run './libtool --help' for help with using this script from the +# command line. + + +## ------------------------------- ## +## User overridable command paths. ## +## ------------------------------- ## + +# After configure completes, it has a better idea of some of the +# shell tools we need than the defaults used by the functions shared +# with bootstrap, so set those here where they can still be over- +# ridden by the user, but otherwise take precedence. + +: ${AUTOCONF="autoconf"} +: ${AUTOMAKE="automake"} + + +## -------------------------- ## +## Source external libraries. ## +## -------------------------- ## + +# Much of our low-level functionality needs to be sourced from external +# libraries, which are installed to $pkgauxdir. + +# Set a version string for this script. +scriptversion=2015-01-20.17; # UTC + +# General shell script boiler plate, and helper functions. +# Written by Gary V. Vaughan, 2004 + +# Copyright (C) 2004-2015 Free Software Foundation, Inc. +# This is free software; see the source for copying conditions. There is NO +# warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. + +# As a special exception to the GNU General Public License, if you distribute +# this file as part of a program or library that is built using GNU Libtool, +# you may include this file under the same distribution terms that you use +# for the rest of that program. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNES FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# Please report bugs or propose patches to gary@gnu.org. + + +## ------ ## +## Usage. ## +## ------ ## + +# Evaluate this file near the top of your script to gain access to +# the functions and variables defined here: +# +# . `echo "$0" | ${SED-sed} 's|[^/]*$||'`/build-aux/funclib.sh +# +# If you need to override any of the default environment variable +# settings, do that before evaluating this file. + + +## -------------------- ## +## Shell normalisation. ## +## -------------------- ## + +# Some shells need a little help to be as Bourne compatible as possible. +# Before doing anything else, make sure all that help has been provided! + +DUALCASE=1; export DUALCASE # for MKS sh +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else + case `(set -o) 2>/dev/null` in *posix*) set -o posix ;; esac +fi + +# NLS nuisances: We save the old values in case they are required later. +_G_user_locale= +_G_safe_locale= +for _G_var in LANG LANGUAGE LC_ALL LC_CTYPE LC_COLLATE LC_MESSAGES +do + eval "if test set = \"\${$_G_var+set}\"; then + save_$_G_var=\$$_G_var + $_G_var=C + export $_G_var + _G_user_locale=\"$_G_var=\\\$save_\$_G_var; \$_G_user_locale\" + _G_safe_locale=\"$_G_var=C; \$_G_safe_locale\" + fi" +done + +# CDPATH. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + +# Make sure IFS has a sensible default +sp=' ' +nl=' +' +IFS="$sp $nl" + +# There are apparently some retarded systems that use ';' as a PATH separator! +if test "${PATH_SEPARATOR+set}" != set; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } +fi + + + +## ------------------------- ## +## Locate command utilities. ## +## ------------------------- ## + + +# func_executable_p FILE +# ---------------------- +# Check that FILE is an executable regular file. +func_executable_p () +{ + test -f "$1" && test -x "$1" +} + + +# func_path_progs PROGS_LIST CHECK_FUNC [PATH] +# -------------------------------------------- +# Search for either a program that responds to --version with output +# containing "GNU", or else returned by CHECK_FUNC otherwise, by +# trying all the directories in PATH with each of the elements of +# PROGS_LIST. +# +# CHECK_FUNC should accept the path to a candidate program, and +# set $func_check_prog_result if it truncates its output less than +# $_G_path_prog_max characters. +func_path_progs () +{ + _G_progs_list=$1 + _G_check_func=$2 + _G_PATH=${3-"$PATH"} + + _G_path_prog_max=0 + _G_path_prog_found=false + _G_save_IFS=$IFS; IFS=${PATH_SEPARATOR-:} + for _G_dir in $_G_PATH; do + IFS=$_G_save_IFS + test -z "$_G_dir" && _G_dir=. + for _G_prog_name in $_G_progs_list; do + for _exeext in '' .EXE; do + _G_path_prog=$_G_dir/$_G_prog_name$_exeext + func_executable_p "$_G_path_prog" || continue + case `"$_G_path_prog" --version 2>&1` in + *GNU*) func_path_progs_result=$_G_path_prog _G_path_prog_found=: ;; + *) $_G_check_func $_G_path_prog + func_path_progs_result=$func_check_prog_result + ;; + esac + $_G_path_prog_found && break 3 + done + done + done + IFS=$_G_save_IFS + test -z "$func_path_progs_result" && { + echo "no acceptable sed could be found in \$PATH" >&2 + exit 1 + } +} + + +# We want to be able to use the functions in this file before configure +# has figured out where the best binaries are kept, which means we have +# to search for them ourselves - except when the results are already set +# where we skip the searches. + +# Unless the user overrides by setting SED, search the path for either GNU +# sed, or the sed that truncates its output the least. +test -z "$SED" && { + _G_sed_script=s/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/ + for _G_i in 1 2 3 4 5 6 7; do + _G_sed_script=$_G_sed_script$nl$_G_sed_script + done + echo "$_G_sed_script" 2>/dev/null | sed 99q >conftest.sed + _G_sed_script= + + func_check_prog_sed () + { + _G_path_prog=$1 + + _G_count=0 + printf 0123456789 >conftest.in + while : + do + cat conftest.in conftest.in >conftest.tmp + mv conftest.tmp conftest.in + cp conftest.in conftest.nl + echo '' >> conftest.nl + "$_G_path_prog" -f conftest.sed conftest.out 2>/dev/null || break + diff conftest.out conftest.nl >/dev/null 2>&1 || break + _G_count=`expr $_G_count + 1` + if test "$_G_count" -gt "$_G_path_prog_max"; then + # Best one so far, save it but keep looking for a better one + func_check_prog_result=$_G_path_prog + _G_path_prog_max=$_G_count + fi + # 10*(2^10) chars as input seems more than enough + test 10 -lt "$_G_count" && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out + } + + func_path_progs "sed gsed" func_check_prog_sed $PATH:/usr/xpg4/bin + rm -f conftest.sed + SED=$func_path_progs_result +} + + +# Unless the user overrides by setting GREP, search the path for either GNU +# grep, or the grep that truncates its output the least. +test -z "$GREP" && { + func_check_prog_grep () + { + _G_path_prog=$1 + + _G_count=0 + _G_path_prog_max=0 + printf 0123456789 >conftest.in + while : + do + cat conftest.in conftest.in >conftest.tmp + mv conftest.tmp conftest.in + cp conftest.in conftest.nl + echo 'GREP' >> conftest.nl + "$_G_path_prog" -e 'GREP$' -e '-(cannot match)-' conftest.out 2>/dev/null || break + diff conftest.out conftest.nl >/dev/null 2>&1 || break + _G_count=`expr $_G_count + 1` + if test "$_G_count" -gt "$_G_path_prog_max"; then + # Best one so far, save it but keep looking for a better one + func_check_prog_result=$_G_path_prog + _G_path_prog_max=$_G_count + fi + # 10*(2^10) chars as input seems more than enough + test 10 -lt "$_G_count" && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out + } + + func_path_progs "grep ggrep" func_check_prog_grep $PATH:/usr/xpg4/bin + GREP=$func_path_progs_result +} + + +## ------------------------------- ## +## User overridable command paths. ## +## ------------------------------- ## + +# All uppercase variable names are used for environment variables. These +# variables can be overridden by the user before calling a script that +# uses them if a suitable command of that name is not already available +# in the command search PATH. + +: ${CP="cp -f"} +: ${ECHO="printf %s\n"} +: ${EGREP="$GREP -E"} +: ${FGREP="$GREP -F"} +: ${LN_S="ln -s"} +: ${MAKE="make"} +: ${MKDIR="mkdir"} +: ${MV="mv -f"} +: ${RM="rm -f"} +: ${SHELL="${CONFIG_SHELL-/bin/sh}"} + + +## -------------------- ## +## Useful sed snippets. ## +## -------------------- ## + +sed_dirname='s|/[^/]*$||' +sed_basename='s|^.*/||' + +# Sed substitution that helps us do robust quoting. It backslashifies +# metacharacters that are still active within double-quoted strings. +sed_quote_subst='s|\([`"$\\]\)|\\\1|g' + +# Same as above, but do not quote variable references. +sed_double_quote_subst='s/\(["`\\]\)/\\\1/g' + +# Sed substitution that turns a string into a regex matching for the +# string literally. +sed_make_literal_regex='s|[].[^$\\*\/]|\\&|g' + +# Sed substitution that converts a w32 file name or path +# that contains forward slashes, into one that contains +# (escaped) backslashes. A very naive implementation. +sed_naive_backslashify='s|\\\\*|\\|g;s|/|\\|g;s|\\|\\\\|g' + +# Re-'\' parameter expansions in output of sed_double_quote_subst that +# were '\'-ed in input to the same. If an odd number of '\' preceded a +# '$' in input to sed_double_quote_subst, that '$' was protected from +# expansion. Since each input '\' is now two '\'s, look for any number +# of runs of four '\'s followed by two '\'s and then a '$'. '\' that '$'. +_G_bs='\\' +_G_bs2='\\\\' +_G_bs4='\\\\\\\\' +_G_dollar='\$' +sed_double_backslash="\ + s/$_G_bs4/&\\ +/g + s/^$_G_bs2$_G_dollar/$_G_bs&/ + s/\\([^$_G_bs]\\)$_G_bs2$_G_dollar/\\1$_G_bs2$_G_bs$_G_dollar/g + s/\n//g" + + +## ----------------- ## +## Global variables. ## +## ----------------- ## + +# Except for the global variables explicitly listed below, the following +# functions in the '^func_' namespace, and the '^require_' namespace +# variables initialised in the 'Resource management' section, sourcing +# this file will not pollute your global namespace with anything +# else. There's no portable way to scope variables in Bourne shell +# though, so actually running these functions will sometimes place +# results into a variable named after the function, and often use +# temporary variables in the '^_G_' namespace. If you are careful to +# avoid using those namespaces casually in your sourcing script, things +# should continue to work as you expect. And, of course, you can freely +# overwrite any of the functions or variables defined here before +# calling anything to customize them. + +EXIT_SUCCESS=0 +EXIT_FAILURE=1 +EXIT_MISMATCH=63 # $? = 63 is used to indicate version mismatch to missing. +EXIT_SKIP=77 # $? = 77 is used to indicate a skipped test to automake. + +# Allow overriding, eg assuming that you follow the convention of +# putting '$debug_cmd' at the start of all your functions, you can get +# bash to show function call trace with: +# +# debug_cmd='eval echo "${FUNCNAME[0]} $*" >&2' bash your-script-name +debug_cmd=${debug_cmd-":"} +exit_cmd=: + +# By convention, finish your script with: +# +# exit $exit_status +# +# so that you can set exit_status to non-zero if you want to indicate +# something went wrong during execution without actually bailing out at +# the point of failure. +exit_status=$EXIT_SUCCESS + +# Work around backward compatibility issue on IRIX 6.5. On IRIX 6.4+, sh +# is ksh but when the shell is invoked as "sh" and the current value of +# the _XPG environment variable is not equal to 1 (one), the special +# positional parameter $0, within a function call, is the name of the +# function. +progpath=$0 + +# The name of this program. +progname=`$ECHO "$progpath" |$SED "$sed_basename"` + +# Make sure we have an absolute progpath for reexecution: +case $progpath in + [\\/]*|[A-Za-z]:\\*) ;; + *[\\/]*) + progdir=`$ECHO "$progpath" |$SED "$sed_dirname"` + progdir=`cd "$progdir" && pwd` + progpath=$progdir/$progname + ;; + *) + _G_IFS=$IFS + IFS=${PATH_SEPARATOR-:} + for progdir in $PATH; do + IFS=$_G_IFS + test -x "$progdir/$progname" && break + done + IFS=$_G_IFS + test -n "$progdir" || progdir=`pwd` + progpath=$progdir/$progname + ;; +esac + + +## ----------------- ## +## Standard options. ## +## ----------------- ## + +# The following options affect the operation of the functions defined +# below, and should be set appropriately depending on run-time para- +# meters passed on the command line. + +opt_dry_run=false +opt_quiet=false +opt_verbose=false + +# Categories 'all' and 'none' are always available. Append any others +# you will pass as the first argument to func_warning from your own +# code. +warning_categories= + +# By default, display warnings according to 'opt_warning_types'. Set +# 'warning_func' to ':' to elide all warnings, or func_fatal_error to +# treat the next displayed warning as a fatal error. +warning_func=func_warn_and_continue + +# Set to 'all' to display all warnings, 'none' to suppress all +# warnings, or a space delimited list of some subset of +# 'warning_categories' to display only the listed warnings. +opt_warning_types=all + + +## -------------------- ## +## Resource management. ## +## -------------------- ## + +# This section contains definitions for functions that each ensure a +# particular resource (a file, or a non-empty configuration variable for +# example) is available, and if appropriate to extract default values +# from pertinent package files. Call them using their associated +# 'require_*' variable to ensure that they are executed, at most, once. +# +# It's entirely deliberate that calling these functions can set +# variables that don't obey the namespace limitations obeyed by the rest +# of this file, in order that that they be as useful as possible to +# callers. + + +# require_term_colors +# ------------------- +# Allow display of bold text on terminals that support it. +require_term_colors=func_require_term_colors +func_require_term_colors () +{ + $debug_cmd + + test -t 1 && { + # COLORTERM and USE_ANSI_COLORS environment variables take + # precedence, because most terminfo databases neglect to describe + # whether color sequences are supported. + test -n "${COLORTERM+set}" && : ${USE_ANSI_COLORS="1"} + + if test 1 = "$USE_ANSI_COLORS"; then + # Standard ANSI escape sequences + tc_reset='' + tc_bold=''; tc_standout='' + tc_red=''; tc_green='' + tc_blue=''; tc_cyan='' + else + # Otherwise trust the terminfo database after all. + test -n "`tput sgr0 2>/dev/null`" && { + tc_reset=`tput sgr0` + test -n "`tput bold 2>/dev/null`" && tc_bold=`tput bold` + tc_standout=$tc_bold + test -n "`tput smso 2>/dev/null`" && tc_standout=`tput smso` + test -n "`tput setaf 1 2>/dev/null`" && tc_red=`tput setaf 1` + test -n "`tput setaf 2 2>/dev/null`" && tc_green=`tput setaf 2` + test -n "`tput setaf 4 2>/dev/null`" && tc_blue=`tput setaf 4` + test -n "`tput setaf 5 2>/dev/null`" && tc_cyan=`tput setaf 5` + } + fi + } + + require_term_colors=: +} + + +## ----------------- ## +## Function library. ## +## ----------------- ## + +# This section contains a variety of useful functions to call in your +# scripts. Take note of the portable wrappers for features provided by +# some modern shells, which will fall back to slower equivalents on +# less featureful shells. + + +# func_append VAR VALUE +# --------------------- +# Append VALUE onto the existing contents of VAR. + + # We should try to minimise forks, especially on Windows where they are + # unreasonably slow, so skip the feature probes when bash or zsh are + # being used: + if test set = "${BASH_VERSION+set}${ZSH_VERSION+set}"; then + : ${_G_HAVE_ARITH_OP="yes"} + : ${_G_HAVE_XSI_OPS="yes"} + # The += operator was introduced in bash 3.1 + case $BASH_VERSION in + [12].* | 3.0 | 3.0*) ;; + *) + : ${_G_HAVE_PLUSEQ_OP="yes"} + ;; + esac + fi + + # _G_HAVE_PLUSEQ_OP + # Can be empty, in which case the shell is probed, "yes" if += is + # useable or anything else if it does not work. + test -z "$_G_HAVE_PLUSEQ_OP" \ + && (eval 'x=a; x+=" b"; test "a b" = "$x"') 2>/dev/null \ + && _G_HAVE_PLUSEQ_OP=yes + +if test yes = "$_G_HAVE_PLUSEQ_OP" +then + # This is an XSI compatible shell, allowing a faster implementation... + eval 'func_append () + { + $debug_cmd + + eval "$1+=\$2" + }' +else + # ...otherwise fall back to using expr, which is often a shell builtin. + func_append () + { + $debug_cmd + + eval "$1=\$$1\$2" + } +fi + + +# func_append_quoted VAR VALUE +# ---------------------------- +# Quote VALUE and append to the end of shell variable VAR, separated +# by a space. +if test yes = "$_G_HAVE_PLUSEQ_OP"; then + eval 'func_append_quoted () + { + $debug_cmd + + func_quote_for_eval "$2" + eval "$1+=\\ \$func_quote_for_eval_result" + }' +else + func_append_quoted () + { + $debug_cmd + + func_quote_for_eval "$2" + eval "$1=\$$1\\ \$func_quote_for_eval_result" + } +fi + + +# func_append_uniq VAR VALUE +# -------------------------- +# Append unique VALUE onto the existing contents of VAR, assuming +# entries are delimited by the first character of VALUE. For example: +# +# func_append_uniq options " --another-option option-argument" +# +# will only append to $options if " --another-option option-argument " +# is not already present somewhere in $options already (note spaces at +# each end implied by leading space in second argument). +func_append_uniq () +{ + $debug_cmd + + eval _G_current_value='`$ECHO $'$1'`' + _G_delim=`expr "$2" : '\(.\)'` + + case $_G_delim$_G_current_value$_G_delim in + *"$2$_G_delim"*) ;; + *) func_append "$@" ;; + esac +} + + +# func_arith TERM... +# ------------------ +# Set func_arith_result to the result of evaluating TERMs. + test -z "$_G_HAVE_ARITH_OP" \ + && (eval 'test 2 = $(( 1 + 1 ))') 2>/dev/null \ + && _G_HAVE_ARITH_OP=yes + +if test yes = "$_G_HAVE_ARITH_OP"; then + eval 'func_arith () + { + $debug_cmd + + func_arith_result=$(( $* )) + }' +else + func_arith () + { + $debug_cmd + + func_arith_result=`expr "$@"` + } +fi + + +# func_basename FILE +# ------------------ +# Set func_basename_result to FILE with everything up to and including +# the last / stripped. +if test yes = "$_G_HAVE_XSI_OPS"; then + # If this shell supports suffix pattern removal, then use it to avoid + # forking. Hide the definitions single quotes in case the shell chokes + # on unsupported syntax... + _b='func_basename_result=${1##*/}' + _d='case $1 in + */*) func_dirname_result=${1%/*}$2 ;; + * ) func_dirname_result=$3 ;; + esac' + +else + # ...otherwise fall back to using sed. + _b='func_basename_result=`$ECHO "$1" |$SED "$sed_basename"`' + _d='func_dirname_result=`$ECHO "$1" |$SED "$sed_dirname"` + if test "X$func_dirname_result" = "X$1"; then + func_dirname_result=$3 + else + func_append func_dirname_result "$2" + fi' +fi + +eval 'func_basename () +{ + $debug_cmd + + '"$_b"' +}' + + +# func_dirname FILE APPEND NONDIR_REPLACEMENT +# ------------------------------------------- +# Compute the dirname of FILE. If nonempty, add APPEND to the result, +# otherwise set result to NONDIR_REPLACEMENT. +eval 'func_dirname () +{ + $debug_cmd + + '"$_d"' +}' + + +# func_dirname_and_basename FILE APPEND NONDIR_REPLACEMENT +# -------------------------------------------------------- +# Perform func_basename and func_dirname in a single function +# call: +# dirname: Compute the dirname of FILE. If nonempty, +# add APPEND to the result, otherwise set result +# to NONDIR_REPLACEMENT. +# value returned in "$func_dirname_result" +# basename: Compute filename of FILE. +# value retuned in "$func_basename_result" +# For efficiency, we do not delegate to the functions above but instead +# duplicate the functionality here. +eval 'func_dirname_and_basename () +{ + $debug_cmd + + '"$_b"' + '"$_d"' +}' + + +# func_echo ARG... +# ---------------- +# Echo program name prefixed message. +func_echo () +{ + $debug_cmd + + _G_message=$* + + func_echo_IFS=$IFS + IFS=$nl + for _G_line in $_G_message; do + IFS=$func_echo_IFS + $ECHO "$progname: $_G_line" + done + IFS=$func_echo_IFS +} + + +# func_echo_all ARG... +# -------------------- +# Invoke $ECHO with all args, space-separated. +func_echo_all () +{ + $ECHO "$*" +} + + +# func_echo_infix_1 INFIX ARG... +# ------------------------------ +# Echo program name, followed by INFIX on the first line, with any +# additional lines not showing INFIX. +func_echo_infix_1 () +{ + $debug_cmd + + $require_term_colors + + _G_infix=$1; shift + _G_indent=$_G_infix + _G_prefix="$progname: $_G_infix: " + _G_message=$* + + # Strip color escape sequences before counting printable length + for _G_tc in "$tc_reset" "$tc_bold" "$tc_standout" "$tc_red" "$tc_green" "$tc_blue" "$tc_cyan" + do + test -n "$_G_tc" && { + _G_esc_tc=`$ECHO "$_G_tc" | $SED "$sed_make_literal_regex"` + _G_indent=`$ECHO "$_G_indent" | $SED "s|$_G_esc_tc||g"` + } + done + _G_indent="$progname: "`echo "$_G_indent" | $SED 's|.| |g'`" " ## exclude from sc_prohibit_nested_quotes + + func_echo_infix_1_IFS=$IFS + IFS=$nl + for _G_line in $_G_message; do + IFS=$func_echo_infix_1_IFS + $ECHO "$_G_prefix$tc_bold$_G_line$tc_reset" >&2 + _G_prefix=$_G_indent + done + IFS=$func_echo_infix_1_IFS +} + + +# func_error ARG... +# ----------------- +# Echo program name prefixed message to standard error. +func_error () +{ + $debug_cmd + + $require_term_colors + + func_echo_infix_1 " $tc_standout${tc_red}error$tc_reset" "$*" >&2 +} + + +# func_fatal_error ARG... +# ----------------------- +# Echo program name prefixed message to standard error, and exit. +func_fatal_error () +{ + $debug_cmd + + func_error "$*" + exit $EXIT_FAILURE +} + + +# func_grep EXPRESSION FILENAME +# ----------------------------- +# Check whether EXPRESSION matches any line of FILENAME, without output. +func_grep () +{ + $debug_cmd + + $GREP "$1" "$2" >/dev/null 2>&1 +} + + +# func_len STRING +# --------------- +# Set func_len_result to the length of STRING. STRING may not +# start with a hyphen. + test -z "$_G_HAVE_XSI_OPS" \ + && (eval 'x=a/b/c; + test 5aa/bb/cc = "${#x}${x%%/*}${x%/*}${x#*/}${x##*/}"') 2>/dev/null \ + && _G_HAVE_XSI_OPS=yes + +if test yes = "$_G_HAVE_XSI_OPS"; then + eval 'func_len () + { + $debug_cmd + + func_len_result=${#1} + }' +else + func_len () + { + $debug_cmd + + func_len_result=`expr "$1" : ".*" 2>/dev/null || echo $max_cmd_len` + } +fi + + +# func_mkdir_p DIRECTORY-PATH +# --------------------------- +# Make sure the entire path to DIRECTORY-PATH is available. +func_mkdir_p () +{ + $debug_cmd + + _G_directory_path=$1 + _G_dir_list= + + if test -n "$_G_directory_path" && test : != "$opt_dry_run"; then + + # Protect directory names starting with '-' + case $_G_directory_path in + -*) _G_directory_path=./$_G_directory_path ;; + esac + + # While some portion of DIR does not yet exist... + while test ! -d "$_G_directory_path"; do + # ...make a list in topmost first order. Use a colon delimited + # list incase some portion of path contains whitespace. + _G_dir_list=$_G_directory_path:$_G_dir_list + + # If the last portion added has no slash in it, the list is done + case $_G_directory_path in */*) ;; *) break ;; esac + + # ...otherwise throw away the child directory and loop + _G_directory_path=`$ECHO "$_G_directory_path" | $SED -e "$sed_dirname"` + done + _G_dir_list=`$ECHO "$_G_dir_list" | $SED 's|:*$||'` + + func_mkdir_p_IFS=$IFS; IFS=: + for _G_dir in $_G_dir_list; do + IFS=$func_mkdir_p_IFS + # mkdir can fail with a 'File exist' error if two processes + # try to create one of the directories concurrently. Don't + # stop in that case! + $MKDIR "$_G_dir" 2>/dev/null || : + done + IFS=$func_mkdir_p_IFS + + # Bail out if we (or some other process) failed to create a directory. + test -d "$_G_directory_path" || \ + func_fatal_error "Failed to create '$1'" + fi +} + + +# func_mktempdir [BASENAME] +# ------------------------- +# Make a temporary directory that won't clash with other running +# libtool processes, and avoids race conditions if possible. If +# given, BASENAME is the basename for that directory. +func_mktempdir () +{ + $debug_cmd + + _G_template=${TMPDIR-/tmp}/${1-$progname} + + if test : = "$opt_dry_run"; then + # Return a directory name, but don't create it in dry-run mode + _G_tmpdir=$_G_template-$$ + else + + # If mktemp works, use that first and foremost + _G_tmpdir=`mktemp -d "$_G_template-XXXXXXXX" 2>/dev/null` + + if test ! -d "$_G_tmpdir"; then + # Failing that, at least try and use $RANDOM to avoid a race + _G_tmpdir=$_G_template-${RANDOM-0}$$ + + func_mktempdir_umask=`umask` + umask 0077 + $MKDIR "$_G_tmpdir" + umask $func_mktempdir_umask + fi + + # If we're not in dry-run mode, bomb out on failure + test -d "$_G_tmpdir" || \ + func_fatal_error "cannot create temporary directory '$_G_tmpdir'" + fi + + $ECHO "$_G_tmpdir" +} + + +# func_normal_abspath PATH +# ------------------------ +# Remove doubled-up and trailing slashes, "." path components, +# and cancel out any ".." path components in PATH after making +# it an absolute path. +func_normal_abspath () +{ + $debug_cmd + + # These SED scripts presuppose an absolute path with a trailing slash. + _G_pathcar='s|^/\([^/]*\).*$|\1|' + _G_pathcdr='s|^/[^/]*||' + _G_removedotparts=':dotsl + s|/\./|/|g + t dotsl + s|/\.$|/|' + _G_collapseslashes='s|/\{1,\}|/|g' + _G_finalslash='s|/*$|/|' + + # Start from root dir and reassemble the path. + func_normal_abspath_result= + func_normal_abspath_tpath=$1 + func_normal_abspath_altnamespace= + case $func_normal_abspath_tpath in + "") + # Empty path, that just means $cwd. + func_stripname '' '/' "`pwd`" + func_normal_abspath_result=$func_stripname_result + return + ;; + # The next three entries are used to spot a run of precisely + # two leading slashes without using negated character classes; + # we take advantage of case's first-match behaviour. + ///*) + # Unusual form of absolute path, do nothing. + ;; + //*) + # Not necessarily an ordinary path; POSIX reserves leading '//' + # and for example Cygwin uses it to access remote file shares + # over CIFS/SMB, so we conserve a leading double slash if found. + func_normal_abspath_altnamespace=/ + ;; + /*) + # Absolute path, do nothing. + ;; + *) + # Relative path, prepend $cwd. + func_normal_abspath_tpath=`pwd`/$func_normal_abspath_tpath + ;; + esac + + # Cancel out all the simple stuff to save iterations. We also want + # the path to end with a slash for ease of parsing, so make sure + # there is one (and only one) here. + func_normal_abspath_tpath=`$ECHO "$func_normal_abspath_tpath" | $SED \ + -e "$_G_removedotparts" -e "$_G_collapseslashes" -e "$_G_finalslash"` + while :; do + # Processed it all yet? + if test / = "$func_normal_abspath_tpath"; then + # If we ascended to the root using ".." the result may be empty now. + if test -z "$func_normal_abspath_result"; then + func_normal_abspath_result=/ + fi + break + fi + func_normal_abspath_tcomponent=`$ECHO "$func_normal_abspath_tpath" | $SED \ + -e "$_G_pathcar"` + func_normal_abspath_tpath=`$ECHO "$func_normal_abspath_tpath" | $SED \ + -e "$_G_pathcdr"` + # Figure out what to do with it + case $func_normal_abspath_tcomponent in + "") + # Trailing empty path component, ignore it. + ;; + ..) + # Parent dir; strip last assembled component from result. + func_dirname "$func_normal_abspath_result" + func_normal_abspath_result=$func_dirname_result + ;; + *) + # Actual path component, append it. + func_append func_normal_abspath_result "/$func_normal_abspath_tcomponent" + ;; + esac + done + # Restore leading double-slash if one was found on entry. + func_normal_abspath_result=$func_normal_abspath_altnamespace$func_normal_abspath_result +} + + +# func_notquiet ARG... +# -------------------- +# Echo program name prefixed message only when not in quiet mode. +func_notquiet () +{ + $debug_cmd + + $opt_quiet || func_echo ${1+"$@"} + + # A bug in bash halts the script if the last line of a function + # fails when set -e is in force, so we need another command to + # work around that: + : +} + + +# func_relative_path SRCDIR DSTDIR +# -------------------------------- +# Set func_relative_path_result to the relative path from SRCDIR to DSTDIR. +func_relative_path () +{ + $debug_cmd + + func_relative_path_result= + func_normal_abspath "$1" + func_relative_path_tlibdir=$func_normal_abspath_result + func_normal_abspath "$2" + func_relative_path_tbindir=$func_normal_abspath_result + + # Ascend the tree starting from libdir + while :; do + # check if we have found a prefix of bindir + case $func_relative_path_tbindir in + $func_relative_path_tlibdir) + # found an exact match + func_relative_path_tcancelled= + break + ;; + $func_relative_path_tlibdir*) + # found a matching prefix + func_stripname "$func_relative_path_tlibdir" '' "$func_relative_path_tbindir" + func_relative_path_tcancelled=$func_stripname_result + if test -z "$func_relative_path_result"; then + func_relative_path_result=. + fi + break + ;; + *) + func_dirname $func_relative_path_tlibdir + func_relative_path_tlibdir=$func_dirname_result + if test -z "$func_relative_path_tlibdir"; then + # Have to descend all the way to the root! + func_relative_path_result=../$func_relative_path_result + func_relative_path_tcancelled=$func_relative_path_tbindir + break + fi + func_relative_path_result=../$func_relative_path_result + ;; + esac + done + + # Now calculate path; take care to avoid doubling-up slashes. + func_stripname '' '/' "$func_relative_path_result" + func_relative_path_result=$func_stripname_result + func_stripname '/' '/' "$func_relative_path_tcancelled" + if test -n "$func_stripname_result"; then + func_append func_relative_path_result "/$func_stripname_result" + fi + + # Normalisation. If bindir is libdir, return '.' else relative path. + if test -n "$func_relative_path_result"; then + func_stripname './' '' "$func_relative_path_result" + func_relative_path_result=$func_stripname_result + fi + + test -n "$func_relative_path_result" || func_relative_path_result=. + + : +} + + +# func_quote_for_eval ARG... +# -------------------------- +# Aesthetically quote ARGs to be evaled later. +# This function returns two values: +# i) func_quote_for_eval_result +# double-quoted, suitable for a subsequent eval +# ii) func_quote_for_eval_unquoted_result +# has all characters that are still active within double +# quotes backslashified. +func_quote_for_eval () +{ + $debug_cmd + + func_quote_for_eval_unquoted_result= + func_quote_for_eval_result= + while test 0 -lt $#; do + case $1 in + *[\\\`\"\$]*) + _G_unquoted_arg=`printf '%s\n' "$1" |$SED "$sed_quote_subst"` ;; + *) + _G_unquoted_arg=$1 ;; + esac + if test -n "$func_quote_for_eval_unquoted_result"; then + func_append func_quote_for_eval_unquoted_result " $_G_unquoted_arg" + else + func_append func_quote_for_eval_unquoted_result "$_G_unquoted_arg" + fi + + case $_G_unquoted_arg in + # Double-quote args containing shell metacharacters to delay + # word splitting, command substitution and variable expansion + # for a subsequent eval. + # Many Bourne shells cannot handle close brackets correctly + # in scan sets, so we specify it separately. + *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*|"") + _G_quoted_arg=\"$_G_unquoted_arg\" + ;; + *) + _G_quoted_arg=$_G_unquoted_arg + ;; + esac + + if test -n "$func_quote_for_eval_result"; then + func_append func_quote_for_eval_result " $_G_quoted_arg" + else + func_append func_quote_for_eval_result "$_G_quoted_arg" + fi + shift + done +} + + +# func_quote_for_expand ARG +# ------------------------- +# Aesthetically quote ARG to be evaled later; same as above, +# but do not quote variable references. +func_quote_for_expand () +{ + $debug_cmd + + case $1 in + *[\\\`\"]*) + _G_arg=`$ECHO "$1" | $SED \ + -e "$sed_double_quote_subst" -e "$sed_double_backslash"` ;; + *) + _G_arg=$1 ;; + esac + + case $_G_arg in + # Double-quote args containing shell metacharacters to delay + # word splitting and command substitution for a subsequent eval. + # Many Bourne shells cannot handle close brackets correctly + # in scan sets, so we specify it separately. + *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*|"") + _G_arg=\"$_G_arg\" + ;; + esac + + func_quote_for_expand_result=$_G_arg +} + + +# func_stripname PREFIX SUFFIX NAME +# --------------------------------- +# strip PREFIX and SUFFIX from NAME, and store in func_stripname_result. +# PREFIX and SUFFIX must not contain globbing or regex special +# characters, hashes, percent signs, but SUFFIX may contain a leading +# dot (in which case that matches only a dot). +if test yes = "$_G_HAVE_XSI_OPS"; then + eval 'func_stripname () + { + $debug_cmd + + # pdksh 5.2.14 does not do ${X%$Y} correctly if both X and Y are + # positional parameters, so assign one to ordinary variable first. + func_stripname_result=$3 + func_stripname_result=${func_stripname_result#"$1"} + func_stripname_result=${func_stripname_result%"$2"} + }' +else + func_stripname () + { + $debug_cmd + + case $2 in + .*) func_stripname_result=`$ECHO "$3" | $SED -e "s%^$1%%" -e "s%\\\\$2\$%%"`;; + *) func_stripname_result=`$ECHO "$3" | $SED -e "s%^$1%%" -e "s%$2\$%%"`;; + esac + } +fi + + +# func_show_eval CMD [FAIL_EXP] +# ----------------------------- +# Unless opt_quiet is true, then output CMD. Then, if opt_dryrun is +# not true, evaluate CMD. If the evaluation of CMD fails, and FAIL_EXP +# is given, then evaluate it. +func_show_eval () +{ + $debug_cmd + + _G_cmd=$1 + _G_fail_exp=${2-':'} + + func_quote_for_expand "$_G_cmd" + eval "func_notquiet $func_quote_for_expand_result" + + $opt_dry_run || { + eval "$_G_cmd" + _G_status=$? + if test 0 -ne "$_G_status"; then + eval "(exit $_G_status); $_G_fail_exp" + fi + } +} + + +# func_show_eval_locale CMD [FAIL_EXP] +# ------------------------------------ +# Unless opt_quiet is true, then output CMD. Then, if opt_dryrun is +# not true, evaluate CMD. If the evaluation of CMD fails, and FAIL_EXP +# is given, then evaluate it. Use the saved locale for evaluation. +func_show_eval_locale () +{ + $debug_cmd + + _G_cmd=$1 + _G_fail_exp=${2-':'} + + $opt_quiet || { + func_quote_for_expand "$_G_cmd" + eval "func_echo $func_quote_for_expand_result" + } + + $opt_dry_run || { + eval "$_G_user_locale + $_G_cmd" + _G_status=$? + eval "$_G_safe_locale" + if test 0 -ne "$_G_status"; then + eval "(exit $_G_status); $_G_fail_exp" + fi + } +} + + +# func_tr_sh +# ---------- +# Turn $1 into a string suitable for a shell variable name. +# Result is stored in $func_tr_sh_result. All characters +# not in the set a-zA-Z0-9_ are replaced with '_'. Further, +# if $1 begins with a digit, a '_' is prepended as well. +func_tr_sh () +{ + $debug_cmd + + case $1 in + [0-9]* | *[!a-zA-Z0-9_]*) + func_tr_sh_result=`$ECHO "$1" | $SED -e 's/^\([0-9]\)/_\1/' -e 's/[^a-zA-Z0-9_]/_/g'` + ;; + * ) + func_tr_sh_result=$1 + ;; + esac +} + + +# func_verbose ARG... +# ------------------- +# Echo program name prefixed message in verbose mode only. +func_verbose () +{ + $debug_cmd + + $opt_verbose && func_echo "$*" + + : +} + + +# func_warn_and_continue ARG... +# ----------------------------- +# Echo program name prefixed warning message to standard error. +func_warn_and_continue () +{ + $debug_cmd + + $require_term_colors + + func_echo_infix_1 "${tc_red}warning$tc_reset" "$*" >&2 +} + + +# func_warning CATEGORY ARG... +# ---------------------------- +# Echo program name prefixed warning message to standard error. Warning +# messages can be filtered according to CATEGORY, where this function +# elides messages where CATEGORY is not listed in the global variable +# 'opt_warning_types'. +func_warning () +{ + $debug_cmd + + # CATEGORY must be in the warning_categories list! + case " $warning_categories " in + *" $1 "*) ;; + *) func_internal_error "invalid warning category '$1'" ;; + esac + + _G_category=$1 + shift + + case " $opt_warning_types " in + *" $_G_category "*) $warning_func ${1+"$@"} ;; + esac +} + + +# func_sort_ver VER1 VER2 +# ----------------------- +# 'sort -V' is not generally available. +# Note this deviates from the version comparison in automake +# in that it treats 1.5 < 1.5.0, and treats 1.4.4a < 1.4-p3a +# but this should suffice as we won't be specifying old +# version formats or redundant trailing .0 in bootstrap.conf. +# If we did want full compatibility then we should probably +# use m4_version_compare from autoconf. +func_sort_ver () +{ + $debug_cmd + + printf '%s\n%s\n' "$1" "$2" \ + | sort -t. -k 1,1n -k 2,2n -k 3,3n -k 4,4n -k 5,5n -k 6,6n -k 7,7n -k 8,8n -k 9,9n +} + +# func_lt_ver PREV CURR +# --------------------- +# Return true if PREV and CURR are in the correct order according to +# func_sort_ver, otherwise false. Use it like this: +# +# func_lt_ver "$prev_ver" "$proposed_ver" || func_fatal_error "..." +func_lt_ver () +{ + $debug_cmd + + test "x$1" = x`func_sort_ver "$1" "$2" | $SED 1q` +} + + +# Local variables: +# mode: shell-script +# sh-indentation: 2 +# eval: (add-hook 'before-save-hook 'time-stamp) +# time-stamp-pattern: "10/scriptversion=%:y-%02m-%02d.%02H; # UTC" +# time-stamp-time-zone: "UTC" +# End: +#! /bin/sh + +# Set a version string for this script. +scriptversion=2014-01-07.03; # UTC + +# A portable, pluggable option parser for Bourne shell. +# Written by Gary V. Vaughan, 2010 + +# Copyright (C) 2010-2015 Free Software Foundation, Inc. +# This is free software; see the source for copying conditions. There is NO +# warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# Please report bugs or propose patches to gary@gnu.org. + + +## ------ ## +## Usage. ## +## ------ ## + +# This file is a library for parsing options in your shell scripts along +# with assorted other useful supporting features that you can make use +# of too. +# +# For the simplest scripts you might need only: +# +# #!/bin/sh +# . relative/path/to/funclib.sh +# . relative/path/to/options-parser +# scriptversion=1.0 +# func_options ${1+"$@"} +# eval set dummy "$func_options_result"; shift +# ...rest of your script... +# +# In order for the '--version' option to work, you will need to have a +# suitably formatted comment like the one at the top of this file +# starting with '# Written by ' and ending with '# warranty; '. +# +# For '-h' and '--help' to work, you will also need a one line +# description of your script's purpose in a comment directly above the +# '# Written by ' line, like the one at the top of this file. +# +# The default options also support '--debug', which will turn on shell +# execution tracing (see the comment above debug_cmd below for another +# use), and '--verbose' and the func_verbose function to allow your script +# to display verbose messages only when your user has specified +# '--verbose'. +# +# After sourcing this file, you can plug processing for additional +# options by amending the variables from the 'Configuration' section +# below, and following the instructions in the 'Option parsing' +# section further down. + +## -------------- ## +## Configuration. ## +## -------------- ## + +# You should override these variables in your script after sourcing this +# file so that they reflect the customisations you have added to the +# option parser. + +# The usage line for option parsing errors and the start of '-h' and +# '--help' output messages. You can embed shell variables for delayed +# expansion at the time the message is displayed, but you will need to +# quote other shell meta-characters carefully to prevent them being +# expanded when the contents are evaled. +usage='$progpath [OPTION]...' + +# Short help message in response to '-h' and '--help'. Add to this or +# override it after sourcing this library to reflect the full set of +# options your script accepts. +usage_message="\ + --debug enable verbose shell tracing + -W, --warnings=CATEGORY + report the warnings falling in CATEGORY [all] + -v, --verbose verbosely report processing + --version print version information and exit + -h, --help print short or long help message and exit +" + +# Additional text appended to 'usage_message' in response to '--help'. +long_help_message=" +Warning categories include: + 'all' show all warnings + 'none' turn off all the warnings + 'error' warnings are treated as fatal errors" + +# Help message printed before fatal option parsing errors. +fatal_help="Try '\$progname --help' for more information." + + + +## ------------------------- ## +## Hook function management. ## +## ------------------------- ## + +# This section contains functions for adding, removing, and running hooks +# to the main code. A hook is just a named list of of function, that can +# be run in order later on. + +# func_hookable FUNC_NAME +# ----------------------- +# Declare that FUNC_NAME will run hooks added with +# 'func_add_hook FUNC_NAME ...'. +func_hookable () +{ + $debug_cmd + + func_append hookable_fns " $1" +} + + +# func_add_hook FUNC_NAME HOOK_FUNC +# --------------------------------- +# Request that FUNC_NAME call HOOK_FUNC before it returns. FUNC_NAME must +# first have been declared "hookable" by a call to 'func_hookable'. +func_add_hook () +{ + $debug_cmd + + case " $hookable_fns " in + *" $1 "*) ;; + *) func_fatal_error "'$1' does not accept hook functions." ;; + esac + + eval func_append ${1}_hooks '" $2"' +} + + +# func_remove_hook FUNC_NAME HOOK_FUNC +# ------------------------------------ +# Remove HOOK_FUNC from the list of functions called by FUNC_NAME. +func_remove_hook () +{ + $debug_cmd + + eval ${1}_hooks='`$ECHO "\$'$1'_hooks" |$SED "s| '$2'||"`' +} + + +# func_run_hooks FUNC_NAME [ARG]... +# --------------------------------- +# Run all hook functions registered to FUNC_NAME. +# It is assumed that the list of hook functions contains nothing more +# than a whitespace-delimited list of legal shell function names, and +# no effort is wasted trying to catch shell meta-characters or preserve +# whitespace. +func_run_hooks () +{ + $debug_cmd + + case " $hookable_fns " in + *" $1 "*) ;; + *) func_fatal_error "'$1' does not support hook funcions.n" ;; + esac + + eval _G_hook_fns=\$$1_hooks; shift + + for _G_hook in $_G_hook_fns; do + eval $_G_hook '"$@"' + + # store returned options list back into positional + # parameters for next 'cmd' execution. + eval _G_hook_result=\$${_G_hook}_result + eval set dummy "$_G_hook_result"; shift + done + + func_quote_for_eval ${1+"$@"} + func_run_hooks_result=$func_quote_for_eval_result +} + + + +## --------------- ## +## Option parsing. ## +## --------------- ## + +# In order to add your own option parsing hooks, you must accept the +# full positional parameter list in your hook function, remove any +# options that you action, and then pass back the remaining unprocessed +# options in '_result', escaped suitably for +# 'eval'. Like this: +# +# my_options_prep () +# { +# $debug_cmd +# +# # Extend the existing usage message. +# usage_message=$usage_message' +# -s, --silent don'\''t print informational messages +# ' +# +# func_quote_for_eval ${1+"$@"} +# my_options_prep_result=$func_quote_for_eval_result +# } +# func_add_hook func_options_prep my_options_prep +# +# +# my_silent_option () +# { +# $debug_cmd +# +# # Note that for efficiency, we parse as many options as we can +# # recognise in a loop before passing the remainder back to the +# # caller on the first unrecognised argument we encounter. +# while test $# -gt 0; do +# opt=$1; shift +# case $opt in +# --silent|-s) opt_silent=: ;; +# # Separate non-argument short options: +# -s*) func_split_short_opt "$_G_opt" +# set dummy "$func_split_short_opt_name" \ +# "-$func_split_short_opt_arg" ${1+"$@"} +# shift +# ;; +# *) set dummy "$_G_opt" "$*"; shift; break ;; +# esac +# done +# +# func_quote_for_eval ${1+"$@"} +# my_silent_option_result=$func_quote_for_eval_result +# } +# func_add_hook func_parse_options my_silent_option +# +# +# my_option_validation () +# { +# $debug_cmd +# +# $opt_silent && $opt_verbose && func_fatal_help "\ +# '--silent' and '--verbose' options are mutually exclusive." +# +# func_quote_for_eval ${1+"$@"} +# my_option_validation_result=$func_quote_for_eval_result +# } +# func_add_hook func_validate_options my_option_validation +# +# You'll alse need to manually amend $usage_message to reflect the extra +# options you parse. It's preferable to append if you can, so that +# multiple option parsing hooks can be added safely. + + +# func_options [ARG]... +# --------------------- +# All the functions called inside func_options are hookable. See the +# individual implementations for details. +func_hookable func_options +func_options () +{ + $debug_cmd + + func_options_prep ${1+"$@"} + eval func_parse_options \ + ${func_options_prep_result+"$func_options_prep_result"} + eval func_validate_options \ + ${func_parse_options_result+"$func_parse_options_result"} + + eval func_run_hooks func_options \ + ${func_validate_options_result+"$func_validate_options_result"} + + # save modified positional parameters for caller + func_options_result=$func_run_hooks_result +} + + +# func_options_prep [ARG]... +# -------------------------- +# All initialisations required before starting the option parse loop. +# Note that when calling hook functions, we pass through the list of +# positional parameters. If a hook function modifies that list, and +# needs to propogate that back to rest of this script, then the complete +# modified list must be put in 'func_run_hooks_result' before +# returning. +func_hookable func_options_prep +func_options_prep () +{ + $debug_cmd + + # Option defaults: + opt_verbose=false + opt_warning_types= + + func_run_hooks func_options_prep ${1+"$@"} + + # save modified positional parameters for caller + func_options_prep_result=$func_run_hooks_result +} + + +# func_parse_options [ARG]... +# --------------------------- +# The main option parsing loop. +func_hookable func_parse_options +func_parse_options () +{ + $debug_cmd + + func_parse_options_result= + + # this just eases exit handling + while test $# -gt 0; do + # Defer to hook functions for initial option parsing, so they + # get priority in the event of reusing an option name. + func_run_hooks func_parse_options ${1+"$@"} + + # Adjust func_parse_options positional parameters to match + eval set dummy "$func_run_hooks_result"; shift + + # Break out of the loop if we already parsed every option. + test $# -gt 0 || break + + _G_opt=$1 + shift + case $_G_opt in + --debug|-x) debug_cmd='set -x' + func_echo "enabling shell trace mode" + $debug_cmd + ;; + + --no-warnings|--no-warning|--no-warn) + set dummy --warnings none ${1+"$@"} + shift + ;; + + --warnings|--warning|-W) + test $# = 0 && func_missing_arg $_G_opt && break + case " $warning_categories $1" in + *" $1 "*) + # trailing space prevents matching last $1 above + func_append_uniq opt_warning_types " $1" + ;; + *all) + opt_warning_types=$warning_categories + ;; + *none) + opt_warning_types=none + warning_func=: + ;; + *error) + opt_warning_types=$warning_categories + warning_func=func_fatal_error + ;; + *) + func_fatal_error \ + "unsupported warning category: '$1'" + ;; + esac + shift + ;; + + --verbose|-v) opt_verbose=: ;; + --version) func_version ;; + -\?|-h) func_usage ;; + --help) func_help ;; + + # Separate optargs to long options (plugins may need this): + --*=*) func_split_equals "$_G_opt" + set dummy "$func_split_equals_lhs" \ + "$func_split_equals_rhs" ${1+"$@"} + shift + ;; + + # Separate optargs to short options: + -W*) + func_split_short_opt "$_G_opt" + set dummy "$func_split_short_opt_name" \ + "$func_split_short_opt_arg" ${1+"$@"} + shift + ;; + + # Separate non-argument short options: + -\?*|-h*|-v*|-x*) + func_split_short_opt "$_G_opt" + set dummy "$func_split_short_opt_name" \ + "-$func_split_short_opt_arg" ${1+"$@"} + shift + ;; + + --) break ;; + -*) func_fatal_help "unrecognised option: '$_G_opt'" ;; + *) set dummy "$_G_opt" ${1+"$@"}; shift; break ;; + esac + done + + # save modified positional parameters for caller + func_quote_for_eval ${1+"$@"} + func_parse_options_result=$func_quote_for_eval_result +} + + +# func_validate_options [ARG]... +# ------------------------------ +# Perform any sanity checks on option settings and/or unconsumed +# arguments. +func_hookable func_validate_options +func_validate_options () +{ + $debug_cmd + + # Display all warnings if -W was not given. + test -n "$opt_warning_types" || opt_warning_types=" $warning_categories" + + func_run_hooks func_validate_options ${1+"$@"} + + # Bail if the options were screwed! + $exit_cmd $EXIT_FAILURE + + # save modified positional parameters for caller + func_validate_options_result=$func_run_hooks_result +} + + + +## ----------------- ## +## Helper functions. ## +## ----------------- ## + +# This section contains the helper functions used by the rest of the +# hookable option parser framework in ascii-betical order. + + +# func_fatal_help ARG... +# ---------------------- +# Echo program name prefixed message to standard error, followed by +# a help hint, and exit. +func_fatal_help () +{ + $debug_cmd + + eval \$ECHO \""Usage: $usage"\" + eval \$ECHO \""$fatal_help"\" + func_error ${1+"$@"} + exit $EXIT_FAILURE +} + + +# func_help +# --------- +# Echo long help message to standard output and exit. +func_help () +{ + $debug_cmd + + func_usage_message + $ECHO "$long_help_message" + exit 0 +} + + +# func_missing_arg ARGNAME +# ------------------------ +# Echo program name prefixed message to standard error and set global +# exit_cmd. +func_missing_arg () +{ + $debug_cmd + + func_error "Missing argument for '$1'." + exit_cmd=exit +} + + +# func_split_equals STRING +# ------------------------ +# Set func_split_equals_lhs and func_split_equals_rhs shell variables after +# splitting STRING at the '=' sign. +test -z "$_G_HAVE_XSI_OPS" \ + && (eval 'x=a/b/c; + test 5aa/bb/cc = "${#x}${x%%/*}${x%/*}${x#*/}${x##*/}"') 2>/dev/null \ + && _G_HAVE_XSI_OPS=yes + +if test yes = "$_G_HAVE_XSI_OPS" +then + # This is an XSI compatible shell, allowing a faster implementation... + eval 'func_split_equals () + { + $debug_cmd + + func_split_equals_lhs=${1%%=*} + func_split_equals_rhs=${1#*=} + test "x$func_split_equals_lhs" = "x$1" \ + && func_split_equals_rhs= + }' +else + # ...otherwise fall back to using expr, which is often a shell builtin. + func_split_equals () + { + $debug_cmd + + func_split_equals_lhs=`expr "x$1" : 'x\([^=]*\)'` + func_split_equals_rhs= + test "x$func_split_equals_lhs" = "x$1" \ + || func_split_equals_rhs=`expr "x$1" : 'x[^=]*=\(.*\)$'` + } +fi #func_split_equals + + +# func_split_short_opt SHORTOPT +# ----------------------------- +# Set func_split_short_opt_name and func_split_short_opt_arg shell +# variables after splitting SHORTOPT after the 2nd character. +if test yes = "$_G_HAVE_XSI_OPS" +then + # This is an XSI compatible shell, allowing a faster implementation... + eval 'func_split_short_opt () + { + $debug_cmd + + func_split_short_opt_arg=${1#??} + func_split_short_opt_name=${1%"$func_split_short_opt_arg"} + }' +else + # ...otherwise fall back to using expr, which is often a shell builtin. + func_split_short_opt () + { + $debug_cmd + + func_split_short_opt_name=`expr "x$1" : 'x-\(.\)'` + func_split_short_opt_arg=`expr "x$1" : 'x-.\(.*\)$'` + } +fi #func_split_short_opt + + +# func_usage +# ---------- +# Echo short help message to standard output and exit. +func_usage () +{ + $debug_cmd + + func_usage_message + $ECHO "Run '$progname --help |${PAGER-more}' for full usage" + exit 0 +} + + +# func_usage_message +# ------------------ +# Echo short help message to standard output. +func_usage_message () +{ + $debug_cmd + + eval \$ECHO \""Usage: $usage"\" + echo + $SED -n 's|^# || + /^Written by/{ + x;p;x + } + h + /^Written by/q' < "$progpath" + echo + eval \$ECHO \""$usage_message"\" +} + + +# func_version +# ------------ +# Echo version message to standard output and exit. +func_version () +{ + $debug_cmd + + printf '%s\n' "$progname $scriptversion" + $SED -n ' + /(C)/!b go + :more + /\./!{ + N + s|\n# | | + b more + } + :go + /^# Written by /,/# warranty; / { + s|^# || + s|^# *$|| + s|\((C)\)[ 0-9,-]*[ ,-]\([1-9][0-9]* \)|\1 \2| + p + } + /^# Written by / { + s|^# || + p + } + /^warranty; /q' < "$progpath" + + exit $? +} + + +# Local variables: +# mode: shell-script +# sh-indentation: 2 +# eval: (add-hook 'before-save-hook 'time-stamp) +# time-stamp-pattern: "10/scriptversion=%:y-%02m-%02d.%02H; # UTC" +# time-stamp-time-zone: "UTC" +# End: + +# Set a version string. +scriptversion='(GNU libtool) 2.4.6' + + +# func_echo ARG... +# ---------------- +# Libtool also displays the current mode in messages, so override +# funclib.sh func_echo with this custom definition. +func_echo () +{ + $debug_cmd + + _G_message=$* + + func_echo_IFS=$IFS + IFS=$nl + for _G_line in $_G_message; do + IFS=$func_echo_IFS + $ECHO "$progname${opt_mode+: $opt_mode}: $_G_line" + done + IFS=$func_echo_IFS +} + + +# func_warning ARG... +# ------------------- +# Libtool warnings are not categorized, so override funclib.sh +# func_warning with this simpler definition. +func_warning () +{ + $debug_cmd + + $warning_func ${1+"$@"} +} + + +## ---------------- ## +## Options parsing. ## +## ---------------- ## + +# Hook in the functions to make sure our own options are parsed during +# the option parsing loop. + +usage='$progpath [OPTION]... [MODE-ARG]...' + +# Short help message in response to '-h'. +usage_message="Options: + --config show all configuration variables + --debug enable verbose shell tracing + -n, --dry-run display commands without modifying any files + --features display basic configuration information and exit + --mode=MODE use operation mode MODE + --no-warnings equivalent to '-Wnone' + --preserve-dup-deps don't remove duplicate dependency libraries + --quiet, --silent don't print informational messages + --tag=TAG use configuration variables from tag TAG + -v, --verbose print more informational messages than default + --version print version information + -W, --warnings=CATEGORY report the warnings falling in CATEGORY [all] + -h, --help, --help-all print short, long, or detailed help message +" + +# Additional text appended to 'usage_message' in response to '--help'. +func_help () +{ + $debug_cmd + + func_usage_message + $ECHO "$long_help_message + +MODE must be one of the following: + + clean remove files from the build directory + compile compile a source file into a libtool object + execute automatically set library path, then run a program + finish complete the installation of libtool libraries + install install libraries or executables + link create a library or an executable + uninstall remove libraries from an installed directory + +MODE-ARGS vary depending on the MODE. When passed as first option, +'--mode=MODE' may be abbreviated as 'MODE' or a unique abbreviation of that. +Try '$progname --help --mode=MODE' for a more detailed description of MODE. + +When reporting a bug, please describe a test case to reproduce it and +include the following information: + + host-triplet: $host + shell: $SHELL + compiler: $LTCC + compiler flags: $LTCFLAGS + linker: $LD (gnu? $with_gnu_ld) + version: $progname (GNU libtool) 2.4.6 + automake: `($AUTOMAKE --version) 2>/dev/null |$SED 1q` + autoconf: `($AUTOCONF --version) 2>/dev/null |$SED 1q` + +Report bugs to . +GNU libtool home page: . +General help using GNU software: ." + exit 0 +} + + +# func_lo2o OBJECT-NAME +# --------------------- +# Transform OBJECT-NAME from a '.lo' suffix to the platform specific +# object suffix. + +lo2o=s/\\.lo\$/.$objext/ +o2lo=s/\\.$objext\$/.lo/ + +if test yes = "$_G_HAVE_XSI_OPS"; then + eval 'func_lo2o () + { + case $1 in + *.lo) func_lo2o_result=${1%.lo}.$objext ;; + * ) func_lo2o_result=$1 ;; + esac + }' + + # func_xform LIBOBJ-OR-SOURCE + # --------------------------- + # Transform LIBOBJ-OR-SOURCE from a '.o' or '.c' (or otherwise) + # suffix to a '.lo' libtool-object suffix. + eval 'func_xform () + { + func_xform_result=${1%.*}.lo + }' +else + # ...otherwise fall back to using sed. + func_lo2o () + { + func_lo2o_result=`$ECHO "$1" | $SED "$lo2o"` + } + + func_xform () + { + func_xform_result=`$ECHO "$1" | $SED 's|\.[^.]*$|.lo|'` + } +fi + + +# func_fatal_configuration ARG... +# ------------------------------- +# Echo program name prefixed message to standard error, followed by +# a configuration failure hint, and exit. +func_fatal_configuration () +{ + func__fatal_error ${1+"$@"} \ + "See the $PACKAGE documentation for more information." \ + "Fatal configuration error." +} + + +# func_config +# ----------- +# Display the configuration for all the tags in this script. +func_config () +{ + re_begincf='^# ### BEGIN LIBTOOL' + re_endcf='^# ### END LIBTOOL' + + # Default configuration. + $SED "1,/$re_begincf CONFIG/d;/$re_endcf CONFIG/,\$d" < "$progpath" + + # Now print the configurations for the tags. + for tagname in $taglist; do + $SED -n "/$re_begincf TAG CONFIG: $tagname\$/,/$re_endcf TAG CONFIG: $tagname\$/p" < "$progpath" + done + + exit $? +} + + +# func_features +# ------------- +# Display the features supported by this script. +func_features () +{ + echo "host: $host" + if test yes = "$build_libtool_libs"; then + echo "enable shared libraries" + else + echo "disable shared libraries" + fi + if test yes = "$build_old_libs"; then + echo "enable static libraries" + else + echo "disable static libraries" + fi + + exit $? +} + + +# func_enable_tag TAGNAME +# ----------------------- +# Verify that TAGNAME is valid, and either flag an error and exit, or +# enable the TAGNAME tag. We also add TAGNAME to the global $taglist +# variable here. +func_enable_tag () +{ + # Global variable: + tagname=$1 + + re_begincf="^# ### BEGIN LIBTOOL TAG CONFIG: $tagname\$" + re_endcf="^# ### END LIBTOOL TAG CONFIG: $tagname\$" + sed_extractcf=/$re_begincf/,/$re_endcf/p + + # Validate tagname. + case $tagname in + *[!-_A-Za-z0-9,/]*) + func_fatal_error "invalid tag name: $tagname" + ;; + esac + + # Don't test for the "default" C tag, as we know it's + # there but not specially marked. + case $tagname in + CC) ;; + *) + if $GREP "$re_begincf" "$progpath" >/dev/null 2>&1; then + taglist="$taglist $tagname" + + # Evaluate the configuration. Be careful to quote the path + # and the sed script, to avoid splitting on whitespace, but + # also don't use non-portable quotes within backquotes within + # quotes we have to do it in 2 steps: + extractedcf=`$SED -n -e "$sed_extractcf" < "$progpath"` + eval "$extractedcf" + else + func_error "ignoring unknown tag $tagname" + fi + ;; + esac +} + + +# func_check_version_match +# ------------------------ +# Ensure that we are using m4 macros, and libtool script from the same +# release of libtool. +func_check_version_match () +{ + if test "$package_revision" != "$macro_revision"; then + if test "$VERSION" != "$macro_version"; then + if test -z "$macro_version"; then + cat >&2 <<_LT_EOF +$progname: Version mismatch error. This is $PACKAGE $VERSION, but the +$progname: definition of this LT_INIT comes from an older release. +$progname: You should recreate aclocal.m4 with macros from $PACKAGE $VERSION +$progname: and run autoconf again. +_LT_EOF + else + cat >&2 <<_LT_EOF +$progname: Version mismatch error. This is $PACKAGE $VERSION, but the +$progname: definition of this LT_INIT comes from $PACKAGE $macro_version. +$progname: You should recreate aclocal.m4 with macros from $PACKAGE $VERSION +$progname: and run autoconf again. +_LT_EOF + fi + else + cat >&2 <<_LT_EOF +$progname: Version mismatch error. This is $PACKAGE $VERSION, revision $package_revision, +$progname: but the definition of this LT_INIT comes from revision $macro_revision. +$progname: You should recreate aclocal.m4 with macros from revision $package_revision +$progname: of $PACKAGE $VERSION and run autoconf again. +_LT_EOF + fi + + exit $EXIT_MISMATCH + fi +} + + +# libtool_options_prep [ARG]... +# ----------------------------- +# Preparation for options parsed by libtool. +libtool_options_prep () +{ + $debug_mode + + # Option defaults: + opt_config=false + opt_dlopen= + opt_dry_run=false + opt_help=false + opt_mode= + opt_preserve_dup_deps=false + opt_quiet=false + + nonopt= + preserve_args= + + # Shorthand for --mode=foo, only valid as the first argument + case $1 in + clean|clea|cle|cl) + shift; set dummy --mode clean ${1+"$@"}; shift + ;; + compile|compil|compi|comp|com|co|c) + shift; set dummy --mode compile ${1+"$@"}; shift + ;; + execute|execut|execu|exec|exe|ex|e) + shift; set dummy --mode execute ${1+"$@"}; shift + ;; + finish|finis|fini|fin|fi|f) + shift; set dummy --mode finish ${1+"$@"}; shift + ;; + install|instal|insta|inst|ins|in|i) + shift; set dummy --mode install ${1+"$@"}; shift + ;; + link|lin|li|l) + shift; set dummy --mode link ${1+"$@"}; shift + ;; + uninstall|uninstal|uninsta|uninst|unins|unin|uni|un|u) + shift; set dummy --mode uninstall ${1+"$@"}; shift + ;; + esac + + # Pass back the list of options. + func_quote_for_eval ${1+"$@"} + libtool_options_prep_result=$func_quote_for_eval_result +} +func_add_hook func_options_prep libtool_options_prep + + +# libtool_parse_options [ARG]... +# --------------------------------- +# Provide handling for libtool specific options. +libtool_parse_options () +{ + $debug_cmd + + # Perform our own loop to consume as many options as possible in + # each iteration. + while test $# -gt 0; do + _G_opt=$1 + shift + case $_G_opt in + --dry-run|--dryrun|-n) + opt_dry_run=: + ;; + + --config) func_config ;; + + --dlopen|-dlopen) + opt_dlopen="${opt_dlopen+$opt_dlopen +}$1" + shift + ;; + + --preserve-dup-deps) + opt_preserve_dup_deps=: ;; + + --features) func_features ;; + + --finish) set dummy --mode finish ${1+"$@"}; shift ;; + + --help) opt_help=: ;; + + --help-all) opt_help=': help-all' ;; + + --mode) test $# = 0 && func_missing_arg $_G_opt && break + opt_mode=$1 + case $1 in + # Valid mode arguments: + clean|compile|execute|finish|install|link|relink|uninstall) ;; + + # Catch anything else as an error + *) func_error "invalid argument for $_G_opt" + exit_cmd=exit + break + ;; + esac + shift + ;; + + --no-silent|--no-quiet) + opt_quiet=false + func_append preserve_args " $_G_opt" + ;; + + --no-warnings|--no-warning|--no-warn) + opt_warning=false + func_append preserve_args " $_G_opt" + ;; + + --no-verbose) + opt_verbose=false + func_append preserve_args " $_G_opt" + ;; + + --silent|--quiet) + opt_quiet=: + opt_verbose=false + func_append preserve_args " $_G_opt" + ;; + + --tag) test $# = 0 && func_missing_arg $_G_opt && break + opt_tag=$1 + func_append preserve_args " $_G_opt $1" + func_enable_tag "$1" + shift + ;; + + --verbose|-v) opt_quiet=false + opt_verbose=: + func_append preserve_args " $_G_opt" + ;; + + # An option not handled by this hook function: + *) set dummy "$_G_opt" ${1+"$@"}; shift; break ;; + esac + done + + + # save modified positional parameters for caller + func_quote_for_eval ${1+"$@"} + libtool_parse_options_result=$func_quote_for_eval_result +} +func_add_hook func_parse_options libtool_parse_options + + + +# libtool_validate_options [ARG]... +# --------------------------------- +# Perform any sanity checks on option settings and/or unconsumed +# arguments. +libtool_validate_options () +{ + # save first non-option argument + if test 0 -lt $#; then + nonopt=$1 + shift + fi + + # preserve --debug + test : = "$debug_cmd" || func_append preserve_args " --debug" + + case $host in + # Solaris2 added to fix http://debbugs.gnu.org/cgi/bugreport.cgi?bug=16452 + # see also: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=59788 + *cygwin* | *mingw* | *pw32* | *cegcc* | *solaris2* | *os2*) + # don't eliminate duplications in $postdeps and $predeps + opt_duplicate_compiler_generated_deps=: + ;; + *) + opt_duplicate_compiler_generated_deps=$opt_preserve_dup_deps + ;; + esac + + $opt_help || { + # Sanity checks first: + func_check_version_match + + test yes != "$build_libtool_libs" \ + && test yes != "$build_old_libs" \ + && func_fatal_configuration "not configured to build any kind of library" + + # Darwin sucks + eval std_shrext=\"$shrext_cmds\" + + # Only execute mode is allowed to have -dlopen flags. + if test -n "$opt_dlopen" && test execute != "$opt_mode"; then + func_error "unrecognized option '-dlopen'" + $ECHO "$help" 1>&2 + exit $EXIT_FAILURE + fi + + # Change the help message to a mode-specific one. + generic_help=$help + help="Try '$progname --help --mode=$opt_mode' for more information." + } + + # Pass back the unparsed argument list + func_quote_for_eval ${1+"$@"} + libtool_validate_options_result=$func_quote_for_eval_result +} +func_add_hook func_validate_options libtool_validate_options + + +# Process options as early as possible so that --help and --version +# can return quickly. +func_options ${1+"$@"} +eval set dummy "$func_options_result"; shift + + + +## ----------- ## +## Main. ## +## ----------- ## + +magic='%%%MAGIC variable%%%' +magic_exe='%%%MAGIC EXE variable%%%' + +# Global variables. +extracted_archives= +extracted_serial=0 + +# If this variable is set in any of the actions, the command in it +# will be execed at the end. This prevents here-documents from being +# left over by shells. +exec_cmd= + + +# A function that is used when there is no print builtin or printf. +func_fallback_echo () +{ + eval 'cat <<_LTECHO_EOF +$1 +_LTECHO_EOF' +} + +# func_generated_by_libtool +# True iff stdin has been generated by Libtool. This function is only +# a basic sanity check; it will hardly flush out determined imposters. +func_generated_by_libtool_p () +{ + $GREP "^# Generated by .*$PACKAGE" > /dev/null 2>&1 +} + +# func_lalib_p file +# True iff FILE is a libtool '.la' library or '.lo' object file. +# This function is only a basic sanity check; it will hardly flush out +# determined imposters. +func_lalib_p () +{ + test -f "$1" && + $SED -e 4q "$1" 2>/dev/null | func_generated_by_libtool_p +} + +# func_lalib_unsafe_p file +# True iff FILE is a libtool '.la' library or '.lo' object file. +# This function implements the same check as func_lalib_p without +# resorting to external programs. To this end, it redirects stdin and +# closes it afterwards, without saving the original file descriptor. +# As a safety measure, use it only where a negative result would be +# fatal anyway. Works if 'file' does not exist. +func_lalib_unsafe_p () +{ + lalib_p=no + if test -f "$1" && test -r "$1" && exec 5<&0 <"$1"; then + for lalib_p_l in 1 2 3 4 + do + read lalib_p_line + case $lalib_p_line in + \#\ Generated\ by\ *$PACKAGE* ) lalib_p=yes; break;; + esac + done + exec 0<&5 5<&- + fi + test yes = "$lalib_p" +} + +# func_ltwrapper_script_p file +# True iff FILE is a libtool wrapper script +# This function is only a basic sanity check; it will hardly flush out +# determined imposters. +func_ltwrapper_script_p () +{ + test -f "$1" && + $lt_truncate_bin < "$1" 2>/dev/null | func_generated_by_libtool_p +} + +# func_ltwrapper_executable_p file +# True iff FILE is a libtool wrapper executable +# This function is only a basic sanity check; it will hardly flush out +# determined imposters. +func_ltwrapper_executable_p () +{ + func_ltwrapper_exec_suffix= + case $1 in + *.exe) ;; + *) func_ltwrapper_exec_suffix=.exe ;; + esac + $GREP "$magic_exe" "$1$func_ltwrapper_exec_suffix" >/dev/null 2>&1 +} + +# func_ltwrapper_scriptname file +# Assumes file is an ltwrapper_executable +# uses $file to determine the appropriate filename for a +# temporary ltwrapper_script. +func_ltwrapper_scriptname () +{ + func_dirname_and_basename "$1" "" "." + func_stripname '' '.exe' "$func_basename_result" + func_ltwrapper_scriptname_result=$func_dirname_result/$objdir/${func_stripname_result}_ltshwrapper +} + +# func_ltwrapper_p file +# True iff FILE is a libtool wrapper script or wrapper executable +# This function is only a basic sanity check; it will hardly flush out +# determined imposters. +func_ltwrapper_p () +{ + func_ltwrapper_script_p "$1" || func_ltwrapper_executable_p "$1" +} + + +# func_execute_cmds commands fail_cmd +# Execute tilde-delimited COMMANDS. +# If FAIL_CMD is given, eval that upon failure. +# FAIL_CMD may read-access the current command in variable CMD! +func_execute_cmds () +{ + $debug_cmd + + save_ifs=$IFS; IFS='~' + for cmd in $1; do + IFS=$sp$nl + eval cmd=\"$cmd\" + IFS=$save_ifs + func_show_eval "$cmd" "${2-:}" + done + IFS=$save_ifs +} + + +# func_source file +# Source FILE, adding directory component if necessary. +# Note that it is not necessary on cygwin/mingw to append a dot to +# FILE even if both FILE and FILE.exe exist: automatic-append-.exe +# behavior happens only for exec(3), not for open(2)! Also, sourcing +# 'FILE.' does not work on cygwin managed mounts. +func_source () +{ + $debug_cmd + + case $1 in + */* | *\\*) . "$1" ;; + *) . "./$1" ;; + esac +} + + +# func_resolve_sysroot PATH +# Replace a leading = in PATH with a sysroot. Store the result into +# func_resolve_sysroot_result +func_resolve_sysroot () +{ + func_resolve_sysroot_result=$1 + case $func_resolve_sysroot_result in + =*) + func_stripname '=' '' "$func_resolve_sysroot_result" + func_resolve_sysroot_result=$lt_sysroot$func_stripname_result + ;; + esac +} + +# func_replace_sysroot PATH +# If PATH begins with the sysroot, replace it with = and +# store the result into func_replace_sysroot_result. +func_replace_sysroot () +{ + case $lt_sysroot:$1 in + ?*:"$lt_sysroot"*) + func_stripname "$lt_sysroot" '' "$1" + func_replace_sysroot_result='='$func_stripname_result + ;; + *) + # Including no sysroot. + func_replace_sysroot_result=$1 + ;; + esac +} + +# func_infer_tag arg +# Infer tagged configuration to use if any are available and +# if one wasn't chosen via the "--tag" command line option. +# Only attempt this if the compiler in the base compile +# command doesn't match the default compiler. +# arg is usually of the form 'gcc ...' +func_infer_tag () +{ + $debug_cmd + + if test -n "$available_tags" && test -z "$tagname"; then + CC_quoted= + for arg in $CC; do + func_append_quoted CC_quoted "$arg" + done + CC_expanded=`func_echo_all $CC` + CC_quoted_expanded=`func_echo_all $CC_quoted` + case $@ in + # Blanks in the command may have been stripped by the calling shell, + # but not from the CC environment variable when configure was run. + " $CC "* | "$CC "* | " $CC_expanded "* | "$CC_expanded "* | \ + " $CC_quoted"* | "$CC_quoted "* | " $CC_quoted_expanded "* | "$CC_quoted_expanded "*) ;; + # Blanks at the start of $base_compile will cause this to fail + # if we don't check for them as well. + *) + for z in $available_tags; do + if $GREP "^# ### BEGIN LIBTOOL TAG CONFIG: $z$" < "$progpath" > /dev/null; then + # Evaluate the configuration. + eval "`$SED -n -e '/^# ### BEGIN LIBTOOL TAG CONFIG: '$z'$/,/^# ### END LIBTOOL TAG CONFIG: '$z'$/p' < $progpath`" + CC_quoted= + for arg in $CC; do + # Double-quote args containing other shell metacharacters. + func_append_quoted CC_quoted "$arg" + done + CC_expanded=`func_echo_all $CC` + CC_quoted_expanded=`func_echo_all $CC_quoted` + case "$@ " in + " $CC "* | "$CC "* | " $CC_expanded "* | "$CC_expanded "* | \ + " $CC_quoted"* | "$CC_quoted "* | " $CC_quoted_expanded "* | "$CC_quoted_expanded "*) + # The compiler in the base compile command matches + # the one in the tagged configuration. + # Assume this is the tagged configuration we want. + tagname=$z + break + ;; + esac + fi + done + # If $tagname still isn't set, then no tagged configuration + # was found and let the user know that the "--tag" command + # line option must be used. + if test -z "$tagname"; then + func_echo "unable to infer tagged configuration" + func_fatal_error "specify a tag with '--tag'" +# else +# func_verbose "using $tagname tagged configuration" + fi + ;; + esac + fi +} + + + +# func_write_libtool_object output_name pic_name nonpic_name +# Create a libtool object file (analogous to a ".la" file), +# but don't create it if we're doing a dry run. +func_write_libtool_object () +{ + write_libobj=$1 + if test yes = "$build_libtool_libs"; then + write_lobj=\'$2\' + else + write_lobj=none + fi + + if test yes = "$build_old_libs"; then + write_oldobj=\'$3\' + else + write_oldobj=none + fi + + $opt_dry_run || { + cat >${write_libobj}T </dev/null` + if test "$?" -eq 0 && test -n "$func_convert_core_file_wine_to_w32_tmp"; then + func_convert_core_file_wine_to_w32_result=`$ECHO "$func_convert_core_file_wine_to_w32_tmp" | + $SED -e "$sed_naive_backslashify"` + else + func_convert_core_file_wine_to_w32_result= + fi + fi +} +# end: func_convert_core_file_wine_to_w32 + + +# func_convert_core_path_wine_to_w32 ARG +# Helper function used by path conversion functions when $build is *nix, and +# $host is mingw, cygwin, or some other w32 environment. Relies on a correctly +# configured wine environment available, with the winepath program in $build's +# $PATH. Assumes ARG has no leading or trailing path separator characters. +# +# ARG is path to be converted from $build format to win32. +# Result is available in $func_convert_core_path_wine_to_w32_result. +# Unconvertible file (directory) names in ARG are skipped; if no directory names +# are convertible, then the result may be empty. +func_convert_core_path_wine_to_w32 () +{ + $debug_cmd + + # unfortunately, winepath doesn't convert paths, only file names + func_convert_core_path_wine_to_w32_result= + if test -n "$1"; then + oldIFS=$IFS + IFS=: + for func_convert_core_path_wine_to_w32_f in $1; do + IFS=$oldIFS + func_convert_core_file_wine_to_w32 "$func_convert_core_path_wine_to_w32_f" + if test -n "$func_convert_core_file_wine_to_w32_result"; then + if test -z "$func_convert_core_path_wine_to_w32_result"; then + func_convert_core_path_wine_to_w32_result=$func_convert_core_file_wine_to_w32_result + else + func_append func_convert_core_path_wine_to_w32_result ";$func_convert_core_file_wine_to_w32_result" + fi + fi + done + IFS=$oldIFS + fi +} +# end: func_convert_core_path_wine_to_w32 + + +# func_cygpath ARGS... +# Wrapper around calling the cygpath program via LT_CYGPATH. This is used when +# when (1) $build is *nix and Cygwin is hosted via a wine environment; or (2) +# $build is MSYS and $host is Cygwin, or (3) $build is Cygwin. In case (1) or +# (2), returns the Cygwin file name or path in func_cygpath_result (input +# file name or path is assumed to be in w32 format, as previously converted +# from $build's *nix or MSYS format). In case (3), returns the w32 file name +# or path in func_cygpath_result (input file name or path is assumed to be in +# Cygwin format). Returns an empty string on error. +# +# ARGS are passed to cygpath, with the last one being the file name or path to +# be converted. +# +# Specify the absolute *nix (or w32) name to cygpath in the LT_CYGPATH +# environment variable; do not put it in $PATH. +func_cygpath () +{ + $debug_cmd + + if test -n "$LT_CYGPATH" && test -f "$LT_CYGPATH"; then + func_cygpath_result=`$LT_CYGPATH "$@" 2>/dev/null` + if test "$?" -ne 0; then + # on failure, ensure result is empty + func_cygpath_result= + fi + else + func_cygpath_result= + func_error "LT_CYGPATH is empty or specifies non-existent file: '$LT_CYGPATH'" + fi +} +#end: func_cygpath + + +# func_convert_core_msys_to_w32 ARG +# Convert file name or path ARG from MSYS format to w32 format. Return +# result in func_convert_core_msys_to_w32_result. +func_convert_core_msys_to_w32 () +{ + $debug_cmd + + # awkward: cmd appends spaces to result + func_convert_core_msys_to_w32_result=`( cmd //c echo "$1" ) 2>/dev/null | + $SED -e 's/[ ]*$//' -e "$sed_naive_backslashify"` +} +#end: func_convert_core_msys_to_w32 + + +# func_convert_file_check ARG1 ARG2 +# Verify that ARG1 (a file name in $build format) was converted to $host +# format in ARG2. Otherwise, emit an error message, but continue (resetting +# func_to_host_file_result to ARG1). +func_convert_file_check () +{ + $debug_cmd + + if test -z "$2" && test -n "$1"; then + func_error "Could not determine host file name corresponding to" + func_error " '$1'" + func_error "Continuing, but uninstalled executables may not work." + # Fallback: + func_to_host_file_result=$1 + fi +} +# end func_convert_file_check + + +# func_convert_path_check FROM_PATHSEP TO_PATHSEP FROM_PATH TO_PATH +# Verify that FROM_PATH (a path in $build format) was converted to $host +# format in TO_PATH. Otherwise, emit an error message, but continue, resetting +# func_to_host_file_result to a simplistic fallback value (see below). +func_convert_path_check () +{ + $debug_cmd + + if test -z "$4" && test -n "$3"; then + func_error "Could not determine the host path corresponding to" + func_error " '$3'" + func_error "Continuing, but uninstalled executables may not work." + # Fallback. This is a deliberately simplistic "conversion" and + # should not be "improved". See libtool.info. + if test "x$1" != "x$2"; then + lt_replace_pathsep_chars="s|$1|$2|g" + func_to_host_path_result=`echo "$3" | + $SED -e "$lt_replace_pathsep_chars"` + else + func_to_host_path_result=$3 + fi + fi +} +# end func_convert_path_check + + +# func_convert_path_front_back_pathsep FRONTPAT BACKPAT REPL ORIG +# Modifies func_to_host_path_result by prepending REPL if ORIG matches FRONTPAT +# and appending REPL if ORIG matches BACKPAT. +func_convert_path_front_back_pathsep () +{ + $debug_cmd + + case $4 in + $1 ) func_to_host_path_result=$3$func_to_host_path_result + ;; + esac + case $4 in + $2 ) func_append func_to_host_path_result "$3" + ;; + esac +} +# end func_convert_path_front_back_pathsep + + +################################################## +# $build to $host FILE NAME CONVERSION FUNCTIONS # +################################################## +# invoked via '$to_host_file_cmd ARG' +# +# In each case, ARG is the path to be converted from $build to $host format. +# Result will be available in $func_to_host_file_result. + + +# func_to_host_file ARG +# Converts the file name ARG from $build format to $host format. Return result +# in func_to_host_file_result. +func_to_host_file () +{ + $debug_cmd + + $to_host_file_cmd "$1" +} +# end func_to_host_file + + +# func_to_tool_file ARG LAZY +# converts the file name ARG from $build format to toolchain format. Return +# result in func_to_tool_file_result. If the conversion in use is listed +# in (the comma separated) LAZY, no conversion takes place. +func_to_tool_file () +{ + $debug_cmd + + case ,$2, in + *,"$to_tool_file_cmd",*) + func_to_tool_file_result=$1 + ;; + *) + $to_tool_file_cmd "$1" + func_to_tool_file_result=$func_to_host_file_result + ;; + esac +} +# end func_to_tool_file + + +# func_convert_file_noop ARG +# Copy ARG to func_to_host_file_result. +func_convert_file_noop () +{ + func_to_host_file_result=$1 +} +# end func_convert_file_noop + + +# func_convert_file_msys_to_w32 ARG +# Convert file name ARG from (mingw) MSYS to (mingw) w32 format; automatic +# conversion to w32 is not available inside the cwrapper. Returns result in +# func_to_host_file_result. +func_convert_file_msys_to_w32 () +{ + $debug_cmd + + func_to_host_file_result=$1 + if test -n "$1"; then + func_convert_core_msys_to_w32 "$1" + func_to_host_file_result=$func_convert_core_msys_to_w32_result + fi + func_convert_file_check "$1" "$func_to_host_file_result" +} +# end func_convert_file_msys_to_w32 + + +# func_convert_file_cygwin_to_w32 ARG +# Convert file name ARG from Cygwin to w32 format. Returns result in +# func_to_host_file_result. +func_convert_file_cygwin_to_w32 () +{ + $debug_cmd + + func_to_host_file_result=$1 + if test -n "$1"; then + # because $build is cygwin, we call "the" cygpath in $PATH; no need to use + # LT_CYGPATH in this case. + func_to_host_file_result=`cygpath -m "$1"` + fi + func_convert_file_check "$1" "$func_to_host_file_result" +} +# end func_convert_file_cygwin_to_w32 + + +# func_convert_file_nix_to_w32 ARG +# Convert file name ARG from *nix to w32 format. Requires a wine environment +# and a working winepath. Returns result in func_to_host_file_result. +func_convert_file_nix_to_w32 () +{ + $debug_cmd + + func_to_host_file_result=$1 + if test -n "$1"; then + func_convert_core_file_wine_to_w32 "$1" + func_to_host_file_result=$func_convert_core_file_wine_to_w32_result + fi + func_convert_file_check "$1" "$func_to_host_file_result" +} +# end func_convert_file_nix_to_w32 + + +# func_convert_file_msys_to_cygwin ARG +# Convert file name ARG from MSYS to Cygwin format. Requires LT_CYGPATH set. +# Returns result in func_to_host_file_result. +func_convert_file_msys_to_cygwin () +{ + $debug_cmd + + func_to_host_file_result=$1 + if test -n "$1"; then + func_convert_core_msys_to_w32 "$1" + func_cygpath -u "$func_convert_core_msys_to_w32_result" + func_to_host_file_result=$func_cygpath_result + fi + func_convert_file_check "$1" "$func_to_host_file_result" +} +# end func_convert_file_msys_to_cygwin + + +# func_convert_file_nix_to_cygwin ARG +# Convert file name ARG from *nix to Cygwin format. Requires Cygwin installed +# in a wine environment, working winepath, and LT_CYGPATH set. Returns result +# in func_to_host_file_result. +func_convert_file_nix_to_cygwin () +{ + $debug_cmd + + func_to_host_file_result=$1 + if test -n "$1"; then + # convert from *nix to w32, then use cygpath to convert from w32 to cygwin. + func_convert_core_file_wine_to_w32 "$1" + func_cygpath -u "$func_convert_core_file_wine_to_w32_result" + func_to_host_file_result=$func_cygpath_result + fi + func_convert_file_check "$1" "$func_to_host_file_result" +} +# end func_convert_file_nix_to_cygwin + + +############################################# +# $build to $host PATH CONVERSION FUNCTIONS # +############################################# +# invoked via '$to_host_path_cmd ARG' +# +# In each case, ARG is the path to be converted from $build to $host format. +# The result will be available in $func_to_host_path_result. +# +# Path separators are also converted from $build format to $host format. If +# ARG begins or ends with a path separator character, it is preserved (but +# converted to $host format) on output. +# +# All path conversion functions are named using the following convention: +# file name conversion function : func_convert_file_X_to_Y () +# path conversion function : func_convert_path_X_to_Y () +# where, for any given $build/$host combination the 'X_to_Y' value is the +# same. If conversion functions are added for new $build/$host combinations, +# the two new functions must follow this pattern, or func_init_to_host_path_cmd +# will break. + + +# func_init_to_host_path_cmd +# Ensures that function "pointer" variable $to_host_path_cmd is set to the +# appropriate value, based on the value of $to_host_file_cmd. +to_host_path_cmd= +func_init_to_host_path_cmd () +{ + $debug_cmd + + if test -z "$to_host_path_cmd"; then + func_stripname 'func_convert_file_' '' "$to_host_file_cmd" + to_host_path_cmd=func_convert_path_$func_stripname_result + fi +} + + +# func_to_host_path ARG +# Converts the path ARG from $build format to $host format. Return result +# in func_to_host_path_result. +func_to_host_path () +{ + $debug_cmd + + func_init_to_host_path_cmd + $to_host_path_cmd "$1" +} +# end func_to_host_path + + +# func_convert_path_noop ARG +# Copy ARG to func_to_host_path_result. +func_convert_path_noop () +{ + func_to_host_path_result=$1 +} +# end func_convert_path_noop + + +# func_convert_path_msys_to_w32 ARG +# Convert path ARG from (mingw) MSYS to (mingw) w32 format; automatic +# conversion to w32 is not available inside the cwrapper. Returns result in +# func_to_host_path_result. +func_convert_path_msys_to_w32 () +{ + $debug_cmd + + func_to_host_path_result=$1 + if test -n "$1"; then + # Remove leading and trailing path separator characters from ARG. MSYS + # behavior is inconsistent here; cygpath turns them into '.;' and ';.'; + # and winepath ignores them completely. + func_stripname : : "$1" + func_to_host_path_tmp1=$func_stripname_result + func_convert_core_msys_to_w32 "$func_to_host_path_tmp1" + func_to_host_path_result=$func_convert_core_msys_to_w32_result + func_convert_path_check : ";" \ + "$func_to_host_path_tmp1" "$func_to_host_path_result" + func_convert_path_front_back_pathsep ":*" "*:" ";" "$1" + fi +} +# end func_convert_path_msys_to_w32 + + +# func_convert_path_cygwin_to_w32 ARG +# Convert path ARG from Cygwin to w32 format. Returns result in +# func_to_host_file_result. +func_convert_path_cygwin_to_w32 () +{ + $debug_cmd + + func_to_host_path_result=$1 + if test -n "$1"; then + # See func_convert_path_msys_to_w32: + func_stripname : : "$1" + func_to_host_path_tmp1=$func_stripname_result + func_to_host_path_result=`cygpath -m -p "$func_to_host_path_tmp1"` + func_convert_path_check : ";" \ + "$func_to_host_path_tmp1" "$func_to_host_path_result" + func_convert_path_front_back_pathsep ":*" "*:" ";" "$1" + fi +} +# end func_convert_path_cygwin_to_w32 + + +# func_convert_path_nix_to_w32 ARG +# Convert path ARG from *nix to w32 format. Requires a wine environment and +# a working winepath. Returns result in func_to_host_file_result. +func_convert_path_nix_to_w32 () +{ + $debug_cmd + + func_to_host_path_result=$1 + if test -n "$1"; then + # See func_convert_path_msys_to_w32: + func_stripname : : "$1" + func_to_host_path_tmp1=$func_stripname_result + func_convert_core_path_wine_to_w32 "$func_to_host_path_tmp1" + func_to_host_path_result=$func_convert_core_path_wine_to_w32_result + func_convert_path_check : ";" \ + "$func_to_host_path_tmp1" "$func_to_host_path_result" + func_convert_path_front_back_pathsep ":*" "*:" ";" "$1" + fi +} +# end func_convert_path_nix_to_w32 + + +# func_convert_path_msys_to_cygwin ARG +# Convert path ARG from MSYS to Cygwin format. Requires LT_CYGPATH set. +# Returns result in func_to_host_file_result. +func_convert_path_msys_to_cygwin () +{ + $debug_cmd + + func_to_host_path_result=$1 + if test -n "$1"; then + # See func_convert_path_msys_to_w32: + func_stripname : : "$1" + func_to_host_path_tmp1=$func_stripname_result + func_convert_core_msys_to_w32 "$func_to_host_path_tmp1" + func_cygpath -u -p "$func_convert_core_msys_to_w32_result" + func_to_host_path_result=$func_cygpath_result + func_convert_path_check : : \ + "$func_to_host_path_tmp1" "$func_to_host_path_result" + func_convert_path_front_back_pathsep ":*" "*:" : "$1" + fi +} +# end func_convert_path_msys_to_cygwin + + +# func_convert_path_nix_to_cygwin ARG +# Convert path ARG from *nix to Cygwin format. Requires Cygwin installed in a +# a wine environment, working winepath, and LT_CYGPATH set. Returns result in +# func_to_host_file_result. +func_convert_path_nix_to_cygwin () +{ + $debug_cmd + + func_to_host_path_result=$1 + if test -n "$1"; then + # Remove leading and trailing path separator characters from + # ARG. msys behavior is inconsistent here, cygpath turns them + # into '.;' and ';.', and winepath ignores them completely. + func_stripname : : "$1" + func_to_host_path_tmp1=$func_stripname_result + func_convert_core_path_wine_to_w32 "$func_to_host_path_tmp1" + func_cygpath -u -p "$func_convert_core_path_wine_to_w32_result" + func_to_host_path_result=$func_cygpath_result + func_convert_path_check : : \ + "$func_to_host_path_tmp1" "$func_to_host_path_result" + func_convert_path_front_back_pathsep ":*" "*:" : "$1" + fi +} +# end func_convert_path_nix_to_cygwin + + +# func_dll_def_p FILE +# True iff FILE is a Windows DLL '.def' file. +# Keep in sync with _LT_DLL_DEF_P in libtool.m4 +func_dll_def_p () +{ + $debug_cmd + + func_dll_def_p_tmp=`$SED -n \ + -e 's/^[ ]*//' \ + -e '/^\(;.*\)*$/d' \ + -e 's/^\(EXPORTS\|LIBRARY\)\([ ].*\)*$/DEF/p' \ + -e q \ + "$1"` + test DEF = "$func_dll_def_p_tmp" +} + + +# func_mode_compile arg... +func_mode_compile () +{ + $debug_cmd + + # Get the compilation command and the source file. + base_compile= + srcfile=$nonopt # always keep a non-empty value in "srcfile" + suppress_opt=yes + suppress_output= + arg_mode=normal + libobj= + later= + pie_flag= + + for arg + do + case $arg_mode in + arg ) + # do not "continue". Instead, add this to base_compile + lastarg=$arg + arg_mode=normal + ;; + + target ) + libobj=$arg + arg_mode=normal + continue + ;; + + normal ) + # Accept any command-line options. + case $arg in + -o) + test -n "$libobj" && \ + func_fatal_error "you cannot specify '-o' more than once" + arg_mode=target + continue + ;; + + -pie | -fpie | -fPIE) + func_append pie_flag " $arg" + continue + ;; + + -shared | -static | -prefer-pic | -prefer-non-pic) + func_append later " $arg" + continue + ;; + + -no-suppress) + suppress_opt=no + continue + ;; + + -Xcompiler) + arg_mode=arg # the next one goes into the "base_compile" arg list + continue # The current "srcfile" will either be retained or + ;; # replaced later. I would guess that would be a bug. + + -Wc,*) + func_stripname '-Wc,' '' "$arg" + args=$func_stripname_result + lastarg= + save_ifs=$IFS; IFS=, + for arg in $args; do + IFS=$save_ifs + func_append_quoted lastarg "$arg" + done + IFS=$save_ifs + func_stripname ' ' '' "$lastarg" + lastarg=$func_stripname_result + + # Add the arguments to base_compile. + func_append base_compile " $lastarg" + continue + ;; + + *) + # Accept the current argument as the source file. + # The previous "srcfile" becomes the current argument. + # + lastarg=$srcfile + srcfile=$arg + ;; + esac # case $arg + ;; + esac # case $arg_mode + + # Aesthetically quote the previous argument. + func_append_quoted base_compile "$lastarg" + done # for arg + + case $arg_mode in + arg) + func_fatal_error "you must specify an argument for -Xcompile" + ;; + target) + func_fatal_error "you must specify a target with '-o'" + ;; + *) + # Get the name of the library object. + test -z "$libobj" && { + func_basename "$srcfile" + libobj=$func_basename_result + } + ;; + esac + + # Recognize several different file suffixes. + # If the user specifies -o file.o, it is replaced with file.lo + case $libobj in + *.[cCFSifmso] | \ + *.ada | *.adb | *.ads | *.asm | \ + *.c++ | *.cc | *.ii | *.class | *.cpp | *.cxx | \ + *.[fF][09]? | *.for | *.java | *.go | *.obj | *.sx | *.cu | *.cup) + func_xform "$libobj" + libobj=$func_xform_result + ;; + esac + + case $libobj in + *.lo) func_lo2o "$libobj"; obj=$func_lo2o_result ;; + *) + func_fatal_error "cannot determine name of library object from '$libobj'" + ;; + esac + + func_infer_tag $base_compile + + for arg in $later; do + case $arg in + -shared) + test yes = "$build_libtool_libs" \ + || func_fatal_configuration "cannot build a shared library" + build_old_libs=no + continue + ;; + + -static) + build_libtool_libs=no + build_old_libs=yes + continue + ;; + + -prefer-pic) + pic_mode=yes + continue + ;; + + -prefer-non-pic) + pic_mode=no + continue + ;; + esac + done + + func_quote_for_eval "$libobj" + test "X$libobj" != "X$func_quote_for_eval_result" \ + && $ECHO "X$libobj" | $GREP '[]~#^*{};<>?"'"'"' &()|`$[]' \ + && func_warning "libobj name '$libobj' may not contain shell special characters." + func_dirname_and_basename "$obj" "/" "" + objname=$func_basename_result + xdir=$func_dirname_result + lobj=$xdir$objdir/$objname + + test -z "$base_compile" && \ + func_fatal_help "you must specify a compilation command" + + # Delete any leftover library objects. + if test yes = "$build_old_libs"; then + removelist="$obj $lobj $libobj ${libobj}T" + else + removelist="$lobj $libobj ${libobj}T" + fi + + # On Cygwin there's no "real" PIC flag so we must build both object types + case $host_os in + cygwin* | mingw* | pw32* | os2* | cegcc*) + pic_mode=default + ;; + esac + if test no = "$pic_mode" && test pass_all != "$deplibs_check_method"; then + # non-PIC code in shared libraries is not supported + pic_mode=default + fi + + # Calculate the filename of the output object if compiler does + # not support -o with -c + if test no = "$compiler_c_o"; then + output_obj=`$ECHO "$srcfile" | $SED 's%^.*/%%; s%\.[^.]*$%%'`.$objext + lockfile=$output_obj.lock + else + output_obj= + need_locks=no + lockfile= + fi + + # Lock this critical section if it is needed + # We use this script file to make the link, it avoids creating a new file + if test yes = "$need_locks"; then + until $opt_dry_run || ln "$progpath" "$lockfile" 2>/dev/null; do + func_echo "Waiting for $lockfile to be removed" + sleep 2 + done + elif test warn = "$need_locks"; then + if test -f "$lockfile"; then + $ECHO "\ +*** ERROR, $lockfile exists and contains: +`cat $lockfile 2>/dev/null` + +This indicates that another process is trying to use the same +temporary object file, and libtool could not work around it because +your compiler does not support '-c' and '-o' together. If you +repeat this compilation, it may succeed, by chance, but you had better +avoid parallel builds (make -j) in this platform, or get a better +compiler." + + $opt_dry_run || $RM $removelist + exit $EXIT_FAILURE + fi + func_append removelist " $output_obj" + $ECHO "$srcfile" > "$lockfile" + fi + + $opt_dry_run || $RM $removelist + func_append removelist " $lockfile" + trap '$opt_dry_run || $RM $removelist; exit $EXIT_FAILURE' 1 2 15 + + func_to_tool_file "$srcfile" func_convert_file_msys_to_w32 + srcfile=$func_to_tool_file_result + func_quote_for_eval "$srcfile" + qsrcfile=$func_quote_for_eval_result + + # Only build a PIC object if we are building libtool libraries. + if test yes = "$build_libtool_libs"; then + # Without this assignment, base_compile gets emptied. + fbsd_hideous_sh_bug=$base_compile + + if test no != "$pic_mode"; then + command="$base_compile $qsrcfile $pic_flag" + else + # Don't build PIC code + command="$base_compile $qsrcfile" + fi + + func_mkdir_p "$xdir$objdir" + + if test -z "$output_obj"; then + # Place PIC objects in $objdir + func_append command " -o $lobj" + fi + + func_show_eval_locale "$command" \ + 'test -n "$output_obj" && $RM $removelist; exit $EXIT_FAILURE' + + if test warn = "$need_locks" && + test "X`cat $lockfile 2>/dev/null`" != "X$srcfile"; then + $ECHO "\ +*** ERROR, $lockfile contains: +`cat $lockfile 2>/dev/null` + +but it should contain: +$srcfile + +This indicates that another process is trying to use the same +temporary object file, and libtool could not work around it because +your compiler does not support '-c' and '-o' together. If you +repeat this compilation, it may succeed, by chance, but you had better +avoid parallel builds (make -j) in this platform, or get a better +compiler." + + $opt_dry_run || $RM $removelist + exit $EXIT_FAILURE + fi + + # Just move the object if needed, then go on to compile the next one + if test -n "$output_obj" && test "X$output_obj" != "X$lobj"; then + func_show_eval '$MV "$output_obj" "$lobj"' \ + 'error=$?; $opt_dry_run || $RM $removelist; exit $error' + fi + + # Allow error messages only from the first compilation. + if test yes = "$suppress_opt"; then + suppress_output=' >/dev/null 2>&1' + fi + fi + + # Only build a position-dependent object if we build old libraries. + if test yes = "$build_old_libs"; then + if test yes != "$pic_mode"; then + # Don't build PIC code + command="$base_compile $qsrcfile$pie_flag" + else + command="$base_compile $qsrcfile $pic_flag" + fi + if test yes = "$compiler_c_o"; then + func_append command " -o $obj" + fi + + # Suppress compiler output if we already did a PIC compilation. + func_append command "$suppress_output" + func_show_eval_locale "$command" \ + '$opt_dry_run || $RM $removelist; exit $EXIT_FAILURE' + + if test warn = "$need_locks" && + test "X`cat $lockfile 2>/dev/null`" != "X$srcfile"; then + $ECHO "\ +*** ERROR, $lockfile contains: +`cat $lockfile 2>/dev/null` + +but it should contain: +$srcfile + +This indicates that another process is trying to use the same +temporary object file, and libtool could not work around it because +your compiler does not support '-c' and '-o' together. If you +repeat this compilation, it may succeed, by chance, but you had better +avoid parallel builds (make -j) in this platform, or get a better +compiler." + + $opt_dry_run || $RM $removelist + exit $EXIT_FAILURE + fi + + # Just move the object if needed + if test -n "$output_obj" && test "X$output_obj" != "X$obj"; then + func_show_eval '$MV "$output_obj" "$obj"' \ + 'error=$?; $opt_dry_run || $RM $removelist; exit $error' + fi + fi + + $opt_dry_run || { + func_write_libtool_object "$libobj" "$objdir/$objname" "$objname" + + # Unlock the critical section if it was locked + if test no != "$need_locks"; then + removelist=$lockfile + $RM "$lockfile" + fi + } + + exit $EXIT_SUCCESS +} + +$opt_help || { + test compile = "$opt_mode" && func_mode_compile ${1+"$@"} +} + +func_mode_help () +{ + # We need to display help for each of the modes. + case $opt_mode in + "") + # Generic help is extracted from the usage comments + # at the start of this file. + func_help + ;; + + clean) + $ECHO \ +"Usage: $progname [OPTION]... --mode=clean RM [RM-OPTION]... FILE... + +Remove files from the build directory. + +RM is the name of the program to use to delete files associated with each FILE +(typically '/bin/rm'). RM-OPTIONS are options (such as '-f') to be passed +to RM. + +If FILE is a libtool library, object or program, all the files associated +with it are deleted. Otherwise, only FILE itself is deleted using RM." + ;; + + compile) + $ECHO \ +"Usage: $progname [OPTION]... --mode=compile COMPILE-COMMAND... SOURCEFILE + +Compile a source file into a libtool library object. + +This mode accepts the following additional options: + + -o OUTPUT-FILE set the output file name to OUTPUT-FILE + -no-suppress do not suppress compiler output for multiple passes + -prefer-pic try to build PIC objects only + -prefer-non-pic try to build non-PIC objects only + -shared do not build a '.o' file suitable for static linking + -static only build a '.o' file suitable for static linking + -Wc,FLAG pass FLAG directly to the compiler + +COMPILE-COMMAND is a command to be used in creating a 'standard' object file +from the given SOURCEFILE. + +The output file name is determined by removing the directory component from +SOURCEFILE, then substituting the C source code suffix '.c' with the +library object suffix, '.lo'." + ;; + + execute) + $ECHO \ +"Usage: $progname [OPTION]... --mode=execute COMMAND [ARGS]... + +Automatically set library path, then run a program. + +This mode accepts the following additional options: + + -dlopen FILE add the directory containing FILE to the library path + +This mode sets the library path environment variable according to '-dlopen' +flags. + +If any of the ARGS are libtool executable wrappers, then they are translated +into their corresponding uninstalled binary, and any of their required library +directories are added to the library path. + +Then, COMMAND is executed, with ARGS as arguments." + ;; + + finish) + $ECHO \ +"Usage: $progname [OPTION]... --mode=finish [LIBDIR]... + +Complete the installation of libtool libraries. + +Each LIBDIR is a directory that contains libtool libraries. + +The commands that this mode executes may require superuser privileges. Use +the '--dry-run' option if you just want to see what would be executed." + ;; + + install) + $ECHO \ +"Usage: $progname [OPTION]... --mode=install INSTALL-COMMAND... + +Install executables or libraries. + +INSTALL-COMMAND is the installation command. The first component should be +either the 'install' or 'cp' program. + +The following components of INSTALL-COMMAND are treated specially: + + -inst-prefix-dir PREFIX-DIR Use PREFIX-DIR as a staging area for installation + +The rest of the components are interpreted as arguments to that command (only +BSD-compatible install options are recognized)." + ;; + + link) + $ECHO \ +"Usage: $progname [OPTION]... --mode=link LINK-COMMAND... + +Link object files or libraries together to form another library, or to +create an executable program. + +LINK-COMMAND is a command using the C compiler that you would use to create +a program from several object files. + +The following components of LINK-COMMAND are treated specially: + + -all-static do not do any dynamic linking at all + -avoid-version do not add a version suffix if possible + -bindir BINDIR specify path to binaries directory (for systems where + libraries must be found in the PATH setting at runtime) + -dlopen FILE '-dlpreopen' FILE if it cannot be dlopened at runtime + -dlpreopen FILE link in FILE and add its symbols to lt_preloaded_symbols + -export-dynamic allow symbols from OUTPUT-FILE to be resolved with dlsym(3) + -export-symbols SYMFILE + try to export only the symbols listed in SYMFILE + -export-symbols-regex REGEX + try to export only the symbols matching REGEX + -LLIBDIR search LIBDIR for required installed libraries + -lNAME OUTPUT-FILE requires the installed library libNAME + -module build a library that can dlopened + -no-fast-install disable the fast-install mode + -no-install link a not-installable executable + -no-undefined declare that a library does not refer to external symbols + -o OUTPUT-FILE create OUTPUT-FILE from the specified objects + -objectlist FILE use a list of object files found in FILE to specify objects + -os2dllname NAME force a short DLL name on OS/2 (no effect on other OSes) + -precious-files-regex REGEX + don't remove output files matching REGEX + -release RELEASE specify package release information + -rpath LIBDIR the created library will eventually be installed in LIBDIR + -R[ ]LIBDIR add LIBDIR to the runtime path of programs and libraries + -shared only do dynamic linking of libtool libraries + -shrext SUFFIX override the standard shared library file extension + -static do not do any dynamic linking of uninstalled libtool libraries + -static-libtool-libs + do not do any dynamic linking of libtool libraries + -version-info CURRENT[:REVISION[:AGE]] + specify library version info [each variable defaults to 0] + -weak LIBNAME declare that the target provides the LIBNAME interface + -Wc,FLAG + -Xcompiler FLAG pass linker-specific FLAG directly to the compiler + -Wl,FLAG + -Xlinker FLAG pass linker-specific FLAG directly to the linker + -XCClinker FLAG pass link-specific FLAG to the compiler driver (CC) + +All other options (arguments beginning with '-') are ignored. + +Every other argument is treated as a filename. Files ending in '.la' are +treated as uninstalled libtool libraries, other files are standard or library +object files. + +If the OUTPUT-FILE ends in '.la', then a libtool library is created, +only library objects ('.lo' files) may be specified, and '-rpath' is +required, except when creating a convenience library. + +If OUTPUT-FILE ends in '.a' or '.lib', then a standard library is created +using 'ar' and 'ranlib', or on Windows using 'lib'. + +If OUTPUT-FILE ends in '.lo' or '.$objext', then a reloadable object file +is created, otherwise an executable program is created." + ;; + + uninstall) + $ECHO \ +"Usage: $progname [OPTION]... --mode=uninstall RM [RM-OPTION]... FILE... + +Remove libraries from an installation directory. + +RM is the name of the program to use to delete files associated with each FILE +(typically '/bin/rm'). RM-OPTIONS are options (such as '-f') to be passed +to RM. + +If FILE is a libtool library, all the files associated with it are deleted. +Otherwise, only FILE itself is deleted using RM." + ;; + + *) + func_fatal_help "invalid operation mode '$opt_mode'" + ;; + esac + + echo + $ECHO "Try '$progname --help' for more information about other modes." +} + +# Now that we've collected a possible --mode arg, show help if necessary +if $opt_help; then + if test : = "$opt_help"; then + func_mode_help + else + { + func_help noexit + for opt_mode in compile link execute install finish uninstall clean; do + func_mode_help + done + } | $SED -n '1p; 2,$s/^Usage:/ or: /p' + { + func_help noexit + for opt_mode in compile link execute install finish uninstall clean; do + echo + func_mode_help + done + } | + $SED '1d + /^When reporting/,/^Report/{ + H + d + } + $x + /information about other modes/d + /more detailed .*MODE/d + s/^Usage:.*--mode=\([^ ]*\) .*/Description of \1 mode:/' + fi + exit $? +fi + + +# func_mode_execute arg... +func_mode_execute () +{ + $debug_cmd + + # The first argument is the command name. + cmd=$nonopt + test -z "$cmd" && \ + func_fatal_help "you must specify a COMMAND" + + # Handle -dlopen flags immediately. + for file in $opt_dlopen; do + test -f "$file" \ + || func_fatal_help "'$file' is not a file" + + dir= + case $file in + *.la) + func_resolve_sysroot "$file" + file=$func_resolve_sysroot_result + + # Check to see that this really is a libtool archive. + func_lalib_unsafe_p "$file" \ + || func_fatal_help "'$lib' is not a valid libtool archive" + + # Read the libtool library. + dlname= + library_names= + func_source "$file" + + # Skip this library if it cannot be dlopened. + if test -z "$dlname"; then + # Warn if it was a shared library. + test -n "$library_names" && \ + func_warning "'$file' was not linked with '-export-dynamic'" + continue + fi + + func_dirname "$file" "" "." + dir=$func_dirname_result + + if test -f "$dir/$objdir/$dlname"; then + func_append dir "/$objdir" + else + if test ! -f "$dir/$dlname"; then + func_fatal_error "cannot find '$dlname' in '$dir' or '$dir/$objdir'" + fi + fi + ;; + + *.lo) + # Just add the directory containing the .lo file. + func_dirname "$file" "" "." + dir=$func_dirname_result + ;; + + *) + func_warning "'-dlopen' is ignored for non-libtool libraries and objects" + continue + ;; + esac + + # Get the absolute pathname. + absdir=`cd "$dir" && pwd` + test -n "$absdir" && dir=$absdir + + # Now add the directory to shlibpath_var. + if eval "test -z \"\$$shlibpath_var\""; then + eval "$shlibpath_var=\"\$dir\"" + else + eval "$shlibpath_var=\"\$dir:\$$shlibpath_var\"" + fi + done + + # This variable tells wrapper scripts just to set shlibpath_var + # rather than running their programs. + libtool_execute_magic=$magic + + # Check if any of the arguments is a wrapper script. + args= + for file + do + case $file in + -* | *.la | *.lo ) ;; + *) + # Do a test to see if this is really a libtool program. + if func_ltwrapper_script_p "$file"; then + func_source "$file" + # Transform arg to wrapped name. + file=$progdir/$program + elif func_ltwrapper_executable_p "$file"; then + func_ltwrapper_scriptname "$file" + func_source "$func_ltwrapper_scriptname_result" + # Transform arg to wrapped name. + file=$progdir/$program + fi + ;; + esac + # Quote arguments (to preserve shell metacharacters). + func_append_quoted args "$file" + done + + if $opt_dry_run; then + # Display what would be done. + if test -n "$shlibpath_var"; then + eval "\$ECHO \"\$shlibpath_var=\$$shlibpath_var\"" + echo "export $shlibpath_var" + fi + $ECHO "$cmd$args" + exit $EXIT_SUCCESS + else + if test -n "$shlibpath_var"; then + # Export the shlibpath_var. + eval "export $shlibpath_var" + fi + + # Restore saved environment variables + for lt_var in LANG LANGUAGE LC_ALL LC_CTYPE LC_COLLATE LC_MESSAGES + do + eval "if test \"\${save_$lt_var+set}\" = set; then + $lt_var=\$save_$lt_var; export $lt_var + else + $lt_unset $lt_var + fi" + done + + # Now prepare to actually exec the command. + exec_cmd=\$cmd$args + fi +} + +test execute = "$opt_mode" && func_mode_execute ${1+"$@"} + + +# func_mode_finish arg... +func_mode_finish () +{ + $debug_cmd + + libs= + libdirs= + admincmds= + + for opt in "$nonopt" ${1+"$@"} + do + if test -d "$opt"; then + func_append libdirs " $opt" + + elif test -f "$opt"; then + if func_lalib_unsafe_p "$opt"; then + func_append libs " $opt" + else + func_warning "'$opt' is not a valid libtool archive" + fi + + else + func_fatal_error "invalid argument '$opt'" + fi + done + + if test -n "$libs"; then + if test -n "$lt_sysroot"; then + sysroot_regex=`$ECHO "$lt_sysroot" | $SED "$sed_make_literal_regex"` + sysroot_cmd="s/\([ ']\)$sysroot_regex/\1/g;" + else + sysroot_cmd= + fi + + # Remove sysroot references + if $opt_dry_run; then + for lib in $libs; do + echo "removing references to $lt_sysroot and '=' prefixes from $lib" + done + else + tmpdir=`func_mktempdir` + for lib in $libs; do + $SED -e "$sysroot_cmd s/\([ ']-[LR]\)=/\1/g; s/\([ ']\)=/\1/g" $lib \ + > $tmpdir/tmp-la + mv -f $tmpdir/tmp-la $lib + done + ${RM}r "$tmpdir" + fi + fi + + if test -n "$finish_cmds$finish_eval" && test -n "$libdirs"; then + for libdir in $libdirs; do + if test -n "$finish_cmds"; then + # Do each command in the finish commands. + func_execute_cmds "$finish_cmds" 'admincmds="$admincmds +'"$cmd"'"' + fi + if test -n "$finish_eval"; then + # Do the single finish_eval. + eval cmds=\"$finish_eval\" + $opt_dry_run || eval "$cmds" || func_append admincmds " + $cmds" + fi + done + fi + + # Exit here if they wanted silent mode. + $opt_quiet && exit $EXIT_SUCCESS + + if test -n "$finish_cmds$finish_eval" && test -n "$libdirs"; then + echo "----------------------------------------------------------------------" + echo "Libraries have been installed in:" + for libdir in $libdirs; do + $ECHO " $libdir" + done + echo + echo "If you ever happen to want to link against installed libraries" + echo "in a given directory, LIBDIR, you must either use libtool, and" + echo "specify the full pathname of the library, or use the '-LLIBDIR'" + echo "flag during linking and do at least one of the following:" + if test -n "$shlibpath_var"; then + echo " - add LIBDIR to the '$shlibpath_var' environment variable" + echo " during execution" + fi + if test -n "$runpath_var"; then + echo " - add LIBDIR to the '$runpath_var' environment variable" + echo " during linking" + fi + if test -n "$hardcode_libdir_flag_spec"; then + libdir=LIBDIR + eval flag=\"$hardcode_libdir_flag_spec\" + + $ECHO " - use the '$flag' linker flag" + fi + if test -n "$admincmds"; then + $ECHO " - have your system administrator run these commands:$admincmds" + fi + if test -f /etc/ld.so.conf; then + echo " - have your system administrator add LIBDIR to '/etc/ld.so.conf'" + fi + echo + + echo "See any operating system documentation about shared libraries for" + case $host in + solaris2.[6789]|solaris2.1[0-9]) + echo "more information, such as the ld(1), crle(1) and ld.so(8) manual" + echo "pages." + ;; + *) + echo "more information, such as the ld(1) and ld.so(8) manual pages." + ;; + esac + echo "----------------------------------------------------------------------" + fi + exit $EXIT_SUCCESS +} + +test finish = "$opt_mode" && func_mode_finish ${1+"$@"} + + +# func_mode_install arg... +func_mode_install () +{ + $debug_cmd + + # There may be an optional sh(1) argument at the beginning of + # install_prog (especially on Windows NT). + if test "$SHELL" = "$nonopt" || test /bin/sh = "$nonopt" || + # Allow the use of GNU shtool's install command. + case $nonopt in *shtool*) :;; *) false;; esac + then + # Aesthetically quote it. + func_quote_for_eval "$nonopt" + install_prog="$func_quote_for_eval_result " + arg=$1 + shift + else + install_prog= + arg=$nonopt + fi + + # The real first argument should be the name of the installation program. + # Aesthetically quote it. + func_quote_for_eval "$arg" + func_append install_prog "$func_quote_for_eval_result" + install_shared_prog=$install_prog + case " $install_prog " in + *[\\\ /]cp\ *) install_cp=: ;; + *) install_cp=false ;; + esac + + # We need to accept at least all the BSD install flags. + dest= + files= + opts= + prev= + install_type= + isdir=false + stripme= + no_mode=: + for arg + do + arg2= + if test -n "$dest"; then + func_append files " $dest" + dest=$arg + continue + fi + + case $arg in + -d) isdir=: ;; + -f) + if $install_cp; then :; else + prev=$arg + fi + ;; + -g | -m | -o) + prev=$arg + ;; + -s) + stripme=" -s" + continue + ;; + -*) + ;; + *) + # If the previous option needed an argument, then skip it. + if test -n "$prev"; then + if test X-m = "X$prev" && test -n "$install_override_mode"; then + arg2=$install_override_mode + no_mode=false + fi + prev= + else + dest=$arg + continue + fi + ;; + esac + + # Aesthetically quote the argument. + func_quote_for_eval "$arg" + func_append install_prog " $func_quote_for_eval_result" + if test -n "$arg2"; then + func_quote_for_eval "$arg2" + fi + func_append install_shared_prog " $func_quote_for_eval_result" + done + + test -z "$install_prog" && \ + func_fatal_help "you must specify an install program" + + test -n "$prev" && \ + func_fatal_help "the '$prev' option requires an argument" + + if test -n "$install_override_mode" && $no_mode; then + if $install_cp; then :; else + func_quote_for_eval "$install_override_mode" + func_append install_shared_prog " -m $func_quote_for_eval_result" + fi + fi + + if test -z "$files"; then + if test -z "$dest"; then + func_fatal_help "no file or destination specified" + else + func_fatal_help "you must specify a destination" + fi + fi + + # Strip any trailing slash from the destination. + func_stripname '' '/' "$dest" + dest=$func_stripname_result + + # Check to see that the destination is a directory. + test -d "$dest" && isdir=: + if $isdir; then + destdir=$dest + destname= + else + func_dirname_and_basename "$dest" "" "." + destdir=$func_dirname_result + destname=$func_basename_result + + # Not a directory, so check to see that there is only one file specified. + set dummy $files; shift + test "$#" -gt 1 && \ + func_fatal_help "'$dest' is not a directory" + fi + case $destdir in + [\\/]* | [A-Za-z]:[\\/]*) ;; + *) + for file in $files; do + case $file in + *.lo) ;; + *) + func_fatal_help "'$destdir' must be an absolute directory name" + ;; + esac + done + ;; + esac + + # This variable tells wrapper scripts just to set variables rather + # than running their programs. + libtool_install_magic=$magic + + staticlibs= + future_libdirs= + current_libdirs= + for file in $files; do + + # Do each installation. + case $file in + *.$libext) + # Do the static libraries later. + func_append staticlibs " $file" + ;; + + *.la) + func_resolve_sysroot "$file" + file=$func_resolve_sysroot_result + + # Check to see that this really is a libtool archive. + func_lalib_unsafe_p "$file" \ + || func_fatal_help "'$file' is not a valid libtool archive" + + library_names= + old_library= + relink_command= + func_source "$file" + + # Add the libdir to current_libdirs if it is the destination. + if test "X$destdir" = "X$libdir"; then + case "$current_libdirs " in + *" $libdir "*) ;; + *) func_append current_libdirs " $libdir" ;; + esac + else + # Note the libdir as a future libdir. + case "$future_libdirs " in + *" $libdir "*) ;; + *) func_append future_libdirs " $libdir" ;; + esac + fi + + func_dirname "$file" "/" "" + dir=$func_dirname_result + func_append dir "$objdir" + + if test -n "$relink_command"; then + # Determine the prefix the user has applied to our future dir. + inst_prefix_dir=`$ECHO "$destdir" | $SED -e "s%$libdir\$%%"` + + # Don't allow the user to place us outside of our expected + # location b/c this prevents finding dependent libraries that + # are installed to the same prefix. + # At present, this check doesn't affect windows .dll's that + # are installed into $libdir/../bin (currently, that works fine) + # but it's something to keep an eye on. + test "$inst_prefix_dir" = "$destdir" && \ + func_fatal_error "error: cannot install '$file' to a directory not ending in $libdir" + + if test -n "$inst_prefix_dir"; then + # Stick the inst_prefix_dir data into the link command. + relink_command=`$ECHO "$relink_command" | $SED "s%@inst_prefix_dir@%-inst-prefix-dir $inst_prefix_dir%"` + else + relink_command=`$ECHO "$relink_command" | $SED "s%@inst_prefix_dir@%%"` + fi + + func_warning "relinking '$file'" + func_show_eval "$relink_command" \ + 'func_fatal_error "error: relink '\''$file'\'' with the above command before installing it"' + fi + + # See the names of the shared library. + set dummy $library_names; shift + if test -n "$1"; then + realname=$1 + shift + + srcname=$realname + test -n "$relink_command" && srcname=${realname}T + + # Install the shared library and build the symlinks. + func_show_eval "$install_shared_prog $dir/$srcname $destdir/$realname" \ + 'exit $?' + tstripme=$stripme + case $host_os in + cygwin* | mingw* | pw32* | cegcc*) + case $realname in + *.dll.a) + tstripme= + ;; + esac + ;; + os2*) + case $realname in + *_dll.a) + tstripme= + ;; + esac + ;; + esac + if test -n "$tstripme" && test -n "$striplib"; then + func_show_eval "$striplib $destdir/$realname" 'exit $?' + fi + + if test "$#" -gt 0; then + # Delete the old symlinks, and create new ones. + # Try 'ln -sf' first, because the 'ln' binary might depend on + # the symlink we replace! Solaris /bin/ln does not understand -f, + # so we also need to try rm && ln -s. + for linkname + do + test "$linkname" != "$realname" \ + && func_show_eval "(cd $destdir && { $LN_S -f $realname $linkname || { $RM $linkname && $LN_S $realname $linkname; }; })" + done + fi + + # Do each command in the postinstall commands. + lib=$destdir/$realname + func_execute_cmds "$postinstall_cmds" 'exit $?' + fi + + # Install the pseudo-library for information purposes. + func_basename "$file" + name=$func_basename_result + instname=$dir/${name}i + func_show_eval "$install_prog $instname $destdir/$name" 'exit $?' + + # Maybe install the static library, too. + test -n "$old_library" && func_append staticlibs " $dir/$old_library" + ;; + + *.lo) + # Install (i.e. copy) a libtool object. + + # Figure out destination file name, if it wasn't already specified. + if test -n "$destname"; then + destfile=$destdir/$destname + else + func_basename "$file" + destfile=$func_basename_result + destfile=$destdir/$destfile + fi + + # Deduce the name of the destination old-style object file. + case $destfile in + *.lo) + func_lo2o "$destfile" + staticdest=$func_lo2o_result + ;; + *.$objext) + staticdest=$destfile + destfile= + ;; + *) + func_fatal_help "cannot copy a libtool object to '$destfile'" + ;; + esac + + # Install the libtool object if requested. + test -n "$destfile" && \ + func_show_eval "$install_prog $file $destfile" 'exit $?' + + # Install the old object if enabled. + if test yes = "$build_old_libs"; then + # Deduce the name of the old-style object file. + func_lo2o "$file" + staticobj=$func_lo2o_result + func_show_eval "$install_prog \$staticobj \$staticdest" 'exit $?' + fi + exit $EXIT_SUCCESS + ;; + + *) + # Figure out destination file name, if it wasn't already specified. + if test -n "$destname"; then + destfile=$destdir/$destname + else + func_basename "$file" + destfile=$func_basename_result + destfile=$destdir/$destfile + fi + + # If the file is missing, and there is a .exe on the end, strip it + # because it is most likely a libtool script we actually want to + # install + stripped_ext= + case $file in + *.exe) + if test ! -f "$file"; then + func_stripname '' '.exe' "$file" + file=$func_stripname_result + stripped_ext=.exe + fi + ;; + esac + + # Do a test to see if this is really a libtool program. + case $host in + *cygwin* | *mingw*) + if func_ltwrapper_executable_p "$file"; then + func_ltwrapper_scriptname "$file" + wrapper=$func_ltwrapper_scriptname_result + else + func_stripname '' '.exe' "$file" + wrapper=$func_stripname_result + fi + ;; + *) + wrapper=$file + ;; + esac + if func_ltwrapper_script_p "$wrapper"; then + notinst_deplibs= + relink_command= + + func_source "$wrapper" + + # Check the variables that should have been set. + test -z "$generated_by_libtool_version" && \ + func_fatal_error "invalid libtool wrapper script '$wrapper'" + + finalize=: + for lib in $notinst_deplibs; do + # Check to see that each library is installed. + libdir= + if test -f "$lib"; then + func_source "$lib" + fi + libfile=$libdir/`$ECHO "$lib" | $SED 's%^.*/%%g'` + if test -n "$libdir" && test ! -f "$libfile"; then + func_warning "'$lib' has not been installed in '$libdir'" + finalize=false + fi + done + + relink_command= + func_source "$wrapper" + + outputname= + if test no = "$fast_install" && test -n "$relink_command"; then + $opt_dry_run || { + if $finalize; then + tmpdir=`func_mktempdir` + func_basename "$file$stripped_ext" + file=$func_basename_result + outputname=$tmpdir/$file + # Replace the output file specification. + relink_command=`$ECHO "$relink_command" | $SED 's%@OUTPUT@%'"$outputname"'%g'` + + $opt_quiet || { + func_quote_for_expand "$relink_command" + eval "func_echo $func_quote_for_expand_result" + } + if eval "$relink_command"; then : + else + func_error "error: relink '$file' with the above command before installing it" + $opt_dry_run || ${RM}r "$tmpdir" + continue + fi + file=$outputname + else + func_warning "cannot relink '$file'" + fi + } + else + # Install the binary that we compiled earlier. + file=`$ECHO "$file$stripped_ext" | $SED "s%\([^/]*\)$%$objdir/\1%"` + fi + fi + + # remove .exe since cygwin /usr/bin/install will append another + # one anyway + case $install_prog,$host in + */usr/bin/install*,*cygwin*) + case $file:$destfile in + *.exe:*.exe) + # this is ok + ;; + *.exe:*) + destfile=$destfile.exe + ;; + *:*.exe) + func_stripname '' '.exe' "$destfile" + destfile=$func_stripname_result + ;; + esac + ;; + esac + func_show_eval "$install_prog\$stripme \$file \$destfile" 'exit $?' + $opt_dry_run || if test -n "$outputname"; then + ${RM}r "$tmpdir" + fi + ;; + esac + done + + for file in $staticlibs; do + func_basename "$file" + name=$func_basename_result + + # Set up the ranlib parameters. + oldlib=$destdir/$name + func_to_tool_file "$oldlib" func_convert_file_msys_to_w32 + tool_oldlib=$func_to_tool_file_result + + func_show_eval "$install_prog \$file \$oldlib" 'exit $?' + + if test -n "$stripme" && test -n "$old_striplib"; then + func_show_eval "$old_striplib $tool_oldlib" 'exit $?' + fi + + # Do each command in the postinstall commands. + func_execute_cmds "$old_postinstall_cmds" 'exit $?' + done + + test -n "$future_libdirs" && \ + func_warning "remember to run '$progname --finish$future_libdirs'" + + if test -n "$current_libdirs"; then + # Maybe just do a dry run. + $opt_dry_run && current_libdirs=" -n$current_libdirs" + exec_cmd='$SHELL "$progpath" $preserve_args --finish$current_libdirs' + else + exit $EXIT_SUCCESS + fi +} + +test install = "$opt_mode" && func_mode_install ${1+"$@"} + + +# func_generate_dlsyms outputname originator pic_p +# Extract symbols from dlprefiles and create ${outputname}S.o with +# a dlpreopen symbol table. +func_generate_dlsyms () +{ + $debug_cmd + + my_outputname=$1 + my_originator=$2 + my_pic_p=${3-false} + my_prefix=`$ECHO "$my_originator" | $SED 's%[^a-zA-Z0-9]%_%g'` + my_dlsyms= + + if test -n "$dlfiles$dlprefiles" || test no != "$dlself"; then + if test -n "$NM" && test -n "$global_symbol_pipe"; then + my_dlsyms=${my_outputname}S.c + else + func_error "not configured to extract global symbols from dlpreopened files" + fi + fi + + if test -n "$my_dlsyms"; then + case $my_dlsyms in + "") ;; + *.c) + # Discover the nlist of each of the dlfiles. + nlist=$output_objdir/$my_outputname.nm + + func_show_eval "$RM $nlist ${nlist}S ${nlist}T" + + # Parse the name list into a source file. + func_verbose "creating $output_objdir/$my_dlsyms" + + $opt_dry_run || $ECHO > "$output_objdir/$my_dlsyms" "\ +/* $my_dlsyms - symbol resolution table for '$my_outputname' dlsym emulation. */ +/* Generated by $PROGRAM (GNU $PACKAGE) $VERSION */ + +#ifdef __cplusplus +extern \"C\" { +#endif + +#if defined __GNUC__ && (((__GNUC__ == 4) && (__GNUC_MINOR__ >= 4)) || (__GNUC__ > 4)) +#pragma GCC diagnostic ignored \"-Wstrict-prototypes\" +#endif + +/* Keep this code in sync between libtool.m4, ltmain, lt_system.h, and tests. */ +#if defined _WIN32 || defined __CYGWIN__ || defined _WIN32_WCE +/* DATA imports from DLLs on WIN32 can't be const, because runtime + relocations are performed -- see ld's documentation on pseudo-relocs. */ +# define LT_DLSYM_CONST +#elif defined __osf__ +/* This system does not cope well with relocations in const data. */ +# define LT_DLSYM_CONST +#else +# define LT_DLSYM_CONST const +#endif + +#define STREQ(s1, s2) (strcmp ((s1), (s2)) == 0) + +/* External symbol declarations for the compiler. */\ +" + + if test yes = "$dlself"; then + func_verbose "generating symbol list for '$output'" + + $opt_dry_run || echo ': @PROGRAM@ ' > "$nlist" + + # Add our own program objects to the symbol list. + progfiles=`$ECHO "$objs$old_deplibs" | $SP2NL | $SED "$lo2o" | $NL2SP` + for progfile in $progfiles; do + func_to_tool_file "$progfile" func_convert_file_msys_to_w32 + func_verbose "extracting global C symbols from '$func_to_tool_file_result'" + $opt_dry_run || eval "$NM $func_to_tool_file_result | $global_symbol_pipe >> '$nlist'" + done + + if test -n "$exclude_expsyms"; then + $opt_dry_run || { + eval '$EGREP -v " ($exclude_expsyms)$" "$nlist" > "$nlist"T' + eval '$MV "$nlist"T "$nlist"' + } + fi + + if test -n "$export_symbols_regex"; then + $opt_dry_run || { + eval '$EGREP -e "$export_symbols_regex" "$nlist" > "$nlist"T' + eval '$MV "$nlist"T "$nlist"' + } + fi + + # Prepare the list of exported symbols + if test -z "$export_symbols"; then + export_symbols=$output_objdir/$outputname.exp + $opt_dry_run || { + $RM $export_symbols + eval "$SED -n -e '/^: @PROGRAM@ $/d' -e 's/^.* \(.*\)$/\1/p' "'< "$nlist" > "$export_symbols"' + case $host in + *cygwin* | *mingw* | *cegcc* ) + eval "echo EXPORTS "'> "$output_objdir/$outputname.def"' + eval 'cat "$export_symbols" >> "$output_objdir/$outputname.def"' + ;; + esac + } + else + $opt_dry_run || { + eval "$SED -e 's/\([].[*^$]\)/\\\\\1/g' -e 's/^/ /' -e 's/$/$/'"' < "$export_symbols" > "$output_objdir/$outputname.exp"' + eval '$GREP -f "$output_objdir/$outputname.exp" < "$nlist" > "$nlist"T' + eval '$MV "$nlist"T "$nlist"' + case $host in + *cygwin* | *mingw* | *cegcc* ) + eval "echo EXPORTS "'> "$output_objdir/$outputname.def"' + eval 'cat "$nlist" >> "$output_objdir/$outputname.def"' + ;; + esac + } + fi + fi + + for dlprefile in $dlprefiles; do + func_verbose "extracting global C symbols from '$dlprefile'" + func_basename "$dlprefile" + name=$func_basename_result + case $host in + *cygwin* | *mingw* | *cegcc* ) + # if an import library, we need to obtain dlname + if func_win32_import_lib_p "$dlprefile"; then + func_tr_sh "$dlprefile" + eval "curr_lafile=\$libfile_$func_tr_sh_result" + dlprefile_dlbasename= + if test -n "$curr_lafile" && func_lalib_p "$curr_lafile"; then + # Use subshell, to avoid clobbering current variable values + dlprefile_dlname=`source "$curr_lafile" && echo "$dlname"` + if test -n "$dlprefile_dlname"; then + func_basename "$dlprefile_dlname" + dlprefile_dlbasename=$func_basename_result + else + # no lafile. user explicitly requested -dlpreopen . + $sharedlib_from_linklib_cmd "$dlprefile" + dlprefile_dlbasename=$sharedlib_from_linklib_result + fi + fi + $opt_dry_run || { + if test -n "$dlprefile_dlbasename"; then + eval '$ECHO ": $dlprefile_dlbasename" >> "$nlist"' + else + func_warning "Could not compute DLL name from $name" + eval '$ECHO ": $name " >> "$nlist"' + fi + func_to_tool_file "$dlprefile" func_convert_file_msys_to_w32 + eval "$NM \"$func_to_tool_file_result\" 2>/dev/null | $global_symbol_pipe | + $SED -e '/I __imp/d' -e 's/I __nm_/D /;s/_nm__//' >> '$nlist'" + } + else # not an import lib + $opt_dry_run || { + eval '$ECHO ": $name " >> "$nlist"' + func_to_tool_file "$dlprefile" func_convert_file_msys_to_w32 + eval "$NM \"$func_to_tool_file_result\" 2>/dev/null | $global_symbol_pipe >> '$nlist'" + } + fi + ;; + *) + $opt_dry_run || { + eval '$ECHO ": $name " >> "$nlist"' + func_to_tool_file "$dlprefile" func_convert_file_msys_to_w32 + eval "$NM \"$func_to_tool_file_result\" 2>/dev/null | $global_symbol_pipe >> '$nlist'" + } + ;; + esac + done + + $opt_dry_run || { + # Make sure we have at least an empty file. + test -f "$nlist" || : > "$nlist" + + if test -n "$exclude_expsyms"; then + $EGREP -v " ($exclude_expsyms)$" "$nlist" > "$nlist"T + $MV "$nlist"T "$nlist" + fi + + # Try sorting and uniquifying the output. + if $GREP -v "^: " < "$nlist" | + if sort -k 3 /dev/null 2>&1; then + sort -k 3 + else + sort +2 + fi | + uniq > "$nlist"S; then + : + else + $GREP -v "^: " < "$nlist" > "$nlist"S + fi + + if test -f "$nlist"S; then + eval "$global_symbol_to_cdecl"' < "$nlist"S >> "$output_objdir/$my_dlsyms"' + else + echo '/* NONE */' >> "$output_objdir/$my_dlsyms" + fi + + func_show_eval '$RM "${nlist}I"' + if test -n "$global_symbol_to_import"; then + eval "$global_symbol_to_import"' < "$nlist"S > "$nlist"I' + fi + + echo >> "$output_objdir/$my_dlsyms" "\ + +/* The mapping between symbol names and symbols. */ +typedef struct { + const char *name; + void *address; +} lt_dlsymlist; +extern LT_DLSYM_CONST lt_dlsymlist +lt_${my_prefix}_LTX_preloaded_symbols[];\ +" + + if test -s "$nlist"I; then + echo >> "$output_objdir/$my_dlsyms" "\ +static void lt_syminit(void) +{ + LT_DLSYM_CONST lt_dlsymlist *symbol = lt_${my_prefix}_LTX_preloaded_symbols; + for (; symbol->name; ++symbol) + {" + $SED 's/.*/ if (STREQ (symbol->name, \"&\")) symbol->address = (void *) \&&;/' < "$nlist"I >> "$output_objdir/$my_dlsyms" + echo >> "$output_objdir/$my_dlsyms" "\ + } +}" + fi + echo >> "$output_objdir/$my_dlsyms" "\ +LT_DLSYM_CONST lt_dlsymlist +lt_${my_prefix}_LTX_preloaded_symbols[] = +{ {\"$my_originator\", (void *) 0}," + + if test -s "$nlist"I; then + echo >> "$output_objdir/$my_dlsyms" "\ + {\"@INIT@\", (void *) <_syminit}," + fi + + case $need_lib_prefix in + no) + eval "$global_symbol_to_c_name_address" < "$nlist" >> "$output_objdir/$my_dlsyms" + ;; + *) + eval "$global_symbol_to_c_name_address_lib_prefix" < "$nlist" >> "$output_objdir/$my_dlsyms" + ;; + esac + echo >> "$output_objdir/$my_dlsyms" "\ + {0, (void *) 0} +}; + +/* This works around a problem in FreeBSD linker */ +#ifdef FREEBSD_WORKAROUND +static const void *lt_preloaded_setup() { + return lt_${my_prefix}_LTX_preloaded_symbols; +} +#endif + +#ifdef __cplusplus +} +#endif\ +" + } # !$opt_dry_run + + pic_flag_for_symtable= + case "$compile_command " in + *" -static "*) ;; + *) + case $host in + # compiling the symbol table file with pic_flag works around + # a FreeBSD bug that causes programs to crash when -lm is + # linked before any other PIC object. But we must not use + # pic_flag when linking with -static. The problem exists in + # FreeBSD 2.2.6 and is fixed in FreeBSD 3.1. + *-*-freebsd2.*|*-*-freebsd3.0*|*-*-freebsdelf3.0*) + pic_flag_for_symtable=" $pic_flag -DFREEBSD_WORKAROUND" ;; + *-*-hpux*) + pic_flag_for_symtable=" $pic_flag" ;; + *) + $my_pic_p && pic_flag_for_symtable=" $pic_flag" + ;; + esac + ;; + esac + symtab_cflags= + for arg in $LTCFLAGS; do + case $arg in + -pie | -fpie | -fPIE) ;; + *) func_append symtab_cflags " $arg" ;; + esac + done + + # Now compile the dynamic symbol file. + func_show_eval '(cd $output_objdir && $LTCC$symtab_cflags -c$no_builtin_flag$pic_flag_for_symtable "$my_dlsyms")' 'exit $?' + + # Clean up the generated files. + func_show_eval '$RM "$output_objdir/$my_dlsyms" "$nlist" "${nlist}S" "${nlist}T" "${nlist}I"' + + # Transform the symbol file into the correct name. + symfileobj=$output_objdir/${my_outputname}S.$objext + case $host in + *cygwin* | *mingw* | *cegcc* ) + if test -f "$output_objdir/$my_outputname.def"; then + compile_command=`$ECHO "$compile_command" | $SED "s%@SYMFILE@%$output_objdir/$my_outputname.def $symfileobj%"` + finalize_command=`$ECHO "$finalize_command" | $SED "s%@SYMFILE@%$output_objdir/$my_outputname.def $symfileobj%"` + else + compile_command=`$ECHO "$compile_command" | $SED "s%@SYMFILE@%$symfileobj%"` + finalize_command=`$ECHO "$finalize_command" | $SED "s%@SYMFILE@%$symfileobj%"` + fi + ;; + *) + compile_command=`$ECHO "$compile_command" | $SED "s%@SYMFILE@%$symfileobj%"` + finalize_command=`$ECHO "$finalize_command" | $SED "s%@SYMFILE@%$symfileobj%"` + ;; + esac + ;; + *) + func_fatal_error "unknown suffix for '$my_dlsyms'" + ;; + esac + else + # We keep going just in case the user didn't refer to + # lt_preloaded_symbols. The linker will fail if global_symbol_pipe + # really was required. + + # Nullify the symbol file. + compile_command=`$ECHO "$compile_command" | $SED "s% @SYMFILE@%%"` + finalize_command=`$ECHO "$finalize_command" | $SED "s% @SYMFILE@%%"` + fi +} + +# func_cygming_gnu_implib_p ARG +# This predicate returns with zero status (TRUE) if +# ARG is a GNU/binutils-style import library. Returns +# with nonzero status (FALSE) otherwise. +func_cygming_gnu_implib_p () +{ + $debug_cmd + + func_to_tool_file "$1" func_convert_file_msys_to_w32 + func_cygming_gnu_implib_tmp=`$NM "$func_to_tool_file_result" | eval "$global_symbol_pipe" | $EGREP ' (_head_[A-Za-z0-9_]+_[ad]l*|[A-Za-z0-9_]+_[ad]l*_iname)$'` + test -n "$func_cygming_gnu_implib_tmp" +} + +# func_cygming_ms_implib_p ARG +# This predicate returns with zero status (TRUE) if +# ARG is an MS-style import library. Returns +# with nonzero status (FALSE) otherwise. +func_cygming_ms_implib_p () +{ + $debug_cmd + + func_to_tool_file "$1" func_convert_file_msys_to_w32 + func_cygming_ms_implib_tmp=`$NM "$func_to_tool_file_result" | eval "$global_symbol_pipe" | $GREP '_NULL_IMPORT_DESCRIPTOR'` + test -n "$func_cygming_ms_implib_tmp" +} + +# func_win32_libid arg +# return the library type of file 'arg' +# +# Need a lot of goo to handle *both* DLLs and import libs +# Has to be a shell function in order to 'eat' the argument +# that is supplied when $file_magic_command is called. +# Despite the name, also deal with 64 bit binaries. +func_win32_libid () +{ + $debug_cmd + + win32_libid_type=unknown + win32_fileres=`file -L $1 2>/dev/null` + case $win32_fileres in + *ar\ archive\ import\ library*) # definitely import + win32_libid_type="x86 archive import" + ;; + *ar\ archive*) # could be an import, or static + # Keep the egrep pattern in sync with the one in _LT_CHECK_MAGIC_METHOD. + if eval $OBJDUMP -f $1 | $SED -e '10q' 2>/dev/null | + $EGREP 'file format (pei*-i386(.*architecture: i386)?|pe-arm-wince|pe-x86-64)' >/dev/null; then + case $nm_interface in + "MS dumpbin") + if func_cygming_ms_implib_p "$1" || + func_cygming_gnu_implib_p "$1" + then + win32_nmres=import + else + win32_nmres= + fi + ;; + *) + func_to_tool_file "$1" func_convert_file_msys_to_w32 + win32_nmres=`eval $NM -f posix -A \"$func_to_tool_file_result\" | + $SED -n -e ' + 1,100{ + / I /{ + s|.*|import| + p + q + } + }'` + ;; + esac + case $win32_nmres in + import*) win32_libid_type="x86 archive import";; + *) win32_libid_type="x86 archive static";; + esac + fi + ;; + *DLL*) + win32_libid_type="x86 DLL" + ;; + *executable*) # but shell scripts are "executable" too... + case $win32_fileres in + *MS\ Windows\ PE\ Intel*) + win32_libid_type="x86 DLL" + ;; + esac + ;; + esac + $ECHO "$win32_libid_type" +} + +# func_cygming_dll_for_implib ARG +# +# Platform-specific function to extract the +# name of the DLL associated with the specified +# import library ARG. +# Invoked by eval'ing the libtool variable +# $sharedlib_from_linklib_cmd +# Result is available in the variable +# $sharedlib_from_linklib_result +func_cygming_dll_for_implib () +{ + $debug_cmd + + sharedlib_from_linklib_result=`$DLLTOOL --identify-strict --identify "$1"` +} + +# func_cygming_dll_for_implib_fallback_core SECTION_NAME LIBNAMEs +# +# The is the core of a fallback implementation of a +# platform-specific function to extract the name of the +# DLL associated with the specified import library LIBNAME. +# +# SECTION_NAME is either .idata$6 or .idata$7, depending +# on the platform and compiler that created the implib. +# +# Echos the name of the DLL associated with the +# specified import library. +func_cygming_dll_for_implib_fallback_core () +{ + $debug_cmd + + match_literal=`$ECHO "$1" | $SED "$sed_make_literal_regex"` + $OBJDUMP -s --section "$1" "$2" 2>/dev/null | + $SED '/^Contents of section '"$match_literal"':/{ + # Place marker at beginning of archive member dllname section + s/.*/====MARK====/ + p + d + } + # These lines can sometimes be longer than 43 characters, but + # are always uninteresting + /:[ ]*file format pe[i]\{,1\}-/d + /^In archive [^:]*:/d + # Ensure marker is printed + /^====MARK====/p + # Remove all lines with less than 43 characters + /^.\{43\}/!d + # From remaining lines, remove first 43 characters + s/^.\{43\}//' | + $SED -n ' + # Join marker and all lines until next marker into a single line + /^====MARK====/ b para + H + $ b para + b + :para + x + s/\n//g + # Remove the marker + s/^====MARK====// + # Remove trailing dots and whitespace + s/[\. \t]*$// + # Print + /./p' | + # we now have a list, one entry per line, of the stringified + # contents of the appropriate section of all members of the + # archive that possess that section. Heuristic: eliminate + # all those that have a first or second character that is + # a '.' (that is, objdump's representation of an unprintable + # character.) This should work for all archives with less than + # 0x302f exports -- but will fail for DLLs whose name actually + # begins with a literal '.' or a single character followed by + # a '.'. + # + # Of those that remain, print the first one. + $SED -e '/^\./d;/^.\./d;q' +} + +# func_cygming_dll_for_implib_fallback ARG +# Platform-specific function to extract the +# name of the DLL associated with the specified +# import library ARG. +# +# This fallback implementation is for use when $DLLTOOL +# does not support the --identify-strict option. +# Invoked by eval'ing the libtool variable +# $sharedlib_from_linklib_cmd +# Result is available in the variable +# $sharedlib_from_linklib_result +func_cygming_dll_for_implib_fallback () +{ + $debug_cmd + + if func_cygming_gnu_implib_p "$1"; then + # binutils import library + sharedlib_from_linklib_result=`func_cygming_dll_for_implib_fallback_core '.idata$7' "$1"` + elif func_cygming_ms_implib_p "$1"; then + # ms-generated import library + sharedlib_from_linklib_result=`func_cygming_dll_for_implib_fallback_core '.idata$6' "$1"` + else + # unknown + sharedlib_from_linklib_result= + fi +} + + +# func_extract_an_archive dir oldlib +func_extract_an_archive () +{ + $debug_cmd + + f_ex_an_ar_dir=$1; shift + f_ex_an_ar_oldlib=$1 + if test yes = "$lock_old_archive_extraction"; then + lockfile=$f_ex_an_ar_oldlib.lock + until $opt_dry_run || ln "$progpath" "$lockfile" 2>/dev/null; do + func_echo "Waiting for $lockfile to be removed" + sleep 2 + done + fi + func_show_eval "(cd \$f_ex_an_ar_dir && $AR x \"\$f_ex_an_ar_oldlib\")" \ + 'stat=$?; rm -f "$lockfile"; exit $stat' + if test yes = "$lock_old_archive_extraction"; then + $opt_dry_run || rm -f "$lockfile" + fi + if ($AR t "$f_ex_an_ar_oldlib" | sort | sort -uc >/dev/null 2>&1); then + : + else + func_fatal_error "object name conflicts in archive: $f_ex_an_ar_dir/$f_ex_an_ar_oldlib" + fi +} + + +# func_extract_archives gentop oldlib ... +func_extract_archives () +{ + $debug_cmd + + my_gentop=$1; shift + my_oldlibs=${1+"$@"} + my_oldobjs= + my_xlib= + my_xabs= + my_xdir= + + for my_xlib in $my_oldlibs; do + # Extract the objects. + case $my_xlib in + [\\/]* | [A-Za-z]:[\\/]*) my_xabs=$my_xlib ;; + *) my_xabs=`pwd`"/$my_xlib" ;; + esac + func_basename "$my_xlib" + my_xlib=$func_basename_result + my_xlib_u=$my_xlib + while :; do + case " $extracted_archives " in + *" $my_xlib_u "*) + func_arith $extracted_serial + 1 + extracted_serial=$func_arith_result + my_xlib_u=lt$extracted_serial-$my_xlib ;; + *) break ;; + esac + done + extracted_archives="$extracted_archives $my_xlib_u" + my_xdir=$my_gentop/$my_xlib_u + + func_mkdir_p "$my_xdir" + + case $host in + *-darwin*) + func_verbose "Extracting $my_xabs" + # Do not bother doing anything if just a dry run + $opt_dry_run || { + darwin_orig_dir=`pwd` + cd $my_xdir || exit $? + darwin_archive=$my_xabs + darwin_curdir=`pwd` + func_basename "$darwin_archive" + darwin_base_archive=$func_basename_result + darwin_arches=`$LIPO -info "$darwin_archive" 2>/dev/null | $GREP Architectures 2>/dev/null || true` + if test -n "$darwin_arches"; then + darwin_arches=`$ECHO "$darwin_arches" | $SED -e 's/.*are://'` + darwin_arch= + func_verbose "$darwin_base_archive has multiple architectures $darwin_arches" + for darwin_arch in $darwin_arches; do + func_mkdir_p "unfat-$$/$darwin_base_archive-$darwin_arch" + $LIPO -thin $darwin_arch -output "unfat-$$/$darwin_base_archive-$darwin_arch/$darwin_base_archive" "$darwin_archive" + cd "unfat-$$/$darwin_base_archive-$darwin_arch" + func_extract_an_archive "`pwd`" "$darwin_base_archive" + cd "$darwin_curdir" + $RM "unfat-$$/$darwin_base_archive-$darwin_arch/$darwin_base_archive" + done # $darwin_arches + ## Okay now we've a bunch of thin objects, gotta fatten them up :) + darwin_filelist=`find unfat-$$ -type f -name \*.o -print -o -name \*.lo -print | $SED -e "$sed_basename" | sort -u` + darwin_file= + darwin_files= + for darwin_file in $darwin_filelist; do + darwin_files=`find unfat-$$ -name $darwin_file -print | sort | $NL2SP` + $LIPO -create -output "$darwin_file" $darwin_files + done # $darwin_filelist + $RM -rf unfat-$$ + cd "$darwin_orig_dir" + else + cd $darwin_orig_dir + func_extract_an_archive "$my_xdir" "$my_xabs" + fi # $darwin_arches + } # !$opt_dry_run + ;; + *) + func_extract_an_archive "$my_xdir" "$my_xabs" + ;; + esac + my_oldobjs="$my_oldobjs "`find $my_xdir -name \*.$objext -print -o -name \*.lo -print | sort | $NL2SP` + done + + func_extract_archives_result=$my_oldobjs +} + + +# func_emit_wrapper [arg=no] +# +# Emit a libtool wrapper script on stdout. +# Don't directly open a file because we may want to +# incorporate the script contents within a cygwin/mingw +# wrapper executable. Must ONLY be called from within +# func_mode_link because it depends on a number of variables +# set therein. +# +# ARG is the value that the WRAPPER_SCRIPT_BELONGS_IN_OBJDIR +# variable will take. If 'yes', then the emitted script +# will assume that the directory where it is stored is +# the $objdir directory. This is a cygwin/mingw-specific +# behavior. +func_emit_wrapper () +{ + func_emit_wrapper_arg1=${1-no} + + $ECHO "\ +#! $SHELL + +# $output - temporary wrapper script for $objdir/$outputname +# Generated by $PROGRAM (GNU $PACKAGE) $VERSION +# +# The $output program cannot be directly executed until all the libtool +# libraries that it depends on are installed. +# +# This wrapper script should never be moved out of the build directory. +# If it is, it will not operate correctly. + +# Sed substitution that helps us do robust quoting. It backslashifies +# metacharacters that are still active within double-quoted strings. +sed_quote_subst='$sed_quote_subst' + +# Be Bourne compatible +if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then + emulate sh + NULLCMD=: + # Zsh 3.x and 4.x performs word splitting on \${1+\"\$@\"}, which + # is contrary to our usage. Disable this feature. + alias -g '\${1+\"\$@\"}'='\"\$@\"' + setopt NO_GLOB_SUBST +else + case \`(set -o) 2>/dev/null\` in *posix*) set -o posix;; esac +fi +BIN_SH=xpg4; export BIN_SH # for Tru64 +DUALCASE=1; export DUALCASE # for MKS sh + +# The HP-UX ksh and POSIX shell print the target directory to stdout +# if CDPATH is set. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + +relink_command=\"$relink_command\" + +# This environment variable determines our operation mode. +if test \"\$libtool_install_magic\" = \"$magic\"; then + # install mode needs the following variables: + generated_by_libtool_version='$macro_version' + notinst_deplibs='$notinst_deplibs' +else + # When we are sourced in execute mode, \$file and \$ECHO are already set. + if test \"\$libtool_execute_magic\" != \"$magic\"; then + file=\"\$0\"" + + qECHO=`$ECHO "$ECHO" | $SED "$sed_quote_subst"` + $ECHO "\ + +# A function that is used when there is no print builtin or printf. +func_fallback_echo () +{ + eval 'cat <<_LTECHO_EOF +\$1 +_LTECHO_EOF' +} + ECHO=\"$qECHO\" + fi + +# Very basic option parsing. These options are (a) specific to +# the libtool wrapper, (b) are identical between the wrapper +# /script/ and the wrapper /executable/ that is used only on +# windows platforms, and (c) all begin with the string "--lt-" +# (application programs are unlikely to have options that match +# this pattern). +# +# There are only two supported options: --lt-debug and +# --lt-dump-script. There is, deliberately, no --lt-help. +# +# The first argument to this parsing function should be the +# script's $0 value, followed by "$@". +lt_option_debug= +func_parse_lt_options () +{ + lt_script_arg0=\$0 + shift + for lt_opt + do + case \"\$lt_opt\" in + --lt-debug) lt_option_debug=1 ;; + --lt-dump-script) + lt_dump_D=\`\$ECHO \"X\$lt_script_arg0\" | $SED -e 's/^X//' -e 's%/[^/]*$%%'\` + test \"X\$lt_dump_D\" = \"X\$lt_script_arg0\" && lt_dump_D=. + lt_dump_F=\`\$ECHO \"X\$lt_script_arg0\" | $SED -e 's/^X//' -e 's%^.*/%%'\` + cat \"\$lt_dump_D/\$lt_dump_F\" + exit 0 + ;; + --lt-*) + \$ECHO \"Unrecognized --lt- option: '\$lt_opt'\" 1>&2 + exit 1 + ;; + esac + done + + # Print the debug banner immediately: + if test -n \"\$lt_option_debug\"; then + echo \"$outputname:$output:\$LINENO: libtool wrapper (GNU $PACKAGE) $VERSION\" 1>&2 + fi +} + +# Used when --lt-debug. Prints its arguments to stdout +# (redirection is the responsibility of the caller) +func_lt_dump_args () +{ + lt_dump_args_N=1; + for lt_arg + do + \$ECHO \"$outputname:$output:\$LINENO: newargv[\$lt_dump_args_N]: \$lt_arg\" + lt_dump_args_N=\`expr \$lt_dump_args_N + 1\` + done +} + +# Core function for launching the target application +func_exec_program_core () +{ +" + case $host in + # Backslashes separate directories on plain windows + *-*-mingw | *-*-os2* | *-cegcc*) + $ECHO "\ + if test -n \"\$lt_option_debug\"; then + \$ECHO \"$outputname:$output:\$LINENO: newargv[0]: \$progdir\\\\\$program\" 1>&2 + func_lt_dump_args \${1+\"\$@\"} 1>&2 + fi + exec \"\$progdir\\\\\$program\" \${1+\"\$@\"} +" + ;; + + *) + $ECHO "\ + if test -n \"\$lt_option_debug\"; then + \$ECHO \"$outputname:$output:\$LINENO: newargv[0]: \$progdir/\$program\" 1>&2 + func_lt_dump_args \${1+\"\$@\"} 1>&2 + fi + exec \"\$progdir/\$program\" \${1+\"\$@\"} +" + ;; + esac + $ECHO "\ + \$ECHO \"\$0: cannot exec \$program \$*\" 1>&2 + exit 1 +} + +# A function to encapsulate launching the target application +# Strips options in the --lt-* namespace from \$@ and +# launches target application with the remaining arguments. +func_exec_program () +{ + case \" \$* \" in + *\\ --lt-*) + for lt_wr_arg + do + case \$lt_wr_arg in + --lt-*) ;; + *) set x \"\$@\" \"\$lt_wr_arg\"; shift;; + esac + shift + done ;; + esac + func_exec_program_core \${1+\"\$@\"} +} + + # Parse options + func_parse_lt_options \"\$0\" \${1+\"\$@\"} + + # Find the directory that this script lives in. + thisdir=\`\$ECHO \"\$file\" | $SED 's%/[^/]*$%%'\` + test \"x\$thisdir\" = \"x\$file\" && thisdir=. + + # Follow symbolic links until we get to the real thisdir. + file=\`ls -ld \"\$file\" | $SED -n 's/.*-> //p'\` + while test -n \"\$file\"; do + destdir=\`\$ECHO \"\$file\" | $SED 's%/[^/]*\$%%'\` + + # If there was a directory component, then change thisdir. + if test \"x\$destdir\" != \"x\$file\"; then + case \"\$destdir\" in + [\\\\/]* | [A-Za-z]:[\\\\/]*) thisdir=\"\$destdir\" ;; + *) thisdir=\"\$thisdir/\$destdir\" ;; + esac + fi + + file=\`\$ECHO \"\$file\" | $SED 's%^.*/%%'\` + file=\`ls -ld \"\$thisdir/\$file\" | $SED -n 's/.*-> //p'\` + done + + # Usually 'no', except on cygwin/mingw when embedded into + # the cwrapper. + WRAPPER_SCRIPT_BELONGS_IN_OBJDIR=$func_emit_wrapper_arg1 + if test \"\$WRAPPER_SCRIPT_BELONGS_IN_OBJDIR\" = \"yes\"; then + # special case for '.' + if test \"\$thisdir\" = \".\"; then + thisdir=\`pwd\` + fi + # remove .libs from thisdir + case \"\$thisdir\" in + *[\\\\/]$objdir ) thisdir=\`\$ECHO \"\$thisdir\" | $SED 's%[\\\\/][^\\\\/]*$%%'\` ;; + $objdir ) thisdir=. ;; + esac + fi + + # Try to get the absolute directory name. + absdir=\`cd \"\$thisdir\" && pwd\` + test -n \"\$absdir\" && thisdir=\"\$absdir\" +" + + if test yes = "$fast_install"; then + $ECHO "\ + program=lt-'$outputname'$exeext + progdir=\"\$thisdir/$objdir\" + + if test ! -f \"\$progdir/\$program\" || + { file=\`ls -1dt \"\$progdir/\$program\" \"\$progdir/../\$program\" 2>/dev/null | $SED 1q\`; \\ + test \"X\$file\" != \"X\$progdir/\$program\"; }; then + + file=\"\$\$-\$program\" + + if test ! -d \"\$progdir\"; then + $MKDIR \"\$progdir\" + else + $RM \"\$progdir/\$file\" + fi" + + $ECHO "\ + + # relink executable if necessary + if test -n \"\$relink_command\"; then + if relink_command_output=\`eval \$relink_command 2>&1\`; then : + else + \$ECHO \"\$relink_command_output\" >&2 + $RM \"\$progdir/\$file\" + exit 1 + fi + fi + + $MV \"\$progdir/\$file\" \"\$progdir/\$program\" 2>/dev/null || + { $RM \"\$progdir/\$program\"; + $MV \"\$progdir/\$file\" \"\$progdir/\$program\"; } + $RM \"\$progdir/\$file\" + fi" + else + $ECHO "\ + program='$outputname' + progdir=\"\$thisdir/$objdir\" +" + fi + + $ECHO "\ + + if test -f \"\$progdir/\$program\"; then" + + # fixup the dll searchpath if we need to. + # + # Fix the DLL searchpath if we need to. Do this before prepending + # to shlibpath, because on Windows, both are PATH and uninstalled + # libraries must come first. + if test -n "$dllsearchpath"; then + $ECHO "\ + # Add the dll search path components to the executable PATH + PATH=$dllsearchpath:\$PATH +" + fi + + # Export our shlibpath_var if we have one. + if test yes = "$shlibpath_overrides_runpath" && test -n "$shlibpath_var" && test -n "$temp_rpath"; then + $ECHO "\ + # Add our own library path to $shlibpath_var + $shlibpath_var=\"$temp_rpath\$$shlibpath_var\" + + # Some systems cannot cope with colon-terminated $shlibpath_var + # The second colon is a workaround for a bug in BeOS R4 sed + $shlibpath_var=\`\$ECHO \"\$$shlibpath_var\" | $SED 's/::*\$//'\` + + export $shlibpath_var +" + fi + + $ECHO "\ + if test \"\$libtool_execute_magic\" != \"$magic\"; then + # Run the actual program with our arguments. + func_exec_program \${1+\"\$@\"} + fi + else + # The program doesn't exist. + \$ECHO \"\$0: error: '\$progdir/\$program' does not exist\" 1>&2 + \$ECHO \"This script is just a wrapper for \$program.\" 1>&2 + \$ECHO \"See the $PACKAGE documentation for more information.\" 1>&2 + exit 1 + fi +fi\ +" +} + + +# func_emit_cwrapperexe_src +# emit the source code for a wrapper executable on stdout +# Must ONLY be called from within func_mode_link because +# it depends on a number of variable set therein. +func_emit_cwrapperexe_src () +{ + cat < +#include +#ifdef _MSC_VER +# include +# include +# include +#else +# include +# include +# ifdef __CYGWIN__ +# include +# endif +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +#define STREQ(s1, s2) (strcmp ((s1), (s2)) == 0) + +/* declarations of non-ANSI functions */ +#if defined __MINGW32__ +# ifdef __STRICT_ANSI__ +int _putenv (const char *); +# endif +#elif defined __CYGWIN__ +# ifdef __STRICT_ANSI__ +char *realpath (const char *, char *); +int putenv (char *); +int setenv (const char *, const char *, int); +# endif +/* #elif defined other_platform || defined ... */ +#endif + +/* portability defines, excluding path handling macros */ +#if defined _MSC_VER +# define setmode _setmode +# define stat _stat +# define chmod _chmod +# define getcwd _getcwd +# define putenv _putenv +# define S_IXUSR _S_IEXEC +#elif defined __MINGW32__ +# define setmode _setmode +# define stat _stat +# define chmod _chmod +# define getcwd _getcwd +# define putenv _putenv +#elif defined __CYGWIN__ +# define HAVE_SETENV +# define FOPEN_WB "wb" +/* #elif defined other platforms ... */ +#endif + +#if defined PATH_MAX +# define LT_PATHMAX PATH_MAX +#elif defined MAXPATHLEN +# define LT_PATHMAX MAXPATHLEN +#else +# define LT_PATHMAX 1024 +#endif + +#ifndef S_IXOTH +# define S_IXOTH 0 +#endif +#ifndef S_IXGRP +# define S_IXGRP 0 +#endif + +/* path handling portability macros */ +#ifndef DIR_SEPARATOR +# define DIR_SEPARATOR '/' +# define PATH_SEPARATOR ':' +#endif + +#if defined _WIN32 || defined __MSDOS__ || defined __DJGPP__ || \ + defined __OS2__ +# define HAVE_DOS_BASED_FILE_SYSTEM +# define FOPEN_WB "wb" +# ifndef DIR_SEPARATOR_2 +# define DIR_SEPARATOR_2 '\\' +# endif +# ifndef PATH_SEPARATOR_2 +# define PATH_SEPARATOR_2 ';' +# endif +#endif + +#ifndef DIR_SEPARATOR_2 +# define IS_DIR_SEPARATOR(ch) ((ch) == DIR_SEPARATOR) +#else /* DIR_SEPARATOR_2 */ +# define IS_DIR_SEPARATOR(ch) \ + (((ch) == DIR_SEPARATOR) || ((ch) == DIR_SEPARATOR_2)) +#endif /* DIR_SEPARATOR_2 */ + +#ifndef PATH_SEPARATOR_2 +# define IS_PATH_SEPARATOR(ch) ((ch) == PATH_SEPARATOR) +#else /* PATH_SEPARATOR_2 */ +# define IS_PATH_SEPARATOR(ch) ((ch) == PATH_SEPARATOR_2) +#endif /* PATH_SEPARATOR_2 */ + +#ifndef FOPEN_WB +# define FOPEN_WB "w" +#endif +#ifndef _O_BINARY +# define _O_BINARY 0 +#endif + +#define XMALLOC(type, num) ((type *) xmalloc ((num) * sizeof(type))) +#define XFREE(stale) do { \ + if (stale) { free (stale); stale = 0; } \ +} while (0) + +#if defined LT_DEBUGWRAPPER +static int lt_debug = 1; +#else +static int lt_debug = 0; +#endif + +const char *program_name = "libtool-wrapper"; /* in case xstrdup fails */ + +void *xmalloc (size_t num); +char *xstrdup (const char *string); +const char *base_name (const char *name); +char *find_executable (const char *wrapper); +char *chase_symlinks (const char *pathspec); +int make_executable (const char *path); +int check_executable (const char *path); +char *strendzap (char *str, const char *pat); +void lt_debugprintf (const char *file, int line, const char *fmt, ...); +void lt_fatal (const char *file, int line, const char *message, ...); +static const char *nonnull (const char *s); +static const char *nonempty (const char *s); +void lt_setenv (const char *name, const char *value); +char *lt_extend_str (const char *orig_value, const char *add, int to_end); +void lt_update_exe_path (const char *name, const char *value); +void lt_update_lib_path (const char *name, const char *value); +char **prepare_spawn (char **argv); +void lt_dump_script (FILE *f); +EOF + + cat <= 0) + && (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) + return 1; + else + return 0; +} + +int +make_executable (const char *path) +{ + int rval = 0; + struct stat st; + + lt_debugprintf (__FILE__, __LINE__, "(make_executable): %s\n", + nonempty (path)); + if ((!path) || (!*path)) + return 0; + + if (stat (path, &st) >= 0) + { + rval = chmod (path, st.st_mode | S_IXOTH | S_IXGRP | S_IXUSR); + } + return rval; +} + +/* Searches for the full path of the wrapper. Returns + newly allocated full path name if found, NULL otherwise + Does not chase symlinks, even on platforms that support them. +*/ +char * +find_executable (const char *wrapper) +{ + int has_slash = 0; + const char *p; + const char *p_next; + /* static buffer for getcwd */ + char tmp[LT_PATHMAX + 1]; + size_t tmp_len; + char *concat_name; + + lt_debugprintf (__FILE__, __LINE__, "(find_executable): %s\n", + nonempty (wrapper)); + + if ((wrapper == NULL) || (*wrapper == '\0')) + return NULL; + + /* Absolute path? */ +#if defined HAVE_DOS_BASED_FILE_SYSTEM + if (isalpha ((unsigned char) wrapper[0]) && wrapper[1] == ':') + { + concat_name = xstrdup (wrapper); + if (check_executable (concat_name)) + return concat_name; + XFREE (concat_name); + } + else + { +#endif + if (IS_DIR_SEPARATOR (wrapper[0])) + { + concat_name = xstrdup (wrapper); + if (check_executable (concat_name)) + return concat_name; + XFREE (concat_name); + } +#if defined HAVE_DOS_BASED_FILE_SYSTEM + } +#endif + + for (p = wrapper; *p; p++) + if (*p == '/') + { + has_slash = 1; + break; + } + if (!has_slash) + { + /* no slashes; search PATH */ + const char *path = getenv ("PATH"); + if (path != NULL) + { + for (p = path; *p; p = p_next) + { + const char *q; + size_t p_len; + for (q = p; *q; q++) + if (IS_PATH_SEPARATOR (*q)) + break; + p_len = (size_t) (q - p); + p_next = (*q == '\0' ? q : q + 1); + if (p_len == 0) + { + /* empty path: current directory */ + if (getcwd (tmp, LT_PATHMAX) == NULL) + lt_fatal (__FILE__, __LINE__, "getcwd failed: %s", + nonnull (strerror (errno))); + tmp_len = strlen (tmp); + concat_name = + XMALLOC (char, tmp_len + 1 + strlen (wrapper) + 1); + memcpy (concat_name, tmp, tmp_len); + concat_name[tmp_len] = '/'; + strcpy (concat_name + tmp_len + 1, wrapper); + } + else + { + concat_name = + XMALLOC (char, p_len + 1 + strlen (wrapper) + 1); + memcpy (concat_name, p, p_len); + concat_name[p_len] = '/'; + strcpy (concat_name + p_len + 1, wrapper); + } + if (check_executable (concat_name)) + return concat_name; + XFREE (concat_name); + } + } + /* not found in PATH; assume curdir */ + } + /* Relative path | not found in path: prepend cwd */ + if (getcwd (tmp, LT_PATHMAX) == NULL) + lt_fatal (__FILE__, __LINE__, "getcwd failed: %s", + nonnull (strerror (errno))); + tmp_len = strlen (tmp); + concat_name = XMALLOC (char, tmp_len + 1 + strlen (wrapper) + 1); + memcpy (concat_name, tmp, tmp_len); + concat_name[tmp_len] = '/'; + strcpy (concat_name + tmp_len + 1, wrapper); + + if (check_executable (concat_name)) + return concat_name; + XFREE (concat_name); + return NULL; +} + +char * +chase_symlinks (const char *pathspec) +{ +#ifndef S_ISLNK + return xstrdup (pathspec); +#else + char buf[LT_PATHMAX]; + struct stat s; + char *tmp_pathspec = xstrdup (pathspec); + char *p; + int has_symlinks = 0; + while (strlen (tmp_pathspec) && !has_symlinks) + { + lt_debugprintf (__FILE__, __LINE__, + "checking path component for symlinks: %s\n", + tmp_pathspec); + if (lstat (tmp_pathspec, &s) == 0) + { + if (S_ISLNK (s.st_mode) != 0) + { + has_symlinks = 1; + break; + } + + /* search backwards for last DIR_SEPARATOR */ + p = tmp_pathspec + strlen (tmp_pathspec) - 1; + while ((p > tmp_pathspec) && (!IS_DIR_SEPARATOR (*p))) + p--; + if ((p == tmp_pathspec) && (!IS_DIR_SEPARATOR (*p))) + { + /* no more DIR_SEPARATORS left */ + break; + } + *p = '\0'; + } + else + { + lt_fatal (__FILE__, __LINE__, + "error accessing file \"%s\": %s", + tmp_pathspec, nonnull (strerror (errno))); + } + } + XFREE (tmp_pathspec); + + if (!has_symlinks) + { + return xstrdup (pathspec); + } + + tmp_pathspec = realpath (pathspec, buf); + if (tmp_pathspec == 0) + { + lt_fatal (__FILE__, __LINE__, + "could not follow symlinks for %s", pathspec); + } + return xstrdup (tmp_pathspec); +#endif +} + +char * +strendzap (char *str, const char *pat) +{ + size_t len, patlen; + + assert (str != NULL); + assert (pat != NULL); + + len = strlen (str); + patlen = strlen (pat); + + if (patlen <= len) + { + str += len - patlen; + if (STREQ (str, pat)) + *str = '\0'; + } + return str; +} + +void +lt_debugprintf (const char *file, int line, const char *fmt, ...) +{ + va_list args; + if (lt_debug) + { + (void) fprintf (stderr, "%s:%s:%d: ", program_name, file, line); + va_start (args, fmt); + (void) vfprintf (stderr, fmt, args); + va_end (args); + } +} + +static void +lt_error_core (int exit_status, const char *file, + int line, const char *mode, + const char *message, va_list ap) +{ + fprintf (stderr, "%s:%s:%d: %s: ", program_name, file, line, mode); + vfprintf (stderr, message, ap); + fprintf (stderr, ".\n"); + + if (exit_status >= 0) + exit (exit_status); +} + +void +lt_fatal (const char *file, int line, const char *message, ...) +{ + va_list ap; + va_start (ap, message); + lt_error_core (EXIT_FAILURE, file, line, "FATAL", message, ap); + va_end (ap); +} + +static const char * +nonnull (const char *s) +{ + return s ? s : "(null)"; +} + +static const char * +nonempty (const char *s) +{ + return (s && !*s) ? "(empty)" : nonnull (s); +} + +void +lt_setenv (const char *name, const char *value) +{ + lt_debugprintf (__FILE__, __LINE__, + "(lt_setenv) setting '%s' to '%s'\n", + nonnull (name), nonnull (value)); + { +#ifdef HAVE_SETENV + /* always make a copy, for consistency with !HAVE_SETENV */ + char *str = xstrdup (value); + setenv (name, str, 1); +#else + size_t len = strlen (name) + 1 + strlen (value) + 1; + char *str = XMALLOC (char, len); + sprintf (str, "%s=%s", name, value); + if (putenv (str) != EXIT_SUCCESS) + { + XFREE (str); + } +#endif + } +} + +char * +lt_extend_str (const char *orig_value, const char *add, int to_end) +{ + char *new_value; + if (orig_value && *orig_value) + { + size_t orig_value_len = strlen (orig_value); + size_t add_len = strlen (add); + new_value = XMALLOC (char, add_len + orig_value_len + 1); + if (to_end) + { + strcpy (new_value, orig_value); + strcpy (new_value + orig_value_len, add); + } + else + { + strcpy (new_value, add); + strcpy (new_value + add_len, orig_value); + } + } + else + { + new_value = xstrdup (add); + } + return new_value; +} + +void +lt_update_exe_path (const char *name, const char *value) +{ + lt_debugprintf (__FILE__, __LINE__, + "(lt_update_exe_path) modifying '%s' by prepending '%s'\n", + nonnull (name), nonnull (value)); + + if (name && *name && value && *value) + { + char *new_value = lt_extend_str (getenv (name), value, 0); + /* some systems can't cope with a ':'-terminated path #' */ + size_t len = strlen (new_value); + while ((len > 0) && IS_PATH_SEPARATOR (new_value[len-1])) + { + new_value[--len] = '\0'; + } + lt_setenv (name, new_value); + XFREE (new_value); + } +} + +void +lt_update_lib_path (const char *name, const char *value) +{ + lt_debugprintf (__FILE__, __LINE__, + "(lt_update_lib_path) modifying '%s' by prepending '%s'\n", + nonnull (name), nonnull (value)); + + if (name && *name && value && *value) + { + char *new_value = lt_extend_str (getenv (name), value, 0); + lt_setenv (name, new_value); + XFREE (new_value); + } +} + +EOF + case $host_os in + mingw*) + cat <<"EOF" + +/* Prepares an argument vector before calling spawn(). + Note that spawn() does not by itself call the command interpreter + (getenv ("COMSPEC") != NULL ? getenv ("COMSPEC") : + ({ OSVERSIONINFO v; v.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + GetVersionEx(&v); + v.dwPlatformId == VER_PLATFORM_WIN32_NT; + }) ? "cmd.exe" : "command.com"). + Instead it simply concatenates the arguments, separated by ' ', and calls + CreateProcess(). We must quote the arguments since Win32 CreateProcess() + interprets characters like ' ', '\t', '\\', '"' (but not '<' and '>') in a + special way: + - Space and tab are interpreted as delimiters. They are not treated as + delimiters if they are surrounded by double quotes: "...". + - Unescaped double quotes are removed from the input. Their only effect is + that within double quotes, space and tab are treated like normal + characters. + - Backslashes not followed by double quotes are not special. + - But 2*n+1 backslashes followed by a double quote become + n backslashes followed by a double quote (n >= 0): + \" -> " + \\\" -> \" + \\\\\" -> \\" + */ +#define SHELL_SPECIAL_CHARS "\"\\ \001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037" +#define SHELL_SPACE_CHARS " \001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037" +char ** +prepare_spawn (char **argv) +{ + size_t argc; + char **new_argv; + size_t i; + + /* Count number of arguments. */ + for (argc = 0; argv[argc] != NULL; argc++) + ; + + /* Allocate new argument vector. */ + new_argv = XMALLOC (char *, argc + 1); + + /* Put quoted arguments into the new argument vector. */ + for (i = 0; i < argc; i++) + { + const char *string = argv[i]; + + if (string[0] == '\0') + new_argv[i] = xstrdup ("\"\""); + else if (strpbrk (string, SHELL_SPECIAL_CHARS) != NULL) + { + int quote_around = (strpbrk (string, SHELL_SPACE_CHARS) != NULL); + size_t length; + unsigned int backslashes; + const char *s; + char *quoted_string; + char *p; + + length = 0; + backslashes = 0; + if (quote_around) + length++; + for (s = string; *s != '\0'; s++) + { + char c = *s; + if (c == '"') + length += backslashes + 1; + length++; + if (c == '\\') + backslashes++; + else + backslashes = 0; + } + if (quote_around) + length += backslashes + 1; + + quoted_string = XMALLOC (char, length + 1); + + p = quoted_string; + backslashes = 0; + if (quote_around) + *p++ = '"'; + for (s = string; *s != '\0'; s++) + { + char c = *s; + if (c == '"') + { + unsigned int j; + for (j = backslashes + 1; j > 0; j--) + *p++ = '\\'; + } + *p++ = c; + if (c == '\\') + backslashes++; + else + backslashes = 0; + } + if (quote_around) + { + unsigned int j; + for (j = backslashes; j > 0; j--) + *p++ = '\\'; + *p++ = '"'; + } + *p = '\0'; + + new_argv[i] = quoted_string; + } + else + new_argv[i] = (char *) string; + } + new_argv[argc] = NULL; + + return new_argv; +} +EOF + ;; + esac + + cat <<"EOF" +void lt_dump_script (FILE* f) +{ +EOF + func_emit_wrapper yes | + $SED -n -e ' +s/^\(.\{79\}\)\(..*\)/\1\ +\2/ +h +s/\([\\"]\)/\\\1/g +s/$/\\n/ +s/\([^\n]*\).*/ fputs ("\1", f);/p +g +D' + cat <<"EOF" +} +EOF +} +# end: func_emit_cwrapperexe_src + +# func_win32_import_lib_p ARG +# True if ARG is an import lib, as indicated by $file_magic_cmd +func_win32_import_lib_p () +{ + $debug_cmd + + case `eval $file_magic_cmd \"\$1\" 2>/dev/null | $SED -e 10q` in + *import*) : ;; + *) false ;; + esac +} + +# func_suncc_cstd_abi +# !!ONLY CALL THIS FOR SUN CC AFTER $compile_command IS FULLY EXPANDED!! +# Several compiler flags select an ABI that is incompatible with the +# Cstd library. Avoid specifying it if any are in CXXFLAGS. +func_suncc_cstd_abi () +{ + $debug_cmd + + case " $compile_command " in + *" -compat=g "*|*\ -std=c++[0-9][0-9]\ *|*" -library=stdcxx4 "*|*" -library=stlport4 "*) + suncc_use_cstd_abi=no + ;; + *) + suncc_use_cstd_abi=yes + ;; + esac +} + +# func_mode_link arg... +func_mode_link () +{ + $debug_cmd + + case $host in + *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-cegcc*) + # It is impossible to link a dll without this setting, and + # we shouldn't force the makefile maintainer to figure out + # what system we are compiling for in order to pass an extra + # flag for every libtool invocation. + # allow_undefined=no + + # FIXME: Unfortunately, there are problems with the above when trying + # to make a dll that has undefined symbols, in which case not + # even a static library is built. For now, we need to specify + # -no-undefined on the libtool link line when we can be certain + # that all symbols are satisfied, otherwise we get a static library. + allow_undefined=yes + ;; + *) + allow_undefined=yes + ;; + esac + libtool_args=$nonopt + base_compile="$nonopt $@" + compile_command=$nonopt + finalize_command=$nonopt + + compile_rpath= + finalize_rpath= + compile_shlibpath= + finalize_shlibpath= + convenience= + old_convenience= + deplibs= + old_deplibs= + compiler_flags= + linker_flags= + dllsearchpath= + lib_search_path=`pwd` + inst_prefix_dir= + new_inherited_linker_flags= + + avoid_version=no + bindir= + dlfiles= + dlprefiles= + dlself=no + export_dynamic=no + export_symbols= + export_symbols_regex= + generated= + libobjs= + ltlibs= + module=no + no_install=no + objs= + os2dllname= + non_pic_objects= + precious_files_regex= + prefer_static_libs=no + preload=false + prev= + prevarg= + release= + rpath= + xrpath= + perm_rpath= + temp_rpath= + thread_safe=no + vinfo= + vinfo_number=no + weak_libs= + single_module=$wl-single_module + func_infer_tag $base_compile + + # We need to know -static, to get the right output filenames. + for arg + do + case $arg in + -shared) + test yes != "$build_libtool_libs" \ + && func_fatal_configuration "cannot build a shared library" + build_old_libs=no + break + ;; + -all-static | -static | -static-libtool-libs) + case $arg in + -all-static) + if test yes = "$build_libtool_libs" && test -z "$link_static_flag"; then + func_warning "complete static linking is impossible in this configuration" + fi + if test -n "$link_static_flag"; then + dlopen_self=$dlopen_self_static + fi + prefer_static_libs=yes + ;; + -static) + if test -z "$pic_flag" && test -n "$link_static_flag"; then + dlopen_self=$dlopen_self_static + fi + prefer_static_libs=built + ;; + -static-libtool-libs) + if test -z "$pic_flag" && test -n "$link_static_flag"; then + dlopen_self=$dlopen_self_static + fi + prefer_static_libs=yes + ;; + esac + build_libtool_libs=no + build_old_libs=yes + break + ;; + esac + done + + # See if our shared archives depend on static archives. + test -n "$old_archive_from_new_cmds" && build_old_libs=yes + + # Go through the arguments, transforming them on the way. + while test "$#" -gt 0; do + arg=$1 + shift + func_quote_for_eval "$arg" + qarg=$func_quote_for_eval_unquoted_result + func_append libtool_args " $func_quote_for_eval_result" + + # If the previous option needs an argument, assign it. + if test -n "$prev"; then + case $prev in + output) + func_append compile_command " @OUTPUT@" + func_append finalize_command " @OUTPUT@" + ;; + esac + + case $prev in + bindir) + bindir=$arg + prev= + continue + ;; + dlfiles|dlprefiles) + $preload || { + # Add the symbol object into the linking commands. + func_append compile_command " @SYMFILE@" + func_append finalize_command " @SYMFILE@" + preload=: + } + case $arg in + *.la | *.lo) ;; # We handle these cases below. + force) + if test no = "$dlself"; then + dlself=needless + export_dynamic=yes + fi + prev= + continue + ;; + self) + if test dlprefiles = "$prev"; then + dlself=yes + elif test dlfiles = "$prev" && test yes != "$dlopen_self"; then + dlself=yes + else + dlself=needless + export_dynamic=yes + fi + prev= + continue + ;; + *) + if test dlfiles = "$prev"; then + func_append dlfiles " $arg" + else + func_append dlprefiles " $arg" + fi + prev= + continue + ;; + esac + ;; + expsyms) + export_symbols=$arg + test -f "$arg" \ + || func_fatal_error "symbol file '$arg' does not exist" + prev= + continue + ;; + expsyms_regex) + export_symbols_regex=$arg + prev= + continue + ;; + framework) + case $host in + *-*-darwin*) + case "$deplibs " in + *" $qarg.ltframework "*) ;; + *) func_append deplibs " $qarg.ltframework" # this is fixed later + ;; + esac + ;; + esac + prev= + continue + ;; + inst_prefix) + inst_prefix_dir=$arg + prev= + continue + ;; + mllvm) + # Clang does not use LLVM to link, so we can simply discard any + # '-mllvm $arg' options when doing the link step. + prev= + continue + ;; + objectlist) + if test -f "$arg"; then + save_arg=$arg + moreargs= + for fil in `cat "$save_arg"` + do +# func_append moreargs " $fil" + arg=$fil + # A libtool-controlled object. + + # Check to see that this really is a libtool object. + if func_lalib_unsafe_p "$arg"; then + pic_object= + non_pic_object= + + # Read the .lo file + func_source "$arg" + + if test -z "$pic_object" || + test -z "$non_pic_object" || + test none = "$pic_object" && + test none = "$non_pic_object"; then + func_fatal_error "cannot find name of object for '$arg'" + fi + + # Extract subdirectory from the argument. + func_dirname "$arg" "/" "" + xdir=$func_dirname_result + + if test none != "$pic_object"; then + # Prepend the subdirectory the object is found in. + pic_object=$xdir$pic_object + + if test dlfiles = "$prev"; then + if test yes = "$build_libtool_libs" && test yes = "$dlopen_support"; then + func_append dlfiles " $pic_object" + prev= + continue + else + # If libtool objects are unsupported, then we need to preload. + prev=dlprefiles + fi + fi + + # CHECK ME: I think I busted this. -Ossama + if test dlprefiles = "$prev"; then + # Preload the old-style object. + func_append dlprefiles " $pic_object" + prev= + fi + + # A PIC object. + func_append libobjs " $pic_object" + arg=$pic_object + fi + + # Non-PIC object. + if test none != "$non_pic_object"; then + # Prepend the subdirectory the object is found in. + non_pic_object=$xdir$non_pic_object + + # A standard non-PIC object + func_append non_pic_objects " $non_pic_object" + if test -z "$pic_object" || test none = "$pic_object"; then + arg=$non_pic_object + fi + else + # If the PIC object exists, use it instead. + # $xdir was prepended to $pic_object above. + non_pic_object=$pic_object + func_append non_pic_objects " $non_pic_object" + fi + else + # Only an error if not doing a dry-run. + if $opt_dry_run; then + # Extract subdirectory from the argument. + func_dirname "$arg" "/" "" + xdir=$func_dirname_result + + func_lo2o "$arg" + pic_object=$xdir$objdir/$func_lo2o_result + non_pic_object=$xdir$func_lo2o_result + func_append libobjs " $pic_object" + func_append non_pic_objects " $non_pic_object" + else + func_fatal_error "'$arg' is not a valid libtool object" + fi + fi + done + else + func_fatal_error "link input file '$arg' does not exist" + fi + arg=$save_arg + prev= + continue + ;; + os2dllname) + os2dllname=$arg + prev= + continue + ;; + precious_regex) + precious_files_regex=$arg + prev= + continue + ;; + release) + release=-$arg + prev= + continue + ;; + rpath | xrpath) + # We need an absolute path. + case $arg in + [\\/]* | [A-Za-z]:[\\/]*) ;; + *) + func_fatal_error "only absolute run-paths are allowed" + ;; + esac + if test rpath = "$prev"; then + case "$rpath " in + *" $arg "*) ;; + *) func_append rpath " $arg" ;; + esac + else + case "$xrpath " in + *" $arg "*) ;; + *) func_append xrpath " $arg" ;; + esac + fi + prev= + continue + ;; + shrext) + shrext_cmds=$arg + prev= + continue + ;; + weak) + func_append weak_libs " $arg" + prev= + continue + ;; + xcclinker) + func_append linker_flags " $qarg" + func_append compiler_flags " $qarg" + prev= + func_append compile_command " $qarg" + func_append finalize_command " $qarg" + continue + ;; + xcompiler) + func_append compiler_flags " $qarg" + prev= + func_append compile_command " $qarg" + func_append finalize_command " $qarg" + continue + ;; + xlinker) + func_append linker_flags " $qarg" + func_append compiler_flags " $wl$qarg" + prev= + func_append compile_command " $wl$qarg" + func_append finalize_command " $wl$qarg" + continue + ;; + *) + eval "$prev=\"\$arg\"" + prev= + continue + ;; + esac + fi # test -n "$prev" + + prevarg=$arg + + case $arg in + -all-static) + if test -n "$link_static_flag"; then + # See comment for -static flag below, for more details. + func_append compile_command " $link_static_flag" + func_append finalize_command " $link_static_flag" + fi + continue + ;; + + -allow-undefined) + # FIXME: remove this flag sometime in the future. + func_fatal_error "'-allow-undefined' must not be used because it is the default" + ;; + + -avoid-version) + avoid_version=yes + continue + ;; + + -bindir) + prev=bindir + continue + ;; + + -dlopen) + prev=dlfiles + continue + ;; + + -dlpreopen) + prev=dlprefiles + continue + ;; + + -export-dynamic) + export_dynamic=yes + continue + ;; + + -export-symbols | -export-symbols-regex) + if test -n "$export_symbols" || test -n "$export_symbols_regex"; then + func_fatal_error "more than one -exported-symbols argument is not allowed" + fi + if test X-export-symbols = "X$arg"; then + prev=expsyms + else + prev=expsyms_regex + fi + continue + ;; + + -framework) + prev=framework + continue + ;; + + -inst-prefix-dir) + prev=inst_prefix + continue + ;; + + # The native IRIX linker understands -LANG:*, -LIST:* and -LNO:* + # so, if we see these flags be careful not to treat them like -L + -L[A-Z][A-Z]*:*) + case $with_gcc/$host in + no/*-*-irix* | /*-*-irix*) + func_append compile_command " $arg" + func_append finalize_command " $arg" + ;; + esac + continue + ;; + + -L*) + func_stripname "-L" '' "$arg" + if test -z "$func_stripname_result"; then + if test "$#" -gt 0; then + func_fatal_error "require no space between '-L' and '$1'" + else + func_fatal_error "need path for '-L' option" + fi + fi + func_resolve_sysroot "$func_stripname_result" + dir=$func_resolve_sysroot_result + # We need an absolute path. + case $dir in + [\\/]* | [A-Za-z]:[\\/]*) ;; + *) + absdir=`cd "$dir" && pwd` + test -z "$absdir" && \ + func_fatal_error "cannot determine absolute directory name of '$dir'" + dir=$absdir + ;; + esac + case "$deplibs " in + *" -L$dir "* | *" $arg "*) + # Will only happen for absolute or sysroot arguments + ;; + *) + # Preserve sysroot, but never include relative directories + case $dir in + [\\/]* | [A-Za-z]:[\\/]* | =*) func_append deplibs " $arg" ;; + *) func_append deplibs " -L$dir" ;; + esac + func_append lib_search_path " $dir" + ;; + esac + case $host in + *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-cegcc*) + testbindir=`$ECHO "$dir" | $SED 's*/lib$*/bin*'` + case :$dllsearchpath: in + *":$dir:"*) ;; + ::) dllsearchpath=$dir;; + *) func_append dllsearchpath ":$dir";; + esac + case :$dllsearchpath: in + *":$testbindir:"*) ;; + ::) dllsearchpath=$testbindir;; + *) func_append dllsearchpath ":$testbindir";; + esac + ;; + esac + continue + ;; + + -l*) + if test X-lc = "X$arg" || test X-lm = "X$arg"; then + case $host in + *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-beos* | *-cegcc* | *-*-haiku*) + # These systems don't actually have a C or math library (as such) + continue + ;; + *-*-os2*) + # These systems don't actually have a C library (as such) + test X-lc = "X$arg" && continue + ;; + *-*-openbsd* | *-*-freebsd* | *-*-dragonfly* | *-*-bitrig*) + # Do not include libc due to us having libc/libc_r. + test X-lc = "X$arg" && continue + ;; + *-*-rhapsody* | *-*-darwin1.[012]) + # Rhapsody C and math libraries are in the System framework + func_append deplibs " System.ltframework" + continue + ;; + *-*-sco3.2v5* | *-*-sco5v6*) + # Causes problems with __ctype + test X-lc = "X$arg" && continue + ;; + *-*-sysv4.2uw2* | *-*-sysv5* | *-*-unixware* | *-*-OpenUNIX*) + # Compiler inserts libc in the correct place for threads to work + test X-lc = "X$arg" && continue + ;; + esac + elif test X-lc_r = "X$arg"; then + case $host in + *-*-openbsd* | *-*-freebsd* | *-*-dragonfly* | *-*-bitrig*) + # Do not include libc_r directly, use -pthread flag. + continue + ;; + esac + fi + func_append deplibs " $arg" + continue + ;; + + -mllvm) + prev=mllvm + continue + ;; + + -module) + module=yes + continue + ;; + + # Tru64 UNIX uses -model [arg] to determine the layout of C++ + # classes, name mangling, and exception handling. + # Darwin uses the -arch flag to determine output architecture. + -model|-arch|-isysroot|--sysroot) + func_append compiler_flags " $arg" + func_append compile_command " $arg" + func_append finalize_command " $arg" + prev=xcompiler + continue + ;; + + -mt|-mthreads|-kthread|-Kthread|-pthread|-pthreads|--thread-safe \ + |-threads|-fopenmp|-openmp|-mp|-xopenmp|-omp|-qsmp=*) + func_append compiler_flags " $arg" + func_append compile_command " $arg" + func_append finalize_command " $arg" + case "$new_inherited_linker_flags " in + *" $arg "*) ;; + * ) func_append new_inherited_linker_flags " $arg" ;; + esac + continue + ;; + + -multi_module) + single_module=$wl-multi_module + continue + ;; + + -no-fast-install) + fast_install=no + continue + ;; + + -no-install) + case $host in + *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-*-darwin* | *-cegcc*) + # The PATH hackery in wrapper scripts is required on Windows + # and Darwin in order for the loader to find any dlls it needs. + func_warning "'-no-install' is ignored for $host" + func_warning "assuming '-no-fast-install' instead" + fast_install=no + ;; + *) no_install=yes ;; + esac + continue + ;; + + -no-undefined) + allow_undefined=no + continue + ;; + + -objectlist) + prev=objectlist + continue + ;; + + -os2dllname) + prev=os2dllname + continue + ;; + + -o) prev=output ;; + + -precious-files-regex) + prev=precious_regex + continue + ;; + + -release) + prev=release + continue + ;; + + -rpath) + prev=rpath + continue + ;; + + -R) + prev=xrpath + continue + ;; + + -R*) + func_stripname '-R' '' "$arg" + dir=$func_stripname_result + # We need an absolute path. + case $dir in + [\\/]* | [A-Za-z]:[\\/]*) ;; + =*) + func_stripname '=' '' "$dir" + dir=$lt_sysroot$func_stripname_result + ;; + *) + func_fatal_error "only absolute run-paths are allowed" + ;; + esac + case "$xrpath " in + *" $dir "*) ;; + *) func_append xrpath " $dir" ;; + esac + continue + ;; + + -shared) + # The effects of -shared are defined in a previous loop. + continue + ;; + + -shrext) + prev=shrext + continue + ;; + + -static | -static-libtool-libs) + # The effects of -static are defined in a previous loop. + # We used to do the same as -all-static on platforms that + # didn't have a PIC flag, but the assumption that the effects + # would be equivalent was wrong. It would break on at least + # Digital Unix and AIX. + continue + ;; + + -thread-safe) + thread_safe=yes + continue + ;; + + -version-info) + prev=vinfo + continue + ;; + + -version-number) + prev=vinfo + vinfo_number=yes + continue + ;; + + -weak) + prev=weak + continue + ;; + + -Wc,*) + func_stripname '-Wc,' '' "$arg" + args=$func_stripname_result + arg= + save_ifs=$IFS; IFS=, + for flag in $args; do + IFS=$save_ifs + func_quote_for_eval "$flag" + func_append arg " $func_quote_for_eval_result" + func_append compiler_flags " $func_quote_for_eval_result" + done + IFS=$save_ifs + func_stripname ' ' '' "$arg" + arg=$func_stripname_result + ;; + + -Wl,*) + func_stripname '-Wl,' '' "$arg" + args=$func_stripname_result + arg= + save_ifs=$IFS; IFS=, + for flag in $args; do + IFS=$save_ifs + func_quote_for_eval "$flag" + func_append arg " $wl$func_quote_for_eval_result" + func_append compiler_flags " $wl$func_quote_for_eval_result" + func_append linker_flags " $func_quote_for_eval_result" + done + IFS=$save_ifs + func_stripname ' ' '' "$arg" + arg=$func_stripname_result + ;; + + -Xcompiler) + prev=xcompiler + continue + ;; + + -Xlinker) + prev=xlinker + continue + ;; + + -XCClinker) + prev=xcclinker + continue + ;; + + # -msg_* for osf cc + -msg_*) + func_quote_for_eval "$arg" + arg=$func_quote_for_eval_result + ;; + + # Flags to be passed through unchanged, with rationale: + # -64, -mips[0-9] enable 64-bit mode for the SGI compiler + # -r[0-9][0-9]* specify processor for the SGI compiler + # -xarch=*, -xtarget=* enable 64-bit mode for the Sun compiler + # +DA*, +DD* enable 64-bit mode for the HP compiler + # -q* compiler args for the IBM compiler + # -m*, -t[45]*, -txscale* architecture-specific flags for GCC + # -F/path path to uninstalled frameworks, gcc on darwin + # -p, -pg, --coverage, -fprofile-* profiling flags for GCC + # -fstack-protector* stack protector flags for GCC + # @file GCC response files + # -tp=* Portland pgcc target processor selection + # --sysroot=* for sysroot support + # -O*, -g*, -flto*, -fwhopr*, -fuse-linker-plugin GCC link-time optimization + # -stdlib=* select c++ std lib with clang + # -fsanitize=* Clang memory and address sanitizer + -64|-mips[0-9]|-r[0-9][0-9]*|-xarch=*|-xtarget=*|+DA*|+DD*|-q*|-m*| \ + -t[45]*|-txscale*|-p|-pg|--coverage|-fprofile-*|-F*|@*|-tp=*|--sysroot=*| \ + -O*|-g*|-flto*|-fwhopr*|-fuse-linker-plugin|-fstack-protector*|-stdlib=*| \ + -fsanitize=*) + func_quote_for_eval "$arg" + arg=$func_quote_for_eval_result + func_append compile_command " $arg" + func_append finalize_command " $arg" + func_append compiler_flags " $arg" + continue + ;; + + -Z*) + if test os2 = "`expr $host : '.*\(os2\)'`"; then + # OS/2 uses -Zxxx to specify OS/2-specific options + compiler_flags="$compiler_flags $arg" + func_append compile_command " $arg" + func_append finalize_command " $arg" + case $arg in + -Zlinker | -Zstack) + prev=xcompiler + ;; + esac + continue + else + # Otherwise treat like 'Some other compiler flag' below + func_quote_for_eval "$arg" + arg=$func_quote_for_eval_result + fi + ;; + + # Some other compiler flag. + -* | +*) + func_quote_for_eval "$arg" + arg=$func_quote_for_eval_result + ;; + + *.$objext) + # A standard object. + func_append objs " $arg" + ;; + + *.lo) + # A libtool-controlled object. + + # Check to see that this really is a libtool object. + if func_lalib_unsafe_p "$arg"; then + pic_object= + non_pic_object= + + # Read the .lo file + func_source "$arg" + + if test -z "$pic_object" || + test -z "$non_pic_object" || + test none = "$pic_object" && + test none = "$non_pic_object"; then + func_fatal_error "cannot find name of object for '$arg'" + fi + + # Extract subdirectory from the argument. + func_dirname "$arg" "/" "" + xdir=$func_dirname_result + + test none = "$pic_object" || { + # Prepend the subdirectory the object is found in. + pic_object=$xdir$pic_object + + if test dlfiles = "$prev"; then + if test yes = "$build_libtool_libs" && test yes = "$dlopen_support"; then + func_append dlfiles " $pic_object" + prev= + continue + else + # If libtool objects are unsupported, then we need to preload. + prev=dlprefiles + fi + fi + + # CHECK ME: I think I busted this. -Ossama + if test dlprefiles = "$prev"; then + # Preload the old-style object. + func_append dlprefiles " $pic_object" + prev= + fi + + # A PIC object. + func_append libobjs " $pic_object" + arg=$pic_object + } + + # Non-PIC object. + if test none != "$non_pic_object"; then + # Prepend the subdirectory the object is found in. + non_pic_object=$xdir$non_pic_object + + # A standard non-PIC object + func_append non_pic_objects " $non_pic_object" + if test -z "$pic_object" || test none = "$pic_object"; then + arg=$non_pic_object + fi + else + # If the PIC object exists, use it instead. + # $xdir was prepended to $pic_object above. + non_pic_object=$pic_object + func_append non_pic_objects " $non_pic_object" + fi + else + # Only an error if not doing a dry-run. + if $opt_dry_run; then + # Extract subdirectory from the argument. + func_dirname "$arg" "/" "" + xdir=$func_dirname_result + + func_lo2o "$arg" + pic_object=$xdir$objdir/$func_lo2o_result + non_pic_object=$xdir$func_lo2o_result + func_append libobjs " $pic_object" + func_append non_pic_objects " $non_pic_object" + else + func_fatal_error "'$arg' is not a valid libtool object" + fi + fi + ;; + + *.$libext) + # An archive. + func_append deplibs " $arg" + func_append old_deplibs " $arg" + continue + ;; + + *.la) + # A libtool-controlled library. + + func_resolve_sysroot "$arg" + if test dlfiles = "$prev"; then + # This library was specified with -dlopen. + func_append dlfiles " $func_resolve_sysroot_result" + prev= + elif test dlprefiles = "$prev"; then + # The library was specified with -dlpreopen. + func_append dlprefiles " $func_resolve_sysroot_result" + prev= + else + func_append deplibs " $func_resolve_sysroot_result" + fi + continue + ;; + + # Some other compiler argument. + *) + # Unknown arguments in both finalize_command and compile_command need + # to be aesthetically quoted because they are evaled later. + func_quote_for_eval "$arg" + arg=$func_quote_for_eval_result + ;; + esac # arg + + # Now actually substitute the argument into the commands. + if test -n "$arg"; then + func_append compile_command " $arg" + func_append finalize_command " $arg" + fi + done # argument parsing loop + + test -n "$prev" && \ + func_fatal_help "the '$prevarg' option requires an argument" + + if test yes = "$export_dynamic" && test -n "$export_dynamic_flag_spec"; then + eval arg=\"$export_dynamic_flag_spec\" + func_append compile_command " $arg" + func_append finalize_command " $arg" + fi + + oldlibs= + # calculate the name of the file, without its directory + func_basename "$output" + outputname=$func_basename_result + libobjs_save=$libobjs + + if test -n "$shlibpath_var"; then + # get the directories listed in $shlibpath_var + eval shlib_search_path=\`\$ECHO \"\$$shlibpath_var\" \| \$SED \'s/:/ /g\'\` + else + shlib_search_path= + fi + eval sys_lib_search_path=\"$sys_lib_search_path_spec\" + eval sys_lib_dlsearch_path=\"$sys_lib_dlsearch_path_spec\" + + # Definition is injected by LT_CONFIG during libtool generation. + func_munge_path_list sys_lib_dlsearch_path "$LT_SYS_LIBRARY_PATH" + + func_dirname "$output" "/" "" + output_objdir=$func_dirname_result$objdir + func_to_tool_file "$output_objdir/" + tool_output_objdir=$func_to_tool_file_result + # Create the object directory. + func_mkdir_p "$output_objdir" + + # Determine the type of output + case $output in + "") + func_fatal_help "you must specify an output file" + ;; + *.$libext) linkmode=oldlib ;; + *.lo | *.$objext) linkmode=obj ;; + *.la) linkmode=lib ;; + *) linkmode=prog ;; # Anything else should be a program. + esac + + specialdeplibs= + + libs= + # Find all interdependent deplibs by searching for libraries + # that are linked more than once (e.g. -la -lb -la) + for deplib in $deplibs; do + if $opt_preserve_dup_deps; then + case "$libs " in + *" $deplib "*) func_append specialdeplibs " $deplib" ;; + esac + fi + func_append libs " $deplib" + done + + if test lib = "$linkmode"; then + libs="$predeps $libs $compiler_lib_search_path $postdeps" + + # Compute libraries that are listed more than once in $predeps + # $postdeps and mark them as special (i.e., whose duplicates are + # not to be eliminated). + pre_post_deps= + if $opt_duplicate_compiler_generated_deps; then + for pre_post_dep in $predeps $postdeps; do + case "$pre_post_deps " in + *" $pre_post_dep "*) func_append specialdeplibs " $pre_post_deps" ;; + esac + func_append pre_post_deps " $pre_post_dep" + done + fi + pre_post_deps= + fi + + deplibs= + newdependency_libs= + newlib_search_path= + need_relink=no # whether we're linking any uninstalled libtool libraries + notinst_deplibs= # not-installed libtool libraries + notinst_path= # paths that contain not-installed libtool libraries + + case $linkmode in + lib) + passes="conv dlpreopen link" + for file in $dlfiles $dlprefiles; do + case $file in + *.la) ;; + *) + func_fatal_help "libraries can '-dlopen' only libtool libraries: $file" + ;; + esac + done + ;; + prog) + compile_deplibs= + finalize_deplibs= + alldeplibs=false + newdlfiles= + newdlprefiles= + passes="conv scan dlopen dlpreopen link" + ;; + *) passes="conv" + ;; + esac + + for pass in $passes; do + # The preopen pass in lib mode reverses $deplibs; put it back here + # so that -L comes before libs that need it for instance... + if test lib,link = "$linkmode,$pass"; then + ## FIXME: Find the place where the list is rebuilt in the wrong + ## order, and fix it there properly + tmp_deplibs= + for deplib in $deplibs; do + tmp_deplibs="$deplib $tmp_deplibs" + done + deplibs=$tmp_deplibs + fi + + if test lib,link = "$linkmode,$pass" || + test prog,scan = "$linkmode,$pass"; then + libs=$deplibs + deplibs= + fi + if test prog = "$linkmode"; then + case $pass in + dlopen) libs=$dlfiles ;; + dlpreopen) libs=$dlprefiles ;; + link) libs="$deplibs %DEPLIBS% $dependency_libs" ;; + esac + fi + if test lib,dlpreopen = "$linkmode,$pass"; then + # Collect and forward deplibs of preopened libtool libs + for lib in $dlprefiles; do + # Ignore non-libtool-libs + dependency_libs= + func_resolve_sysroot "$lib" + case $lib in + *.la) func_source "$func_resolve_sysroot_result" ;; + esac + + # Collect preopened libtool deplibs, except any this library + # has declared as weak libs + for deplib in $dependency_libs; do + func_basename "$deplib" + deplib_base=$func_basename_result + case " $weak_libs " in + *" $deplib_base "*) ;; + *) func_append deplibs " $deplib" ;; + esac + done + done + libs=$dlprefiles + fi + if test dlopen = "$pass"; then + # Collect dlpreopened libraries + save_deplibs=$deplibs + deplibs= + fi + + for deplib in $libs; do + lib= + found=false + case $deplib in + -mt|-mthreads|-kthread|-Kthread|-pthread|-pthreads|--thread-safe \ + |-threads|-fopenmp|-openmp|-mp|-xopenmp|-omp|-qsmp=*) + if test prog,link = "$linkmode,$pass"; then + compile_deplibs="$deplib $compile_deplibs" + finalize_deplibs="$deplib $finalize_deplibs" + else + func_append compiler_flags " $deplib" + if test lib = "$linkmode"; then + case "$new_inherited_linker_flags " in + *" $deplib "*) ;; + * ) func_append new_inherited_linker_flags " $deplib" ;; + esac + fi + fi + continue + ;; + -l*) + if test lib != "$linkmode" && test prog != "$linkmode"; then + func_warning "'-l' is ignored for archives/objects" + continue + fi + func_stripname '-l' '' "$deplib" + name=$func_stripname_result + if test lib = "$linkmode"; then + searchdirs="$newlib_search_path $lib_search_path $compiler_lib_search_dirs $sys_lib_search_path $shlib_search_path" + else + searchdirs="$newlib_search_path $lib_search_path $sys_lib_search_path $shlib_search_path" + fi + for searchdir in $searchdirs; do + for search_ext in .la $std_shrext .so .a; do + # Search the libtool library + lib=$searchdir/lib$name$search_ext + if test -f "$lib"; then + if test .la = "$search_ext"; then + found=: + else + found=false + fi + break 2 + fi + done + done + if $found; then + # deplib is a libtool library + # If $allow_libtool_libs_with_static_runtimes && $deplib is a stdlib, + # We need to do some special things here, and not later. + if test yes = "$allow_libtool_libs_with_static_runtimes"; then + case " $predeps $postdeps " in + *" $deplib "*) + if func_lalib_p "$lib"; then + library_names= + old_library= + func_source "$lib" + for l in $old_library $library_names; do + ll=$l + done + if test "X$ll" = "X$old_library"; then # only static version available + found=false + func_dirname "$lib" "" "." + ladir=$func_dirname_result + lib=$ladir/$old_library + if test prog,link = "$linkmode,$pass"; then + compile_deplibs="$deplib $compile_deplibs" + finalize_deplibs="$deplib $finalize_deplibs" + else + deplibs="$deplib $deplibs" + test lib = "$linkmode" && newdependency_libs="$deplib $newdependency_libs" + fi + continue + fi + fi + ;; + *) ;; + esac + fi + else + # deplib doesn't seem to be a libtool library + if test prog,link = "$linkmode,$pass"; then + compile_deplibs="$deplib $compile_deplibs" + finalize_deplibs="$deplib $finalize_deplibs" + else + deplibs="$deplib $deplibs" + test lib = "$linkmode" && newdependency_libs="$deplib $newdependency_libs" + fi + continue + fi + ;; # -l + *.ltframework) + if test prog,link = "$linkmode,$pass"; then + compile_deplibs="$deplib $compile_deplibs" + finalize_deplibs="$deplib $finalize_deplibs" + else + deplibs="$deplib $deplibs" + if test lib = "$linkmode"; then + case "$new_inherited_linker_flags " in + *" $deplib "*) ;; + * ) func_append new_inherited_linker_flags " $deplib" ;; + esac + fi + fi + continue + ;; + -L*) + case $linkmode in + lib) + deplibs="$deplib $deplibs" + test conv = "$pass" && continue + newdependency_libs="$deplib $newdependency_libs" + func_stripname '-L' '' "$deplib" + func_resolve_sysroot "$func_stripname_result" + func_append newlib_search_path " $func_resolve_sysroot_result" + ;; + prog) + if test conv = "$pass"; then + deplibs="$deplib $deplibs" + continue + fi + if test scan = "$pass"; then + deplibs="$deplib $deplibs" + else + compile_deplibs="$deplib $compile_deplibs" + finalize_deplibs="$deplib $finalize_deplibs" + fi + func_stripname '-L' '' "$deplib" + func_resolve_sysroot "$func_stripname_result" + func_append newlib_search_path " $func_resolve_sysroot_result" + ;; + *) + func_warning "'-L' is ignored for archives/objects" + ;; + esac # linkmode + continue + ;; # -L + -R*) + if test link = "$pass"; then + func_stripname '-R' '' "$deplib" + func_resolve_sysroot "$func_stripname_result" + dir=$func_resolve_sysroot_result + # Make sure the xrpath contains only unique directories. + case "$xrpath " in + *" $dir "*) ;; + *) func_append xrpath " $dir" ;; + esac + fi + deplibs="$deplib $deplibs" + continue + ;; + *.la) + func_resolve_sysroot "$deplib" + lib=$func_resolve_sysroot_result + ;; + *.$libext) + if test conv = "$pass"; then + deplibs="$deplib $deplibs" + continue + fi + case $linkmode in + lib) + # Linking convenience modules into shared libraries is allowed, + # but linking other static libraries is non-portable. + case " $dlpreconveniencelibs " in + *" $deplib "*) ;; + *) + valid_a_lib=false + case $deplibs_check_method in + match_pattern*) + set dummy $deplibs_check_method; shift + match_pattern_regex=`expr "$deplibs_check_method" : "$1 \(.*\)"` + if eval "\$ECHO \"$deplib\"" 2>/dev/null | $SED 10q \ + | $EGREP "$match_pattern_regex" > /dev/null; then + valid_a_lib=: + fi + ;; + pass_all) + valid_a_lib=: + ;; + esac + if $valid_a_lib; then + echo + $ECHO "*** Warning: Linking the shared library $output against the" + $ECHO "*** static library $deplib is not portable!" + deplibs="$deplib $deplibs" + else + echo + $ECHO "*** Warning: Trying to link with static lib archive $deplib." + echo "*** I have the capability to make that library automatically link in when" + echo "*** you link to this library. But I can only do this if you have a" + echo "*** shared version of the library, which you do not appear to have" + echo "*** because the file extensions .$libext of this argument makes me believe" + echo "*** that it is just a static archive that I should not use here." + fi + ;; + esac + continue + ;; + prog) + if test link != "$pass"; then + deplibs="$deplib $deplibs" + else + compile_deplibs="$deplib $compile_deplibs" + finalize_deplibs="$deplib $finalize_deplibs" + fi + continue + ;; + esac # linkmode + ;; # *.$libext + *.lo | *.$objext) + if test conv = "$pass"; then + deplibs="$deplib $deplibs" + elif test prog = "$linkmode"; then + if test dlpreopen = "$pass" || test yes != "$dlopen_support" || test no = "$build_libtool_libs"; then + # If there is no dlopen support or we're linking statically, + # we need to preload. + func_append newdlprefiles " $deplib" + compile_deplibs="$deplib $compile_deplibs" + finalize_deplibs="$deplib $finalize_deplibs" + else + func_append newdlfiles " $deplib" + fi + fi + continue + ;; + %DEPLIBS%) + alldeplibs=: + continue + ;; + esac # case $deplib + + $found || test -f "$lib" \ + || func_fatal_error "cannot find the library '$lib' or unhandled argument '$deplib'" + + # Check to see that this really is a libtool archive. + func_lalib_unsafe_p "$lib" \ + || func_fatal_error "'$lib' is not a valid libtool archive" + + func_dirname "$lib" "" "." + ladir=$func_dirname_result + + dlname= + dlopen= + dlpreopen= + libdir= + library_names= + old_library= + inherited_linker_flags= + # If the library was installed with an old release of libtool, + # it will not redefine variables installed, or shouldnotlink + installed=yes + shouldnotlink=no + avoidtemprpath= + + + # Read the .la file + func_source "$lib" + + # Convert "-framework foo" to "foo.ltframework" + if test -n "$inherited_linker_flags"; then + tmp_inherited_linker_flags=`$ECHO "$inherited_linker_flags" | $SED 's/-framework \([^ $]*\)/\1.ltframework/g'` + for tmp_inherited_linker_flag in $tmp_inherited_linker_flags; do + case " $new_inherited_linker_flags " in + *" $tmp_inherited_linker_flag "*) ;; + *) func_append new_inherited_linker_flags " $tmp_inherited_linker_flag";; + esac + done + fi + dependency_libs=`$ECHO " $dependency_libs" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'` + if test lib,link = "$linkmode,$pass" || + test prog,scan = "$linkmode,$pass" || + { test prog != "$linkmode" && test lib != "$linkmode"; }; then + test -n "$dlopen" && func_append dlfiles " $dlopen" + test -n "$dlpreopen" && func_append dlprefiles " $dlpreopen" + fi + + if test conv = "$pass"; then + # Only check for convenience libraries + deplibs="$lib $deplibs" + if test -z "$libdir"; then + if test -z "$old_library"; then + func_fatal_error "cannot find name of link library for '$lib'" + fi + # It is a libtool convenience library, so add in its objects. + func_append convenience " $ladir/$objdir/$old_library" + func_append old_convenience " $ladir/$objdir/$old_library" + elif test prog != "$linkmode" && test lib != "$linkmode"; then + func_fatal_error "'$lib' is not a convenience library" + fi + tmp_libs= + for deplib in $dependency_libs; do + deplibs="$deplib $deplibs" + if $opt_preserve_dup_deps; then + case "$tmp_libs " in + *" $deplib "*) func_append specialdeplibs " $deplib" ;; + esac + fi + func_append tmp_libs " $deplib" + done + continue + fi # $pass = conv + + + # Get the name of the library we link against. + linklib= + if test -n "$old_library" && + { test yes = "$prefer_static_libs" || + test built,no = "$prefer_static_libs,$installed"; }; then + linklib=$old_library + else + for l in $old_library $library_names; do + linklib=$l + done + fi + if test -z "$linklib"; then + func_fatal_error "cannot find name of link library for '$lib'" + fi + + # This library was specified with -dlopen. + if test dlopen = "$pass"; then + test -z "$libdir" \ + && func_fatal_error "cannot -dlopen a convenience library: '$lib'" + if test -z "$dlname" || + test yes != "$dlopen_support" || + test no = "$build_libtool_libs" + then + # If there is no dlname, no dlopen support or we're linking + # statically, we need to preload. We also need to preload any + # dependent libraries so libltdl's deplib preloader doesn't + # bomb out in the load deplibs phase. + func_append dlprefiles " $lib $dependency_libs" + else + func_append newdlfiles " $lib" + fi + continue + fi # $pass = dlopen + + # We need an absolute path. + case $ladir in + [\\/]* | [A-Za-z]:[\\/]*) abs_ladir=$ladir ;; + *) + abs_ladir=`cd "$ladir" && pwd` + if test -z "$abs_ladir"; then + func_warning "cannot determine absolute directory name of '$ladir'" + func_warning "passing it literally to the linker, although it might fail" + abs_ladir=$ladir + fi + ;; + esac + func_basename "$lib" + laname=$func_basename_result + + # Find the relevant object directory and library name. + if test yes = "$installed"; then + if test ! -f "$lt_sysroot$libdir/$linklib" && test -f "$abs_ladir/$linklib"; then + func_warning "library '$lib' was moved." + dir=$ladir + absdir=$abs_ladir + libdir=$abs_ladir + else + dir=$lt_sysroot$libdir + absdir=$lt_sysroot$libdir + fi + test yes = "$hardcode_automatic" && avoidtemprpath=yes + else + if test ! -f "$ladir/$objdir/$linklib" && test -f "$abs_ladir/$linklib"; then + dir=$ladir + absdir=$abs_ladir + # Remove this search path later + func_append notinst_path " $abs_ladir" + else + dir=$ladir/$objdir + absdir=$abs_ladir/$objdir + # Remove this search path later + func_append notinst_path " $abs_ladir" + fi + fi # $installed = yes + func_stripname 'lib' '.la' "$laname" + name=$func_stripname_result + + # This library was specified with -dlpreopen. + if test dlpreopen = "$pass"; then + if test -z "$libdir" && test prog = "$linkmode"; then + func_fatal_error "only libraries may -dlpreopen a convenience library: '$lib'" + fi + case $host in + # special handling for platforms with PE-DLLs. + *cygwin* | *mingw* | *cegcc* ) + # Linker will automatically link against shared library if both + # static and shared are present. Therefore, ensure we extract + # symbols from the import library if a shared library is present + # (otherwise, the dlopen module name will be incorrect). We do + # this by putting the import library name into $newdlprefiles. + # We recover the dlopen module name by 'saving' the la file + # name in a special purpose variable, and (later) extracting the + # dlname from the la file. + if test -n "$dlname"; then + func_tr_sh "$dir/$linklib" + eval "libfile_$func_tr_sh_result=\$abs_ladir/\$laname" + func_append newdlprefiles " $dir/$linklib" + else + func_append newdlprefiles " $dir/$old_library" + # Keep a list of preopened convenience libraries to check + # that they are being used correctly in the link pass. + test -z "$libdir" && \ + func_append dlpreconveniencelibs " $dir/$old_library" + fi + ;; + * ) + # Prefer using a static library (so that no silly _DYNAMIC symbols + # are required to link). + if test -n "$old_library"; then + func_append newdlprefiles " $dir/$old_library" + # Keep a list of preopened convenience libraries to check + # that they are being used correctly in the link pass. + test -z "$libdir" && \ + func_append dlpreconveniencelibs " $dir/$old_library" + # Otherwise, use the dlname, so that lt_dlopen finds it. + elif test -n "$dlname"; then + func_append newdlprefiles " $dir/$dlname" + else + func_append newdlprefiles " $dir/$linklib" + fi + ;; + esac + fi # $pass = dlpreopen + + if test -z "$libdir"; then + # Link the convenience library + if test lib = "$linkmode"; then + deplibs="$dir/$old_library $deplibs" + elif test prog,link = "$linkmode,$pass"; then + compile_deplibs="$dir/$old_library $compile_deplibs" + finalize_deplibs="$dir/$old_library $finalize_deplibs" + else + deplibs="$lib $deplibs" # used for prog,scan pass + fi + continue + fi + + + if test prog = "$linkmode" && test link != "$pass"; then + func_append newlib_search_path " $ladir" + deplibs="$lib $deplibs" + + linkalldeplibs=false + if test no != "$link_all_deplibs" || test -z "$library_names" || + test no = "$build_libtool_libs"; then + linkalldeplibs=: + fi + + tmp_libs= + for deplib in $dependency_libs; do + case $deplib in + -L*) func_stripname '-L' '' "$deplib" + func_resolve_sysroot "$func_stripname_result" + func_append newlib_search_path " $func_resolve_sysroot_result" + ;; + esac + # Need to link against all dependency_libs? + if $linkalldeplibs; then + deplibs="$deplib $deplibs" + else + # Need to hardcode shared library paths + # or/and link against static libraries + newdependency_libs="$deplib $newdependency_libs" + fi + if $opt_preserve_dup_deps; then + case "$tmp_libs " in + *" $deplib "*) func_append specialdeplibs " $deplib" ;; + esac + fi + func_append tmp_libs " $deplib" + done # for deplib + continue + fi # $linkmode = prog... + + if test prog,link = "$linkmode,$pass"; then + if test -n "$library_names" && + { { test no = "$prefer_static_libs" || + test built,yes = "$prefer_static_libs,$installed"; } || + test -z "$old_library"; }; then + # We need to hardcode the library path + if test -n "$shlibpath_var" && test -z "$avoidtemprpath"; then + # Make sure the rpath contains only unique directories. + case $temp_rpath: in + *"$absdir:"*) ;; + *) func_append temp_rpath "$absdir:" ;; + esac + fi + + # Hardcode the library path. + # Skip directories that are in the system default run-time + # search path. + case " $sys_lib_dlsearch_path " in + *" $absdir "*) ;; + *) + case "$compile_rpath " in + *" $absdir "*) ;; + *) func_append compile_rpath " $absdir" ;; + esac + ;; + esac + case " $sys_lib_dlsearch_path " in + *" $libdir "*) ;; + *) + case "$finalize_rpath " in + *" $libdir "*) ;; + *) func_append finalize_rpath " $libdir" ;; + esac + ;; + esac + fi # $linkmode,$pass = prog,link... + + if $alldeplibs && + { test pass_all = "$deplibs_check_method" || + { test yes = "$build_libtool_libs" && + test -n "$library_names"; }; }; then + # We only need to search for static libraries + continue + fi + fi + + link_static=no # Whether the deplib will be linked statically + use_static_libs=$prefer_static_libs + if test built = "$use_static_libs" && test yes = "$installed"; then + use_static_libs=no + fi + if test -n "$library_names" && + { test no = "$use_static_libs" || test -z "$old_library"; }; then + case $host in + *cygwin* | *mingw* | *cegcc* | *os2*) + # No point in relinking DLLs because paths are not encoded + func_append notinst_deplibs " $lib" + need_relink=no + ;; + *) + if test no = "$installed"; then + func_append notinst_deplibs " $lib" + need_relink=yes + fi + ;; + esac + # This is a shared library + + # Warn about portability, can't link against -module's on some + # systems (darwin). Don't bleat about dlopened modules though! + dlopenmodule= + for dlpremoduletest in $dlprefiles; do + if test "X$dlpremoduletest" = "X$lib"; then + dlopenmodule=$dlpremoduletest + break + fi + done + if test -z "$dlopenmodule" && test yes = "$shouldnotlink" && test link = "$pass"; then + echo + if test prog = "$linkmode"; then + $ECHO "*** Warning: Linking the executable $output against the loadable module" + else + $ECHO "*** Warning: Linking the shared library $output against the loadable module" + fi + $ECHO "*** $linklib is not portable!" + fi + if test lib = "$linkmode" && + test yes = "$hardcode_into_libs"; then + # Hardcode the library path. + # Skip directories that are in the system default run-time + # search path. + case " $sys_lib_dlsearch_path " in + *" $absdir "*) ;; + *) + case "$compile_rpath " in + *" $absdir "*) ;; + *) func_append compile_rpath " $absdir" ;; + esac + ;; + esac + case " $sys_lib_dlsearch_path " in + *" $libdir "*) ;; + *) + case "$finalize_rpath " in + *" $libdir "*) ;; + *) func_append finalize_rpath " $libdir" ;; + esac + ;; + esac + fi + + if test -n "$old_archive_from_expsyms_cmds"; then + # figure out the soname + set dummy $library_names + shift + realname=$1 + shift + libname=`eval "\\$ECHO \"$libname_spec\""` + # use dlname if we got it. it's perfectly good, no? + if test -n "$dlname"; then + soname=$dlname + elif test -n "$soname_spec"; then + # bleh windows + case $host in + *cygwin* | mingw* | *cegcc* | *os2*) + func_arith $current - $age + major=$func_arith_result + versuffix=-$major + ;; + esac + eval soname=\"$soname_spec\" + else + soname=$realname + fi + + # Make a new name for the extract_expsyms_cmds to use + soroot=$soname + func_basename "$soroot" + soname=$func_basename_result + func_stripname 'lib' '.dll' "$soname" + newlib=libimp-$func_stripname_result.a + + # If the library has no export list, then create one now + if test -f "$output_objdir/$soname-def"; then : + else + func_verbose "extracting exported symbol list from '$soname'" + func_execute_cmds "$extract_expsyms_cmds" 'exit $?' + fi + + # Create $newlib + if test -f "$output_objdir/$newlib"; then :; else + func_verbose "generating import library for '$soname'" + func_execute_cmds "$old_archive_from_expsyms_cmds" 'exit $?' + fi + # make sure the library variables are pointing to the new library + dir=$output_objdir + linklib=$newlib + fi # test -n "$old_archive_from_expsyms_cmds" + + if test prog = "$linkmode" || test relink != "$opt_mode"; then + add_shlibpath= + add_dir= + add= + lib_linked=yes + case $hardcode_action in + immediate | unsupported) + if test no = "$hardcode_direct"; then + add=$dir/$linklib + case $host in + *-*-sco3.2v5.0.[024]*) add_dir=-L$dir ;; + *-*-sysv4*uw2*) add_dir=-L$dir ;; + *-*-sysv5OpenUNIX* | *-*-sysv5UnixWare7.[01].[10]* | \ + *-*-unixware7*) add_dir=-L$dir ;; + *-*-darwin* ) + # if the lib is a (non-dlopened) module then we cannot + # link against it, someone is ignoring the earlier warnings + if /usr/bin/file -L $add 2> /dev/null | + $GREP ": [^:]* bundle" >/dev/null; then + if test "X$dlopenmodule" != "X$lib"; then + $ECHO "*** Warning: lib $linklib is a module, not a shared library" + if test -z "$old_library"; then + echo + echo "*** And there doesn't seem to be a static archive available" + echo "*** The link will probably fail, sorry" + else + add=$dir/$old_library + fi + elif test -n "$old_library"; then + add=$dir/$old_library + fi + fi + esac + elif test no = "$hardcode_minus_L"; then + case $host in + *-*-sunos*) add_shlibpath=$dir ;; + esac + add_dir=-L$dir + add=-l$name + elif test no = "$hardcode_shlibpath_var"; then + add_shlibpath=$dir + add=-l$name + else + lib_linked=no + fi + ;; + relink) + if test yes = "$hardcode_direct" && + test no = "$hardcode_direct_absolute"; then + add=$dir/$linklib + elif test yes = "$hardcode_minus_L"; then + add_dir=-L$absdir + # Try looking first in the location we're being installed to. + if test -n "$inst_prefix_dir"; then + case $libdir in + [\\/]*) + func_append add_dir " -L$inst_prefix_dir$libdir" + ;; + esac + fi + add=-l$name + elif test yes = "$hardcode_shlibpath_var"; then + add_shlibpath=$dir + add=-l$name + else + lib_linked=no + fi + ;; + *) lib_linked=no ;; + esac + + if test yes != "$lib_linked"; then + func_fatal_configuration "unsupported hardcode properties" + fi + + if test -n "$add_shlibpath"; then + case :$compile_shlibpath: in + *":$add_shlibpath:"*) ;; + *) func_append compile_shlibpath "$add_shlibpath:" ;; + esac + fi + if test prog = "$linkmode"; then + test -n "$add_dir" && compile_deplibs="$add_dir $compile_deplibs" + test -n "$add" && compile_deplibs="$add $compile_deplibs" + else + test -n "$add_dir" && deplibs="$add_dir $deplibs" + test -n "$add" && deplibs="$add $deplibs" + if test yes != "$hardcode_direct" && + test yes != "$hardcode_minus_L" && + test yes = "$hardcode_shlibpath_var"; then + case :$finalize_shlibpath: in + *":$libdir:"*) ;; + *) func_append finalize_shlibpath "$libdir:" ;; + esac + fi + fi + fi + + if test prog = "$linkmode" || test relink = "$opt_mode"; then + add_shlibpath= + add_dir= + add= + # Finalize command for both is simple: just hardcode it. + if test yes = "$hardcode_direct" && + test no = "$hardcode_direct_absolute"; then + add=$libdir/$linklib + elif test yes = "$hardcode_minus_L"; then + add_dir=-L$libdir + add=-l$name + elif test yes = "$hardcode_shlibpath_var"; then + case :$finalize_shlibpath: in + *":$libdir:"*) ;; + *) func_append finalize_shlibpath "$libdir:" ;; + esac + add=-l$name + elif test yes = "$hardcode_automatic"; then + if test -n "$inst_prefix_dir" && + test -f "$inst_prefix_dir$libdir/$linklib"; then + add=$inst_prefix_dir$libdir/$linklib + else + add=$libdir/$linklib + fi + else + # We cannot seem to hardcode it, guess we'll fake it. + add_dir=-L$libdir + # Try looking first in the location we're being installed to. + if test -n "$inst_prefix_dir"; then + case $libdir in + [\\/]*) + func_append add_dir " -L$inst_prefix_dir$libdir" + ;; + esac + fi + add=-l$name + fi + + if test prog = "$linkmode"; then + test -n "$add_dir" && finalize_deplibs="$add_dir $finalize_deplibs" + test -n "$add" && finalize_deplibs="$add $finalize_deplibs" + else + test -n "$add_dir" && deplibs="$add_dir $deplibs" + test -n "$add" && deplibs="$add $deplibs" + fi + fi + elif test prog = "$linkmode"; then + # Here we assume that one of hardcode_direct or hardcode_minus_L + # is not unsupported. This is valid on all known static and + # shared platforms. + if test unsupported != "$hardcode_direct"; then + test -n "$old_library" && linklib=$old_library + compile_deplibs="$dir/$linklib $compile_deplibs" + finalize_deplibs="$dir/$linklib $finalize_deplibs" + else + compile_deplibs="-l$name -L$dir $compile_deplibs" + finalize_deplibs="-l$name -L$dir $finalize_deplibs" + fi + elif test yes = "$build_libtool_libs"; then + # Not a shared library + if test pass_all != "$deplibs_check_method"; then + # We're trying link a shared library against a static one + # but the system doesn't support it. + + # Just print a warning and add the library to dependency_libs so + # that the program can be linked against the static library. + echo + $ECHO "*** Warning: This system cannot link to static lib archive $lib." + echo "*** I have the capability to make that library automatically link in when" + echo "*** you link to this library. But I can only do this if you have a" + echo "*** shared version of the library, which you do not appear to have." + if test yes = "$module"; then + echo "*** But as you try to build a module library, libtool will still create " + echo "*** a static module, that should work as long as the dlopening application" + echo "*** is linked with the -dlopen flag to resolve symbols at runtime." + if test -z "$global_symbol_pipe"; then + echo + echo "*** However, this would only work if libtool was able to extract symbol" + echo "*** lists from a program, using 'nm' or equivalent, but libtool could" + echo "*** not find such a program. So, this module is probably useless." + echo "*** 'nm' from GNU binutils and a full rebuild may help." + fi + if test no = "$build_old_libs"; then + build_libtool_libs=module + build_old_libs=yes + else + build_libtool_libs=no + fi + fi + else + deplibs="$dir/$old_library $deplibs" + link_static=yes + fi + fi # link shared/static library? + + if test lib = "$linkmode"; then + if test -n "$dependency_libs" && + { test yes != "$hardcode_into_libs" || + test yes = "$build_old_libs" || + test yes = "$link_static"; }; then + # Extract -R from dependency_libs + temp_deplibs= + for libdir in $dependency_libs; do + case $libdir in + -R*) func_stripname '-R' '' "$libdir" + temp_xrpath=$func_stripname_result + case " $xrpath " in + *" $temp_xrpath "*) ;; + *) func_append xrpath " $temp_xrpath";; + esac;; + *) func_append temp_deplibs " $libdir";; + esac + done + dependency_libs=$temp_deplibs + fi + + func_append newlib_search_path " $absdir" + # Link against this library + test no = "$link_static" && newdependency_libs="$abs_ladir/$laname $newdependency_libs" + # ... and its dependency_libs + tmp_libs= + for deplib in $dependency_libs; do + newdependency_libs="$deplib $newdependency_libs" + case $deplib in + -L*) func_stripname '-L' '' "$deplib" + func_resolve_sysroot "$func_stripname_result";; + *) func_resolve_sysroot "$deplib" ;; + esac + if $opt_preserve_dup_deps; then + case "$tmp_libs " in + *" $func_resolve_sysroot_result "*) + func_append specialdeplibs " $func_resolve_sysroot_result" ;; + esac + fi + func_append tmp_libs " $func_resolve_sysroot_result" + done + + if test no != "$link_all_deplibs"; then + # Add the search paths of all dependency libraries + for deplib in $dependency_libs; do + path= + case $deplib in + -L*) path=$deplib ;; + *.la) + func_resolve_sysroot "$deplib" + deplib=$func_resolve_sysroot_result + func_dirname "$deplib" "" "." + dir=$func_dirname_result + # We need an absolute path. + case $dir in + [\\/]* | [A-Za-z]:[\\/]*) absdir=$dir ;; + *) + absdir=`cd "$dir" && pwd` + if test -z "$absdir"; then + func_warning "cannot determine absolute directory name of '$dir'" + absdir=$dir + fi + ;; + esac + if $GREP "^installed=no" $deplib > /dev/null; then + case $host in + *-*-darwin*) + depdepl= + eval deplibrary_names=`$SED -n -e 's/^library_names=\(.*\)$/\1/p' $deplib` + if test -n "$deplibrary_names"; then + for tmp in $deplibrary_names; do + depdepl=$tmp + done + if test -f "$absdir/$objdir/$depdepl"; then + depdepl=$absdir/$objdir/$depdepl + darwin_install_name=`$OTOOL -L $depdepl | awk '{if (NR == 2) {print $1;exit}}'` + if test -z "$darwin_install_name"; then + darwin_install_name=`$OTOOL64 -L $depdepl | awk '{if (NR == 2) {print $1;exit}}'` + fi + func_append compiler_flags " $wl-dylib_file $wl$darwin_install_name:$depdepl" + func_append linker_flags " -dylib_file $darwin_install_name:$depdepl" + path= + fi + fi + ;; + *) + path=-L$absdir/$objdir + ;; + esac + else + eval libdir=`$SED -n -e 's/^libdir=\(.*\)$/\1/p' $deplib` + test -z "$libdir" && \ + func_fatal_error "'$deplib' is not a valid libtool archive" + test "$absdir" != "$libdir" && \ + func_warning "'$deplib' seems to be moved" + + path=-L$absdir + fi + ;; + esac + case " $deplibs " in + *" $path "*) ;; + *) deplibs="$path $deplibs" ;; + esac + done + fi # link_all_deplibs != no + fi # linkmode = lib + done # for deplib in $libs + if test link = "$pass"; then + if test prog = "$linkmode"; then + compile_deplibs="$new_inherited_linker_flags $compile_deplibs" + finalize_deplibs="$new_inherited_linker_flags $finalize_deplibs" + else + compiler_flags="$compiler_flags "`$ECHO " $new_inherited_linker_flags" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'` + fi + fi + dependency_libs=$newdependency_libs + if test dlpreopen = "$pass"; then + # Link the dlpreopened libraries before other libraries + for deplib in $save_deplibs; do + deplibs="$deplib $deplibs" + done + fi + if test dlopen != "$pass"; then + test conv = "$pass" || { + # Make sure lib_search_path contains only unique directories. + lib_search_path= + for dir in $newlib_search_path; do + case "$lib_search_path " in + *" $dir "*) ;; + *) func_append lib_search_path " $dir" ;; + esac + done + newlib_search_path= + } + + if test prog,link = "$linkmode,$pass"; then + vars="compile_deplibs finalize_deplibs" + else + vars=deplibs + fi + for var in $vars dependency_libs; do + # Add libraries to $var in reverse order + eval tmp_libs=\"\$$var\" + new_libs= + for deplib in $tmp_libs; do + # FIXME: Pedantically, this is the right thing to do, so + # that some nasty dependency loop isn't accidentally + # broken: + #new_libs="$deplib $new_libs" + # Pragmatically, this seems to cause very few problems in + # practice: + case $deplib in + -L*) new_libs="$deplib $new_libs" ;; + -R*) ;; + *) + # And here is the reason: when a library appears more + # than once as an explicit dependence of a library, or + # is implicitly linked in more than once by the + # compiler, it is considered special, and multiple + # occurrences thereof are not removed. Compare this + # with having the same library being listed as a + # dependency of multiple other libraries: in this case, + # we know (pedantically, we assume) the library does not + # need to be listed more than once, so we keep only the + # last copy. This is not always right, but it is rare + # enough that we require users that really mean to play + # such unportable linking tricks to link the library + # using -Wl,-lname, so that libtool does not consider it + # for duplicate removal. + case " $specialdeplibs " in + *" $deplib "*) new_libs="$deplib $new_libs" ;; + *) + case " $new_libs " in + *" $deplib "*) ;; + *) new_libs="$deplib $new_libs" ;; + esac + ;; + esac + ;; + esac + done + tmp_libs= + for deplib in $new_libs; do + case $deplib in + -L*) + case " $tmp_libs " in + *" $deplib "*) ;; + *) func_append tmp_libs " $deplib" ;; + esac + ;; + *) func_append tmp_libs " $deplib" ;; + esac + done + eval $var=\"$tmp_libs\" + done # for var + fi + + # Add Sun CC postdeps if required: + test CXX = "$tagname" && { + case $host_os in + linux*) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) # Sun C++ 5.9 + func_suncc_cstd_abi + + if test no != "$suncc_use_cstd_abi"; then + func_append postdeps ' -library=Cstd -library=Crun' + fi + ;; + esac + ;; + + solaris*) + func_cc_basename "$CC" + case $func_cc_basename_result in + CC* | sunCC*) + func_suncc_cstd_abi + + if test no != "$suncc_use_cstd_abi"; then + func_append postdeps ' -library=Cstd -library=Crun' + fi + ;; + esac + ;; + esac + } + + # Last step: remove runtime libs from dependency_libs + # (they stay in deplibs) + tmp_libs= + for i in $dependency_libs; do + case " $predeps $postdeps $compiler_lib_search_path " in + *" $i "*) + i= + ;; + esac + if test -n "$i"; then + func_append tmp_libs " $i" + fi + done + dependency_libs=$tmp_libs + done # for pass + if test prog = "$linkmode"; then + dlfiles=$newdlfiles + fi + if test prog = "$linkmode" || test lib = "$linkmode"; then + dlprefiles=$newdlprefiles + fi + + case $linkmode in + oldlib) + if test -n "$dlfiles$dlprefiles" || test no != "$dlself"; then + func_warning "'-dlopen' is ignored for archives" + fi + + case " $deplibs" in + *\ -l* | *\ -L*) + func_warning "'-l' and '-L' are ignored for archives" ;; + esac + + test -n "$rpath" && \ + func_warning "'-rpath' is ignored for archives" + + test -n "$xrpath" && \ + func_warning "'-R' is ignored for archives" + + test -n "$vinfo" && \ + func_warning "'-version-info/-version-number' is ignored for archives" + + test -n "$release" && \ + func_warning "'-release' is ignored for archives" + + test -n "$export_symbols$export_symbols_regex" && \ + func_warning "'-export-symbols' is ignored for archives" + + # Now set the variables for building old libraries. + build_libtool_libs=no + oldlibs=$output + func_append objs "$old_deplibs" + ;; + + lib) + # Make sure we only generate libraries of the form 'libNAME.la'. + case $outputname in + lib*) + func_stripname 'lib' '.la' "$outputname" + name=$func_stripname_result + eval shared_ext=\"$shrext_cmds\" + eval libname=\"$libname_spec\" + ;; + *) + test no = "$module" \ + && func_fatal_help "libtool library '$output' must begin with 'lib'" + + if test no != "$need_lib_prefix"; then + # Add the "lib" prefix for modules if required + func_stripname '' '.la' "$outputname" + name=$func_stripname_result + eval shared_ext=\"$shrext_cmds\" + eval libname=\"$libname_spec\" + else + func_stripname '' '.la' "$outputname" + libname=$func_stripname_result + fi + ;; + esac + + if test -n "$objs"; then + if test pass_all != "$deplibs_check_method"; then + func_fatal_error "cannot build libtool library '$output' from non-libtool objects on this host:$objs" + else + echo + $ECHO "*** Warning: Linking the shared library $output against the non-libtool" + $ECHO "*** objects $objs is not portable!" + func_append libobjs " $objs" + fi + fi + + test no = "$dlself" \ + || func_warning "'-dlopen self' is ignored for libtool libraries" + + set dummy $rpath + shift + test 1 -lt "$#" \ + && func_warning "ignoring multiple '-rpath's for a libtool library" + + install_libdir=$1 + + oldlibs= + if test -z "$rpath"; then + if test yes = "$build_libtool_libs"; then + # Building a libtool convenience library. + # Some compilers have problems with a '.al' extension so + # convenience libraries should have the same extension an + # archive normally would. + oldlibs="$output_objdir/$libname.$libext $oldlibs" + build_libtool_libs=convenience + build_old_libs=yes + fi + + test -n "$vinfo" && \ + func_warning "'-version-info/-version-number' is ignored for convenience libraries" + + test -n "$release" && \ + func_warning "'-release' is ignored for convenience libraries" + else + + # Parse the version information argument. + save_ifs=$IFS; IFS=: + set dummy $vinfo 0 0 0 + shift + IFS=$save_ifs + + test -n "$7" && \ + func_fatal_help "too many parameters to '-version-info'" + + # convert absolute version numbers to libtool ages + # this retains compatibility with .la files and attempts + # to make the code below a bit more comprehensible + + case $vinfo_number in + yes) + number_major=$1 + number_minor=$2 + number_revision=$3 + # + # There are really only two kinds -- those that + # use the current revision as the major version + # and those that subtract age and use age as + # a minor version. But, then there is irix + # that has an extra 1 added just for fun + # + case $version_type in + # correct linux to gnu/linux during the next big refactor + darwin|freebsd-elf|linux|osf|windows|none) + func_arith $number_major + $number_minor + current=$func_arith_result + age=$number_minor + revision=$number_revision + ;; + freebsd-aout|qnx|sunos) + current=$number_major + revision=$number_minor + age=0 + ;; + irix|nonstopux) + func_arith $number_major + $number_minor + current=$func_arith_result + age=$number_minor + revision=$number_minor + lt_irix_increment=no + ;; + esac + ;; + no) + current=$1 + revision=$2 + age=$3 + ;; + esac + + # Check that each of the things are valid numbers. + case $current in + 0|[1-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9]|[1-9][0-9][0-9][0-9][0-9]) ;; + *) + func_error "CURRENT '$current' must be a nonnegative integer" + func_fatal_error "'$vinfo' is not valid version information" + ;; + esac + + case $revision in + 0|[1-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9]|[1-9][0-9][0-9][0-9][0-9]) ;; + *) + func_error "REVISION '$revision' must be a nonnegative integer" + func_fatal_error "'$vinfo' is not valid version information" + ;; + esac + + case $age in + 0|[1-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9]|[1-9][0-9][0-9][0-9][0-9]) ;; + *) + func_error "AGE '$age' must be a nonnegative integer" + func_fatal_error "'$vinfo' is not valid version information" + ;; + esac + + if test "$age" -gt "$current"; then + func_error "AGE '$age' is greater than the current interface number '$current'" + func_fatal_error "'$vinfo' is not valid version information" + fi + + # Calculate the version variables. + major= + versuffix= + verstring= + case $version_type in + none) ;; + + darwin) + # Like Linux, but with the current version available in + # verstring for coding it into the library header + func_arith $current - $age + major=.$func_arith_result + versuffix=$major.$age.$revision + # Darwin ld doesn't like 0 for these options... + func_arith $current + 1 + minor_current=$func_arith_result + xlcverstring="$wl-compatibility_version $wl$minor_current $wl-current_version $wl$minor_current.$revision" + verstring="-compatibility_version $minor_current -current_version $minor_current.$revision" + # On Darwin other compilers + case $CC in + nagfor*) + verstring="$wl-compatibility_version $wl$minor_current $wl-current_version $wl$minor_current.$revision" + ;; + *) + verstring="-compatibility_version $minor_current -current_version $minor_current.$revision" + ;; + esac + ;; + + freebsd-aout) + major=.$current + versuffix=.$current.$revision + ;; + + freebsd-elf) + func_arith $current - $age + major=.$func_arith_result + versuffix=$major.$age.$revision + ;; + + irix | nonstopux) + if test no = "$lt_irix_increment"; then + func_arith $current - $age + else + func_arith $current - $age + 1 + fi + major=$func_arith_result + + case $version_type in + nonstopux) verstring_prefix=nonstopux ;; + *) verstring_prefix=sgi ;; + esac + verstring=$verstring_prefix$major.$revision + + # Add in all the interfaces that we are compatible with. + loop=$revision + while test 0 -ne "$loop"; do + func_arith $revision - $loop + iface=$func_arith_result + func_arith $loop - 1 + loop=$func_arith_result + verstring=$verstring_prefix$major.$iface:$verstring + done + + # Before this point, $major must not contain '.'. + major=.$major + versuffix=$major.$revision + ;; + + linux) # correct to gnu/linux during the next big refactor + func_arith $current - $age + major=.$func_arith_result + versuffix=$major.$age.$revision + ;; + + osf) + func_arith $current - $age + major=.$func_arith_result + versuffix=.$current.$age.$revision + verstring=$current.$age.$revision + + # Add in all the interfaces that we are compatible with. + loop=$age + while test 0 -ne "$loop"; do + func_arith $current - $loop + iface=$func_arith_result + func_arith $loop - 1 + loop=$func_arith_result + verstring=$verstring:$iface.0 + done + + # Make executables depend on our current version. + func_append verstring ":$current.0" + ;; + + qnx) + major=.$current + versuffix=.$current + ;; + + sco) + major=.$current + versuffix=.$current + ;; + + sunos) + major=.$current + versuffix=.$current.$revision + ;; + + windows) + # Use '-' rather than '.', since we only want one + # extension on DOS 8.3 file systems. + func_arith $current - $age + major=$func_arith_result + versuffix=-$major + ;; + + *) + func_fatal_configuration "unknown library version type '$version_type'" + ;; + esac + + # Clear the version info if we defaulted, and they specified a release. + if test -z "$vinfo" && test -n "$release"; then + major= + case $version_type in + darwin) + # we can't check for "0.0" in archive_cmds due to quoting + # problems, so we reset it completely + verstring= + ;; + *) + verstring=0.0 + ;; + esac + if test no = "$need_version"; then + versuffix= + else + versuffix=.0.0 + fi + fi + + # Remove version info from name if versioning should be avoided + if test yes,no = "$avoid_version,$need_version"; then + major= + versuffix= + verstring= + fi + + # Check to see if the archive will have undefined symbols. + if test yes = "$allow_undefined"; then + if test unsupported = "$allow_undefined_flag"; then + if test yes = "$build_old_libs"; then + func_warning "undefined symbols not allowed in $host shared libraries; building static only" + build_libtool_libs=no + else + func_fatal_error "can't build $host shared library unless -no-undefined is specified" + fi + fi + else + # Don't allow undefined symbols. + allow_undefined_flag=$no_undefined_flag + fi + + fi + + func_generate_dlsyms "$libname" "$libname" : + func_append libobjs " $symfileobj" + test " " = "$libobjs" && libobjs= + + if test relink != "$opt_mode"; then + # Remove our outputs, but don't remove object files since they + # may have been created when compiling PIC objects. + removelist= + tempremovelist=`$ECHO "$output_objdir/*"` + for p in $tempremovelist; do + case $p in + *.$objext | *.gcno) + ;; + $output_objdir/$outputname | $output_objdir/$libname.* | $output_objdir/$libname$release.*) + if test -n "$precious_files_regex"; then + if $ECHO "$p" | $EGREP -e "$precious_files_regex" >/dev/null 2>&1 + then + continue + fi + fi + func_append removelist " $p" + ;; + *) ;; + esac + done + test -n "$removelist" && \ + func_show_eval "${RM}r \$removelist" + fi + + # Now set the variables for building old libraries. + if test yes = "$build_old_libs" && test convenience != "$build_libtool_libs"; then + func_append oldlibs " $output_objdir/$libname.$libext" + + # Transform .lo files to .o files. + oldobjs="$objs "`$ECHO "$libobjs" | $SP2NL | $SED "/\.$libext$/d; $lo2o" | $NL2SP` + fi + + # Eliminate all temporary directories. + #for path in $notinst_path; do + # lib_search_path=`$ECHO "$lib_search_path " | $SED "s% $path % %g"` + # deplibs=`$ECHO "$deplibs " | $SED "s% -L$path % %g"` + # dependency_libs=`$ECHO "$dependency_libs " | $SED "s% -L$path % %g"` + #done + + if test -n "$xrpath"; then + # If the user specified any rpath flags, then add them. + temp_xrpath= + for libdir in $xrpath; do + func_replace_sysroot "$libdir" + func_append temp_xrpath " -R$func_replace_sysroot_result" + case "$finalize_rpath " in + *" $libdir "*) ;; + *) func_append finalize_rpath " $libdir" ;; + esac + done + if test yes != "$hardcode_into_libs" || test yes = "$build_old_libs"; then + dependency_libs="$temp_xrpath $dependency_libs" + fi + fi + + # Make sure dlfiles contains only unique files that won't be dlpreopened + old_dlfiles=$dlfiles + dlfiles= + for lib in $old_dlfiles; do + case " $dlprefiles $dlfiles " in + *" $lib "*) ;; + *) func_append dlfiles " $lib" ;; + esac + done + + # Make sure dlprefiles contains only unique files + old_dlprefiles=$dlprefiles + dlprefiles= + for lib in $old_dlprefiles; do + case "$dlprefiles " in + *" $lib "*) ;; + *) func_append dlprefiles " $lib" ;; + esac + done + + if test yes = "$build_libtool_libs"; then + if test -n "$rpath"; then + case $host in + *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-*-beos* | *-cegcc* | *-*-haiku*) + # these systems don't actually have a c library (as such)! + ;; + *-*-rhapsody* | *-*-darwin1.[012]) + # Rhapsody C library is in the System framework + func_append deplibs " System.ltframework" + ;; + *-*-netbsd*) + # Don't link with libc until the a.out ld.so is fixed. + ;; + *-*-openbsd* | *-*-freebsd* | *-*-dragonfly*) + # Do not include libc due to us having libc/libc_r. + ;; + *-*-sco3.2v5* | *-*-sco5v6*) + # Causes problems with __ctype + ;; + *-*-sysv4.2uw2* | *-*-sysv5* | *-*-unixware* | *-*-OpenUNIX*) + # Compiler inserts libc in the correct place for threads to work + ;; + *) + # Add libc to deplibs on all other systems if necessary. + if test yes = "$build_libtool_need_lc"; then + func_append deplibs " -lc" + fi + ;; + esac + fi + + # Transform deplibs into only deplibs that can be linked in shared. + name_save=$name + libname_save=$libname + release_save=$release + versuffix_save=$versuffix + major_save=$major + # I'm not sure if I'm treating the release correctly. I think + # release should show up in the -l (ie -lgmp5) so we don't want to + # add it in twice. Is that correct? + release= + versuffix= + major= + newdeplibs= + droppeddeps=no + case $deplibs_check_method in + pass_all) + # Don't check for shared/static. Everything works. + # This might be a little naive. We might want to check + # whether the library exists or not. But this is on + # osf3 & osf4 and I'm not really sure... Just + # implementing what was already the behavior. + newdeplibs=$deplibs + ;; + test_compile) + # This code stresses the "libraries are programs" paradigm to its + # limits. Maybe even breaks it. We compile a program, linking it + # against the deplibs as a proxy for the library. Then we can check + # whether they linked in statically or dynamically with ldd. + $opt_dry_run || $RM conftest.c + cat > conftest.c </dev/null` + $nocaseglob + else + potential_libs=`ls $i/$libnameglob[.-]* 2>/dev/null` + fi + for potent_lib in $potential_libs; do + # Follow soft links. + if ls -lLd "$potent_lib" 2>/dev/null | + $GREP " -> " >/dev/null; then + continue + fi + # The statement above tries to avoid entering an + # endless loop below, in case of cyclic links. + # We might still enter an endless loop, since a link + # loop can be closed while we follow links, + # but so what? + potlib=$potent_lib + while test -h "$potlib" 2>/dev/null; do + potliblink=`ls -ld $potlib | $SED 's/.* -> //'` + case $potliblink in + [\\/]* | [A-Za-z]:[\\/]*) potlib=$potliblink;; + *) potlib=`$ECHO "$potlib" | $SED 's|[^/]*$||'`"$potliblink";; + esac + done + if eval $file_magic_cmd \"\$potlib\" 2>/dev/null | + $SED -e 10q | + $EGREP "$file_magic_regex" > /dev/null; then + func_append newdeplibs " $a_deplib" + a_deplib= + break 2 + fi + done + done + fi + if test -n "$a_deplib"; then + droppeddeps=yes + echo + $ECHO "*** Warning: linker path does not have real file for library $a_deplib." + echo "*** I have the capability to make that library automatically link in when" + echo "*** you link to this library. But I can only do this if you have a" + echo "*** shared version of the library, which you do not appear to have" + echo "*** because I did check the linker path looking for a file starting" + if test -z "$potlib"; then + $ECHO "*** with $libname but no candidates were found. (...for file magic test)" + else + $ECHO "*** with $libname and none of the candidates passed a file format test" + $ECHO "*** using a file magic. Last file checked: $potlib" + fi + fi + ;; + *) + # Add a -L argument. + func_append newdeplibs " $a_deplib" + ;; + esac + done # Gone through all deplibs. + ;; + match_pattern*) + set dummy $deplibs_check_method; shift + match_pattern_regex=`expr "$deplibs_check_method" : "$1 \(.*\)"` + for a_deplib in $deplibs; do + case $a_deplib in + -l*) + func_stripname -l '' "$a_deplib" + name=$func_stripname_result + if test yes = "$allow_libtool_libs_with_static_runtimes"; then + case " $predeps $postdeps " in + *" $a_deplib "*) + func_append newdeplibs " $a_deplib" + a_deplib= + ;; + esac + fi + if test -n "$a_deplib"; then + libname=`eval "\\$ECHO \"$libname_spec\""` + for i in $lib_search_path $sys_lib_search_path $shlib_search_path; do + potential_libs=`ls $i/$libname[.-]* 2>/dev/null` + for potent_lib in $potential_libs; do + potlib=$potent_lib # see symlink-check above in file_magic test + if eval "\$ECHO \"$potent_lib\"" 2>/dev/null | $SED 10q | \ + $EGREP "$match_pattern_regex" > /dev/null; then + func_append newdeplibs " $a_deplib" + a_deplib= + break 2 + fi + done + done + fi + if test -n "$a_deplib"; then + droppeddeps=yes + echo + $ECHO "*** Warning: linker path does not have real file for library $a_deplib." + echo "*** I have the capability to make that library automatically link in when" + echo "*** you link to this library. But I can only do this if you have a" + echo "*** shared version of the library, which you do not appear to have" + echo "*** because I did check the linker path looking for a file starting" + if test -z "$potlib"; then + $ECHO "*** with $libname but no candidates were found. (...for regex pattern test)" + else + $ECHO "*** with $libname and none of the candidates passed a file format test" + $ECHO "*** using a regex pattern. Last file checked: $potlib" + fi + fi + ;; + *) + # Add a -L argument. + func_append newdeplibs " $a_deplib" + ;; + esac + done # Gone through all deplibs. + ;; + none | unknown | *) + newdeplibs= + tmp_deplibs=`$ECHO " $deplibs" | $SED 's/ -lc$//; s/ -[LR][^ ]*//g'` + if test yes = "$allow_libtool_libs_with_static_runtimes"; then + for i in $predeps $postdeps; do + # can't use Xsed below, because $i might contain '/' + tmp_deplibs=`$ECHO " $tmp_deplibs" | $SED "s|$i||"` + done + fi + case $tmp_deplibs in + *[!\ \ ]*) + echo + if test none = "$deplibs_check_method"; then + echo "*** Warning: inter-library dependencies are not supported in this platform." + else + echo "*** Warning: inter-library dependencies are not known to be supported." + fi + echo "*** All declared inter-library dependencies are being dropped." + droppeddeps=yes + ;; + esac + ;; + esac + versuffix=$versuffix_save + major=$major_save + release=$release_save + libname=$libname_save + name=$name_save + + case $host in + *-*-rhapsody* | *-*-darwin1.[012]) + # On Rhapsody replace the C library with the System framework + newdeplibs=`$ECHO " $newdeplibs" | $SED 's/ -lc / System.ltframework /'` + ;; + esac + + if test yes = "$droppeddeps"; then + if test yes = "$module"; then + echo + echo "*** Warning: libtool could not satisfy all declared inter-library" + $ECHO "*** dependencies of module $libname. Therefore, libtool will create" + echo "*** a static module, that should work as long as the dlopening" + echo "*** application is linked with the -dlopen flag." + if test -z "$global_symbol_pipe"; then + echo + echo "*** However, this would only work if libtool was able to extract symbol" + echo "*** lists from a program, using 'nm' or equivalent, but libtool could" + echo "*** not find such a program. So, this module is probably useless." + echo "*** 'nm' from GNU binutils and a full rebuild may help." + fi + if test no = "$build_old_libs"; then + oldlibs=$output_objdir/$libname.$libext + build_libtool_libs=module + build_old_libs=yes + else + build_libtool_libs=no + fi + else + echo "*** The inter-library dependencies that have been dropped here will be" + echo "*** automatically added whenever a program is linked with this library" + echo "*** or is declared to -dlopen it." + + if test no = "$allow_undefined"; then + echo + echo "*** Since this library must not contain undefined symbols," + echo "*** because either the platform does not support them or" + echo "*** it was explicitly requested with -no-undefined," + echo "*** libtool will only create a static version of it." + if test no = "$build_old_libs"; then + oldlibs=$output_objdir/$libname.$libext + build_libtool_libs=module + build_old_libs=yes + else + build_libtool_libs=no + fi + fi + fi + fi + # Done checking deplibs! + deplibs=$newdeplibs + fi + # Time to change all our "foo.ltframework" stuff back to "-framework foo" + case $host in + *-*-darwin*) + newdeplibs=`$ECHO " $newdeplibs" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'` + new_inherited_linker_flags=`$ECHO " $new_inherited_linker_flags" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'` + deplibs=`$ECHO " $deplibs" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'` + ;; + esac + + # move library search paths that coincide with paths to not yet + # installed libraries to the beginning of the library search list + new_libs= + for path in $notinst_path; do + case " $new_libs " in + *" -L$path/$objdir "*) ;; + *) + case " $deplibs " in + *" -L$path/$objdir "*) + func_append new_libs " -L$path/$objdir" ;; + esac + ;; + esac + done + for deplib in $deplibs; do + case $deplib in + -L*) + case " $new_libs " in + *" $deplib "*) ;; + *) func_append new_libs " $deplib" ;; + esac + ;; + *) func_append new_libs " $deplib" ;; + esac + done + deplibs=$new_libs + + # All the library-specific variables (install_libdir is set above). + library_names= + old_library= + dlname= + + # Test again, we may have decided not to build it any more + if test yes = "$build_libtool_libs"; then + # Remove $wl instances when linking with ld. + # FIXME: should test the right _cmds variable. + case $archive_cmds in + *\$LD\ *) wl= ;; + esac + if test yes = "$hardcode_into_libs"; then + # Hardcode the library paths + hardcode_libdirs= + dep_rpath= + rpath=$finalize_rpath + test relink = "$opt_mode" || rpath=$compile_rpath$rpath + for libdir in $rpath; do + if test -n "$hardcode_libdir_flag_spec"; then + if test -n "$hardcode_libdir_separator"; then + func_replace_sysroot "$libdir" + libdir=$func_replace_sysroot_result + if test -z "$hardcode_libdirs"; then + hardcode_libdirs=$libdir + else + # Just accumulate the unique libdirs. + case $hardcode_libdir_separator$hardcode_libdirs$hardcode_libdir_separator in + *"$hardcode_libdir_separator$libdir$hardcode_libdir_separator"*) + ;; + *) + func_append hardcode_libdirs "$hardcode_libdir_separator$libdir" + ;; + esac + fi + else + eval flag=\"$hardcode_libdir_flag_spec\" + func_append dep_rpath " $flag" + fi + elif test -n "$runpath_var"; then + case "$perm_rpath " in + *" $libdir "*) ;; + *) func_append perm_rpath " $libdir" ;; + esac + fi + done + # Substitute the hardcoded libdirs into the rpath. + if test -n "$hardcode_libdir_separator" && + test -n "$hardcode_libdirs"; then + libdir=$hardcode_libdirs + eval "dep_rpath=\"$hardcode_libdir_flag_spec\"" + fi + if test -n "$runpath_var" && test -n "$perm_rpath"; then + # We should set the runpath_var. + rpath= + for dir in $perm_rpath; do + func_append rpath "$dir:" + done + eval "$runpath_var='$rpath\$$runpath_var'; export $runpath_var" + fi + test -n "$dep_rpath" && deplibs="$dep_rpath $deplibs" + fi + + shlibpath=$finalize_shlibpath + test relink = "$opt_mode" || shlibpath=$compile_shlibpath$shlibpath + if test -n "$shlibpath"; then + eval "$shlibpath_var='$shlibpath\$$shlibpath_var'; export $shlibpath_var" + fi + + # Get the real and link names of the library. + eval shared_ext=\"$shrext_cmds\" + eval library_names=\"$library_names_spec\" + set dummy $library_names + shift + realname=$1 + shift + + if test -n "$soname_spec"; then + eval soname=\"$soname_spec\" + else + soname=$realname + fi + if test -z "$dlname"; then + dlname=$soname + fi + + lib=$output_objdir/$realname + linknames= + for link + do + func_append linknames " $link" + done + + # Use standard objects if they are pic + test -z "$pic_flag" && libobjs=`$ECHO "$libobjs" | $SP2NL | $SED "$lo2o" | $NL2SP` + test "X$libobjs" = "X " && libobjs= + + delfiles= + if test -n "$export_symbols" && test -n "$include_expsyms"; then + $opt_dry_run || cp "$export_symbols" "$output_objdir/$libname.uexp" + export_symbols=$output_objdir/$libname.uexp + func_append delfiles " $export_symbols" + fi + + orig_export_symbols= + case $host_os in + cygwin* | mingw* | cegcc*) + if test -n "$export_symbols" && test -z "$export_symbols_regex"; then + # exporting using user supplied symfile + func_dll_def_p "$export_symbols" || { + # and it's NOT already a .def file. Must figure out + # which of the given symbols are data symbols and tag + # them as such. So, trigger use of export_symbols_cmds. + # export_symbols gets reassigned inside the "prepare + # the list of exported symbols" if statement, so the + # include_expsyms logic still works. + orig_export_symbols=$export_symbols + export_symbols= + always_export_symbols=yes + } + fi + ;; + esac + + # Prepare the list of exported symbols + if test -z "$export_symbols"; then + if test yes = "$always_export_symbols" || test -n "$export_symbols_regex"; then + func_verbose "generating symbol list for '$libname.la'" + export_symbols=$output_objdir/$libname.exp + $opt_dry_run || $RM $export_symbols + cmds=$export_symbols_cmds + save_ifs=$IFS; IFS='~' + for cmd1 in $cmds; do + IFS=$save_ifs + # Take the normal branch if the nm_file_list_spec branch + # doesn't work or if tool conversion is not needed. + case $nm_file_list_spec~$to_tool_file_cmd in + *~func_convert_file_noop | *~func_convert_file_msys_to_w32 | ~*) + try_normal_branch=yes + eval cmd=\"$cmd1\" + func_len " $cmd" + len=$func_len_result + ;; + *) + try_normal_branch=no + ;; + esac + if test yes = "$try_normal_branch" \ + && { test "$len" -lt "$max_cmd_len" \ + || test "$max_cmd_len" -le -1; } + then + func_show_eval "$cmd" 'exit $?' + skipped_export=false + elif test -n "$nm_file_list_spec"; then + func_basename "$output" + output_la=$func_basename_result + save_libobjs=$libobjs + save_output=$output + output=$output_objdir/$output_la.nm + func_to_tool_file "$output" + libobjs=$nm_file_list_spec$func_to_tool_file_result + func_append delfiles " $output" + func_verbose "creating $NM input file list: $output" + for obj in $save_libobjs; do + func_to_tool_file "$obj" + $ECHO "$func_to_tool_file_result" + done > "$output" + eval cmd=\"$cmd1\" + func_show_eval "$cmd" 'exit $?' + output=$save_output + libobjs=$save_libobjs + skipped_export=false + else + # The command line is too long to execute in one step. + func_verbose "using reloadable object file for export list..." + skipped_export=: + # Break out early, otherwise skipped_export may be + # set to false by a later but shorter cmd. + break + fi + done + IFS=$save_ifs + if test -n "$export_symbols_regex" && test : != "$skipped_export"; then + func_show_eval '$EGREP -e "$export_symbols_regex" "$export_symbols" > "${export_symbols}T"' + func_show_eval '$MV "${export_symbols}T" "$export_symbols"' + fi + fi + fi + + if test -n "$export_symbols" && test -n "$include_expsyms"; then + tmp_export_symbols=$export_symbols + test -n "$orig_export_symbols" && tmp_export_symbols=$orig_export_symbols + $opt_dry_run || eval '$ECHO "$include_expsyms" | $SP2NL >> "$tmp_export_symbols"' + fi + + if test : != "$skipped_export" && test -n "$orig_export_symbols"; then + # The given exports_symbols file has to be filtered, so filter it. + func_verbose "filter symbol list for '$libname.la' to tag DATA exports" + # FIXME: $output_objdir/$libname.filter potentially contains lots of + # 's' commands, which not all seds can handle. GNU sed should be fine + # though. Also, the filter scales superlinearly with the number of + # global variables. join(1) would be nice here, but unfortunately + # isn't a blessed tool. + $opt_dry_run || $SED -e '/[ ,]DATA/!d;s,\(.*\)\([ \,].*\),s|^\1$|\1\2|,' < $export_symbols > $output_objdir/$libname.filter + func_append delfiles " $export_symbols $output_objdir/$libname.filter" + export_symbols=$output_objdir/$libname.def + $opt_dry_run || $SED -f $output_objdir/$libname.filter < $orig_export_symbols > $export_symbols + fi + + tmp_deplibs= + for test_deplib in $deplibs; do + case " $convenience " in + *" $test_deplib "*) ;; + *) + func_append tmp_deplibs " $test_deplib" + ;; + esac + done + deplibs=$tmp_deplibs + + if test -n "$convenience"; then + if test -n "$whole_archive_flag_spec" && + test yes = "$compiler_needs_object" && + test -z "$libobjs"; then + # extract the archives, so we have objects to list. + # TODO: could optimize this to just extract one archive. + whole_archive_flag_spec= + fi + if test -n "$whole_archive_flag_spec"; then + save_libobjs=$libobjs + eval libobjs=\"\$libobjs $whole_archive_flag_spec\" + test "X$libobjs" = "X " && libobjs= + else + gentop=$output_objdir/${outputname}x + func_append generated " $gentop" + + func_extract_archives $gentop $convenience + func_append libobjs " $func_extract_archives_result" + test "X$libobjs" = "X " && libobjs= + fi + fi + + if test yes = "$thread_safe" && test -n "$thread_safe_flag_spec"; then + eval flag=\"$thread_safe_flag_spec\" + func_append linker_flags " $flag" + fi + + # Make a backup of the uninstalled library when relinking + if test relink = "$opt_mode"; then + $opt_dry_run || eval '(cd $output_objdir && $RM ${realname}U && $MV $realname ${realname}U)' || exit $? + fi + + # Do each of the archive commands. + if test yes = "$module" && test -n "$module_cmds"; then + if test -n "$export_symbols" && test -n "$module_expsym_cmds"; then + eval test_cmds=\"$module_expsym_cmds\" + cmds=$module_expsym_cmds + else + eval test_cmds=\"$module_cmds\" + cmds=$module_cmds + fi + else + if test -n "$export_symbols" && test -n "$archive_expsym_cmds"; then + eval test_cmds=\"$archive_expsym_cmds\" + cmds=$archive_expsym_cmds + else + eval test_cmds=\"$archive_cmds\" + cmds=$archive_cmds + fi + fi + + if test : != "$skipped_export" && + func_len " $test_cmds" && + len=$func_len_result && + test "$len" -lt "$max_cmd_len" || test "$max_cmd_len" -le -1; then + : + else + # The command line is too long to link in one step, link piecewise + # or, if using GNU ld and skipped_export is not :, use a linker + # script. + + # Save the value of $output and $libobjs because we want to + # use them later. If we have whole_archive_flag_spec, we + # want to use save_libobjs as it was before + # whole_archive_flag_spec was expanded, because we can't + # assume the linker understands whole_archive_flag_spec. + # This may have to be revisited, in case too many + # convenience libraries get linked in and end up exceeding + # the spec. + if test -z "$convenience" || test -z "$whole_archive_flag_spec"; then + save_libobjs=$libobjs + fi + save_output=$output + func_basename "$output" + output_la=$func_basename_result + + # Clear the reloadable object creation command queue and + # initialize k to one. + test_cmds= + concat_cmds= + objlist= + last_robj= + k=1 + + if test -n "$save_libobjs" && test : != "$skipped_export" && test yes = "$with_gnu_ld"; then + output=$output_objdir/$output_la.lnkscript + func_verbose "creating GNU ld script: $output" + echo 'INPUT (' > $output + for obj in $save_libobjs + do + func_to_tool_file "$obj" + $ECHO "$func_to_tool_file_result" >> $output + done + echo ')' >> $output + func_append delfiles " $output" + func_to_tool_file "$output" + output=$func_to_tool_file_result + elif test -n "$save_libobjs" && test : != "$skipped_export" && test -n "$file_list_spec"; then + output=$output_objdir/$output_la.lnk + func_verbose "creating linker input file list: $output" + : > $output + set x $save_libobjs + shift + firstobj= + if test yes = "$compiler_needs_object"; then + firstobj="$1 " + shift + fi + for obj + do + func_to_tool_file "$obj" + $ECHO "$func_to_tool_file_result" >> $output + done + func_append delfiles " $output" + func_to_tool_file "$output" + output=$firstobj\"$file_list_spec$func_to_tool_file_result\" + else + if test -n "$save_libobjs"; then + func_verbose "creating reloadable object files..." + output=$output_objdir/$output_la-$k.$objext + eval test_cmds=\"$reload_cmds\" + func_len " $test_cmds" + len0=$func_len_result + len=$len0 + + # Loop over the list of objects to be linked. + for obj in $save_libobjs + do + func_len " $obj" + func_arith $len + $func_len_result + len=$func_arith_result + if test -z "$objlist" || + test "$len" -lt "$max_cmd_len"; then + func_append objlist " $obj" + else + # The command $test_cmds is almost too long, add a + # command to the queue. + if test 1 -eq "$k"; then + # The first file doesn't have a previous command to add. + reload_objs=$objlist + eval concat_cmds=\"$reload_cmds\" + else + # All subsequent reloadable object files will link in + # the last one created. + reload_objs="$objlist $last_robj" + eval concat_cmds=\"\$concat_cmds~$reload_cmds~\$RM $last_robj\" + fi + last_robj=$output_objdir/$output_la-$k.$objext + func_arith $k + 1 + k=$func_arith_result + output=$output_objdir/$output_la-$k.$objext + objlist=" $obj" + func_len " $last_robj" + func_arith $len0 + $func_len_result + len=$func_arith_result + fi + done + # Handle the remaining objects by creating one last + # reloadable object file. All subsequent reloadable object + # files will link in the last one created. + test -z "$concat_cmds" || concat_cmds=$concat_cmds~ + reload_objs="$objlist $last_robj" + eval concat_cmds=\"\$concat_cmds$reload_cmds\" + if test -n "$last_robj"; then + eval concat_cmds=\"\$concat_cmds~\$RM $last_robj\" + fi + func_append delfiles " $output" + + else + output= + fi + + ${skipped_export-false} && { + func_verbose "generating symbol list for '$libname.la'" + export_symbols=$output_objdir/$libname.exp + $opt_dry_run || $RM $export_symbols + libobjs=$output + # Append the command to create the export file. + test -z "$concat_cmds" || concat_cmds=$concat_cmds~ + eval concat_cmds=\"\$concat_cmds$export_symbols_cmds\" + if test -n "$last_robj"; then + eval concat_cmds=\"\$concat_cmds~\$RM $last_robj\" + fi + } + + test -n "$save_libobjs" && + func_verbose "creating a temporary reloadable object file: $output" + + # Loop through the commands generated above and execute them. + save_ifs=$IFS; IFS='~' + for cmd in $concat_cmds; do + IFS=$save_ifs + $opt_quiet || { + func_quote_for_expand "$cmd" + eval "func_echo $func_quote_for_expand_result" + } + $opt_dry_run || eval "$cmd" || { + lt_exit=$? + + # Restore the uninstalled library and exit + if test relink = "$opt_mode"; then + ( cd "$output_objdir" && \ + $RM "${realname}T" && \ + $MV "${realname}U" "$realname" ) + fi + + exit $lt_exit + } + done + IFS=$save_ifs + + if test -n "$export_symbols_regex" && ${skipped_export-false}; then + func_show_eval '$EGREP -e "$export_symbols_regex" "$export_symbols" > "${export_symbols}T"' + func_show_eval '$MV "${export_symbols}T" "$export_symbols"' + fi + fi + + ${skipped_export-false} && { + if test -n "$export_symbols" && test -n "$include_expsyms"; then + tmp_export_symbols=$export_symbols + test -n "$orig_export_symbols" && tmp_export_symbols=$orig_export_symbols + $opt_dry_run || eval '$ECHO "$include_expsyms" | $SP2NL >> "$tmp_export_symbols"' + fi + + if test -n "$orig_export_symbols"; then + # The given exports_symbols file has to be filtered, so filter it. + func_verbose "filter symbol list for '$libname.la' to tag DATA exports" + # FIXME: $output_objdir/$libname.filter potentially contains lots of + # 's' commands, which not all seds can handle. GNU sed should be fine + # though. Also, the filter scales superlinearly with the number of + # global variables. join(1) would be nice here, but unfortunately + # isn't a blessed tool. + $opt_dry_run || $SED -e '/[ ,]DATA/!d;s,\(.*\)\([ \,].*\),s|^\1$|\1\2|,' < $export_symbols > $output_objdir/$libname.filter + func_append delfiles " $export_symbols $output_objdir/$libname.filter" + export_symbols=$output_objdir/$libname.def + $opt_dry_run || $SED -f $output_objdir/$libname.filter < $orig_export_symbols > $export_symbols + fi + } + + libobjs=$output + # Restore the value of output. + output=$save_output + + if test -n "$convenience" && test -n "$whole_archive_flag_spec"; then + eval libobjs=\"\$libobjs $whole_archive_flag_spec\" + test "X$libobjs" = "X " && libobjs= + fi + # Expand the library linking commands again to reset the + # value of $libobjs for piecewise linking. + + # Do each of the archive commands. + if test yes = "$module" && test -n "$module_cmds"; then + if test -n "$export_symbols" && test -n "$module_expsym_cmds"; then + cmds=$module_expsym_cmds + else + cmds=$module_cmds + fi + else + if test -n "$export_symbols" && test -n "$archive_expsym_cmds"; then + cmds=$archive_expsym_cmds + else + cmds=$archive_cmds + fi + fi + fi + + if test -n "$delfiles"; then + # Append the command to remove temporary files to $cmds. + eval cmds=\"\$cmds~\$RM $delfiles\" + fi + + # Add any objects from preloaded convenience libraries + if test -n "$dlprefiles"; then + gentop=$output_objdir/${outputname}x + func_append generated " $gentop" + + func_extract_archives $gentop $dlprefiles + func_append libobjs " $func_extract_archives_result" + test "X$libobjs" = "X " && libobjs= + fi + + save_ifs=$IFS; IFS='~' + for cmd in $cmds; do + IFS=$sp$nl + eval cmd=\"$cmd\" + IFS=$save_ifs + $opt_quiet || { + func_quote_for_expand "$cmd" + eval "func_echo $func_quote_for_expand_result" + } + $opt_dry_run || eval "$cmd" || { + lt_exit=$? + + # Restore the uninstalled library and exit + if test relink = "$opt_mode"; then + ( cd "$output_objdir" && \ + $RM "${realname}T" && \ + $MV "${realname}U" "$realname" ) + fi + + exit $lt_exit + } + done + IFS=$save_ifs + + # Restore the uninstalled library and exit + if test relink = "$opt_mode"; then + $opt_dry_run || eval '(cd $output_objdir && $RM ${realname}T && $MV $realname ${realname}T && $MV ${realname}U $realname)' || exit $? + + if test -n "$convenience"; then + if test -z "$whole_archive_flag_spec"; then + func_show_eval '${RM}r "$gentop"' + fi + fi + + exit $EXIT_SUCCESS + fi + + # Create links to the real library. + for linkname in $linknames; do + if test "$realname" != "$linkname"; then + func_show_eval '(cd "$output_objdir" && $RM "$linkname" && $LN_S "$realname" "$linkname")' 'exit $?' + fi + done + + # If -module or -export-dynamic was specified, set the dlname. + if test yes = "$module" || test yes = "$export_dynamic"; then + # On all known operating systems, these are identical. + dlname=$soname + fi + fi + ;; + + obj) + if test -n "$dlfiles$dlprefiles" || test no != "$dlself"; then + func_warning "'-dlopen' is ignored for objects" + fi + + case " $deplibs" in + *\ -l* | *\ -L*) + func_warning "'-l' and '-L' are ignored for objects" ;; + esac + + test -n "$rpath" && \ + func_warning "'-rpath' is ignored for objects" + + test -n "$xrpath" && \ + func_warning "'-R' is ignored for objects" + + test -n "$vinfo" && \ + func_warning "'-version-info' is ignored for objects" + + test -n "$release" && \ + func_warning "'-release' is ignored for objects" + + case $output in + *.lo) + test -n "$objs$old_deplibs" && \ + func_fatal_error "cannot build library object '$output' from non-libtool objects" + + libobj=$output + func_lo2o "$libobj" + obj=$func_lo2o_result + ;; + *) + libobj= + obj=$output + ;; + esac + + # Delete the old objects. + $opt_dry_run || $RM $obj $libobj + + # Objects from convenience libraries. This assumes + # single-version convenience libraries. Whenever we create + # different ones for PIC/non-PIC, this we'll have to duplicate + # the extraction. + reload_conv_objs= + gentop= + # if reload_cmds runs $LD directly, get rid of -Wl from + # whole_archive_flag_spec and hope we can get by with turning comma + # into space. + case $reload_cmds in + *\$LD[\ \$]*) wl= ;; + esac + if test -n "$convenience"; then + if test -n "$whole_archive_flag_spec"; then + eval tmp_whole_archive_flags=\"$whole_archive_flag_spec\" + test -n "$wl" || tmp_whole_archive_flags=`$ECHO "$tmp_whole_archive_flags" | $SED 's|,| |g'` + reload_conv_objs=$reload_objs\ $tmp_whole_archive_flags + else + gentop=$output_objdir/${obj}x + func_append generated " $gentop" + + func_extract_archives $gentop $convenience + reload_conv_objs="$reload_objs $func_extract_archives_result" + fi + fi + + # If we're not building shared, we need to use non_pic_objs + test yes = "$build_libtool_libs" || libobjs=$non_pic_objects + + # Create the old-style object. + reload_objs=$objs$old_deplibs' '`$ECHO "$libobjs" | $SP2NL | $SED "/\.$libext$/d; /\.lib$/d; $lo2o" | $NL2SP`' '$reload_conv_objs + + output=$obj + func_execute_cmds "$reload_cmds" 'exit $?' + + # Exit if we aren't doing a library object file. + if test -z "$libobj"; then + if test -n "$gentop"; then + func_show_eval '${RM}r "$gentop"' + fi + + exit $EXIT_SUCCESS + fi + + test yes = "$build_libtool_libs" || { + if test -n "$gentop"; then + func_show_eval '${RM}r "$gentop"' + fi + + # Create an invalid libtool object if no PIC, so that we don't + # accidentally link it into a program. + # $show "echo timestamp > $libobj" + # $opt_dry_run || eval "echo timestamp > $libobj" || exit $? + exit $EXIT_SUCCESS + } + + if test -n "$pic_flag" || test default != "$pic_mode"; then + # Only do commands if we really have different PIC objects. + reload_objs="$libobjs $reload_conv_objs" + output=$libobj + func_execute_cmds "$reload_cmds" 'exit $?' + fi + + if test -n "$gentop"; then + func_show_eval '${RM}r "$gentop"' + fi + + exit $EXIT_SUCCESS + ;; + + prog) + case $host in + *cygwin*) func_stripname '' '.exe' "$output" + output=$func_stripname_result.exe;; + esac + test -n "$vinfo" && \ + func_warning "'-version-info' is ignored for programs" + + test -n "$release" && \ + func_warning "'-release' is ignored for programs" + + $preload \ + && test unknown,unknown,unknown = "$dlopen_support,$dlopen_self,$dlopen_self_static" \ + && func_warning "'LT_INIT([dlopen])' not used. Assuming no dlopen support." + + case $host in + *-*-rhapsody* | *-*-darwin1.[012]) + # On Rhapsody replace the C library is the System framework + compile_deplibs=`$ECHO " $compile_deplibs" | $SED 's/ -lc / System.ltframework /'` + finalize_deplibs=`$ECHO " $finalize_deplibs" | $SED 's/ -lc / System.ltframework /'` + ;; + esac + + case $host in + *-*-darwin*) + # Don't allow lazy linking, it breaks C++ global constructors + # But is supposedly fixed on 10.4 or later (yay!). + if test CXX = "$tagname"; then + case ${MACOSX_DEPLOYMENT_TARGET-10.0} in + 10.[0123]) + func_append compile_command " $wl-bind_at_load" + func_append finalize_command " $wl-bind_at_load" + ;; + esac + fi + # Time to change all our "foo.ltframework" stuff back to "-framework foo" + compile_deplibs=`$ECHO " $compile_deplibs" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'` + finalize_deplibs=`$ECHO " $finalize_deplibs" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'` + ;; + esac + + + # move library search paths that coincide with paths to not yet + # installed libraries to the beginning of the library search list + new_libs= + for path in $notinst_path; do + case " $new_libs " in + *" -L$path/$objdir "*) ;; + *) + case " $compile_deplibs " in + *" -L$path/$objdir "*) + func_append new_libs " -L$path/$objdir" ;; + esac + ;; + esac + done + for deplib in $compile_deplibs; do + case $deplib in + -L*) + case " $new_libs " in + *" $deplib "*) ;; + *) func_append new_libs " $deplib" ;; + esac + ;; + *) func_append new_libs " $deplib" ;; + esac + done + compile_deplibs=$new_libs + + + func_append compile_command " $compile_deplibs" + func_append finalize_command " $finalize_deplibs" + + if test -n "$rpath$xrpath"; then + # If the user specified any rpath flags, then add them. + for libdir in $rpath $xrpath; do + # This is the magic to use -rpath. + case "$finalize_rpath " in + *" $libdir "*) ;; + *) func_append finalize_rpath " $libdir" ;; + esac + done + fi + + # Now hardcode the library paths + rpath= + hardcode_libdirs= + for libdir in $compile_rpath $finalize_rpath; do + if test -n "$hardcode_libdir_flag_spec"; then + if test -n "$hardcode_libdir_separator"; then + if test -z "$hardcode_libdirs"; then + hardcode_libdirs=$libdir + else + # Just accumulate the unique libdirs. + case $hardcode_libdir_separator$hardcode_libdirs$hardcode_libdir_separator in + *"$hardcode_libdir_separator$libdir$hardcode_libdir_separator"*) + ;; + *) + func_append hardcode_libdirs "$hardcode_libdir_separator$libdir" + ;; + esac + fi + else + eval flag=\"$hardcode_libdir_flag_spec\" + func_append rpath " $flag" + fi + elif test -n "$runpath_var"; then + case "$perm_rpath " in + *" $libdir "*) ;; + *) func_append perm_rpath " $libdir" ;; + esac + fi + case $host in + *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-cegcc*) + testbindir=`$ECHO "$libdir" | $SED -e 's*/lib$*/bin*'` + case :$dllsearchpath: in + *":$libdir:"*) ;; + ::) dllsearchpath=$libdir;; + *) func_append dllsearchpath ":$libdir";; + esac + case :$dllsearchpath: in + *":$testbindir:"*) ;; + ::) dllsearchpath=$testbindir;; + *) func_append dllsearchpath ":$testbindir";; + esac + ;; + esac + done + # Substitute the hardcoded libdirs into the rpath. + if test -n "$hardcode_libdir_separator" && + test -n "$hardcode_libdirs"; then + libdir=$hardcode_libdirs + eval rpath=\" $hardcode_libdir_flag_spec\" + fi + compile_rpath=$rpath + + rpath= + hardcode_libdirs= + for libdir in $finalize_rpath; do + if test -n "$hardcode_libdir_flag_spec"; then + if test -n "$hardcode_libdir_separator"; then + if test -z "$hardcode_libdirs"; then + hardcode_libdirs=$libdir + else + # Just accumulate the unique libdirs. + case $hardcode_libdir_separator$hardcode_libdirs$hardcode_libdir_separator in + *"$hardcode_libdir_separator$libdir$hardcode_libdir_separator"*) + ;; + *) + func_append hardcode_libdirs "$hardcode_libdir_separator$libdir" + ;; + esac + fi + else + eval flag=\"$hardcode_libdir_flag_spec\" + func_append rpath " $flag" + fi + elif test -n "$runpath_var"; then + case "$finalize_perm_rpath " in + *" $libdir "*) ;; + *) func_append finalize_perm_rpath " $libdir" ;; + esac + fi + done + # Substitute the hardcoded libdirs into the rpath. + if test -n "$hardcode_libdir_separator" && + test -n "$hardcode_libdirs"; then + libdir=$hardcode_libdirs + eval rpath=\" $hardcode_libdir_flag_spec\" + fi + finalize_rpath=$rpath + + if test -n "$libobjs" && test yes = "$build_old_libs"; then + # Transform all the library objects into standard objects. + compile_command=`$ECHO "$compile_command" | $SP2NL | $SED "$lo2o" | $NL2SP` + finalize_command=`$ECHO "$finalize_command" | $SP2NL | $SED "$lo2o" | $NL2SP` + fi + + func_generate_dlsyms "$outputname" "@PROGRAM@" false + + # template prelinking step + if test -n "$prelink_cmds"; then + func_execute_cmds "$prelink_cmds" 'exit $?' + fi + + wrappers_required=: + case $host in + *cegcc* | *mingw32ce*) + # Disable wrappers for cegcc and mingw32ce hosts, we are cross compiling anyway. + wrappers_required=false + ;; + *cygwin* | *mingw* ) + test yes = "$build_libtool_libs" || wrappers_required=false + ;; + *) + if test no = "$need_relink" || test yes != "$build_libtool_libs"; then + wrappers_required=false + fi + ;; + esac + $wrappers_required || { + # Replace the output file specification. + compile_command=`$ECHO "$compile_command" | $SED 's%@OUTPUT@%'"$output"'%g'` + link_command=$compile_command$compile_rpath + + # We have no uninstalled library dependencies, so finalize right now. + exit_status=0 + func_show_eval "$link_command" 'exit_status=$?' + + if test -n "$postlink_cmds"; then + func_to_tool_file "$output" + postlink_cmds=`func_echo_all "$postlink_cmds" | $SED -e 's%@OUTPUT@%'"$output"'%g' -e 's%@TOOL_OUTPUT@%'"$func_to_tool_file_result"'%g'` + func_execute_cmds "$postlink_cmds" 'exit $?' + fi + + # Delete the generated files. + if test -f "$output_objdir/${outputname}S.$objext"; then + func_show_eval '$RM "$output_objdir/${outputname}S.$objext"' + fi + + exit $exit_status + } + + if test -n "$compile_shlibpath$finalize_shlibpath"; then + compile_command="$shlibpath_var=\"$compile_shlibpath$finalize_shlibpath\$$shlibpath_var\" $compile_command" + fi + if test -n "$finalize_shlibpath"; then + finalize_command="$shlibpath_var=\"$finalize_shlibpath\$$shlibpath_var\" $finalize_command" + fi + + compile_var= + finalize_var= + if test -n "$runpath_var"; then + if test -n "$perm_rpath"; then + # We should set the runpath_var. + rpath= + for dir in $perm_rpath; do + func_append rpath "$dir:" + done + compile_var="$runpath_var=\"$rpath\$$runpath_var\" " + fi + if test -n "$finalize_perm_rpath"; then + # We should set the runpath_var. + rpath= + for dir in $finalize_perm_rpath; do + func_append rpath "$dir:" + done + finalize_var="$runpath_var=\"$rpath\$$runpath_var\" " + fi + fi + + if test yes = "$no_install"; then + # We don't need to create a wrapper script. + link_command=$compile_var$compile_command$compile_rpath + # Replace the output file specification. + link_command=`$ECHO "$link_command" | $SED 's%@OUTPUT@%'"$output"'%g'` + # Delete the old output file. + $opt_dry_run || $RM $output + # Link the executable and exit + func_show_eval "$link_command" 'exit $?' + + if test -n "$postlink_cmds"; then + func_to_tool_file "$output" + postlink_cmds=`func_echo_all "$postlink_cmds" | $SED -e 's%@OUTPUT@%'"$output"'%g' -e 's%@TOOL_OUTPUT@%'"$func_to_tool_file_result"'%g'` + func_execute_cmds "$postlink_cmds" 'exit $?' + fi + + exit $EXIT_SUCCESS + fi + + case $hardcode_action,$fast_install in + relink,*) + # Fast installation is not supported + link_command=$compile_var$compile_command$compile_rpath + relink_command=$finalize_var$finalize_command$finalize_rpath + + func_warning "this platform does not like uninstalled shared libraries" + func_warning "'$output' will be relinked during installation" + ;; + *,yes) + link_command=$finalize_var$compile_command$finalize_rpath + relink_command=`$ECHO "$compile_var$compile_command$compile_rpath" | $SED 's%@OUTPUT@%\$progdir/\$file%g'` + ;; + *,no) + link_command=$compile_var$compile_command$compile_rpath + relink_command=$finalize_var$finalize_command$finalize_rpath + ;; + *,needless) + link_command=$finalize_var$compile_command$finalize_rpath + relink_command= + ;; + esac + + # Replace the output file specification. + link_command=`$ECHO "$link_command" | $SED 's%@OUTPUT@%'"$output_objdir/$outputname"'%g'` + + # Delete the old output files. + $opt_dry_run || $RM $output $output_objdir/$outputname $output_objdir/lt-$outputname + + func_show_eval "$link_command" 'exit $?' + + if test -n "$postlink_cmds"; then + func_to_tool_file "$output_objdir/$outputname" + postlink_cmds=`func_echo_all "$postlink_cmds" | $SED -e 's%@OUTPUT@%'"$output_objdir/$outputname"'%g' -e 's%@TOOL_OUTPUT@%'"$func_to_tool_file_result"'%g'` + func_execute_cmds "$postlink_cmds" 'exit $?' + fi + + # Now create the wrapper script. + func_verbose "creating $output" + + # Quote the relink command for shipping. + if test -n "$relink_command"; then + # Preserve any variables that may affect compiler behavior + for var in $variables_saved_for_relink; do + if eval test -z \"\${$var+set}\"; then + relink_command="{ test -z \"\${$var+set}\" || $lt_unset $var || { $var=; export $var; }; }; $relink_command" + elif eval var_value=\$$var; test -z "$var_value"; then + relink_command="$var=; export $var; $relink_command" + else + func_quote_for_eval "$var_value" + relink_command="$var=$func_quote_for_eval_result; export $var; $relink_command" + fi + done + relink_command="(cd `pwd`; $relink_command)" + relink_command=`$ECHO "$relink_command" | $SED "$sed_quote_subst"` + fi + + # Only actually do things if not in dry run mode. + $opt_dry_run || { + # win32 will think the script is a binary if it has + # a .exe suffix, so we strip it off here. + case $output in + *.exe) func_stripname '' '.exe' "$output" + output=$func_stripname_result ;; + esac + # test for cygwin because mv fails w/o .exe extensions + case $host in + *cygwin*) + exeext=.exe + func_stripname '' '.exe' "$outputname" + outputname=$func_stripname_result ;; + *) exeext= ;; + esac + case $host in + *cygwin* | *mingw* ) + func_dirname_and_basename "$output" "" "." + output_name=$func_basename_result + output_path=$func_dirname_result + cwrappersource=$output_path/$objdir/lt-$output_name.c + cwrapper=$output_path/$output_name.exe + $RM $cwrappersource $cwrapper + trap "$RM $cwrappersource $cwrapper; exit $EXIT_FAILURE" 1 2 15 + + func_emit_cwrapperexe_src > $cwrappersource + + # The wrapper executable is built using the $host compiler, + # because it contains $host paths and files. If cross- + # compiling, it, like the target executable, must be + # executed on the $host or under an emulation environment. + $opt_dry_run || { + $LTCC $LTCFLAGS -o $cwrapper $cwrappersource + $STRIP $cwrapper + } + + # Now, create the wrapper script for func_source use: + func_ltwrapper_scriptname $cwrapper + $RM $func_ltwrapper_scriptname_result + trap "$RM $func_ltwrapper_scriptname_result; exit $EXIT_FAILURE" 1 2 15 + $opt_dry_run || { + # note: this script will not be executed, so do not chmod. + if test "x$build" = "x$host"; then + $cwrapper --lt-dump-script > $func_ltwrapper_scriptname_result + else + func_emit_wrapper no > $func_ltwrapper_scriptname_result + fi + } + ;; + * ) + $RM $output + trap "$RM $output; exit $EXIT_FAILURE" 1 2 15 + + func_emit_wrapper no > $output + chmod +x $output + ;; + esac + } + exit $EXIT_SUCCESS + ;; + esac + + # See if we need to build an old-fashioned archive. + for oldlib in $oldlibs; do + + case $build_libtool_libs in + convenience) + oldobjs="$libobjs_save $symfileobj" + addlibs=$convenience + build_libtool_libs=no + ;; + module) + oldobjs=$libobjs_save + addlibs=$old_convenience + build_libtool_libs=no + ;; + *) + oldobjs="$old_deplibs $non_pic_objects" + $preload && test -f "$symfileobj" \ + && func_append oldobjs " $symfileobj" + addlibs=$old_convenience + ;; + esac + + if test -n "$addlibs"; then + gentop=$output_objdir/${outputname}x + func_append generated " $gentop" + + func_extract_archives $gentop $addlibs + func_append oldobjs " $func_extract_archives_result" + fi + + # Do each command in the archive commands. + if test -n "$old_archive_from_new_cmds" && test yes = "$build_libtool_libs"; then + cmds=$old_archive_from_new_cmds + else + + # Add any objects from preloaded convenience libraries + if test -n "$dlprefiles"; then + gentop=$output_objdir/${outputname}x + func_append generated " $gentop" + + func_extract_archives $gentop $dlprefiles + func_append oldobjs " $func_extract_archives_result" + fi + + # POSIX demands no paths to be encoded in archives. We have + # to avoid creating archives with duplicate basenames if we + # might have to extract them afterwards, e.g., when creating a + # static archive out of a convenience library, or when linking + # the entirety of a libtool archive into another (currently + # not supported by libtool). + if (for obj in $oldobjs + do + func_basename "$obj" + $ECHO "$func_basename_result" + done | sort | sort -uc >/dev/null 2>&1); then + : + else + echo "copying selected object files to avoid basename conflicts..." + gentop=$output_objdir/${outputname}x + func_append generated " $gentop" + func_mkdir_p "$gentop" + save_oldobjs=$oldobjs + oldobjs= + counter=1 + for obj in $save_oldobjs + do + func_basename "$obj" + objbase=$func_basename_result + case " $oldobjs " in + " ") oldobjs=$obj ;; + *[\ /]"$objbase "*) + while :; do + # Make sure we don't pick an alternate name that also + # overlaps. + newobj=lt$counter-$objbase + func_arith $counter + 1 + counter=$func_arith_result + case " $oldobjs " in + *[\ /]"$newobj "*) ;; + *) if test ! -f "$gentop/$newobj"; then break; fi ;; + esac + done + func_show_eval "ln $obj $gentop/$newobj || cp $obj $gentop/$newobj" + func_append oldobjs " $gentop/$newobj" + ;; + *) func_append oldobjs " $obj" ;; + esac + done + fi + func_to_tool_file "$oldlib" func_convert_file_msys_to_w32 + tool_oldlib=$func_to_tool_file_result + eval cmds=\"$old_archive_cmds\" + + func_len " $cmds" + len=$func_len_result + if test "$len" -lt "$max_cmd_len" || test "$max_cmd_len" -le -1; then + cmds=$old_archive_cmds + elif test -n "$archiver_list_spec"; then + func_verbose "using command file archive linking..." + for obj in $oldobjs + do + func_to_tool_file "$obj" + $ECHO "$func_to_tool_file_result" + done > $output_objdir/$libname.libcmd + func_to_tool_file "$output_objdir/$libname.libcmd" + oldobjs=" $archiver_list_spec$func_to_tool_file_result" + cmds=$old_archive_cmds + else + # the command line is too long to link in one step, link in parts + func_verbose "using piecewise archive linking..." + save_RANLIB=$RANLIB + RANLIB=: + objlist= + concat_cmds= + save_oldobjs=$oldobjs + oldobjs= + # Is there a better way of finding the last object in the list? + for obj in $save_oldobjs + do + last_oldobj=$obj + done + eval test_cmds=\"$old_archive_cmds\" + func_len " $test_cmds" + len0=$func_len_result + len=$len0 + for obj in $save_oldobjs + do + func_len " $obj" + func_arith $len + $func_len_result + len=$func_arith_result + func_append objlist " $obj" + if test "$len" -lt "$max_cmd_len"; then + : + else + # the above command should be used before it gets too long + oldobjs=$objlist + if test "$obj" = "$last_oldobj"; then + RANLIB=$save_RANLIB + fi + test -z "$concat_cmds" || concat_cmds=$concat_cmds~ + eval concat_cmds=\"\$concat_cmds$old_archive_cmds\" + objlist= + len=$len0 + fi + done + RANLIB=$save_RANLIB + oldobjs=$objlist + if test -z "$oldobjs"; then + eval cmds=\"\$concat_cmds\" + else + eval cmds=\"\$concat_cmds~\$old_archive_cmds\" + fi + fi + fi + func_execute_cmds "$cmds" 'exit $?' + done + + test -n "$generated" && \ + func_show_eval "${RM}r$generated" + + # Now create the libtool archive. + case $output in + *.la) + old_library= + test yes = "$build_old_libs" && old_library=$libname.$libext + func_verbose "creating $output" + + # Preserve any variables that may affect compiler behavior + for var in $variables_saved_for_relink; do + if eval test -z \"\${$var+set}\"; then + relink_command="{ test -z \"\${$var+set}\" || $lt_unset $var || { $var=; export $var; }; }; $relink_command" + elif eval var_value=\$$var; test -z "$var_value"; then + relink_command="$var=; export $var; $relink_command" + else + func_quote_for_eval "$var_value" + relink_command="$var=$func_quote_for_eval_result; export $var; $relink_command" + fi + done + # Quote the link command for shipping. + relink_command="(cd `pwd`; $SHELL \"$progpath\" $preserve_args --mode=relink $libtool_args @inst_prefix_dir@)" + relink_command=`$ECHO "$relink_command" | $SED "$sed_quote_subst"` + if test yes = "$hardcode_automatic"; then + relink_command= + fi + + # Only create the output if not a dry run. + $opt_dry_run || { + for installed in no yes; do + if test yes = "$installed"; then + if test -z "$install_libdir"; then + break + fi + output=$output_objdir/${outputname}i + # Replace all uninstalled libtool libraries with the installed ones + newdependency_libs= + for deplib in $dependency_libs; do + case $deplib in + *.la) + func_basename "$deplib" + name=$func_basename_result + func_resolve_sysroot "$deplib" + eval libdir=`$SED -n -e 's/^libdir=\(.*\)$/\1/p' $func_resolve_sysroot_result` + test -z "$libdir" && \ + func_fatal_error "'$deplib' is not a valid libtool archive" + func_append newdependency_libs " ${lt_sysroot:+=}$libdir/$name" + ;; + -L*) + func_stripname -L '' "$deplib" + func_replace_sysroot "$func_stripname_result" + func_append newdependency_libs " -L$func_replace_sysroot_result" + ;; + -R*) + func_stripname -R '' "$deplib" + func_replace_sysroot "$func_stripname_result" + func_append newdependency_libs " -R$func_replace_sysroot_result" + ;; + *) func_append newdependency_libs " $deplib" ;; + esac + done + dependency_libs=$newdependency_libs + newdlfiles= + + for lib in $dlfiles; do + case $lib in + *.la) + func_basename "$lib" + name=$func_basename_result + eval libdir=`$SED -n -e 's/^libdir=\(.*\)$/\1/p' $lib` + test -z "$libdir" && \ + func_fatal_error "'$lib' is not a valid libtool archive" + func_append newdlfiles " ${lt_sysroot:+=}$libdir/$name" + ;; + *) func_append newdlfiles " $lib" ;; + esac + done + dlfiles=$newdlfiles + newdlprefiles= + for lib in $dlprefiles; do + case $lib in + *.la) + # Only pass preopened files to the pseudo-archive (for + # eventual linking with the app. that links it) if we + # didn't already link the preopened objects directly into + # the library: + func_basename "$lib" + name=$func_basename_result + eval libdir=`$SED -n -e 's/^libdir=\(.*\)$/\1/p' $lib` + test -z "$libdir" && \ + func_fatal_error "'$lib' is not a valid libtool archive" + func_append newdlprefiles " ${lt_sysroot:+=}$libdir/$name" + ;; + esac + done + dlprefiles=$newdlprefiles + else + newdlfiles= + for lib in $dlfiles; do + case $lib in + [\\/]* | [A-Za-z]:[\\/]*) abs=$lib ;; + *) abs=`pwd`"/$lib" ;; + esac + func_append newdlfiles " $abs" + done + dlfiles=$newdlfiles + newdlprefiles= + for lib in $dlprefiles; do + case $lib in + [\\/]* | [A-Za-z]:[\\/]*) abs=$lib ;; + *) abs=`pwd`"/$lib" ;; + esac + func_append newdlprefiles " $abs" + done + dlprefiles=$newdlprefiles + fi + $RM $output + # place dlname in correct position for cygwin + # In fact, it would be nice if we could use this code for all target + # systems that can't hard-code library paths into their executables + # and that have no shared library path variable independent of PATH, + # but it turns out we can't easily determine that from inspecting + # libtool variables, so we have to hard-code the OSs to which it + # applies here; at the moment, that means platforms that use the PE + # object format with DLL files. See the long comment at the top of + # tests/bindir.at for full details. + tdlname=$dlname + case $host,$output,$installed,$module,$dlname in + *cygwin*,*lai,yes,no,*.dll | *mingw*,*lai,yes,no,*.dll | *cegcc*,*lai,yes,no,*.dll) + # If a -bindir argument was supplied, place the dll there. + if test -n "$bindir"; then + func_relative_path "$install_libdir" "$bindir" + tdlname=$func_relative_path_result/$dlname + else + # Otherwise fall back on heuristic. + tdlname=../bin/$dlname + fi + ;; + esac + $ECHO > $output "\ +# $outputname - a libtool library file +# Generated by $PROGRAM (GNU $PACKAGE) $VERSION +# +# Please DO NOT delete this file! +# It is necessary for linking the library. + +# The name that we can dlopen(3). +dlname='$tdlname' + +# Names of this library. +library_names='$library_names' + +# The name of the static archive. +old_library='$old_library' + +# Linker flags that cannot go in dependency_libs. +inherited_linker_flags='$new_inherited_linker_flags' + +# Libraries that this one depends upon. +dependency_libs='$dependency_libs' + +# Names of additional weak libraries provided by this library +weak_library_names='$weak_libs' + +# Version information for $libname. +current=$current +age=$age +revision=$revision + +# Is this an already installed library? +installed=$installed + +# Should we warn about portability when linking against -modules? +shouldnotlink=$module + +# Files to dlopen/dlpreopen +dlopen='$dlfiles' +dlpreopen='$dlprefiles' + +# Directory that this library needs to be installed in: +libdir='$install_libdir'" + if test no,yes = "$installed,$need_relink"; then + $ECHO >> $output "\ +relink_command=\"$relink_command\"" + fi + done + } + + # Do a symbolic link so that the libtool archive can be found in + # LD_LIBRARY_PATH before the program is installed. + func_show_eval '( cd "$output_objdir" && $RM "$outputname" && $LN_S "../$outputname" "$outputname" )' 'exit $?' + ;; + esac + exit $EXIT_SUCCESS +} + +if test link = "$opt_mode" || test relink = "$opt_mode"; then + func_mode_link ${1+"$@"} +fi + + +# func_mode_uninstall arg... +func_mode_uninstall () +{ + $debug_cmd + + RM=$nonopt + files= + rmforce=false + exit_status=0 + + # This variable tells wrapper scripts just to set variables rather + # than running their programs. + libtool_install_magic=$magic + + for arg + do + case $arg in + -f) func_append RM " $arg"; rmforce=: ;; + -*) func_append RM " $arg" ;; + *) func_append files " $arg" ;; + esac + done + + test -z "$RM" && \ + func_fatal_help "you must specify an RM program" + + rmdirs= + + for file in $files; do + func_dirname "$file" "" "." + dir=$func_dirname_result + if test . = "$dir"; then + odir=$objdir + else + odir=$dir/$objdir + fi + func_basename "$file" + name=$func_basename_result + test uninstall = "$opt_mode" && odir=$dir + + # Remember odir for removal later, being careful to avoid duplicates + if test clean = "$opt_mode"; then + case " $rmdirs " in + *" $odir "*) ;; + *) func_append rmdirs " $odir" ;; + esac + fi + + # Don't error if the file doesn't exist and rm -f was used. + if { test -L "$file"; } >/dev/null 2>&1 || + { test -h "$file"; } >/dev/null 2>&1 || + test -f "$file"; then + : + elif test -d "$file"; then + exit_status=1 + continue + elif $rmforce; then + continue + fi + + rmfiles=$file + + case $name in + *.la) + # Possibly a libtool archive, so verify it. + if func_lalib_p "$file"; then + func_source $dir/$name + + # Delete the libtool libraries and symlinks. + for n in $library_names; do + func_append rmfiles " $odir/$n" + done + test -n "$old_library" && func_append rmfiles " $odir/$old_library" + + case $opt_mode in + clean) + case " $library_names " in + *" $dlname "*) ;; + *) test -n "$dlname" && func_append rmfiles " $odir/$dlname" ;; + esac + test -n "$libdir" && func_append rmfiles " $odir/$name $odir/${name}i" + ;; + uninstall) + if test -n "$library_names"; then + # Do each command in the postuninstall commands. + func_execute_cmds "$postuninstall_cmds" '$rmforce || exit_status=1' + fi + + if test -n "$old_library"; then + # Do each command in the old_postuninstall commands. + func_execute_cmds "$old_postuninstall_cmds" '$rmforce || exit_status=1' + fi + # FIXME: should reinstall the best remaining shared library. + ;; + esac + fi + ;; + + *.lo) + # Possibly a libtool object, so verify it. + if func_lalib_p "$file"; then + + # Read the .lo file + func_source $dir/$name + + # Add PIC object to the list of files to remove. + if test -n "$pic_object" && test none != "$pic_object"; then + func_append rmfiles " $dir/$pic_object" + fi + + # Add non-PIC object to the list of files to remove. + if test -n "$non_pic_object" && test none != "$non_pic_object"; then + func_append rmfiles " $dir/$non_pic_object" + fi + fi + ;; + + *) + if test clean = "$opt_mode"; then + noexename=$name + case $file in + *.exe) + func_stripname '' '.exe' "$file" + file=$func_stripname_result + func_stripname '' '.exe' "$name" + noexename=$func_stripname_result + # $file with .exe has already been added to rmfiles, + # add $file without .exe + func_append rmfiles " $file" + ;; + esac + # Do a test to see if this is a libtool program. + if func_ltwrapper_p "$file"; then + if func_ltwrapper_executable_p "$file"; then + func_ltwrapper_scriptname "$file" + relink_command= + func_source $func_ltwrapper_scriptname_result + func_append rmfiles " $func_ltwrapper_scriptname_result" + else + relink_command= + func_source $dir/$noexename + fi + + # note $name still contains .exe if it was in $file originally + # as does the version of $file that was added into $rmfiles + func_append rmfiles " $odir/$name $odir/${name}S.$objext" + if test yes = "$fast_install" && test -n "$relink_command"; then + func_append rmfiles " $odir/lt-$name" + fi + if test "X$noexename" != "X$name"; then + func_append rmfiles " $odir/lt-$noexename.c" + fi + fi + fi + ;; + esac + func_show_eval "$RM $rmfiles" 'exit_status=1' + done + + # Try to remove the $objdir's in the directories where we deleted files + for dir in $rmdirs; do + if test -d "$dir"; then + func_show_eval "rmdir $dir >/dev/null 2>&1" + fi + done + + exit $exit_status +} + +if test uninstall = "$opt_mode" || test clean = "$opt_mode"; then + func_mode_uninstall ${1+"$@"} +fi + +test -z "$opt_mode" && { + help=$generic_help + func_fatal_help "you must specify a MODE" +} + +test -z "$exec_cmd" && \ + func_fatal_help "invalid operation mode '$opt_mode'" + +if test -n "$exec_cmd"; then + eval exec "$exec_cmd" + exit $EXIT_FAILURE +fi + +exit $exit_status + + +# The TAGs below are defined such that we never get into a situation +# where we disable both kinds of libraries. Given conflicting +# choices, we go for a static library, that is the most portable, +# since we can't tell whether shared libraries were disabled because +# the user asked for that or because the platform doesn't support +# them. This is particularly important on AIX, because we don't +# support having both static and shared libraries enabled at the same +# time on that platform, so we default to a shared-only configuration. +# If a disable-shared tag is given, we'll fallback to a static-only +# configuration. But we'll never go from static-only to shared-only. + +# ### BEGIN LIBTOOL TAG CONFIG: disable-shared +build_libtool_libs=no +build_old_libs=yes +# ### END LIBTOOL TAG CONFIG: disable-shared + +# ### BEGIN LIBTOOL TAG CONFIG: disable-static +build_old_libs=`case $build_libtool_libs in yes) echo no;; *) echo yes;; esac` +# ### END LIBTOOL TAG CONFIG: disable-static + +# Local Variables: +# mode:shell-script +# sh-indentation:2 +# End: diff --git a/build-aux/missing b/build-aux/missing new file mode 100755 index 0000000..f62bbae --- /dev/null +++ b/build-aux/missing @@ -0,0 +1,215 @@ +#! /bin/sh +# Common wrapper for a few potentially missing GNU programs. + +scriptversion=2013-10-28.13; # UTC + +# Copyright (C) 1996-2014 Free Software Foundation, Inc. +# Originally written by Fran,cois Pinard , 1996. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +if test $# -eq 0; then + echo 1>&2 "Try '$0 --help' for more information" + exit 1 +fi + +case $1 in + + --is-lightweight) + # Used by our autoconf macros to check whether the available missing + # script is modern enough. + exit 0 + ;; + + --run) + # Back-compat with the calling convention used by older automake. + shift + ;; + + -h|--h|--he|--hel|--help) + echo "\ +$0 [OPTION]... PROGRAM [ARGUMENT]... + +Run 'PROGRAM [ARGUMENT]...', returning a proper advice when this fails due +to PROGRAM being missing or too old. + +Options: + -h, --help display this help and exit + -v, --version output version information and exit + +Supported PROGRAM values: + aclocal autoconf autoheader autom4te automake makeinfo + bison yacc flex lex help2man + +Version suffixes to PROGRAM as well as the prefixes 'gnu-', 'gnu', and +'g' are ignored when checking the name. + +Send bug reports to ." + exit $? + ;; + + -v|--v|--ve|--ver|--vers|--versi|--versio|--version) + echo "missing $scriptversion (GNU Automake)" + exit $? + ;; + + -*) + echo 1>&2 "$0: unknown '$1' option" + echo 1>&2 "Try '$0 --help' for more information" + exit 1 + ;; + +esac + +# Run the given program, remember its exit status. +"$@"; st=$? + +# If it succeeded, we are done. +test $st -eq 0 && exit 0 + +# Also exit now if we it failed (or wasn't found), and '--version' was +# passed; such an option is passed most likely to detect whether the +# program is present and works. +case $2 in --version|--help) exit $st;; esac + +# Exit code 63 means version mismatch. This often happens when the user +# tries to use an ancient version of a tool on a file that requires a +# minimum version. +if test $st -eq 63; then + msg="probably too old" +elif test $st -eq 127; then + # Program was missing. + msg="missing on your system" +else + # Program was found and executed, but failed. Give up. + exit $st +fi + +perl_URL=http://www.perl.org/ +flex_URL=http://flex.sourceforge.net/ +gnu_software_URL=http://www.gnu.org/software + +program_details () +{ + case $1 in + aclocal|automake) + echo "The '$1' program is part of the GNU Automake package:" + echo "<$gnu_software_URL/automake>" + echo "It also requires GNU Autoconf, GNU m4 and Perl in order to run:" + echo "<$gnu_software_URL/autoconf>" + echo "<$gnu_software_URL/m4/>" + echo "<$perl_URL>" + ;; + autoconf|autom4te|autoheader) + echo "The '$1' program is part of the GNU Autoconf package:" + echo "<$gnu_software_URL/autoconf/>" + echo "It also requires GNU m4 and Perl in order to run:" + echo "<$gnu_software_URL/m4/>" + echo "<$perl_URL>" + ;; + esac +} + +give_advice () +{ + # Normalize program name to check for. + normalized_program=`echo "$1" | sed ' + s/^gnu-//; t + s/^gnu//; t + s/^g//; t'` + + printf '%s\n' "'$1' is $msg." + + configure_deps="'configure.ac' or m4 files included by 'configure.ac'" + case $normalized_program in + autoconf*) + echo "You should only need it if you modified 'configure.ac'," + echo "or m4 files included by it." + program_details 'autoconf' + ;; + autoheader*) + echo "You should only need it if you modified 'acconfig.h' or" + echo "$configure_deps." + program_details 'autoheader' + ;; + automake*) + echo "You should only need it if you modified 'Makefile.am' or" + echo "$configure_deps." + program_details 'automake' + ;; + aclocal*) + echo "You should only need it if you modified 'acinclude.m4' or" + echo "$configure_deps." + program_details 'aclocal' + ;; + autom4te*) + echo "You might have modified some maintainer files that require" + echo "the 'autom4te' program to be rebuilt." + program_details 'autom4te' + ;; + bison*|yacc*) + echo "You should only need it if you modified a '.y' file." + echo "You may want to install the GNU Bison package:" + echo "<$gnu_software_URL/bison/>" + ;; + lex*|flex*) + echo "You should only need it if you modified a '.l' file." + echo "You may want to install the Fast Lexical Analyzer package:" + echo "<$flex_URL>" + ;; + help2man*) + echo "You should only need it if you modified a dependency" \ + "of a man page." + echo "You may want to install the GNU Help2man package:" + echo "<$gnu_software_URL/help2man/>" + ;; + makeinfo*) + echo "You should only need it if you modified a '.texi' file, or" + echo "any other file indirectly affecting the aspect of the manual." + echo "You might want to install the Texinfo package:" + echo "<$gnu_software_URL/texinfo/>" + echo "The spurious makeinfo call might also be the consequence of" + echo "using a buggy 'make' (AIX, DU, IRIX), in which case you might" + echo "want to install GNU make:" + echo "<$gnu_software_URL/make/>" + ;; + *) + echo "You might have modified some files without having the proper" + echo "tools for further handling them. Check the 'README' file, it" + echo "often tells you about the needed prerequisites for installing" + echo "this package. You may also peek at any GNU archive site, in" + echo "case some other package contains this missing '$1' program." + ;; + esac +} + +give_advice "$1" | sed -e '1s/^/WARNING: /' \ + -e '2,$s/^/ /' >&2 + +# Propagate the correct exit status (expected to be 127 for a program +# not found, 63 for a program that failed due to version mismatch). +exit $st + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC" +# time-stamp-end: "; # UTC" +# End: diff --git a/build-aux/test-driver b/build-aux/test-driver new file mode 100755 index 0000000..8e575b0 --- /dev/null +++ b/build-aux/test-driver @@ -0,0 +1,148 @@ +#! /bin/sh +# test-driver - basic testsuite driver script. + +scriptversion=2013-07-13.22; # UTC + +# Copyright (C) 2011-2014 Free Software Foundation, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# This file is maintained in Automake, please report +# bugs to or send patches to +# . + +# Make unconditional expansion of undefined variables an error. This +# helps a lot in preventing typo-related bugs. +set -u + +usage_error () +{ + echo "$0: $*" >&2 + print_usage >&2 + exit 2 +} + +print_usage () +{ + cat <$log_file 2>&1 +estatus=$? + +if test $enable_hard_errors = no && test $estatus -eq 99; then + tweaked_estatus=1 +else + tweaked_estatus=$estatus +fi + +case $tweaked_estatus:$expect_failure in + 0:yes) col=$red res=XPASS recheck=yes gcopy=yes;; + 0:*) col=$grn res=PASS recheck=no gcopy=no;; + 77:*) col=$blu res=SKIP recheck=no gcopy=yes;; + 99:*) col=$mgn res=ERROR recheck=yes gcopy=yes;; + *:yes) col=$lgn res=XFAIL recheck=no gcopy=yes;; + *:*) col=$red res=FAIL recheck=yes gcopy=yes;; +esac + +# Report the test outcome and exit status in the logs, so that one can +# know whether the test passed or failed simply by looking at the '.log' +# file, without the need of also peaking into the corresponding '.trs' +# file (automake bug#11814). +echo "$res $test_name (exit status: $estatus)" >>$log_file + +# Report outcome to console. +echo "${col}${res}${std}: $test_name" + +# Register the test result, and other relevant metadata. +echo ":test-result: $res" > $trs_file +echo ":global-test-result: $res" >> $trs_file +echo ":recheck: $recheck" >> $trs_file +echo ":copy-in-global-log: $gcopy" >> $trs_file + +# Local Variables: +# mode: shell-script +# sh-indentation: 2 +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC" +# time-stamp-end: "; # UTC" +# End: diff --git a/configure b/configure new file mode 100755 index 0000000..6000069 --- /dev/null +++ b/configure @@ -0,0 +1,22884 @@ +#! /bin/sh +# Guess values for system-dependent variables and create Makefiles. +# Generated by GNU Autoconf 2.69 for libtorrent-rasterbar 1.1.1. +# +# Report bugs to . +# +# +# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. +# +# +# This configure script is free software; the Free Software Foundation +# gives unlimited permission to copy, distribute and modify it. +## -------------------- ## +## M4sh Initialization. ## +## -------------------- ## + +# Be more Bourne compatible +DUALCASE=1; export DUALCASE # for MKS sh +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else + case `(set -o) 2>/dev/null` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi + + +as_nl=' +' +export as_nl +# Printing a long string crashes Solaris 7 /usr/bin/printf. +as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo +# Prefer a ksh shell builtin over an external printf program on Solaris, +# but without wasting forks for bash or zsh. +if test -z "$BASH_VERSION$ZSH_VERSION" \ + && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='print -r --' + as_echo_n='print -rn --' +elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='printf %s\n' + as_echo_n='printf %s' +else + if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then + as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' + as_echo_n='/usr/ucb/echo -n' + else + as_echo_body='eval expr "X$1" : "X\\(.*\\)"' + as_echo_n_body='eval + arg=$1; + case $arg in #( + *"$as_nl"*) + expr "X$arg" : "X\\(.*\\)$as_nl"; + arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; + esac; + expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" + ' + export as_echo_n_body + as_echo_n='sh -c $as_echo_n_body as_echo' + fi + export as_echo_body + as_echo='sh -c $as_echo_body as_echo' +fi + +# The user is always right. +if test "${PATH_SEPARATOR+set}" != set; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } +fi + + +# IFS +# We need space, tab and new line, in precisely that order. Quoting is +# there to prevent editors from complaining about space-tab. +# (If _AS_PATH_WALK were called with IFS unset, it would disable word +# splitting by setting IFS to empty value.) +IFS=" "" $as_nl" + +# Find who we are. Look in the path if we contain no directory separator. +as_myself= +case $0 in #(( + *[\\/]* ) as_myself=$0 ;; + *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break + done +IFS=$as_save_IFS + + ;; +esac +# We did not find ourselves, most probably we were run as `sh COMMAND' +# in which case we are not to be found in the path. +if test "x$as_myself" = x; then + as_myself=$0 +fi +if test ! -f "$as_myself"; then + $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + exit 1 +fi + +# Unset variables that we do not need and which cause bugs (e.g. in +# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" +# suppresses any "Segmentation fault" message there. '((' could +# trigger a bug in pdksh 5.2.14. +for as_var in BASH_ENV ENV MAIL MAILPATH +do eval test x\${$as_var+set} = xset \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done +PS1='$ ' +PS2='> ' +PS4='+ ' + +# NLS nuisances. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# CDPATH. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + +# Use a proper internal environment variable to ensure we don't fall + # into an infinite loop, continuously re-executing ourselves. + if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then + _as_can_reexec=no; export _as_can_reexec; + # We cannot yet assume a decent shell, so we have to provide a +# neutralization value for shells without unset; and this also +# works around shells that cannot unset nonexistent variables. +# Preserve -v and -x to the replacement shell. +BASH_ENV=/dev/null +ENV=/dev/null +(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV +case $- in # (((( + *v*x* | *x*v* ) as_opts=-vx ;; + *v* ) as_opts=-v ;; + *x* ) as_opts=-x ;; + * ) as_opts= ;; +esac +exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} +# Admittedly, this is quite paranoid, since all the known shells bail +# out after a failed `exec'. +$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 +as_fn_exit 255 + fi + # We don't want this to propagate to other subprocesses. + { _as_can_reexec=; unset _as_can_reexec;} +if test "x$CONFIG_SHELL" = x; then + as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which + # is contrary to our usage. Disable this feature. + alias -g '\${1+\"\$@\"}'='\"\$@\"' + setopt NO_GLOB_SUBST +else + case \`(set -o) 2>/dev/null\` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi +" + as_required="as_fn_return () { (exit \$1); } +as_fn_success () { as_fn_return 0; } +as_fn_failure () { as_fn_return 1; } +as_fn_ret_success () { return 0; } +as_fn_ret_failure () { return 1; } + +exitcode=0 +as_fn_success || { exitcode=1; echo as_fn_success failed.; } +as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; } +as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; } +as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } +if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then : + +else + exitcode=1; echo positional parameters were not saved. +fi +test x\$exitcode = x0 || exit 1 +test -x / || exit 1" + as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO + as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO + eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && + test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1 + + test -n \"\${ZSH_VERSION+set}\${BASH_VERSION+set}\" || ( + ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' + ECHO=\$ECHO\$ECHO\$ECHO\$ECHO\$ECHO + ECHO=\$ECHO\$ECHO\$ECHO\$ECHO\$ECHO\$ECHO + PATH=/empty FPATH=/empty; export PATH FPATH + test \"X\`printf %s \$ECHO\`\" = \"X\$ECHO\" \\ + || test \"X\`print -r -- \$ECHO\`\" = \"X\$ECHO\" ) || exit 1 +test \$(( 1 + 1 )) = 2 || exit 1" + if (eval "$as_required") 2>/dev/null; then : + as_have_required=yes +else + as_have_required=no +fi + if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null; then : + +else + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +as_found=false +for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + as_found=: + case $as_dir in #( + /*) + for as_base in sh bash ksh sh5; do + # Try only shells that exist, to save several forks. + as_shell=$as_dir/$as_base + if { test -f "$as_shell" || test -f "$as_shell.exe"; } && + { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$as_shell"; } 2>/dev/null; then : + CONFIG_SHELL=$as_shell as_have_required=yes + if { $as_echo "$as_bourne_compatible""$as_suggested" | as_run=a "$as_shell"; } 2>/dev/null; then : + break 2 +fi +fi + done;; + esac + as_found=false +done +$as_found || { if { test -f "$SHELL" || test -f "$SHELL.exe"; } && + { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$SHELL"; } 2>/dev/null; then : + CONFIG_SHELL=$SHELL as_have_required=yes +fi; } +IFS=$as_save_IFS + + + if test "x$CONFIG_SHELL" != x; then : + export CONFIG_SHELL + # We cannot yet assume a decent shell, so we have to provide a +# neutralization value for shells without unset; and this also +# works around shells that cannot unset nonexistent variables. +# Preserve -v and -x to the replacement shell. +BASH_ENV=/dev/null +ENV=/dev/null +(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV +case $- in # (((( + *v*x* | *x*v* ) as_opts=-vx ;; + *v* ) as_opts=-v ;; + *x* ) as_opts=-x ;; + * ) as_opts= ;; +esac +exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} +# Admittedly, this is quite paranoid, since all the known shells bail +# out after a failed `exec'. +$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 +exit 255 +fi + + if test x$as_have_required = xno; then : + $as_echo "$0: This script requires a shell more modern than all" + $as_echo "$0: the shells that I found on your system." + if test x${ZSH_VERSION+set} = xset ; then + $as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should" + $as_echo "$0: be upgraded to zsh 4.3.4 or later." + else + $as_echo "$0: Please tell bug-autoconf@gnu.org and +$0: arvid@libtorrent.org about your system, including any +$0: error possibly output before this message. Then install +$0: a modern shell, or manually run the script under such a +$0: shell if you do have one." + fi + exit 1 +fi +fi +fi +SHELL=${CONFIG_SHELL-/bin/sh} +export SHELL +# Unset more variables known to interfere with behavior of common tools. +CLICOLOR_FORCE= GREP_OPTIONS= +unset CLICOLOR_FORCE GREP_OPTIONS + +## --------------------- ## +## M4sh Shell Functions. ## +## --------------------- ## +# as_fn_unset VAR +# --------------- +# Portably unset VAR. +as_fn_unset () +{ + { eval $1=; unset $1;} +} +as_unset=as_fn_unset + +# as_fn_set_status STATUS +# ----------------------- +# Set $? to STATUS, without forking. +as_fn_set_status () +{ + return $1 +} # as_fn_set_status + +# as_fn_exit STATUS +# ----------------- +# Exit the shell with STATUS, even in a "trap 0" or "set -e" context. +as_fn_exit () +{ + set +e + as_fn_set_status $1 + exit $1 +} # as_fn_exit + +# as_fn_mkdir_p +# ------------- +# Create "$as_dir" as a directory, including parents if necessary. +as_fn_mkdir_p () +{ + + case $as_dir in #( + -*) as_dir=./$as_dir;; + esac + test -d "$as_dir" || eval $as_mkdir_p || { + as_dirs= + while :; do + case $as_dir in #( + *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *) as_qdir=$as_dir;; + esac + as_dirs="'$as_qdir' $as_dirs" + as_dir=`$as_dirname -- "$as_dir" || +$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_dir" : 'X\(//\)[^/]' \| \ + X"$as_dir" : 'X\(//\)$' \| \ + X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_dir" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + test -d "$as_dir" && break + done + test -z "$as_dirs" || eval "mkdir $as_dirs" + } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" + + +} # as_fn_mkdir_p + +# as_fn_executable_p FILE +# ----------------------- +# Test if FILE is an executable regular file. +as_fn_executable_p () +{ + test -f "$1" && test -x "$1" +} # as_fn_executable_p +# as_fn_append VAR VALUE +# ---------------------- +# Append the text in VALUE to the end of the definition contained in VAR. Take +# advantage of any shell optimizations that allow amortized linear growth over +# repeated appends, instead of the typical quadratic growth present in naive +# implementations. +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : + eval 'as_fn_append () + { + eval $1+=\$2 + }' +else + as_fn_append () + { + eval $1=\$$1\$2 + } +fi # as_fn_append + +# as_fn_arith ARG... +# ------------------ +# Perform arithmetic evaluation on the ARGs, and store the result in the +# global $as_val. Take advantage of shells that can avoid forks. The arguments +# must be portable across $(()) and expr. +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : + eval 'as_fn_arith () + { + as_val=$(( $* )) + }' +else + as_fn_arith () + { + as_val=`expr "$@" || test $? -eq 1` + } +fi # as_fn_arith + + +# as_fn_error STATUS ERROR [LINENO LOG_FD] +# ---------------------------------------- +# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are +# provided, also output the error to LOG_FD, referencing LINENO. Then exit the +# script with STATUS, using 1 if that was 0. +as_fn_error () +{ + as_status=$1; test $as_status -eq 0 && as_status=1 + if test "$4"; then + as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + fi + $as_echo "$as_me: error: $2" >&2 + as_fn_exit $as_status +} # as_fn_error + +if expr a : '\(a\)' >/dev/null 2>&1 && + test "X`expr 00001 : '.*\(...\)'`" = X001; then + as_expr=expr +else + as_expr=false +fi + +if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then + as_basename=basename +else + as_basename=false +fi + +if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then + as_dirname=dirname +else + as_dirname=false +fi + +as_me=`$as_basename -- "$0" || +$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ + X"$0" : 'X\(//\)$' \| \ + X"$0" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X/"$0" | + sed '/^.*\/\([^/][^/]*\)\/*$/{ + s//\1/ + q + } + /^X\/\(\/\/\)$/{ + s//\1/ + q + } + /^X\/\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + +# Avoid depending upon Character Ranges. +as_cr_letters='abcdefghijklmnopqrstuvwxyz' +as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' +as_cr_Letters=$as_cr_letters$as_cr_LETTERS +as_cr_digits='0123456789' +as_cr_alnum=$as_cr_Letters$as_cr_digits + + + as_lineno_1=$LINENO as_lineno_1a=$LINENO + as_lineno_2=$LINENO as_lineno_2a=$LINENO + eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" && + test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || { + # Blame Lee E. McMahon (1931-1989) for sed's syntax. :-) + sed -n ' + p + /[$]LINENO/= + ' <$as_myself | + sed ' + s/[$]LINENO.*/&-/ + t lineno + b + :lineno + N + :loop + s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/ + t loop + s/-\n.*// + ' >$as_me.lineno && + chmod +x "$as_me.lineno" || + { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } + + # If we had to re-execute with $CONFIG_SHELL, we're ensured to have + # already done that, so ensure we don't try to do so again and fall + # in an infinite loop. This has already happened in practice. + _as_can_reexec=no; export _as_can_reexec + # Don't try to exec as it changes $[0], causing all sort of problems + # (the dirname of $[0] is not the place where we might find the + # original and so on. Autoconf is especially sensitive to this). + . "./$as_me.lineno" + # Exit status is that of the last command. + exit +} + +ECHO_C= ECHO_N= ECHO_T= +case `echo -n x` in #((((( +-n*) + case `echo 'xy\c'` in + *c*) ECHO_T=' ';; # ECHO_T is single tab character. + xy) ECHO_C='\c';; + *) echo `echo ksh88 bug on AIX 6.1` > /dev/null + ECHO_T=' ';; + esac;; +*) + ECHO_N='-n';; +esac + +rm -f conf$$ conf$$.exe conf$$.file +if test -d conf$$.dir; then + rm -f conf$$.dir/conf$$.file +else + rm -f conf$$.dir + mkdir conf$$.dir 2>/dev/null +fi +if (echo >conf$$.file) 2>/dev/null; then + if ln -s conf$$.file conf$$ 2>/dev/null; then + as_ln_s='ln -s' + # ... but there are two gotchas: + # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. + # In both cases, we have to default to `cp -pR'. + ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || + as_ln_s='cp -pR' + elif ln conf$$.file conf$$ 2>/dev/null; then + as_ln_s=ln + else + as_ln_s='cp -pR' + fi +else + as_ln_s='cp -pR' +fi +rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file +rmdir conf$$.dir 2>/dev/null + +if mkdir -p . 2>/dev/null; then + as_mkdir_p='mkdir -p "$as_dir"' +else + test -d ./-p && rmdir ./-p + as_mkdir_p=false +fi + +as_test_x='test -x' +as_executable_p=as_fn_executable_p + +# Sed expression to map a string onto a valid CPP name. +as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" + +# Sed expression to map a string onto a valid variable name. +as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" + +SHELL=${CONFIG_SHELL-/bin/sh} + + +test -n "$DJDIR" || exec 7<&0 &1 + +# Name of the host. +# hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status, +# so uname gets run too. +ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q` + +# +# Initializations. +# +ac_default_prefix=/usr/local +ac_clean_files= +ac_config_libobj_dir=. +LIBOBJS= +cross_compiling=no +subdirs= +MFLAGS= +MAKEFLAGS= + +# Identity of this package. +PACKAGE_NAME='libtorrent-rasterbar' +PACKAGE_TARNAME='libtorrent-rasterbar' +PACKAGE_VERSION='1.1.1' +PACKAGE_STRING='libtorrent-rasterbar 1.1.1' +PACKAGE_BUGREPORT='arvid@libtorrent.org' +PACKAGE_URL='http://www.libtorrent.org' + +ac_unique_file="src/torrent.cpp" +# Factoring default headers for most tests. +ac_includes_default="\ +#include +#ifdef HAVE_SYS_TYPES_H +# include +#endif +#ifdef HAVE_SYS_STAT_H +# include +#endif +#ifdef STDC_HEADERS +# include +# include +#else +# ifdef HAVE_STDLIB_H +# include +# endif +#endif +#ifdef HAVE_STRING_H +# if !defined STDC_HEADERS && defined HAVE_MEMORY_H +# include +# endif +# include +#endif +#ifdef HAVE_STRINGS_H +# include +#endif +#ifdef HAVE_INTTYPES_H +# include +#endif +#ifdef HAVE_STDINT_H +# include +#endif +#ifdef HAVE_UNISTD_H +# include +#endif" + +ac_subst_vars='am__EXEEXT_FALSE +am__EXEEXT_TRUE +LTLIBOBJS +LIBOBJS +COMPILETIME_OPTIONS +DEBUGFLAGS +PYTHON_INSTALL_PARAMS +WITH_OPENSSL_FALSE +WITH_OPENSSL_TRUE +ENABLE_PYTHON_BINDING_FALSE +ENABLE_PYTHON_BINDING_TRUE +ENABLE_TESTS_FALSE +ENABLE_TESTS_TRUE +ENABLE_EXAMPLES_FALSE +ENABLE_EXAMPLES_TRUE +ENABLE_DHT_FALSE +ENABLE_DHT_TRUE +ICONV_LIBS +LTLIBICONV +LIBICONV +BOOST_PYTHON_LIB +PYTHON_EXTRA_LIBS +PYTHON_EXTRA_LDFLAGS +PYTHON_SITE_PKG +PYTHON_LIBS +PYTHON_CPPFLAGS +pkgpyexecdir +pyexecdir +pkgpythondir +pythondir +PYTHON_PLATFORM +PYTHON_EXEC_PREFIX +PYTHON_PREFIX +PYTHON_VERSION +PYTHON +OPENSSL_LDFLAGS +OPENSSL_LIBS +OPENSSL_INCLUDES +PKG_CONFIG +BOOST_RANDOM_LIB +BOOST_CHRONO_LIB +BOOST_SYSTEM_LIB +BOOST_LDFLAGS +BOOST_CPPFLAGS +PTHREAD_CFLAGS +PTHREAD_LIBS +PTHREAD_CC +ax_pthread_config +LT_SYS_LIBRARY_PATH +OTOOL64 +OTOOL +LIPO +NMEDIT +DSYMUTIL +MANIFEST_TOOL +RANLIB +ac_ct_AR +AR +DLLTOOL +OBJDUMP +LN_S +NM +ac_ct_DUMPBIN +DUMPBIN +LD +FGREP +EGREP +GREP +SED +LIBTOOL +am__fastdepCXX_FALSE +am__fastdepCXX_TRUE +CXXDEPMODE +am__fastdepCC_FALSE +am__fastdepCC_TRUE +CCDEPMODE +am__nodep +AMDEPBACKSLASH +AMDEP_FALSE +AMDEP_TRUE +am__quote +am__include +DEPDIR +am__untar +am__tar +AMTAR +am__leading_dot +SET_MAKE +AWK +mkdir_p +MKDIR_P +INSTALL_STRIP_PROGRAM +STRIP +install_sh +MAKEINFO +AUTOHEADER +AUTOMAKE +AUTOCONF +ACLOCAL +VERSION +PACKAGE +CYGPATH_W +am__isrc +INSTALL_DATA +INSTALL_SCRIPT +INSTALL_PROGRAM +target_os +target_vendor +target_cpu +target +host_os +host_vendor +host_cpu +host +build_os +build_vendor +build_cpu +build +CXXCPP +ac_ct_CXX +CXXFLAGS +CXX +CPP +OBJEXT +EXEEXT +ac_ct_CC +CPPFLAGS +LDFLAGS +CFLAGS +CC +INTERFACE_VERSION_INFO +AM_BACKSLASH +AM_DEFAULT_VERBOSITY +AM_DEFAULT_V +AM_V +target_alias +host_alias +build_alias +LIBS +ECHO_T +ECHO_N +ECHO_C +DEFS +mandir +localedir +libdir +psdir +pdfdir +dvidir +htmldir +infodir +docdir +oldincludedir +includedir +localstatedir +sharedstatedir +sysconfdir +datadir +datarootdir +libexecdir +sbindir +bindir +program_transform_name +prefix +exec_prefix +PACKAGE_URL +PACKAGE_BUGREPORT +PACKAGE_STRING +PACKAGE_VERSION +PACKAGE_TARNAME +PACKAGE_NAME +PATH_SEPARATOR +SHELL' +ac_subst_files='' +ac_user_opts=' +enable_option_checking +enable_silent_rules +enable_dependency_tracking +enable_shared +enable_static +with_pic +enable_fast_install +with_aix_soname +with_gnu_ld +with_sysroot +enable_libtool_lock +with_boost +with_boost_libdir +with_boost_system +with_boost_chrono +with_boost_random +enable_largefile +enable_logging +enable_debug +enable_dht +enable_encryption +enable_export_all +enable_pool_allocators +enable_invariant_checks +enable_deprecated_functions +enable_disk_stats +enable_examples +enable_tests +enable_python_binding +with_libiconv +with_openssl +with_boost_python +with_libiconv_prefix +' + ac_precious_vars='build_alias +host_alias +target_alias +CC +CFLAGS +LDFLAGS +LIBS +CPPFLAGS +CPP +CXX +CXXFLAGS +CCC +CXXCPP +LT_SYS_LIBRARY_PATH +PKG_CONFIG +PYTHON +PYTHON_VERSION +PYTHON_INSTALL_PARAMS' + + +# Initialize some variables set by options. +ac_init_help= +ac_init_version=false +ac_unrecognized_opts= +ac_unrecognized_sep= +# The variables have the same names as the options, with +# dashes changed to underlines. +cache_file=/dev/null +exec_prefix=NONE +no_create= +no_recursion= +prefix=NONE +program_prefix=NONE +program_suffix=NONE +program_transform_name=s,x,x, +silent= +site= +srcdir= +verbose= +x_includes=NONE +x_libraries=NONE + +# Installation directory options. +# These are left unexpanded so users can "make install exec_prefix=/foo" +# and all the variables that are supposed to be based on exec_prefix +# by default will actually change. +# Use braces instead of parens because sh, perl, etc. also accept them. +# (The list follows the same order as the GNU Coding Standards.) +bindir='${exec_prefix}/bin' +sbindir='${exec_prefix}/sbin' +libexecdir='${exec_prefix}/libexec' +datarootdir='${prefix}/share' +datadir='${datarootdir}' +sysconfdir='${prefix}/etc' +sharedstatedir='${prefix}/com' +localstatedir='${prefix}/var' +includedir='${prefix}/include' +oldincludedir='/usr/include' +docdir='${datarootdir}/doc/${PACKAGE_TARNAME}' +infodir='${datarootdir}/info' +htmldir='${docdir}' +dvidir='${docdir}' +pdfdir='${docdir}' +psdir='${docdir}' +libdir='${exec_prefix}/lib' +localedir='${datarootdir}/locale' +mandir='${datarootdir}/man' + +ac_prev= +ac_dashdash= +for ac_option +do + # If the previous option needs an argument, assign it. + if test -n "$ac_prev"; then + eval $ac_prev=\$ac_option + ac_prev= + continue + fi + + case $ac_option in + *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;; + *=) ac_optarg= ;; + *) ac_optarg=yes ;; + esac + + # Accept the important Cygnus configure options, so we can diagnose typos. + + case $ac_dashdash$ac_option in + --) + ac_dashdash=yes ;; + + -bindir | --bindir | --bindi | --bind | --bin | --bi) + ac_prev=bindir ;; + -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) + bindir=$ac_optarg ;; + + -build | --build | --buil | --bui | --bu) + ac_prev=build_alias ;; + -build=* | --build=* | --buil=* | --bui=* | --bu=*) + build_alias=$ac_optarg ;; + + -cache-file | --cache-file | --cache-fil | --cache-fi \ + | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) + ac_prev=cache_file ;; + -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ + | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) + cache_file=$ac_optarg ;; + + --config-cache | -C) + cache_file=config.cache ;; + + -datadir | --datadir | --datadi | --datad) + ac_prev=datadir ;; + -datadir=* | --datadir=* | --datadi=* | --datad=*) + datadir=$ac_optarg ;; + + -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \ + | --dataroo | --dataro | --datar) + ac_prev=datarootdir ;; + -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \ + | --dataroot=* | --dataroo=* | --dataro=* | --datar=*) + datarootdir=$ac_optarg ;; + + -disable-* | --disable-*) + ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid feature name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"enable_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval enable_$ac_useropt=no ;; + + -docdir | --docdir | --docdi | --doc | --do) + ac_prev=docdir ;; + -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*) + docdir=$ac_optarg ;; + + -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv) + ac_prev=dvidir ;; + -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*) + dvidir=$ac_optarg ;; + + -enable-* | --enable-*) + ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid feature name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"enable_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval enable_$ac_useropt=\$ac_optarg ;; + + -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ + | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ + | --exec | --exe | --ex) + ac_prev=exec_prefix ;; + -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ + | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ + | --exec=* | --exe=* | --ex=*) + exec_prefix=$ac_optarg ;; + + -gas | --gas | --ga | --g) + # Obsolete; use --with-gas. + with_gas=yes ;; + + -help | --help | --hel | --he | -h) + ac_init_help=long ;; + -help=r* | --help=r* | --hel=r* | --he=r* | -hr*) + ac_init_help=recursive ;; + -help=s* | --help=s* | --hel=s* | --he=s* | -hs*) + ac_init_help=short ;; + + -host | --host | --hos | --ho) + ac_prev=host_alias ;; + -host=* | --host=* | --hos=* | --ho=*) + host_alias=$ac_optarg ;; + + -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht) + ac_prev=htmldir ;; + -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \ + | --ht=*) + htmldir=$ac_optarg ;; + + -includedir | --includedir | --includedi | --included | --include \ + | --includ | --inclu | --incl | --inc) + ac_prev=includedir ;; + -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ + | --includ=* | --inclu=* | --incl=* | --inc=*) + includedir=$ac_optarg ;; + + -infodir | --infodir | --infodi | --infod | --info | --inf) + ac_prev=infodir ;; + -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) + infodir=$ac_optarg ;; + + -libdir | --libdir | --libdi | --libd) + ac_prev=libdir ;; + -libdir=* | --libdir=* | --libdi=* | --libd=*) + libdir=$ac_optarg ;; + + -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ + | --libexe | --libex | --libe) + ac_prev=libexecdir ;; + -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ + | --libexe=* | --libex=* | --libe=*) + libexecdir=$ac_optarg ;; + + -localedir | --localedir | --localedi | --localed | --locale) + ac_prev=localedir ;; + -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*) + localedir=$ac_optarg ;; + + -localstatedir | --localstatedir | --localstatedi | --localstated \ + | --localstate | --localstat | --localsta | --localst | --locals) + ac_prev=localstatedir ;; + -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ + | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*) + localstatedir=$ac_optarg ;; + + -mandir | --mandir | --mandi | --mand | --man | --ma | --m) + ac_prev=mandir ;; + -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) + mandir=$ac_optarg ;; + + -nfp | --nfp | --nf) + # Obsolete; use --without-fp. + with_fp=no ;; + + -no-create | --no-create | --no-creat | --no-crea | --no-cre \ + | --no-cr | --no-c | -n) + no_create=yes ;; + + -no-recursion | --no-recursion | --no-recursio | --no-recursi \ + | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) + no_recursion=yes ;; + + -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ + | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ + | --oldin | --oldi | --old | --ol | --o) + ac_prev=oldincludedir ;; + -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ + | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ + | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) + oldincludedir=$ac_optarg ;; + + -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) + ac_prev=prefix ;; + -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) + prefix=$ac_optarg ;; + + -program-prefix | --program-prefix | --program-prefi | --program-pref \ + | --program-pre | --program-pr | --program-p) + ac_prev=program_prefix ;; + -program-prefix=* | --program-prefix=* | --program-prefi=* \ + | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) + program_prefix=$ac_optarg ;; + + -program-suffix | --program-suffix | --program-suffi | --program-suff \ + | --program-suf | --program-su | --program-s) + ac_prev=program_suffix ;; + -program-suffix=* | --program-suffix=* | --program-suffi=* \ + | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) + program_suffix=$ac_optarg ;; + + -program-transform-name | --program-transform-name \ + | --program-transform-nam | --program-transform-na \ + | --program-transform-n | --program-transform- \ + | --program-transform | --program-transfor \ + | --program-transfo | --program-transf \ + | --program-trans | --program-tran \ + | --progr-tra | --program-tr | --program-t) + ac_prev=program_transform_name ;; + -program-transform-name=* | --program-transform-name=* \ + | --program-transform-nam=* | --program-transform-na=* \ + | --program-transform-n=* | --program-transform-=* \ + | --program-transform=* | --program-transfor=* \ + | --program-transfo=* | --program-transf=* \ + | --program-trans=* | --program-tran=* \ + | --progr-tra=* | --program-tr=* | --program-t=*) + program_transform_name=$ac_optarg ;; + + -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd) + ac_prev=pdfdir ;; + -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*) + pdfdir=$ac_optarg ;; + + -psdir | --psdir | --psdi | --psd | --ps) + ac_prev=psdir ;; + -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*) + psdir=$ac_optarg ;; + + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + silent=yes ;; + + -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) + ac_prev=sbindir ;; + -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ + | --sbi=* | --sb=*) + sbindir=$ac_optarg ;; + + -sharedstatedir | --sharedstatedir | --sharedstatedi \ + | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ + | --sharedst | --shareds | --shared | --share | --shar \ + | --sha | --sh) + ac_prev=sharedstatedir ;; + -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ + | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ + | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ + | --sha=* | --sh=*) + sharedstatedir=$ac_optarg ;; + + -site | --site | --sit) + ac_prev=site ;; + -site=* | --site=* | --sit=*) + site=$ac_optarg ;; + + -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) + ac_prev=srcdir ;; + -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) + srcdir=$ac_optarg ;; + + -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ + | --syscon | --sysco | --sysc | --sys | --sy) + ac_prev=sysconfdir ;; + -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ + | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) + sysconfdir=$ac_optarg ;; + + -target | --target | --targe | --targ | --tar | --ta | --t) + ac_prev=target_alias ;; + -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) + target_alias=$ac_optarg ;; + + -v | -verbose | --verbose | --verbos | --verbo | --verb) + verbose=yes ;; + + -version | --version | --versio | --versi | --vers | -V) + ac_init_version=: ;; + + -with-* | --with-*) + ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid package name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"with_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval with_$ac_useropt=\$ac_optarg ;; + + -without-* | --without-*) + ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid package name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"with_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval with_$ac_useropt=no ;; + + --x) + # Obsolete; use --with-x. + with_x=yes ;; + + -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ + | --x-incl | --x-inc | --x-in | --x-i) + ac_prev=x_includes ;; + -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ + | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) + x_includes=$ac_optarg ;; + + -x-libraries | --x-libraries | --x-librarie | --x-librari \ + | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) + ac_prev=x_libraries ;; + -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ + | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) + x_libraries=$ac_optarg ;; + + -*) as_fn_error $? "unrecognized option: \`$ac_option' +Try \`$0 --help' for more information" + ;; + + *=*) + ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='` + # Reject names that are not valid shell variable names. + case $ac_envvar in #( + '' | [0-9]* | *[!_$as_cr_alnum]* ) + as_fn_error $? "invalid variable name: \`$ac_envvar'" ;; + esac + eval $ac_envvar=\$ac_optarg + export $ac_envvar ;; + + *) + # FIXME: should be removed in autoconf 3.0. + $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2 + expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && + $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2 + : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}" + ;; + + esac +done + +if test -n "$ac_prev"; then + ac_option=--`echo $ac_prev | sed 's/_/-/g'` + as_fn_error $? "missing argument to $ac_option" +fi + +if test -n "$ac_unrecognized_opts"; then + case $enable_option_checking in + no) ;; + fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;; + *) $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; + esac +fi + +# Check all directory arguments for consistency. +for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ + datadir sysconfdir sharedstatedir localstatedir includedir \ + oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ + libdir localedir mandir +do + eval ac_val=\$$ac_var + # Remove trailing slashes. + case $ac_val in + */ ) + ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'` + eval $ac_var=\$ac_val;; + esac + # Be sure to have absolute directory names. + case $ac_val in + [\\/$]* | ?:[\\/]* ) continue;; + NONE | '' ) case $ac_var in *prefix ) continue;; esac;; + esac + as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" +done + +# There might be people who depend on the old broken behavior: `$host' +# used to hold the argument of --host etc. +# FIXME: To remove some day. +build=$build_alias +host=$host_alias +target=$target_alias + +# FIXME: To remove some day. +if test "x$host_alias" != x; then + if test "x$build_alias" = x; then + cross_compiling=maybe + elif test "x$build_alias" != "x$host_alias"; then + cross_compiling=yes + fi +fi + +ac_tool_prefix= +test -n "$host_alias" && ac_tool_prefix=$host_alias- + +test "$silent" = yes && exec 6>/dev/null + + +ac_pwd=`pwd` && test -n "$ac_pwd" && +ac_ls_di=`ls -di .` && +ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` || + as_fn_error $? "working directory cannot be determined" +test "X$ac_ls_di" = "X$ac_pwd_ls_di" || + as_fn_error $? "pwd does not report name of working directory" + + +# Find the source files, if location was not specified. +if test -z "$srcdir"; then + ac_srcdir_defaulted=yes + # Try the directory containing this script, then the parent directory. + ac_confdir=`$as_dirname -- "$as_myself" || +$as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_myself" : 'X\(//\)[^/]' \| \ + X"$as_myself" : 'X\(//\)$' \| \ + X"$as_myself" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_myself" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + srcdir=$ac_confdir + if test ! -r "$srcdir/$ac_unique_file"; then + srcdir=.. + fi +else + ac_srcdir_defaulted=no +fi +if test ! -r "$srcdir/$ac_unique_file"; then + test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." + as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir" +fi +ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work" +ac_abs_confdir=`( + cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg" + pwd)` +# When building in place, set srcdir=. +if test "$ac_abs_confdir" = "$ac_pwd"; then + srcdir=. +fi +# Remove unnecessary trailing slashes from srcdir. +# Double slashes in file names in object file debugging info +# mess up M-x gdb in Emacs. +case $srcdir in +*/) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;; +esac +for ac_var in $ac_precious_vars; do + eval ac_env_${ac_var}_set=\${${ac_var}+set} + eval ac_env_${ac_var}_value=\$${ac_var} + eval ac_cv_env_${ac_var}_set=\${${ac_var}+set} + eval ac_cv_env_${ac_var}_value=\$${ac_var} +done + +# +# Report the --help message. +# +if test "$ac_init_help" = "long"; then + # Omit some internal or obsolete options to make the list less imposing. + # This message is too long to be a string in the A/UX 3.1 sh. + cat <<_ACEOF +\`configure' configures libtorrent-rasterbar 1.1.1 to adapt to many kinds of systems. + +Usage: $0 [OPTION]... [VAR=VALUE]... + +To assign environment variables (e.g., CC, CFLAGS...), specify them as +VAR=VALUE. See below for descriptions of some of the useful variables. + +Defaults for the options are specified in brackets. + +Configuration: + -h, --help display this help and exit + --help=short display options specific to this package + --help=recursive display the short help of all the included packages + -V, --version display version information and exit + -q, --quiet, --silent do not print \`checking ...' messages + --cache-file=FILE cache test results in FILE [disabled] + -C, --config-cache alias for \`--cache-file=config.cache' + -n, --no-create do not create output files + --srcdir=DIR find the sources in DIR [configure dir or \`..'] + +Installation directories: + --prefix=PREFIX install architecture-independent files in PREFIX + [$ac_default_prefix] + --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX + [PREFIX] + +By default, \`make install' will install all the files in +\`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify +an installation prefix other than \`$ac_default_prefix' using \`--prefix', +for instance \`--prefix=\$HOME'. + +For better control, use the options below. + +Fine tuning of the installation directories: + --bindir=DIR user executables [EPREFIX/bin] + --sbindir=DIR system admin executables [EPREFIX/sbin] + --libexecdir=DIR program executables [EPREFIX/libexec] + --sysconfdir=DIR read-only single-machine data [PREFIX/etc] + --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] + --localstatedir=DIR modifiable single-machine data [PREFIX/var] + --libdir=DIR object code libraries [EPREFIX/lib] + --includedir=DIR C header files [PREFIX/include] + --oldincludedir=DIR C header files for non-gcc [/usr/include] + --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] + --datadir=DIR read-only architecture-independent data [DATAROOTDIR] + --infodir=DIR info documentation [DATAROOTDIR/info] + --localedir=DIR locale-dependent data [DATAROOTDIR/locale] + --mandir=DIR man documentation [DATAROOTDIR/man] + --docdir=DIR documentation root + [DATAROOTDIR/doc/libtorrent-rasterbar] + --htmldir=DIR html documentation [DOCDIR] + --dvidir=DIR dvi documentation [DOCDIR] + --pdfdir=DIR pdf documentation [DOCDIR] + --psdir=DIR ps documentation [DOCDIR] +_ACEOF + + cat <<\_ACEOF + +Program names: + --program-prefix=PREFIX prepend PREFIX to installed program names + --program-suffix=SUFFIX append SUFFIX to installed program names + --program-transform-name=PROGRAM run sed PROGRAM on installed program names + +System types: + --build=BUILD configure for building on BUILD [guessed] + --host=HOST cross-compile to build programs to run on HOST [BUILD] + --target=TARGET configure for building compilers for TARGET [HOST] +_ACEOF +fi + +if test -n "$ac_init_help"; then + case $ac_init_help in + short | recursive ) echo "Configuration of libtorrent-rasterbar 1.1.1:";; + esac + cat <<\_ACEOF + +Optional Features: + --disable-option-checking ignore unrecognized --enable/--with options + --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) + --enable-FEATURE[=ARG] include FEATURE [ARG=yes] + --enable-silent-rules less verbose build output (undo: "make V=1") + --disable-silent-rules verbose build output (undo: "make V=0") + --enable-dependency-tracking + do not reject slow dependency extractors + --disable-dependency-tracking + speeds up one-time build + --enable-shared[=PKGS] build shared libraries [default=yes] + --enable-static[=PKGS] build static libraries [default=yes] + --enable-fast-install[=PKGS] + optimize for fast installation [default=yes] + --disable-libtool-lock avoid locking (might break parallel builds) + --disable-largefile omit support for large files + --enable-logging enable logging to disk (use value "verbose" to + enable verbose peer wire logging or "errors" limit + logging to errors ) [default=no] + --enable-debug enable debug build [default=no] + --enable-dht enable dht support (use value "logging" to add extra + logging) [default=yes] + --enable-encryption enable encryption support (requires OpenSSL to be + installed on your system, you can use --with-openssl + to set the path) [default=yes] + --enable-export-all export all symbols from libtorrent, including + non-public ones [default=no] + --enable-pool-allocators + enable pool allocators for send buffers + [default=yes] + --enable-invariant-checks + enable invariant checks (use value "full" to turn on + extra expensive invariant checks) [default=yes if + debug is enabled, no otherwhise] + --enable-deprecated-functions + enable deprecated functions in the API [default=yes] + --enable-disk-stats enable disk activity logging feature [default=no] + --enable-examples build example files [default=no] + --enable-tests build test files [default=no] + --enable-python-binding build python bindings [default=no] + +Optional Packages: + --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] + --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) + --with-pic[=PKGS] try to use only PIC/non-PIC objects [default=use + both] + --with-aix-soname=aix|svr4|both + shared library versioning (aka "SONAME") variant to + provide on AIX, [default=aix]. + --with-gnu-ld assume the C compiler uses GNU ld [default=no] + --with-sysroot[=DIR] Search for dependent libraries within DIR (or the + compiler's sysroot if not specified). + --with-boost[=ARG] use Boost library from a standard location + (ARG=yes), from the specified location (ARG=), + or disable it (ARG=no) [ARG=yes] + --with-boost-libdir=LIB_DIR + Force given directory for boost libraries. Note that + this will override library path detection, so use + this parameter only if default library detection + fails and you know exactly where your boost + libraries are located. + --with-boost-system[=special-lib] + use the System library from boost - it is possible + to specify a certain library for the linker e.g. + --with-boost-system=boost_system-gcc-mt + --with-boost-chrono[=special-lib] + use the Chrono library from boost - it is possible + to specify a certain library for the linker e.g. + --with-boost-chrono=boost_chrono-gcc-mt + --with-boost-random[=special-lib] + use the Random library from boost - it is possible + to specify a certain library for the linker e.g. + --with-boost-random=boost_random-gcc-mt + --with-libiconv enable linking against system libiconv [default=no] + --with-openssl=DIR root of the OpenSSL directory + --with-boost-python specify yes/no or the boost python library or suffix + to use + --with-libiconv-prefix[=DIR] search for libiconv in DIR/include and DIR/lib + --without-libiconv-prefix don't search for libiconv in includedir and libdir + +Some influential environment variables: + CC C compiler command + CFLAGS C compiler flags + LDFLAGS linker flags, e.g. -L if you have libraries in a + nonstandard directory + LIBS libraries to pass to the linker, e.g. -l + CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I if + you have headers in a nonstandard directory + CPP C preprocessor + CXX C++ compiler command + CXXFLAGS C++ compiler flags + CXXCPP C++ preprocessor + LT_SYS_LIBRARY_PATH + User-defined run-time library search path. + PKG_CONFIG path to pkg-config utility + PYTHON the Python interpreter + PYTHON_VERSION + The installed Python version to use, for example '2.3'. This + string will be appended to the Python interpreter canonical + name. + PYTHON_INSTALL_PARAMS + Set specific install parameters for python bindings. + +Use these variables to override the choices made by `configure' or to help +it to find libraries and programs with nonstandard names/locations. + +Report bugs to . +libtorrent-rasterbar home page: . +_ACEOF +ac_status=$? +fi + +if test "$ac_init_help" = "recursive"; then + # If there are subdirs, report their specific --help. + for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue + test -d "$ac_dir" || + { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } || + continue + ac_builddir=. + +case "$ac_dir" in +.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; +*) + ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` + # A ".." for each directory in $ac_dir_suffix. + ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + case $ac_top_builddir_sub in + "") ac_top_builddir_sub=. ac_top_build_prefix= ;; + *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; + esac ;; +esac +ac_abs_top_builddir=$ac_pwd +ac_abs_builddir=$ac_pwd$ac_dir_suffix +# for backward compatibility: +ac_top_builddir=$ac_top_build_prefix + +case $srcdir in + .) # We are building in place. + ac_srcdir=. + ac_top_srcdir=$ac_top_builddir_sub + ac_abs_top_srcdir=$ac_pwd ;; + [\\/]* | ?:[\\/]* ) # Absolute name. + ac_srcdir=$srcdir$ac_dir_suffix; + ac_top_srcdir=$srcdir + ac_abs_top_srcdir=$srcdir ;; + *) # Relative name. + ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix + ac_top_srcdir=$ac_top_build_prefix$srcdir + ac_abs_top_srcdir=$ac_pwd/$srcdir ;; +esac +ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix + + cd "$ac_dir" || { ac_status=$?; continue; } + # Check for guested configure. + if test -f "$ac_srcdir/configure.gnu"; then + echo && + $SHELL "$ac_srcdir/configure.gnu" --help=recursive + elif test -f "$ac_srcdir/configure"; then + echo && + $SHELL "$ac_srcdir/configure" --help=recursive + else + $as_echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2 + fi || ac_status=$? + cd "$ac_pwd" || { ac_status=$?; break; } + done +fi + +test -n "$ac_init_help" && exit $ac_status +if $ac_init_version; then + cat <<\_ACEOF +libtorrent-rasterbar configure 1.1.1 +generated by GNU Autoconf 2.69 + +Copyright (C) 2012 Free Software Foundation, Inc. +This configure script is free software; the Free Software Foundation +gives unlimited permission to copy, distribute and modify it. +_ACEOF + exit +fi + +## ------------------------ ## +## Autoconf initialization. ## +## ------------------------ ## + +# ac_fn_c_try_compile LINENO +# -------------------------- +# Try to compile conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_compile () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext + if { { ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compile") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_compile + +# ac_fn_c_try_cpp LINENO +# ---------------------- +# Try to preprocess conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_cpp () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if { { ac_try="$ac_cpp conftest.$ac_ext" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } > conftest.i && { + test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" || + test ! -s conftest.err + }; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_cpp + +# ac_fn_cxx_try_compile LINENO +# ---------------------------- +# Try to compile conftest.$ac_ext, and return whether this succeeded. +ac_fn_cxx_try_compile () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext + if { { ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compile") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_cxx_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_cxx_try_compile + +# ac_fn_cxx_try_cpp LINENO +# ------------------------ +# Try to preprocess conftest.$ac_ext, and return whether this succeeded. +ac_fn_cxx_try_cpp () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if { { ac_try="$ac_cpp conftest.$ac_ext" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } > conftest.i && { + test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" || + test ! -s conftest.err + }; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_cxx_try_cpp + +# ac_fn_c_try_link LINENO +# ----------------------- +# Try to link conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_link () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext conftest$ac_exeext + if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + test -x conftest$ac_exeext + }; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information + # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would + # interfere with the next link command; also delete a directory that is + # left behind by Apple's compiler. We do this before executing the actions. + rm -rf conftest.dSYM conftest_ipa8_conftest.oo + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_link + +# ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES +# ------------------------------------------------------- +# Tests whether HEADER exists and can be compiled using the include files in +# INCLUDES, setting the cache variable VAR accordingly. +ac_fn_c_check_header_compile () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$4 +#include <$2> +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + eval "$3=yes" +else + eval "$3=no" +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_header_compile + +# ac_fn_c_try_run LINENO +# ---------------------- +# Try to link conftest.$ac_ext, and return whether this succeeded. Assumes +# that executables *can* be run. +ac_fn_c_try_run () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { ac_try='./conftest$ac_exeext' + { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; }; then : + ac_retval=0 +else + $as_echo "$as_me: program exited with status $ac_status" >&5 + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=$ac_status +fi + rm -rf conftest.dSYM conftest_ipa8_conftest.oo + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_run + +# ac_fn_c_check_func LINENO FUNC VAR +# ---------------------------------- +# Tests whether FUNC exists, setting the cache variable VAR accordingly +ac_fn_c_check_func () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +/* Define $2 to an innocuous variant, in case declares $2. + For example, HP-UX 11i declares gettimeofday. */ +#define $2 innocuous_$2 + +/* System header to define __stub macros and hopefully few prototypes, + which can conflict with char $2 (); below. + Prefer to if __STDC__ is defined, since + exists even on freestanding compilers. */ + +#ifdef __STDC__ +# include +#else +# include +#endif + +#undef $2 + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char $2 (); +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined __stub_$2 || defined __stub___$2 +choke me +#endif + +int +main () +{ +return $2 (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + eval "$3=yes" +else + eval "$3=no" +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_func + +# ac_fn_cxx_try_link LINENO +# ------------------------- +# Try to link conftest.$ac_ext, and return whether this succeeded. +ac_fn_cxx_try_link () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext conftest$ac_exeext + if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_cxx_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + test -x conftest$ac_exeext + }; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information + # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would + # interfere with the next link command; also delete a directory that is + # left behind by Apple's compiler. We do this before executing the actions. + rm -rf conftest.dSYM conftest_ipa8_conftest.oo + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_cxx_try_link +cat >config.log <<_ACEOF +This file contains any messages produced by compilers while +running configure, to aid debugging if configure makes a mistake. + +It was created by libtorrent-rasterbar $as_me 1.1.1, which was +generated by GNU Autoconf 2.69. Invocation command line was + + $ $0 $@ + +_ACEOF +exec 5>>config.log +{ +cat <<_ASUNAME +## --------- ## +## Platform. ## +## --------- ## + +hostname = `(hostname || uname -n) 2>/dev/null | sed 1q` +uname -m = `(uname -m) 2>/dev/null || echo unknown` +uname -r = `(uname -r) 2>/dev/null || echo unknown` +uname -s = `(uname -s) 2>/dev/null || echo unknown` +uname -v = `(uname -v) 2>/dev/null || echo unknown` + +/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown` +/bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown` + +/bin/arch = `(/bin/arch) 2>/dev/null || echo unknown` +/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown` +/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown` +/usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown` +/bin/machine = `(/bin/machine) 2>/dev/null || echo unknown` +/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown` +/bin/universe = `(/bin/universe) 2>/dev/null || echo unknown` + +_ASUNAME + +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + $as_echo "PATH: $as_dir" + done +IFS=$as_save_IFS + +} >&5 + +cat >&5 <<_ACEOF + + +## ----------- ## +## Core tests. ## +## ----------- ## + +_ACEOF + + +# Keep a trace of the command line. +# Strip out --no-create and --no-recursion so they do not pile up. +# Strip out --silent because we don't want to record it for future runs. +# Also quote any args containing shell meta-characters. +# Make two passes to allow for proper duplicate-argument suppression. +ac_configure_args= +ac_configure_args0= +ac_configure_args1= +ac_must_keep_next=false +for ac_pass in 1 2 +do + for ac_arg + do + case $ac_arg in + -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;; + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + continue ;; + *\'*) + ac_arg=`$as_echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; + esac + case $ac_pass in + 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;; + 2) + as_fn_append ac_configure_args1 " '$ac_arg'" + if test $ac_must_keep_next = true; then + ac_must_keep_next=false # Got value, back to normal. + else + case $ac_arg in + *=* | --config-cache | -C | -disable-* | --disable-* \ + | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \ + | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \ + | -with-* | --with-* | -without-* | --without-* | --x) + case "$ac_configure_args0 " in + "$ac_configure_args1"*" '$ac_arg' "* ) continue ;; + esac + ;; + -* ) ac_must_keep_next=true ;; + esac + fi + as_fn_append ac_configure_args " '$ac_arg'" + ;; + esac + done +done +{ ac_configure_args0=; unset ac_configure_args0;} +{ ac_configure_args1=; unset ac_configure_args1;} + +# When interrupted or exit'd, cleanup temporary files, and complete +# config.log. We remove comments because anyway the quotes in there +# would cause problems or look ugly. +# WARNING: Use '\'' to represent an apostrophe within the trap. +# WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug. +trap 'exit_status=$? + # Save into config.log some information that might help in debugging. + { + echo + + $as_echo "## ---------------- ## +## Cache variables. ## +## ---------------- ##" + echo + # The following way of writing the cache mishandles newlines in values, +( + for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do + eval ac_val=\$$ac_var + case $ac_val in #( + *${as_nl}*) + case $ac_var in #( + *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + esac + case $ac_var in #( + _ | IFS | as_nl) ;; #( + BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( + *) { eval $ac_var=; unset $ac_var;} ;; + esac ;; + esac + done + (set) 2>&1 | + case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #( + *${as_nl}ac_space=\ *) + sed -n \ + "s/'\''/'\''\\\\'\'''\''/g; + s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p" + ;; #( + *) + sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" + ;; + esac | + sort +) + echo + + $as_echo "## ----------------- ## +## Output variables. ## +## ----------------- ##" + echo + for ac_var in $ac_subst_vars + do + eval ac_val=\$$ac_var + case $ac_val in + *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + esac + $as_echo "$ac_var='\''$ac_val'\''" + done | sort + echo + + if test -n "$ac_subst_files"; then + $as_echo "## ------------------- ## +## File substitutions. ## +## ------------------- ##" + echo + for ac_var in $ac_subst_files + do + eval ac_val=\$$ac_var + case $ac_val in + *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + esac + $as_echo "$ac_var='\''$ac_val'\''" + done | sort + echo + fi + + if test -s confdefs.h; then + $as_echo "## ----------- ## +## confdefs.h. ## +## ----------- ##" + echo + cat confdefs.h + echo + fi + test "$ac_signal" != 0 && + $as_echo "$as_me: caught signal $ac_signal" + $as_echo "$as_me: exit $exit_status" + } >&5 + rm -f core *.core core.conftest.* && + rm -f -r conftest* confdefs* conf$$* $ac_clean_files && + exit $exit_status +' 0 +for ac_signal in 1 2 13 15; do + trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal +done +ac_signal=0 + +# confdefs.h avoids OS command line length limits that DEFS can exceed. +rm -f -r conftest* confdefs.h + +$as_echo "/* confdefs.h */" > confdefs.h + +# Predefined preprocessor variables. + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_NAME "$PACKAGE_NAME" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_TARNAME "$PACKAGE_TARNAME" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_VERSION "$PACKAGE_VERSION" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_STRING "$PACKAGE_STRING" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_URL "$PACKAGE_URL" +_ACEOF + + +# Let the site file select an alternate cache file if it wants to. +# Prefer an explicitly selected file to automatically selected ones. +ac_site_file1=NONE +ac_site_file2=NONE +if test -n "$CONFIG_SITE"; then + # We do not want a PATH search for config.site. + case $CONFIG_SITE in #(( + -*) ac_site_file1=./$CONFIG_SITE;; + */*) ac_site_file1=$CONFIG_SITE;; + *) ac_site_file1=./$CONFIG_SITE;; + esac +elif test "x$prefix" != xNONE; then + ac_site_file1=$prefix/share/config.site + ac_site_file2=$prefix/etc/config.site +else + ac_site_file1=$ac_default_prefix/share/config.site + ac_site_file2=$ac_default_prefix/etc/config.site +fi +for ac_site_file in "$ac_site_file1" "$ac_site_file2" +do + test "x$ac_site_file" = xNONE && continue + if test /dev/null != "$ac_site_file" && test -r "$ac_site_file"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 +$as_echo "$as_me: loading site script $ac_site_file" >&6;} + sed 's/^/| /' "$ac_site_file" >&5 + . "$ac_site_file" \ + || { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "failed to load site script $ac_site_file +See \`config.log' for more details" "$LINENO" 5; } + fi +done + +if test -r "$cache_file"; then + # Some versions of bash will fail to source /dev/null (special files + # actually), so we avoid doing that. DJGPP emulates it as a regular file. + if test /dev/null != "$cache_file" && test -f "$cache_file"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 +$as_echo "$as_me: loading cache $cache_file" >&6;} + case $cache_file in + [\\/]* | ?:[\\/]* ) . "$cache_file";; + *) . "./$cache_file";; + esac + fi +else + { $as_echo "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 +$as_echo "$as_me: creating cache $cache_file" >&6;} + >$cache_file +fi + +# Check that the precious variables saved in the cache have kept the same +# value. +ac_cache_corrupted=false +for ac_var in $ac_precious_vars; do + eval ac_old_set=\$ac_cv_env_${ac_var}_set + eval ac_new_set=\$ac_env_${ac_var}_set + eval ac_old_val=\$ac_cv_env_${ac_var}_value + eval ac_new_val=\$ac_env_${ac_var}_value + case $ac_old_set,$ac_new_set in + set,) + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5 +$as_echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;} + ac_cache_corrupted=: ;; + ,set) + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5 +$as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} + ac_cache_corrupted=: ;; + ,);; + *) + if test "x$ac_old_val" != "x$ac_new_val"; then + # differences in whitespace do not lead to failure. + ac_old_val_w=`echo x $ac_old_val` + ac_new_val_w=`echo x $ac_new_val` + if test "$ac_old_val_w" != "$ac_new_val_w"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5 +$as_echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;} + ac_cache_corrupted=: + else + { $as_echo "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5 +$as_echo "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;} + eval $ac_var=\$ac_old_val + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: former value: \`$ac_old_val'" >&5 +$as_echo "$as_me: former value: \`$ac_old_val'" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: current value: \`$ac_new_val'" >&5 +$as_echo "$as_me: current value: \`$ac_new_val'" >&2;} + fi;; + esac + # Pass precious variables to config.status. + if test "$ac_new_set" = set; then + case $ac_new_val in + *\'*) ac_arg=$ac_var=`$as_echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; + *) ac_arg=$ac_var=$ac_new_val ;; + esac + case " $ac_configure_args " in + *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy. + *) as_fn_append ac_configure_args " '$ac_arg'" ;; + esac + fi +done +if $ac_cache_corrupted; then + { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 +$as_echo "$as_me: error: changes in the environment can compromise the build" >&2;} + as_fn_error $? "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5 +fi +## -------------------- ## +## Main body of script. ## +## -------------------- ## + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + +ac_aux_dir= +for ac_dir in build-aux "$srcdir"/build-aux; do + if test -f "$ac_dir/install-sh"; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/install-sh -c" + break + elif test -f "$ac_dir/install.sh"; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/install.sh -c" + break + elif test -f "$ac_dir/shtool"; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/shtool install -c" + break + fi +done +if test -z "$ac_aux_dir"; then + as_fn_error $? "cannot find install-sh, install.sh, or shtool in build-aux \"$srcdir\"/build-aux" "$LINENO" 5 +fi + +# These three variables are undocumented and unsupported, +# and are intended to be withdrawn in a future Autoconf release. +# They can cause serious problems if a builder's source tree is in a directory +# whose full name contains unusual characters. +ac_config_guess="$SHELL $ac_aux_dir/config.guess" # Please don't use this var. +ac_config_sub="$SHELL $ac_aux_dir/config.sub" # Please don't use this var. +ac_configure="$SHELL $ac_aux_dir/configure" # Please don't use this var. + + + + +# Silencing build output (automake-1.11) +# Check whether --enable-silent-rules was given. +if test "${enable_silent_rules+set}" = set; then : + enableval=$enable_silent_rules; +fi + +case $enable_silent_rules in # ((( + yes) AM_DEFAULT_VERBOSITY=0;; + no) AM_DEFAULT_VERBOSITY=1;; + *) AM_DEFAULT_VERBOSITY=0;; +esac +am_make=${MAKE-make} +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $am_make supports nested variables" >&5 +$as_echo_n "checking whether $am_make supports nested variables... " >&6; } +if ${am_cv_make_support_nested_variables+:} false; then : + $as_echo_n "(cached) " >&6 +else + if $as_echo 'TRUE=$(BAR$(V)) +BAR0=false +BAR1=true +V=1 +am__doit: + @$(TRUE) +.PHONY: am__doit' | $am_make -f - >/dev/null 2>&1; then + am_cv_make_support_nested_variables=yes +else + am_cv_make_support_nested_variables=no +fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_make_support_nested_variables" >&5 +$as_echo "$am_cv_make_support_nested_variables" >&6; } +if test $am_cv_make_support_nested_variables = yes; then + AM_V='$(V)' + AM_DEFAULT_V='$(AM_DEFAULT_VERBOSITY)' +else + AM_V=$AM_DEFAULT_VERBOSITY + AM_DEFAULT_V=$AM_DEFAULT_VERBOSITY +fi +AM_BACKSLASH='\' + + + +############################################################################### + + + + +INTERFACE_VERSION_INFO=9:0:0 + +############################################################################### + + +############################################################################### +# Start +############################################################################### + +$as_echo +$as_echo "Building $PACKAGE_STRING" + + +############################################################################### +# Performing some basic checks and initializing the build system +############################################################################### + +$as_echo +$as_echo "Checking for a C/C++ compiler to use:" +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args. +set dummy ${ac_tool_prefix}gcc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="${ac_tool_prefix}gcc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_CC"; then + ac_ct_CC=$CC + # Extract the first word of "gcc", so it can be a program name with args. +set dummy gcc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CC="gcc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +else + CC="$ac_cv_prog_CC" +fi + +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args. +set dummy ${ac_tool_prefix}cc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="${ac_tool_prefix}cc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + fi +fi +if test -z "$CC"; then + # Extract the first word of "cc", so it can be a program name with args. +set dummy cc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else + ac_prog_rejected=no +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then + ac_prog_rejected=yes + continue + fi + ac_cv_prog_CC="cc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +if test $ac_prog_rejected = yes; then + # We found a bogon in the path, so make sure we never use it. + set dummy $ac_cv_prog_CC + shift + if test $# != 0; then + # We chose a different compiler from the bogus one. + # However, it has the same basename, so the bogon will be chosen + # first if we set CC to just the basename; use the full file name. + shift + ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@" + fi +fi +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + for ac_prog in cl.exe + do + # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. +set dummy $ac_tool_prefix$ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="$ac_tool_prefix$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$CC" && break + done +fi +if test -z "$CC"; then + ac_ct_CC=$CC + for ac_prog in cl.exe +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CC="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$ac_ct_CC" && break +done + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +fi + +fi + + +test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "no acceptable C compiler found in \$PATH +See \`config.log' for more details" "$LINENO" 5; } + +# Provide some information about the compiler. +$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 +set X $ac_compile +ac_compiler=$2 +for ac_option in --version -v -V -qversion; do + { { ac_try="$ac_compiler $ac_option >&5" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compiler $ac_option >&5") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + sed '10a\ +... rest of stderr output deleted ... + 10q' conftest.err >conftest.er1 + cat conftest.er1 >&5 + fi + rm -f conftest.er1 conftest.err + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } +done + +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +ac_clean_files_save=$ac_clean_files +ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out" +# Try to create an executable without -o first, disregard a.out. +# It will help us diagnose broken compilers, and finding out an intuition +# of exeext. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5 +$as_echo_n "checking whether the C compiler works... " >&6; } +ac_link_default=`$as_echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'` + +# The possible output files: +ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*" + +ac_rmfiles= +for ac_file in $ac_files +do + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; + * ) ac_rmfiles="$ac_rmfiles $ac_file";; + esac +done +rm -f $ac_rmfiles + +if { { ac_try="$ac_link_default" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link_default") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + # Autoconf-2.13 could set the ac_cv_exeext variable to `no'. +# So ignore a value of `no', otherwise this would lead to `EXEEXT = no' +# in a Makefile. We should not override ac_cv_exeext if it was cached, +# so that the user can short-circuit this test for compilers unknown to +# Autoconf. +for ac_file in $ac_files '' +do + test -f "$ac_file" || continue + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) + ;; + [ab].out ) + # We found the default executable, but exeext='' is most + # certainly right. + break;; + *.* ) + if test "${ac_cv_exeext+set}" = set && test "$ac_cv_exeext" != no; + then :; else + ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` + fi + # We set ac_cv_exeext here because the later test for it is not + # safe: cross compilers may not add the suffix if given an `-o' + # argument, so we may need to know it at that point already. + # Even if this section looks crufty: it has the advantage of + # actually working. + break;; + * ) + break;; + esac +done +test "$ac_cv_exeext" = no && ac_cv_exeext= + +else + ac_file='' +fi +if test -z "$ac_file"; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +$as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error 77 "C compiler cannot create executables +See \`config.log' for more details" "$LINENO" 5; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5 +$as_echo_n "checking for C compiler default output file name... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5 +$as_echo "$ac_file" >&6; } +ac_exeext=$ac_cv_exeext + +rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out +ac_clean_files=$ac_clean_files_save +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5 +$as_echo_n "checking for suffix of executables... " >&6; } +if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + # If both `conftest.exe' and `conftest' are `present' (well, observable) +# catch `conftest.exe'. For instance with Cygwin, `ls conftest' will +# work properly (i.e., refer to `conftest.exe'), while it won't with +# `rm'. +for ac_file in conftest.exe conftest conftest.*; do + test -f "$ac_file" || continue + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; + *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` + break;; + * ) break;; + esac +done +else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot compute suffix of executables: cannot compile and link +See \`config.log' for more details" "$LINENO" 5; } +fi +rm -f conftest conftest$ac_cv_exeext +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 +$as_echo "$ac_cv_exeext" >&6; } + +rm -f conftest.$ac_ext +EXEEXT=$ac_cv_exeext +ac_exeext=$EXEEXT +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main () +{ +FILE *f = fopen ("conftest.out", "w"); + return ferror (f) || fclose (f) != 0; + + ; + return 0; +} +_ACEOF +ac_clean_files="$ac_clean_files conftest.out" +# Check that the compiler produces executables we can run. If not, either +# the compiler is broken, or we cross compile. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5 +$as_echo_n "checking whether we are cross compiling... " >&6; } +if test "$cross_compiling" != yes; then + { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } + if { ac_try='./conftest$ac_cv_exeext' + { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; }; then + cross_compiling=no + else + if test "$cross_compiling" = maybe; then + cross_compiling=yes + else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot run C compiled programs. +If you meant to cross compile, use \`--host'. +See \`config.log' for more details" "$LINENO" 5; } + fi + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5 +$as_echo "$cross_compiling" >&6; } + +rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out +ac_clean_files=$ac_clean_files_save +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 +$as_echo_n "checking for suffix of object files... " >&6; } +if ${ac_cv_objext+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +rm -f conftest.o conftest.obj +if { { ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compile") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + for ac_file in conftest.o conftest.obj conftest.*; do + test -f "$ac_file" || continue; + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;; + *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'` + break;; + esac +done +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot compute suffix of object files: cannot compile +See \`config.log' for more details" "$LINENO" 5; } +fi +rm -f conftest.$ac_cv_objext conftest.$ac_ext +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5 +$as_echo "$ac_cv_objext" >&6; } +OBJEXT=$ac_cv_objext +ac_objext=$OBJEXT +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5 +$as_echo_n "checking whether we are using the GNU C compiler... " >&6; } +if ${ac_cv_c_compiler_gnu+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ +#ifndef __GNUC__ + choke me +#endif + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_compiler_gnu=yes +else + ac_compiler_gnu=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +ac_cv_c_compiler_gnu=$ac_compiler_gnu + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 +$as_echo "$ac_cv_c_compiler_gnu" >&6; } +if test $ac_compiler_gnu = yes; then + GCC=yes +else + GCC= +fi +ac_test_CFLAGS=${CFLAGS+set} +ac_save_CFLAGS=$CFLAGS +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 +$as_echo_n "checking whether $CC accepts -g... " >&6; } +if ${ac_cv_prog_cc_g+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_save_c_werror_flag=$ac_c_werror_flag + ac_c_werror_flag=yes + ac_cv_prog_cc_g=no + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_g=yes +else + CFLAGS="" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + +else + ac_c_werror_flag=$ac_save_c_werror_flag + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_g=yes +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_c_werror_flag=$ac_save_c_werror_flag +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 +$as_echo "$ac_cv_prog_cc_g" >&6; } +if test "$ac_test_CFLAGS" = set; then + CFLAGS=$ac_save_CFLAGS +elif test $ac_cv_prog_cc_g = yes; then + if test "$GCC" = yes; then + CFLAGS="-g -O2" + else + CFLAGS="-g" + fi +else + if test "$GCC" = yes; then + CFLAGS="-O2" + else + CFLAGS= + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5 +$as_echo_n "checking for $CC option to accept ISO C89... " >&6; } +if ${ac_cv_prog_cc_c89+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_cv_prog_cc_c89=no +ac_save_CC=$CC +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +struct stat; +/* Most of the following tests are stolen from RCS 5.7's src/conf.sh. */ +struct buf { int x; }; +FILE * (*rcsopen) (struct buf *, struct stat *, int); +static char *e (p, i) + char **p; + int i; +{ + return p[i]; +} +static char *f (char * (*g) (char **, int), char **p, ...) +{ + char *s; + va_list v; + va_start (v,p); + s = g (p, va_arg (v,int)); + va_end (v); + return s; +} + +/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has + function prototypes and stuff, but not '\xHH' hex character constants. + These don't provoke an error unfortunately, instead are silently treated + as 'x'. The following induces an error, until -std is added to get + proper ANSI mode. Curiously '\x00'!='x' always comes out true, for an + array size at least. It's necessary to write '\x00'==0 to get something + that's true only with -std. */ +int osf4_cc_array ['\x00' == 0 ? 1 : -1]; + +/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters + inside strings and character constants. */ +#define FOO(x) 'x' +int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1]; + +int test (int i, double x); +struct s1 {int (*f) (int a);}; +struct s2 {int (*f) (double a);}; +int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int); +int argc; +char **argv; +int +main () +{ +return f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]; + ; + return 0; +} +_ACEOF +for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \ + -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" +do + CC="$ac_save_CC $ac_arg" + if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_c89=$ac_arg +fi +rm -f core conftest.err conftest.$ac_objext + test "x$ac_cv_prog_cc_c89" != "xno" && break +done +rm -f conftest.$ac_ext +CC=$ac_save_CC + +fi +# AC_CACHE_VAL +case "x$ac_cv_prog_cc_c89" in + x) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 +$as_echo "none needed" >&6; } ;; + xno) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 +$as_echo "unsupported" >&6; } ;; + *) + CC="$CC $ac_cv_prog_cc_c89" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 +$as_echo "$ac_cv_prog_cc_c89" >&6; } ;; +esac +if test "x$ac_cv_prog_cc_c89" != xno; then : + +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +# Expand $ac_aux_dir to an absolute path. +am_aux_dir=`cd "$ac_aux_dir" && pwd` + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC understands -c and -o together" >&5 +$as_echo_n "checking whether $CC understands -c and -o together... " >&6; } +if ${am_cv_prog_cc_c_o+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF + # Make sure it works both with $CC and with simple cc. + # Following AC_PROG_CC_C_O, we do the test twice because some + # compilers refuse to overwrite an existing .o file with -o, + # though they will create one. + am_cv_prog_cc_c_o=yes + for am_i in 1 2; do + if { echo "$as_me:$LINENO: $CC -c conftest.$ac_ext -o conftest2.$ac_objext" >&5 + ($CC -c conftest.$ac_ext -o conftest2.$ac_objext) >&5 2>&5 + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } \ + && test -f conftest2.$ac_objext; then + : OK + else + am_cv_prog_cc_c_o=no + break + fi + done + rm -f core conftest* + unset am_i +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_prog_cc_c_o" >&5 +$as_echo "$am_cv_prog_cc_c_o" >&6; } +if test "$am_cv_prog_cc_c_o" != yes; then + # Losing compiler, so override with the script. + # FIXME: It is wrong to rewrite CC. + # But if we don't then we get into trouble of one sort or another. + # A longer-term fix would be to have automake use am__CC in this case, + # and then we could set am__CC="\$(top_srcdir)/compile \$(CC)" + CC="$am_aux_dir/compile $CC" +fi +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5 +$as_echo_n "checking how to run the C preprocessor... " >&6; } +# On Suns, sometimes $CPP names a directory. +if test -n "$CPP" && test -d "$CPP"; then + CPP= +fi +if test -z "$CPP"; then + if ${ac_cv_prog_CPP+:} false; then : + $as_echo_n "(cached) " >&6 +else + # Double quotes because CPP needs to be expanded + for CPP in "$CC -E" "$CC -E -traditional-cpp" "/lib/cpp" + do + ac_preproc_ok=false +for ac_c_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer to if __STDC__ is defined, since + # exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifdef __STDC__ +# include +#else +# include +#endif + Syntax error +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + +else + # Broken: fails on valid input. +continue +fi +rm -f conftest.err conftest.i conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + # Broken: success on invalid input. +continue +else + # Passes both tests. +ac_preproc_ok=: +break +fi +rm -f conftest.err conftest.i conftest.$ac_ext + +done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok; then : + break +fi + + done + ac_cv_prog_CPP=$CPP + +fi + CPP=$ac_cv_prog_CPP +else + ac_cv_prog_CPP=$CPP +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5 +$as_echo "$CPP" >&6; } +ac_preproc_ok=false +for ac_c_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer to if __STDC__ is defined, since + # exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifdef __STDC__ +# include +#else +# include +#endif + Syntax error +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + +else + # Broken: fails on valid input. +continue +fi +rm -f conftest.err conftest.i conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + # Broken: success on invalid input. +continue +else + # Passes both tests. +ac_preproc_ok=: +break +fi +rm -f conftest.err conftest.i conftest.$ac_ext + +done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok; then : + +else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "C preprocessor \"$CPP\" fails sanity check +See \`config.log' for more details" "$LINENO" 5; } +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +if test "x$CC" != xcc; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC and cc understand -c and -o together" >&5 +$as_echo_n "checking whether $CC and cc understand -c and -o together... " >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether cc understands -c and -o together" >&5 +$as_echo_n "checking whether cc understands -c and -o together... " >&6; } +fi +set dummy $CC; ac_cc=`$as_echo "$2" | + sed 's/[^a-zA-Z0-9_]/_/g;s/^[0-9]/_/'` +if eval \${ac_cv_prog_cc_${ac_cc}_c_o+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +# Make sure it works both with $CC and with simple cc. +# We do the test twice because some compilers refuse to overwrite an +# existing .o file with -o, though they will create one. +ac_try='$CC -c conftest.$ac_ext -o conftest2.$ac_objext >&5' +rm -f conftest2.* +if { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && + test -f conftest2.$ac_objext && { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; +then + eval ac_cv_prog_cc_${ac_cc}_c_o=yes + if test "x$CC" != xcc; then + # Test first that cc exists at all. + if { ac_try='cc -c conftest.$ac_ext >&5' + { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; }; then + ac_try='cc -c conftest.$ac_ext -o conftest2.$ac_objext >&5' + rm -f conftest2.* + if { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && + test -f conftest2.$ac_objext && { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; + then + # cc works too. + : + else + # cc exists but doesn't like -o. + eval ac_cv_prog_cc_${ac_cc}_c_o=no + fi + fi + fi +else + eval ac_cv_prog_cc_${ac_cc}_c_o=no +fi +rm -f core conftest* + +fi +if eval test \$ac_cv_prog_cc_${ac_cc}_c_o = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +$as_echo "#define NO_MINUS_C_MINUS_O 1" >>confdefs.h + +fi + +ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu +if test -z "$CXX"; then + if test -n "$CCC"; then + CXX=$CCC + else + if test -n "$ac_tool_prefix"; then + for ac_prog in g++ c++ gpp aCC CC cxx cc++ cl.exe FCC KCC RCC xlC_r xlC + do + # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. +set dummy $ac_tool_prefix$ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CXX+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CXX"; then + ac_cv_prog_CXX="$CXX" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CXX="$ac_tool_prefix$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CXX=$ac_cv_prog_CXX +if test -n "$CXX"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CXX" >&5 +$as_echo "$CXX" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$CXX" && break + done +fi +if test -z "$CXX"; then + ac_ct_CXX=$CXX + for ac_prog in g++ c++ gpp aCC CC cxx cc++ cl.exe FCC KCC RCC xlC_r xlC +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CXX+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CXX"; then + ac_cv_prog_ac_ct_CXX="$ac_ct_CXX" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CXX="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_CXX=$ac_cv_prog_ac_ct_CXX +if test -n "$ac_ct_CXX"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CXX" >&5 +$as_echo "$ac_ct_CXX" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$ac_ct_CXX" && break +done + + if test "x$ac_ct_CXX" = x; then + CXX="g++" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CXX=$ac_ct_CXX + fi +fi + + fi +fi +# Provide some information about the compiler. +$as_echo "$as_me:${as_lineno-$LINENO}: checking for C++ compiler version" >&5 +set X $ac_compile +ac_compiler=$2 +for ac_option in --version -v -V -qversion; do + { { ac_try="$ac_compiler $ac_option >&5" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compiler $ac_option >&5") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + sed '10a\ +... rest of stderr output deleted ... + 10q' conftest.err >conftest.er1 + cat conftest.er1 >&5 + fi + rm -f conftest.er1 conftest.err + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } +done + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C++ compiler" >&5 +$as_echo_n "checking whether we are using the GNU C++ compiler... " >&6; } +if ${ac_cv_cxx_compiler_gnu+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ +#ifndef __GNUC__ + choke me +#endif + + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + ac_compiler_gnu=yes +else + ac_compiler_gnu=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +ac_cv_cxx_compiler_gnu=$ac_compiler_gnu + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_cxx_compiler_gnu" >&5 +$as_echo "$ac_cv_cxx_compiler_gnu" >&6; } +if test $ac_compiler_gnu = yes; then + GXX=yes +else + GXX= +fi +ac_test_CXXFLAGS=${CXXFLAGS+set} +ac_save_CXXFLAGS=$CXXFLAGS +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CXX accepts -g" >&5 +$as_echo_n "checking whether $CXX accepts -g... " >&6; } +if ${ac_cv_prog_cxx_g+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_save_cxx_werror_flag=$ac_cxx_werror_flag + ac_cxx_werror_flag=yes + ac_cv_prog_cxx_g=no + CXXFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + ac_cv_prog_cxx_g=yes +else + CXXFLAGS="" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + +else + ac_cxx_werror_flag=$ac_save_cxx_werror_flag + CXXFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + ac_cv_prog_cxx_g=yes +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_cxx_werror_flag=$ac_save_cxx_werror_flag +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cxx_g" >&5 +$as_echo "$ac_cv_prog_cxx_g" >&6; } +if test "$ac_test_CXXFLAGS" = set; then + CXXFLAGS=$ac_save_CXXFLAGS +elif test $ac_cv_prog_cxx_g = yes; then + if test "$GXX" = yes; then + CXXFLAGS="-g -O2" + else + CXXFLAGS="-g" + fi +else + if test "$GXX" = yes; then + CXXFLAGS="-O2" + else + CXXFLAGS= + fi +fi +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C++ preprocessor" >&5 +$as_echo_n "checking how to run the C++ preprocessor... " >&6; } +if test -z "$CXXCPP"; then + if ${ac_cv_prog_CXXCPP+:} false; then : + $as_echo_n "(cached) " >&6 +else + # Double quotes because CXXCPP needs to be expanded + for CXXCPP in "$CXX -E" "/lib/cpp" + do + ac_preproc_ok=false +for ac_cxx_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer to if __STDC__ is defined, since + # exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifdef __STDC__ +# include +#else +# include +#endif + Syntax error +_ACEOF +if ac_fn_cxx_try_cpp "$LINENO"; then : + +else + # Broken: fails on valid input. +continue +fi +rm -f conftest.err conftest.i conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +_ACEOF +if ac_fn_cxx_try_cpp "$LINENO"; then : + # Broken: success on invalid input. +continue +else + # Passes both tests. +ac_preproc_ok=: +break +fi +rm -f conftest.err conftest.i conftest.$ac_ext + +done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok; then : + break +fi + + done + ac_cv_prog_CXXCPP=$CXXCPP + +fi + CXXCPP=$ac_cv_prog_CXXCPP +else + ac_cv_prog_CXXCPP=$CXXCPP +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CXXCPP" >&5 +$as_echo "$CXXCPP" >&6; } +ac_preproc_ok=false +for ac_cxx_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer to if __STDC__ is defined, since + # exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifdef __STDC__ +# include +#else +# include +#endif + Syntax error +_ACEOF +if ac_fn_cxx_try_cpp "$LINENO"; then : + +else + # Broken: fails on valid input. +continue +fi +rm -f conftest.err conftest.i conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +_ACEOF +if ac_fn_cxx_try_cpp "$LINENO"; then : + # Broken: success on invalid input. +continue +else + # Passes both tests. +ac_preproc_ok=: +break +fi +rm -f conftest.err conftest.i conftest.$ac_ext + +done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok; then : + +else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "C++ preprocessor \"$CXXCPP\" fails sanity check +See \`config.log' for more details" "$LINENO" 5; } +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CXX understands -c and -o together" >&5 +$as_echo_n "checking whether $CXX understands -c and -o together... " >&6; } +if ${ac_cv_prog_cxx_c_o+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +# We test twice because some compilers refuse to overwrite an existing +# `.o' file with `-o', although they will create one. +ac_try='$CXX $CXXFLAGS -c conftest.$ac_ext -o conftest2.$ac_objext >&5' +rm -f conftest2.* +if { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && + test -f conftest2.$ac_objext && + { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + ac_cv_prog_cxx_c_o=yes +else + ac_cv_prog_cxx_c_o=no +fi +rm -f conftest* +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cxx_c_o" >&5 +$as_echo "$ac_cv_prog_cxx_c_o" >&6; } +if test $ac_cv_prog_cxx_c_o = no; then + +$as_echo "#define CXX_NO_MINUS_C_MINUS_O 1" >>confdefs.h + +fi +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + +$as_echo +$as_echo "Checking system type:" +# Make sure we can run config.sub. +$SHELL "$ac_aux_dir/config.sub" sun4 >/dev/null 2>&1 || + as_fn_error $? "cannot run $SHELL $ac_aux_dir/config.sub" "$LINENO" 5 + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking build system type" >&5 +$as_echo_n "checking build system type... " >&6; } +if ${ac_cv_build+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_build_alias=$build_alias +test "x$ac_build_alias" = x && + ac_build_alias=`$SHELL "$ac_aux_dir/config.guess"` +test "x$ac_build_alias" = x && + as_fn_error $? "cannot guess build type; you must specify one" "$LINENO" 5 +ac_cv_build=`$SHELL "$ac_aux_dir/config.sub" $ac_build_alias` || + as_fn_error $? "$SHELL $ac_aux_dir/config.sub $ac_build_alias failed" "$LINENO" 5 + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_build" >&5 +$as_echo "$ac_cv_build" >&6; } +case $ac_cv_build in +*-*-*) ;; +*) as_fn_error $? "invalid value of canonical build" "$LINENO" 5;; +esac +build=$ac_cv_build +ac_save_IFS=$IFS; IFS='-' +set x $ac_cv_build +shift +build_cpu=$1 +build_vendor=$2 +shift; shift +# Remember, the first character of IFS is used to create $*, +# except with old shells: +build_os=$* +IFS=$ac_save_IFS +case $build_os in *\ *) build_os=`echo "$build_os" | sed 's/ /-/g'`;; esac + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking host system type" >&5 +$as_echo_n "checking host system type... " >&6; } +if ${ac_cv_host+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test "x$host_alias" = x; then + ac_cv_host=$ac_cv_build +else + ac_cv_host=`$SHELL "$ac_aux_dir/config.sub" $host_alias` || + as_fn_error $? "$SHELL $ac_aux_dir/config.sub $host_alias failed" "$LINENO" 5 +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_host" >&5 +$as_echo "$ac_cv_host" >&6; } +case $ac_cv_host in +*-*-*) ;; +*) as_fn_error $? "invalid value of canonical host" "$LINENO" 5;; +esac +host=$ac_cv_host +ac_save_IFS=$IFS; IFS='-' +set x $ac_cv_host +shift +host_cpu=$1 +host_vendor=$2 +shift; shift +# Remember, the first character of IFS is used to create $*, +# except with old shells: +host_os=$* +IFS=$ac_save_IFS +case $host_os in *\ *) host_os=`echo "$host_os" | sed 's/ /-/g'`;; esac + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking target system type" >&5 +$as_echo_n "checking target system type... " >&6; } +if ${ac_cv_target+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test "x$target_alias" = x; then + ac_cv_target=$ac_cv_host +else + ac_cv_target=`$SHELL "$ac_aux_dir/config.sub" $target_alias` || + as_fn_error $? "$SHELL $ac_aux_dir/config.sub $target_alias failed" "$LINENO" 5 +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_target" >&5 +$as_echo "$ac_cv_target" >&6; } +case $ac_cv_target in +*-*-*) ;; +*) as_fn_error $? "invalid value of canonical target" "$LINENO" 5;; +esac +target=$ac_cv_target +ac_save_IFS=$IFS; IFS='-' +set x $ac_cv_target +shift +target_cpu=$1 +target_vendor=$2 +shift; shift +# Remember, the first character of IFS is used to create $*, +# except with old shells: +target_os=$* +IFS=$ac_save_IFS +case $target_os in *\ *) target_os=`echo "$target_os" | sed 's/ /-/g'`;; esac + + +# The aliases save the names the user supplied, while $host etc. +# will get canonicalized. +test -n "$target_alias" && + test "$program_prefix$program_suffix$program_transform_name" = \ + NONENONEs,x,x, && + program_prefix=${target_alias}- + +$as_echo +$as_echo "Initializing Automake:" +am__api_version='1.15' + +# Find a good install program. We prefer a C program (faster), +# so one script is as good as another. But avoid the broken or +# incompatible versions: +# SysV /etc/install, /usr/sbin/install +# SunOS /usr/etc/install +# IRIX /sbin/install +# AIX /bin/install +# AmigaOS /C/install, which installs bootblocks on floppy discs +# AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag +# AFS /usr/afsws/bin/install, which mishandles nonexistent args +# SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff" +# OS/2's system install, which has a completely different semantic +# ./install, which can be erroneously created by make from ./install.sh. +# Reject install programs that cannot install multiple files. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a BSD-compatible install" >&5 +$as_echo_n "checking for a BSD-compatible install... " >&6; } +if test -z "$INSTALL"; then +if ${ac_cv_path_install+:} false; then : + $as_echo_n "(cached) " >&6 +else + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + # Account for people who put trailing slashes in PATH elements. +case $as_dir/ in #(( + ./ | .// | /[cC]/* | \ + /etc/* | /usr/sbin/* | /usr/etc/* | /sbin/* | /usr/afsws/bin/* | \ + ?:[\\/]os2[\\/]install[\\/]* | ?:[\\/]OS2[\\/]INSTALL[\\/]* | \ + /usr/ucb/* ) ;; + *) + # OSF1 and SCO ODT 3.0 have their own names for install. + # Don't use installbsd from OSF since it installs stuff as root + # by default. + for ac_prog in ginstall scoinst install; do + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_prog$ac_exec_ext"; then + if test $ac_prog = install && + grep dspmsg "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then + # AIX install. It has an incompatible calling convention. + : + elif test $ac_prog = install && + grep pwplus "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then + # program-specific install script used by HP pwplus--don't use. + : + else + rm -rf conftest.one conftest.two conftest.dir + echo one > conftest.one + echo two > conftest.two + mkdir conftest.dir + if "$as_dir/$ac_prog$ac_exec_ext" -c conftest.one conftest.two "`pwd`/conftest.dir" && + test -s conftest.one && test -s conftest.two && + test -s conftest.dir/conftest.one && + test -s conftest.dir/conftest.two + then + ac_cv_path_install="$as_dir/$ac_prog$ac_exec_ext -c" + break 3 + fi + fi + fi + done + done + ;; +esac + + done +IFS=$as_save_IFS + +rm -rf conftest.one conftest.two conftest.dir + +fi + if test "${ac_cv_path_install+set}" = set; then + INSTALL=$ac_cv_path_install + else + # As a last resort, use the slow shell script. Don't cache a + # value for INSTALL within a source directory, because that will + # break other packages using the cache if that directory is + # removed, or if the value is a relative name. + INSTALL=$ac_install_sh + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $INSTALL" >&5 +$as_echo "$INSTALL" >&6; } + +# Use test -z because SunOS4 sh mishandles braces in ${var-val}. +# It thinks the first close brace ends the variable substitution. +test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}' + +test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}' + +test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether build environment is sane" >&5 +$as_echo_n "checking whether build environment is sane... " >&6; } +# Reject unsafe characters in $srcdir or the absolute working directory +# name. Accept space and tab only in the latter. +am_lf=' +' +case `pwd` in + *[\\\"\#\$\&\'\`$am_lf]*) + as_fn_error $? "unsafe absolute working directory name" "$LINENO" 5;; +esac +case $srcdir in + *[\\\"\#\$\&\'\`$am_lf\ \ ]*) + as_fn_error $? "unsafe srcdir value: '$srcdir'" "$LINENO" 5;; +esac + +# Do 'set' in a subshell so we don't clobber the current shell's +# arguments. Must try -L first in case configure is actually a +# symlink; some systems play weird games with the mod time of symlinks +# (eg FreeBSD returns the mod time of the symlink's containing +# directory). +if ( + am_has_slept=no + for am_try in 1 2; do + echo "timestamp, slept: $am_has_slept" > conftest.file + set X `ls -Lt "$srcdir/configure" conftest.file 2> /dev/null` + if test "$*" = "X"; then + # -L didn't work. + set X `ls -t "$srcdir/configure" conftest.file` + fi + if test "$*" != "X $srcdir/configure conftest.file" \ + && test "$*" != "X conftest.file $srcdir/configure"; then + + # If neither matched, then we have a broken ls. This can happen + # if, for instance, CONFIG_SHELL is bash and it inherits a + # broken ls alias from the environment. This has actually + # happened. Such a system could not be considered "sane". + as_fn_error $? "ls -t appears to fail. Make sure there is not a broken + alias in your environment" "$LINENO" 5 + fi + if test "$2" = conftest.file || test $am_try -eq 2; then + break + fi + # Just in case. + sleep 1 + am_has_slept=yes + done + test "$2" = conftest.file + ) +then + # Ok. + : +else + as_fn_error $? "newly created file is older than distributed files! +Check your system clock" "$LINENO" 5 +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +# If we didn't sleep, we still need to ensure time stamps of config.status and +# generated files are strictly newer. +am_sleep_pid= +if grep 'slept: no' conftest.file >/dev/null 2>&1; then + ( sleep 1 ) & + am_sleep_pid=$! +fi + +rm -f conftest.file + +test "$program_prefix" != NONE && + program_transform_name="s&^&$program_prefix&;$program_transform_name" +# Use a double $ so make ignores it. +test "$program_suffix" != NONE && + program_transform_name="s&\$&$program_suffix&;$program_transform_name" +# Double any \ or $. +# By default was `s,x,x', remove it if useless. +ac_script='s/[\\$]/&&/g;s/;s,x,x,$//' +program_transform_name=`$as_echo "$program_transform_name" | sed "$ac_script"` + +if test x"${MISSING+set}" != xset; then + case $am_aux_dir in + *\ * | *\ *) + MISSING="\${SHELL} \"$am_aux_dir/missing\"" ;; + *) + MISSING="\${SHELL} $am_aux_dir/missing" ;; + esac +fi +# Use eval to expand $SHELL +if eval "$MISSING --is-lightweight"; then + am_missing_run="$MISSING " +else + am_missing_run= + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: 'missing' script is too old or missing" >&5 +$as_echo "$as_me: WARNING: 'missing' script is too old or missing" >&2;} +fi + +if test x"${install_sh+set}" != xset; then + case $am_aux_dir in + *\ * | *\ *) + install_sh="\${SHELL} '$am_aux_dir/install-sh'" ;; + *) + install_sh="\${SHELL} $am_aux_dir/install-sh" + esac +fi + +# Installed binaries are usually stripped using 'strip' when the user +# run "make install-strip". However 'strip' might not be the right +# tool to use in cross-compilation environments, therefore Automake +# will honor the 'STRIP' environment variable to overrule this program. +if test "$cross_compiling" != no; then + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}strip", so it can be a program name with args. +set dummy ${ac_tool_prefix}strip; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_STRIP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$STRIP"; then + ac_cv_prog_STRIP="$STRIP" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_STRIP="${ac_tool_prefix}strip" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +STRIP=$ac_cv_prog_STRIP +if test -n "$STRIP"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $STRIP" >&5 +$as_echo "$STRIP" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_STRIP"; then + ac_ct_STRIP=$STRIP + # Extract the first word of "strip", so it can be a program name with args. +set dummy strip; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_STRIP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_STRIP"; then + ac_cv_prog_ac_ct_STRIP="$ac_ct_STRIP" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_STRIP="strip" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_STRIP=$ac_cv_prog_ac_ct_STRIP +if test -n "$ac_ct_STRIP"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_STRIP" >&5 +$as_echo "$ac_ct_STRIP" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_STRIP" = x; then + STRIP=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + STRIP=$ac_ct_STRIP + fi +else + STRIP="$ac_cv_prog_STRIP" +fi + +fi +INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s" + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a thread-safe mkdir -p" >&5 +$as_echo_n "checking for a thread-safe mkdir -p... " >&6; } +if test -z "$MKDIR_P"; then + if ${ac_cv_path_mkdir+:} false; then : + $as_echo_n "(cached) " >&6 +else + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/opt/sfw/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in mkdir gmkdir; do + for ac_exec_ext in '' $ac_executable_extensions; do + as_fn_executable_p "$as_dir/$ac_prog$ac_exec_ext" || continue + case `"$as_dir/$ac_prog$ac_exec_ext" --version 2>&1` in #( + 'mkdir (GNU coreutils) '* | \ + 'mkdir (coreutils) '* | \ + 'mkdir (fileutils) '4.1*) + ac_cv_path_mkdir=$as_dir/$ac_prog$ac_exec_ext + break 3;; + esac + done + done + done +IFS=$as_save_IFS + +fi + + test -d ./--version && rmdir ./--version + if test "${ac_cv_path_mkdir+set}" = set; then + MKDIR_P="$ac_cv_path_mkdir -p" + else + # As a last resort, use the slow shell script. Don't cache a + # value for MKDIR_P within a source directory, because that will + # break other packages using the cache if that directory is + # removed, or if the value is a relative name. + MKDIR_P="$ac_install_sh -d" + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $MKDIR_P" >&5 +$as_echo "$MKDIR_P" >&6; } + +for ac_prog in gawk mawk nawk awk +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_AWK+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$AWK"; then + ac_cv_prog_AWK="$AWK" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_AWK="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +AWK=$ac_cv_prog_AWK +if test -n "$AWK"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $AWK" >&5 +$as_echo "$AWK" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$AWK" && break +done + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} sets \$(MAKE)" >&5 +$as_echo_n "checking whether ${MAKE-make} sets \$(MAKE)... " >&6; } +set x ${MAKE-make} +ac_make=`$as_echo "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'` +if eval \${ac_cv_prog_make_${ac_make}_set+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat >conftest.make <<\_ACEOF +SHELL = /bin/sh +all: + @echo '@@@%%%=$(MAKE)=@@@%%%' +_ACEOF +# GNU make sometimes prints "make[1]: Entering ...", which would confuse us. +case `${MAKE-make} -f conftest.make 2>/dev/null` in + *@@@%%%=?*=@@@%%%*) + eval ac_cv_prog_make_${ac_make}_set=yes;; + *) + eval ac_cv_prog_make_${ac_make}_set=no;; +esac +rm -f conftest.make +fi +if eval test \$ac_cv_prog_make_${ac_make}_set = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + SET_MAKE= +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + SET_MAKE="MAKE=${MAKE-make}" +fi + +rm -rf .tst 2>/dev/null +mkdir .tst 2>/dev/null +if test -d .tst; then + am__leading_dot=. +else + am__leading_dot=_ +fi +rmdir .tst 2>/dev/null + +DEPDIR="${am__leading_dot}deps" + +ac_config_commands="$ac_config_commands depfiles" + + +am_make=${MAKE-make} +cat > confinc << 'END' +am__doit: + @echo this is the am__doit target +.PHONY: am__doit +END +# If we don't find an include directive, just comment out the code. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for style of include used by $am_make" >&5 +$as_echo_n "checking for style of include used by $am_make... " >&6; } +am__include="#" +am__quote= +_am_result=none +# First try GNU make style include. +echo "include confinc" > confmf +# Ignore all kinds of additional output from 'make'. +case `$am_make -s -f confmf 2> /dev/null` in #( +*the\ am__doit\ target*) + am__include=include + am__quote= + _am_result=GNU + ;; +esac +# Now try BSD make style include. +if test "$am__include" = "#"; then + echo '.include "confinc"' > confmf + case `$am_make -s -f confmf 2> /dev/null` in #( + *the\ am__doit\ target*) + am__include=.include + am__quote="\"" + _am_result=BSD + ;; + esac +fi + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $_am_result" >&5 +$as_echo "$_am_result" >&6; } +rm -f confinc confmf + +# Check whether --enable-dependency-tracking was given. +if test "${enable_dependency_tracking+set}" = set; then : + enableval=$enable_dependency_tracking; +fi + +if test "x$enable_dependency_tracking" != xno; then + am_depcomp="$ac_aux_dir/depcomp" + AMDEPBACKSLASH='\' + am__nodep='_no' +fi + if test "x$enable_dependency_tracking" != xno; then + AMDEP_TRUE= + AMDEP_FALSE='#' +else + AMDEP_TRUE='#' + AMDEP_FALSE= +fi + + +if test "`cd $srcdir && pwd`" != "`pwd`"; then + # Use -I$(srcdir) only when $(srcdir) != ., so that make's output + # is not polluted with repeated "-I." + am__isrc=' -I$(srcdir)' + # test to see if srcdir already configured + if test -f $srcdir/config.status; then + as_fn_error $? "source directory already configured; run \"make distclean\" there first" "$LINENO" 5 + fi +fi + +# test whether we have cygpath +if test -z "$CYGPATH_W"; then + if (cygpath --version) >/dev/null 2>/dev/null; then + CYGPATH_W='cygpath -w' + else + CYGPATH_W=echo + fi +fi + + +# Define the identity of the package. + PACKAGE='libtorrent-rasterbar' + VERSION='1.1.1' + + +cat >>confdefs.h <<_ACEOF +#define PACKAGE "$PACKAGE" +_ACEOF + + +cat >>confdefs.h <<_ACEOF +#define VERSION "$VERSION" +_ACEOF + +# Some tools Automake needs. + +ACLOCAL=${ACLOCAL-"${am_missing_run}aclocal-${am__api_version}"} + + +AUTOCONF=${AUTOCONF-"${am_missing_run}autoconf"} + + +AUTOMAKE=${AUTOMAKE-"${am_missing_run}automake-${am__api_version}"} + + +AUTOHEADER=${AUTOHEADER-"${am_missing_run}autoheader"} + + +MAKEINFO=${MAKEINFO-"${am_missing_run}makeinfo"} + +# For better backward compatibility. To be removed once Automake 1.9.x +# dies out for good. For more background, see: +# +# +mkdir_p='$(MKDIR_P)' + +# We need awk for the "check" target (and possibly the TAP driver). The +# system "awk" is bad on some platforms. +# Always define AMTAR for backward compatibility. Yes, it's still used +# in the wild :-( We should find a proper way to deprecate it ... +AMTAR='$${TAR-tar}' + + +# We'll loop over all known methods to create a tar archive until one works. +_am_tools='gnutar pax cpio none' + +am__tar='$${TAR-tar} chof - "$$tardir"' am__untar='$${TAR-tar} xf -' + + + + + +depcc="$CC" am_compiler_list= + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking dependency style of $depcc" >&5 +$as_echo_n "checking dependency style of $depcc... " >&6; } +if ${am_cv_CC_dependencies_compiler_type+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then + # We make a subdir and do the tests there. Otherwise we can end up + # making bogus files that we don't know about and never remove. For + # instance it was reported that on HP-UX the gcc test will end up + # making a dummy file named 'D' -- because '-MD' means "put the output + # in D". + rm -rf conftest.dir + mkdir conftest.dir + # Copy depcomp to subdir because otherwise we won't find it if we're + # using a relative directory. + cp "$am_depcomp" conftest.dir + cd conftest.dir + # We will build objects and dependencies in a subdirectory because + # it helps to detect inapplicable dependency modes. For instance + # both Tru64's cc and ICC support -MD to output dependencies as a + # side effect of compilation, but ICC will put the dependencies in + # the current directory while Tru64 will put them in the object + # directory. + mkdir sub + + am_cv_CC_dependencies_compiler_type=none + if test "$am_compiler_list" = ""; then + am_compiler_list=`sed -n 's/^#*\([a-zA-Z0-9]*\))$/\1/p' < ./depcomp` + fi + am__universal=false + case " $depcc " in #( + *\ -arch\ *\ -arch\ *) am__universal=true ;; + esac + + for depmode in $am_compiler_list; do + # Setup a source with many dependencies, because some compilers + # like to wrap large dependency lists on column 80 (with \), and + # we should not choose a depcomp mode which is confused by this. + # + # We need to recreate these files for each test, as the compiler may + # overwrite some of them when testing with obscure command lines. + # This happens at least with the AIX C compiler. + : > sub/conftest.c + for i in 1 2 3 4 5 6; do + echo '#include "conftst'$i'.h"' >> sub/conftest.c + # Using ": > sub/conftst$i.h" creates only sub/conftst1.h with + # Solaris 10 /bin/sh. + echo '/* dummy */' > sub/conftst$i.h + done + echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf + + # We check with '-c' and '-o' for the sake of the "dashmstdout" + # mode. It turns out that the SunPro C++ compiler does not properly + # handle '-M -o', and we need to detect this. Also, some Intel + # versions had trouble with output in subdirs. + am__obj=sub/conftest.${OBJEXT-o} + am__minus_obj="-o $am__obj" + case $depmode in + gcc) + # This depmode causes a compiler race in universal mode. + test "$am__universal" = false || continue + ;; + nosideeffect) + # After this tag, mechanisms are not by side-effect, so they'll + # only be used when explicitly requested. + if test "x$enable_dependency_tracking" = xyes; then + continue + else + break + fi + ;; + msvc7 | msvc7msys | msvisualcpp | msvcmsys) + # This compiler won't grok '-c -o', but also, the minuso test has + # not run yet. These depmodes are late enough in the game, and + # so weak that their functioning should not be impacted. + am__obj=conftest.${OBJEXT-o} + am__minus_obj= + ;; + none) break ;; + esac + if depmode=$depmode \ + source=sub/conftest.c object=$am__obj \ + depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \ + $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \ + >/dev/null 2>conftest.err && + grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 && + grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 && + grep $am__obj sub/conftest.Po > /dev/null 2>&1 && + ${MAKE-make} -s -f confmf > /dev/null 2>&1; then + # icc doesn't choke on unknown options, it will just issue warnings + # or remarks (even with -Werror). So we grep stderr for any message + # that says an option was ignored or not supported. + # When given -MP, icc 7.0 and 7.1 complain thusly: + # icc: Command line warning: ignoring option '-M'; no argument required + # The diagnosis changed in icc 8.0: + # icc: Command line remark: option '-MP' not supported + if (grep 'ignoring option' conftest.err || + grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else + am_cv_CC_dependencies_compiler_type=$depmode + break + fi + fi + done + + cd .. + rm -rf conftest.dir +else + am_cv_CC_dependencies_compiler_type=none +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_CC_dependencies_compiler_type" >&5 +$as_echo "$am_cv_CC_dependencies_compiler_type" >&6; } +CCDEPMODE=depmode=$am_cv_CC_dependencies_compiler_type + + if + test "x$enable_dependency_tracking" != xno \ + && test "$am_cv_CC_dependencies_compiler_type" = gcc3; then + am__fastdepCC_TRUE= + am__fastdepCC_FALSE='#' +else + am__fastdepCC_TRUE='#' + am__fastdepCC_FALSE= +fi + + +depcc="$CXX" am_compiler_list= + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking dependency style of $depcc" >&5 +$as_echo_n "checking dependency style of $depcc... " >&6; } +if ${am_cv_CXX_dependencies_compiler_type+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then + # We make a subdir and do the tests there. Otherwise we can end up + # making bogus files that we don't know about and never remove. For + # instance it was reported that on HP-UX the gcc test will end up + # making a dummy file named 'D' -- because '-MD' means "put the output + # in D". + rm -rf conftest.dir + mkdir conftest.dir + # Copy depcomp to subdir because otherwise we won't find it if we're + # using a relative directory. + cp "$am_depcomp" conftest.dir + cd conftest.dir + # We will build objects and dependencies in a subdirectory because + # it helps to detect inapplicable dependency modes. For instance + # both Tru64's cc and ICC support -MD to output dependencies as a + # side effect of compilation, but ICC will put the dependencies in + # the current directory while Tru64 will put them in the object + # directory. + mkdir sub + + am_cv_CXX_dependencies_compiler_type=none + if test "$am_compiler_list" = ""; then + am_compiler_list=`sed -n 's/^#*\([a-zA-Z0-9]*\))$/\1/p' < ./depcomp` + fi + am__universal=false + case " $depcc " in #( + *\ -arch\ *\ -arch\ *) am__universal=true ;; + esac + + for depmode in $am_compiler_list; do + # Setup a source with many dependencies, because some compilers + # like to wrap large dependency lists on column 80 (with \), and + # we should not choose a depcomp mode which is confused by this. + # + # We need to recreate these files for each test, as the compiler may + # overwrite some of them when testing with obscure command lines. + # This happens at least with the AIX C compiler. + : > sub/conftest.c + for i in 1 2 3 4 5 6; do + echo '#include "conftst'$i'.h"' >> sub/conftest.c + # Using ": > sub/conftst$i.h" creates only sub/conftst1.h with + # Solaris 10 /bin/sh. + echo '/* dummy */' > sub/conftst$i.h + done + echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf + + # We check with '-c' and '-o' for the sake of the "dashmstdout" + # mode. It turns out that the SunPro C++ compiler does not properly + # handle '-M -o', and we need to detect this. Also, some Intel + # versions had trouble with output in subdirs. + am__obj=sub/conftest.${OBJEXT-o} + am__minus_obj="-o $am__obj" + case $depmode in + gcc) + # This depmode causes a compiler race in universal mode. + test "$am__universal" = false || continue + ;; + nosideeffect) + # After this tag, mechanisms are not by side-effect, so they'll + # only be used when explicitly requested. + if test "x$enable_dependency_tracking" = xyes; then + continue + else + break + fi + ;; + msvc7 | msvc7msys | msvisualcpp | msvcmsys) + # This compiler won't grok '-c -o', but also, the minuso test has + # not run yet. These depmodes are late enough in the game, and + # so weak that their functioning should not be impacted. + am__obj=conftest.${OBJEXT-o} + am__minus_obj= + ;; + none) break ;; + esac + if depmode=$depmode \ + source=sub/conftest.c object=$am__obj \ + depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \ + $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \ + >/dev/null 2>conftest.err && + grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 && + grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 && + grep $am__obj sub/conftest.Po > /dev/null 2>&1 && + ${MAKE-make} -s -f confmf > /dev/null 2>&1; then + # icc doesn't choke on unknown options, it will just issue warnings + # or remarks (even with -Werror). So we grep stderr for any message + # that says an option was ignored or not supported. + # When given -MP, icc 7.0 and 7.1 complain thusly: + # icc: Command line warning: ignoring option '-M'; no argument required + # The diagnosis changed in icc 8.0: + # icc: Command line remark: option '-MP' not supported + if (grep 'ignoring option' conftest.err || + grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else + am_cv_CXX_dependencies_compiler_type=$depmode + break + fi + fi + done + + cd .. + rm -rf conftest.dir +else + am_cv_CXX_dependencies_compiler_type=none +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_CXX_dependencies_compiler_type" >&5 +$as_echo "$am_cv_CXX_dependencies_compiler_type" >&6; } +CXXDEPMODE=depmode=$am_cv_CXX_dependencies_compiler_type + + if + test "x$enable_dependency_tracking" != xno \ + && test "$am_cv_CXX_dependencies_compiler_type" = gcc3; then + am__fastdepCXX_TRUE= + am__fastdepCXX_FALSE='#' +else + am__fastdepCXX_TRUE='#' + am__fastdepCXX_FALSE= +fi + + + +# POSIX will say in a future version that running "rm -f" with no argument +# is OK; and we want to be able to make that assumption in our Makefile +# recipes. So use an aggressive probe to check that the usage we want is +# actually supported "in the wild" to an acceptable degree. +# See automake bug#10828. +# To make any issue more visible, cause the running configure to be aborted +# by default if the 'rm' program in use doesn't match our expectations; the +# user can still override this though. +if rm -f && rm -fr && rm -rf; then : OK; else + cat >&2 <<'END' +Oops! + +Your 'rm' program seems unable to run without file operands specified +on the command line, even when the '-f' option is present. This is contrary +to the behaviour of most rm programs out there, and not conforming with +the upcoming POSIX standard: + +Please tell bug-automake@gnu.org about your system, including the value +of your $PATH and any error possibly output before this message. This +can help us improve future automake versions. + +END + if test x"$ACCEPT_INFERIOR_RM_PROGRAM" = x"yes"; then + echo 'Configuration will proceed anyway, since you have set the' >&2 + echo 'ACCEPT_INFERIOR_RM_PROGRAM variable to "yes"' >&2 + echo >&2 + else + cat >&2 <<'END' +Aborting the configuration process, to ensure you take notice of the issue. + +You can download and install GNU coreutils to get an 'rm' implementation +that behaves properly: . + +If you want to complete the configuration process using your problematic +'rm' anyway, export the environment variable ACCEPT_INFERIOR_RM_PROGRAM +to "yes", and re-run configure. + +END + as_fn_error $? "Your 'rm' program is bad, sorry." "$LINENO" 5 + fi +fi + + + +$as_echo +$as_echo "Initializing Libtool:" + +case `pwd` in + *\ * | *\ *) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Libtool does not cope well with whitespace in \`pwd\`" >&5 +$as_echo "$as_me: WARNING: Libtool does not cope well with whitespace in \`pwd\`" >&2;} ;; +esac + + + +macro_version='2.4.6' +macro_revision='2.4.6' + + + + + + + + + + + + + +ltmain=$ac_aux_dir/ltmain.sh + +# Backslashify metacharacters that are still active within +# double-quoted strings. +sed_quote_subst='s/\(["`$\\]\)/\\\1/g' + +# Same as above, but do not quote variable references. +double_quote_subst='s/\(["`\\]\)/\\\1/g' + +# Sed substitution to delay expansion of an escaped shell variable in a +# double_quote_subst'ed string. +delay_variable_subst='s/\\\\\\\\\\\$/\\\\\\$/g' + +# Sed substitution to delay expansion of an escaped single quote. +delay_single_quote_subst='s/'\''/'\'\\\\\\\'\''/g' + +# Sed substitution to avoid accidental globbing in evaled expressions +no_glob_subst='s/\*/\\\*/g' + +ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO +ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO$ECHO + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to print strings" >&5 +$as_echo_n "checking how to print strings... " >&6; } +# Test print first, because it will be a builtin if present. +if test "X`( print -r -- -n ) 2>/dev/null`" = X-n && \ + test "X`print -r -- $ECHO 2>/dev/null`" = "X$ECHO"; then + ECHO='print -r --' +elif test "X`printf %s $ECHO 2>/dev/null`" = "X$ECHO"; then + ECHO='printf %s\n' +else + # Use this function as a fallback that always works. + func_fallback_echo () + { + eval 'cat <<_LTECHO_EOF +$1 +_LTECHO_EOF' + } + ECHO='func_fallback_echo' +fi + +# func_echo_all arg... +# Invoke $ECHO with all args, space-separated. +func_echo_all () +{ + $ECHO "" +} + +case $ECHO in + printf*) { $as_echo "$as_me:${as_lineno-$LINENO}: result: printf" >&5 +$as_echo "printf" >&6; } ;; + print*) { $as_echo "$as_me:${as_lineno-$LINENO}: result: print -r" >&5 +$as_echo "print -r" >&6; } ;; + *) { $as_echo "$as_me:${as_lineno-$LINENO}: result: cat" >&5 +$as_echo "cat" >&6; } ;; +esac + + + + + + + + + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a sed that does not truncate output" >&5 +$as_echo_n "checking for a sed that does not truncate output... " >&6; } +if ${ac_cv_path_SED+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_script=s/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/ + for ac_i in 1 2 3 4 5 6 7; do + ac_script="$ac_script$as_nl$ac_script" + done + echo "$ac_script" 2>/dev/null | sed 99q >conftest.sed + { ac_script=; unset ac_script;} + if test -z "$SED"; then + ac_path_SED_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in sed gsed; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_SED="$as_dir/$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_SED" || continue +# Check for GNU ac_path_SED and select it if it is found. + # Check for GNU $ac_path_SED +case `"$ac_path_SED" --version 2>&1` in +*GNU*) + ac_cv_path_SED="$ac_path_SED" ac_path_SED_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo '' >> "conftest.nl" + "$ac_path_SED" -f conftest.sed < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_SED_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_SED="$ac_path_SED" + ac_path_SED_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_SED_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_SED"; then + as_fn_error $? "no acceptable sed could be found in \$PATH" "$LINENO" 5 + fi +else + ac_cv_path_SED=$SED +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_SED" >&5 +$as_echo "$ac_cv_path_SED" >&6; } + SED="$ac_cv_path_SED" + rm -f conftest.sed + +test -z "$SED" && SED=sed +Xsed="$SED -e 1s/^X//" + + + + + + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5 +$as_echo_n "checking for grep that handles long lines and -e... " >&6; } +if ${ac_cv_path_GREP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -z "$GREP"; then + ac_path_GREP_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in grep ggrep; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_GREP="$as_dir/$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_GREP" || continue +# Check for GNU ac_path_GREP and select it if it is found. + # Check for GNU $ac_path_GREP +case `"$ac_path_GREP" --version 2>&1` in +*GNU*) + ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo 'GREP' >> "conftest.nl" + "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_GREP_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_GREP="$ac_path_GREP" + ac_path_GREP_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_GREP_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_GREP"; then + as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 + fi +else + ac_cv_path_GREP=$GREP +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5 +$as_echo "$ac_cv_path_GREP" >&6; } + GREP="$ac_cv_path_GREP" + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5 +$as_echo_n "checking for egrep... " >&6; } +if ${ac_cv_path_EGREP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if echo a | $GREP -E '(a|b)' >/dev/null 2>&1 + then ac_cv_path_EGREP="$GREP -E" + else + if test -z "$EGREP"; then + ac_path_EGREP_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in egrep; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_EGREP="$as_dir/$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_EGREP" || continue +# Check for GNU ac_path_EGREP and select it if it is found. + # Check for GNU $ac_path_EGREP +case `"$ac_path_EGREP" --version 2>&1` in +*GNU*) + ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo 'EGREP' >> "conftest.nl" + "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_EGREP_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_EGREP="$ac_path_EGREP" + ac_path_EGREP_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_EGREP_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_EGREP"; then + as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 + fi +else + ac_cv_path_EGREP=$EGREP +fi + + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5 +$as_echo "$ac_cv_path_EGREP" >&6; } + EGREP="$ac_cv_path_EGREP" + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for fgrep" >&5 +$as_echo_n "checking for fgrep... " >&6; } +if ${ac_cv_path_FGREP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if echo 'ab*c' | $GREP -F 'ab*c' >/dev/null 2>&1 + then ac_cv_path_FGREP="$GREP -F" + else + if test -z "$FGREP"; then + ac_path_FGREP_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in fgrep; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_FGREP="$as_dir/$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_FGREP" || continue +# Check for GNU ac_path_FGREP and select it if it is found. + # Check for GNU $ac_path_FGREP +case `"$ac_path_FGREP" --version 2>&1` in +*GNU*) + ac_cv_path_FGREP="$ac_path_FGREP" ac_path_FGREP_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo 'FGREP' >> "conftest.nl" + "$ac_path_FGREP" FGREP < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_FGREP_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_FGREP="$ac_path_FGREP" + ac_path_FGREP_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_FGREP_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_FGREP"; then + as_fn_error $? "no acceptable fgrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 + fi +else + ac_cv_path_FGREP=$FGREP +fi + + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_FGREP" >&5 +$as_echo "$ac_cv_path_FGREP" >&6; } + FGREP="$ac_cv_path_FGREP" + + +test -z "$GREP" && GREP=grep + + + + + + + + + + + + + + + + + + + +# Check whether --with-gnu-ld was given. +if test "${with_gnu_ld+set}" = set; then : + withval=$with_gnu_ld; test no = "$withval" || with_gnu_ld=yes +else + with_gnu_ld=no +fi + +ac_prog=ld +if test yes = "$GCC"; then + # Check if gcc -print-prog-name=ld gives a path. + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ld used by $CC" >&5 +$as_echo_n "checking for ld used by $CC... " >&6; } + case $host in + *-*-mingw*) + # gcc leaves a trailing carriage return, which upsets mingw + ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;; + *) + ac_prog=`($CC -print-prog-name=ld) 2>&5` ;; + esac + case $ac_prog in + # Accept absolute paths. + [\\/]* | ?:[\\/]*) + re_direlt='/[^/][^/]*/\.\./' + # Canonicalize the pathname of ld + ac_prog=`$ECHO "$ac_prog"| $SED 's%\\\\%/%g'` + while $ECHO "$ac_prog" | $GREP "$re_direlt" > /dev/null 2>&1; do + ac_prog=`$ECHO $ac_prog| $SED "s%$re_direlt%/%"` + done + test -z "$LD" && LD=$ac_prog + ;; + "") + # If it fails, then pretend we aren't using GCC. + ac_prog=ld + ;; + *) + # If it is relative, then search for the first ld in PATH. + with_gnu_ld=unknown + ;; + esac +elif test yes = "$with_gnu_ld"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for GNU ld" >&5 +$as_echo_n "checking for GNU ld... " >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for non-GNU ld" >&5 +$as_echo_n "checking for non-GNU ld... " >&6; } +fi +if ${lt_cv_path_LD+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -z "$LD"; then + lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR + for ac_dir in $PATH; do + IFS=$lt_save_ifs + test -z "$ac_dir" && ac_dir=. + if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then + lt_cv_path_LD=$ac_dir/$ac_prog + # Check to see if the program is GNU ld. I'd rather use --version, + # but apparently some variants of GNU ld only accept -v. + # Break only if it was the GNU/non-GNU ld that we prefer. + case `"$lt_cv_path_LD" -v 2>&1 &5 +$as_echo "$LD" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi +test -z "$LD" && as_fn_error $? "no acceptable ld found in \$PATH" "$LINENO" 5 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if the linker ($LD) is GNU ld" >&5 +$as_echo_n "checking if the linker ($LD) is GNU ld... " >&6; } +if ${lt_cv_prog_gnu_ld+:} false; then : + $as_echo_n "(cached) " >&6 +else + # I'd rather use --version here, but apparently some GNU lds only accept -v. +case `$LD -v 2>&1 &5 +$as_echo "$lt_cv_prog_gnu_ld" >&6; } +with_gnu_ld=$lt_cv_prog_gnu_ld + + + + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for BSD- or MS-compatible name lister (nm)" >&5 +$as_echo_n "checking for BSD- or MS-compatible name lister (nm)... " >&6; } +if ${lt_cv_path_NM+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$NM"; then + # Let the user override the test. + lt_cv_path_NM=$NM +else + lt_nm_to_check=${ac_tool_prefix}nm + if test -n "$ac_tool_prefix" && test "$build" = "$host"; then + lt_nm_to_check="$lt_nm_to_check nm" + fi + for lt_tmp_nm in $lt_nm_to_check; do + lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR + for ac_dir in $PATH /usr/ccs/bin/elf /usr/ccs/bin /usr/ucb /bin; do + IFS=$lt_save_ifs + test -z "$ac_dir" && ac_dir=. + tmp_nm=$ac_dir/$lt_tmp_nm + if test -f "$tmp_nm" || test -f "$tmp_nm$ac_exeext"; then + # Check to see if the nm accepts a BSD-compat flag. + # Adding the 'sed 1q' prevents false positives on HP-UX, which says: + # nm: unknown option "B" ignored + # Tru64's nm complains that /dev/null is an invalid object file + # MSYS converts /dev/null to NUL, MinGW nm treats NUL as empty + case $build_os in + mingw*) lt_bad_file=conftest.nm/nofile ;; + *) lt_bad_file=/dev/null ;; + esac + case `"$tmp_nm" -B $lt_bad_file 2>&1 | sed '1q'` in + *$lt_bad_file* | *'Invalid file or object type'*) + lt_cv_path_NM="$tmp_nm -B" + break 2 + ;; + *) + case `"$tmp_nm" -p /dev/null 2>&1 | sed '1q'` in + */dev/null*) + lt_cv_path_NM="$tmp_nm -p" + break 2 + ;; + *) + lt_cv_path_NM=${lt_cv_path_NM="$tmp_nm"} # keep the first match, but + continue # so that we can try to find one that supports BSD flags + ;; + esac + ;; + esac + fi + done + IFS=$lt_save_ifs + done + : ${lt_cv_path_NM=no} +fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_path_NM" >&5 +$as_echo "$lt_cv_path_NM" >&6; } +if test no != "$lt_cv_path_NM"; then + NM=$lt_cv_path_NM +else + # Didn't find any BSD compatible name lister, look for dumpbin. + if test -n "$DUMPBIN"; then : + # Let the user override the test. + else + if test -n "$ac_tool_prefix"; then + for ac_prog in dumpbin "link -dump" + do + # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. +set dummy $ac_tool_prefix$ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_DUMPBIN+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$DUMPBIN"; then + ac_cv_prog_DUMPBIN="$DUMPBIN" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_DUMPBIN="$ac_tool_prefix$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +DUMPBIN=$ac_cv_prog_DUMPBIN +if test -n "$DUMPBIN"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $DUMPBIN" >&5 +$as_echo "$DUMPBIN" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$DUMPBIN" && break + done +fi +if test -z "$DUMPBIN"; then + ac_ct_DUMPBIN=$DUMPBIN + for ac_prog in dumpbin "link -dump" +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_DUMPBIN+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_DUMPBIN"; then + ac_cv_prog_ac_ct_DUMPBIN="$ac_ct_DUMPBIN" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_DUMPBIN="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_DUMPBIN=$ac_cv_prog_ac_ct_DUMPBIN +if test -n "$ac_ct_DUMPBIN"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_DUMPBIN" >&5 +$as_echo "$ac_ct_DUMPBIN" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$ac_ct_DUMPBIN" && break +done + + if test "x$ac_ct_DUMPBIN" = x; then + DUMPBIN=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + DUMPBIN=$ac_ct_DUMPBIN + fi +fi + + case `$DUMPBIN -symbols -headers /dev/null 2>&1 | sed '1q'` in + *COFF*) + DUMPBIN="$DUMPBIN -symbols -headers" + ;; + *) + DUMPBIN=: + ;; + esac + fi + + if test : != "$DUMPBIN"; then + NM=$DUMPBIN + fi +fi +test -z "$NM" && NM=nm + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking the name lister ($NM) interface" >&5 +$as_echo_n "checking the name lister ($NM) interface... " >&6; } +if ${lt_cv_nm_interface+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_nm_interface="BSD nm" + echo "int some_variable = 0;" > conftest.$ac_ext + (eval echo "\"\$as_me:$LINENO: $ac_compile\"" >&5) + (eval "$ac_compile" 2>conftest.err) + cat conftest.err >&5 + (eval echo "\"\$as_me:$LINENO: $NM \\\"conftest.$ac_objext\\\"\"" >&5) + (eval "$NM \"conftest.$ac_objext\"" 2>conftest.err > conftest.out) + cat conftest.err >&5 + (eval echo "\"\$as_me:$LINENO: output\"" >&5) + cat conftest.out >&5 + if $GREP 'External.*some_variable' conftest.out > /dev/null; then + lt_cv_nm_interface="MS dumpbin" + fi + rm -f conftest* +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_nm_interface" >&5 +$as_echo "$lt_cv_nm_interface" >&6; } + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ln -s works" >&5 +$as_echo_n "checking whether ln -s works... " >&6; } +LN_S=$as_ln_s +if test "$LN_S" = "ln -s"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no, using $LN_S" >&5 +$as_echo "no, using $LN_S" >&6; } +fi + +# find the maximum length of command line arguments +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking the maximum length of command line arguments" >&5 +$as_echo_n "checking the maximum length of command line arguments... " >&6; } +if ${lt_cv_sys_max_cmd_len+:} false; then : + $as_echo_n "(cached) " >&6 +else + i=0 + teststring=ABCD + + case $build_os in + msdosdjgpp*) + # On DJGPP, this test can blow up pretty badly due to problems in libc + # (any single argument exceeding 2000 bytes causes a buffer overrun + # during glob expansion). Even if it were fixed, the result of this + # check would be larger than it should be. + lt_cv_sys_max_cmd_len=12288; # 12K is about right + ;; + + gnu*) + # Under GNU Hurd, this test is not required because there is + # no limit to the length of command line arguments. + # Libtool will interpret -1 as no limit whatsoever + lt_cv_sys_max_cmd_len=-1; + ;; + + cygwin* | mingw* | cegcc*) + # On Win9x/ME, this test blows up -- it succeeds, but takes + # about 5 minutes as the teststring grows exponentially. + # Worse, since 9x/ME are not pre-emptively multitasking, + # you end up with a "frozen" computer, even though with patience + # the test eventually succeeds (with a max line length of 256k). + # Instead, let's just punt: use the minimum linelength reported by + # all of the supported platforms: 8192 (on NT/2K/XP). + lt_cv_sys_max_cmd_len=8192; + ;; + + mint*) + # On MiNT this can take a long time and run out of memory. + lt_cv_sys_max_cmd_len=8192; + ;; + + amigaos*) + # On AmigaOS with pdksh, this test takes hours, literally. + # So we just punt and use a minimum line length of 8192. + lt_cv_sys_max_cmd_len=8192; + ;; + + bitrig* | darwin* | dragonfly* | freebsd* | netbsd* | openbsd*) + # This has been around since 386BSD, at least. Likely further. + if test -x /sbin/sysctl; then + lt_cv_sys_max_cmd_len=`/sbin/sysctl -n kern.argmax` + elif test -x /usr/sbin/sysctl; then + lt_cv_sys_max_cmd_len=`/usr/sbin/sysctl -n kern.argmax` + else + lt_cv_sys_max_cmd_len=65536 # usable default for all BSDs + fi + # And add a safety zone + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4` + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3` + ;; + + interix*) + # We know the value 262144 and hardcode it with a safety zone (like BSD) + lt_cv_sys_max_cmd_len=196608 + ;; + + os2*) + # The test takes a long time on OS/2. + lt_cv_sys_max_cmd_len=8192 + ;; + + osf*) + # Dr. Hans Ekkehard Plesser reports seeing a kernel panic running configure + # due to this test when exec_disable_arg_limit is 1 on Tru64. It is not + # nice to cause kernel panics so lets avoid the loop below. + # First set a reasonable default. + lt_cv_sys_max_cmd_len=16384 + # + if test -x /sbin/sysconfig; then + case `/sbin/sysconfig -q proc exec_disable_arg_limit` in + *1*) lt_cv_sys_max_cmd_len=-1 ;; + esac + fi + ;; + sco3.2v5*) + lt_cv_sys_max_cmd_len=102400 + ;; + sysv5* | sco5v6* | sysv4.2uw2*) + kargmax=`grep ARG_MAX /etc/conf/cf.d/stune 2>/dev/null` + if test -n "$kargmax"; then + lt_cv_sys_max_cmd_len=`echo $kargmax | sed 's/.*[ ]//'` + else + lt_cv_sys_max_cmd_len=32768 + fi + ;; + *) + lt_cv_sys_max_cmd_len=`(getconf ARG_MAX) 2> /dev/null` + if test -n "$lt_cv_sys_max_cmd_len" && \ + test undefined != "$lt_cv_sys_max_cmd_len"; then + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4` + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3` + else + # Make teststring a little bigger before we do anything with it. + # a 1K string should be a reasonable start. + for i in 1 2 3 4 5 6 7 8; do + teststring=$teststring$teststring + done + SHELL=${SHELL-${CONFIG_SHELL-/bin/sh}} + # If test is not a shell built-in, we'll probably end up computing a + # maximum length that is only half of the actual maximum length, but + # we can't tell. + while { test X`env echo "$teststring$teststring" 2>/dev/null` \ + = "X$teststring$teststring"; } >/dev/null 2>&1 && + test 17 != "$i" # 1/2 MB should be enough + do + i=`expr $i + 1` + teststring=$teststring$teststring + done + # Only check the string length outside the loop. + lt_cv_sys_max_cmd_len=`expr "X$teststring" : ".*" 2>&1` + teststring= + # Add a significant safety factor because C++ compilers can tack on + # massive amounts of additional arguments before passing them to the + # linker. It appears as though 1/2 is a usable value. + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 2` + fi + ;; + esac + +fi + +if test -n "$lt_cv_sys_max_cmd_len"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_sys_max_cmd_len" >&5 +$as_echo "$lt_cv_sys_max_cmd_len" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: none" >&5 +$as_echo "none" >&6; } +fi +max_cmd_len=$lt_cv_sys_max_cmd_len + + + + + + +: ${CP="cp -f"} +: ${MV="mv -f"} +: ${RM="rm -f"} + +if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then + lt_unset=unset +else + lt_unset=false +fi + + + + + +# test EBCDIC or ASCII +case `echo X|tr X '\101'` in + A) # ASCII based system + # \n is not interpreted correctly by Solaris 8 /usr/ucb/tr + lt_SP2NL='tr \040 \012' + lt_NL2SP='tr \015\012 \040\040' + ;; + *) # EBCDIC based system + lt_SP2NL='tr \100 \n' + lt_NL2SP='tr \r\n \100\100' + ;; +esac + + + + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to convert $build file names to $host format" >&5 +$as_echo_n "checking how to convert $build file names to $host format... " >&6; } +if ${lt_cv_to_host_file_cmd+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $host in + *-*-mingw* ) + case $build in + *-*-mingw* ) # actually msys + lt_cv_to_host_file_cmd=func_convert_file_msys_to_w32 + ;; + *-*-cygwin* ) + lt_cv_to_host_file_cmd=func_convert_file_cygwin_to_w32 + ;; + * ) # otherwise, assume *nix + lt_cv_to_host_file_cmd=func_convert_file_nix_to_w32 + ;; + esac + ;; + *-*-cygwin* ) + case $build in + *-*-mingw* ) # actually msys + lt_cv_to_host_file_cmd=func_convert_file_msys_to_cygwin + ;; + *-*-cygwin* ) + lt_cv_to_host_file_cmd=func_convert_file_noop + ;; + * ) # otherwise, assume *nix + lt_cv_to_host_file_cmd=func_convert_file_nix_to_cygwin + ;; + esac + ;; + * ) # unhandled hosts (and "normal" native builds) + lt_cv_to_host_file_cmd=func_convert_file_noop + ;; +esac + +fi + +to_host_file_cmd=$lt_cv_to_host_file_cmd +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_to_host_file_cmd" >&5 +$as_echo "$lt_cv_to_host_file_cmd" >&6; } + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to convert $build file names to toolchain format" >&5 +$as_echo_n "checking how to convert $build file names to toolchain format... " >&6; } +if ${lt_cv_to_tool_file_cmd+:} false; then : + $as_echo_n "(cached) " >&6 +else + #assume ordinary cross tools, or native build. +lt_cv_to_tool_file_cmd=func_convert_file_noop +case $host in + *-*-mingw* ) + case $build in + *-*-mingw* ) # actually msys + lt_cv_to_tool_file_cmd=func_convert_file_msys_to_w32 + ;; + esac + ;; +esac + +fi + +to_tool_file_cmd=$lt_cv_to_tool_file_cmd +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_to_tool_file_cmd" >&5 +$as_echo "$lt_cv_to_tool_file_cmd" >&6; } + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $LD option to reload object files" >&5 +$as_echo_n "checking for $LD option to reload object files... " >&6; } +if ${lt_cv_ld_reload_flag+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_ld_reload_flag='-r' +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ld_reload_flag" >&5 +$as_echo "$lt_cv_ld_reload_flag" >&6; } +reload_flag=$lt_cv_ld_reload_flag +case $reload_flag in +"" | " "*) ;; +*) reload_flag=" $reload_flag" ;; +esac +reload_cmds='$LD$reload_flag -o $output$reload_objs' +case $host_os in + cygwin* | mingw* | pw32* | cegcc*) + if test yes != "$GCC"; then + reload_cmds=false + fi + ;; + darwin*) + if test yes = "$GCC"; then + reload_cmds='$LTCC $LTCFLAGS -nostdlib $wl-r -o $output$reload_objs' + else + reload_cmds='$LD$reload_flag -o $output$reload_objs' + fi + ;; +esac + + + + + + + + + +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}objdump", so it can be a program name with args. +set dummy ${ac_tool_prefix}objdump; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_OBJDUMP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$OBJDUMP"; then + ac_cv_prog_OBJDUMP="$OBJDUMP" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_OBJDUMP="${ac_tool_prefix}objdump" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +OBJDUMP=$ac_cv_prog_OBJDUMP +if test -n "$OBJDUMP"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $OBJDUMP" >&5 +$as_echo "$OBJDUMP" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_OBJDUMP"; then + ac_ct_OBJDUMP=$OBJDUMP + # Extract the first word of "objdump", so it can be a program name with args. +set dummy objdump; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_OBJDUMP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_OBJDUMP"; then + ac_cv_prog_ac_ct_OBJDUMP="$ac_ct_OBJDUMP" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_OBJDUMP="objdump" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_OBJDUMP=$ac_cv_prog_ac_ct_OBJDUMP +if test -n "$ac_ct_OBJDUMP"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_OBJDUMP" >&5 +$as_echo "$ac_ct_OBJDUMP" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_OBJDUMP" = x; then + OBJDUMP="false" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + OBJDUMP=$ac_ct_OBJDUMP + fi +else + OBJDUMP="$ac_cv_prog_OBJDUMP" +fi + +test -z "$OBJDUMP" && OBJDUMP=objdump + + + + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to recognize dependent libraries" >&5 +$as_echo_n "checking how to recognize dependent libraries... " >&6; } +if ${lt_cv_deplibs_check_method+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_file_magic_cmd='$MAGIC_CMD' +lt_cv_file_magic_test_file= +lt_cv_deplibs_check_method='unknown' +# Need to set the preceding variable on all platforms that support +# interlibrary dependencies. +# 'none' -- dependencies not supported. +# 'unknown' -- same as none, but documents that we really don't know. +# 'pass_all' -- all dependencies passed with no checks. +# 'test_compile' -- check by making test program. +# 'file_magic [[regex]]' -- check by looking for files in library path +# that responds to the $file_magic_cmd with a given extended regex. +# If you have 'file' or equivalent on your system and you're not sure +# whether 'pass_all' will *always* work, you probably want this one. + +case $host_os in +aix[4-9]*) + lt_cv_deplibs_check_method=pass_all + ;; + +beos*) + lt_cv_deplibs_check_method=pass_all + ;; + +bsdi[45]*) + lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (shared object|dynamic lib)' + lt_cv_file_magic_cmd='/usr/bin/file -L' + lt_cv_file_magic_test_file=/shlib/libc.so + ;; + +cygwin*) + # func_win32_libid is a shell function defined in ltmain.sh + lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL' + lt_cv_file_magic_cmd='func_win32_libid' + ;; + +mingw* | pw32*) + # Base MSYS/MinGW do not provide the 'file' command needed by + # func_win32_libid shell function, so use a weaker test based on 'objdump', + # unless we find 'file', for example because we are cross-compiling. + if ( file / ) >/dev/null 2>&1; then + lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL' + lt_cv_file_magic_cmd='func_win32_libid' + else + # Keep this pattern in sync with the one in func_win32_libid. + lt_cv_deplibs_check_method='file_magic file format (pei*-i386(.*architecture: i386)?|pe-arm-wince|pe-x86-64)' + lt_cv_file_magic_cmd='$OBJDUMP -f' + fi + ;; + +cegcc*) + # use the weaker test based on 'objdump'. See mingw*. + lt_cv_deplibs_check_method='file_magic file format pe-arm-.*little(.*architecture: arm)?' + lt_cv_file_magic_cmd='$OBJDUMP -f' + ;; + +darwin* | rhapsody*) + lt_cv_deplibs_check_method=pass_all + ;; + +freebsd* | dragonfly*) + if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then + case $host_cpu in + i*86 ) + # Not sure whether the presence of OpenBSD here was a mistake. + # Let's accept both of them until this is cleared up. + lt_cv_deplibs_check_method='file_magic (FreeBSD|OpenBSD|DragonFly)/i[3-9]86 (compact )?demand paged shared library' + lt_cv_file_magic_cmd=/usr/bin/file + lt_cv_file_magic_test_file=`echo /usr/lib/libc.so.*` + ;; + esac + else + lt_cv_deplibs_check_method=pass_all + fi + ;; + +haiku*) + lt_cv_deplibs_check_method=pass_all + ;; + +hpux10.20* | hpux11*) + lt_cv_file_magic_cmd=/usr/bin/file + case $host_cpu in + ia64*) + lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|ELF-[0-9][0-9]) shared object file - IA64' + lt_cv_file_magic_test_file=/usr/lib/hpux32/libc.so + ;; + hppa*64*) + lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|ELF[ -][0-9][0-9])(-bit)?( [LM]SB)? shared object( file)?[, -]* PA-RISC [0-9]\.[0-9]' + lt_cv_file_magic_test_file=/usr/lib/pa20_64/libc.sl + ;; + *) + lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|PA-RISC[0-9]\.[0-9]) shared library' + lt_cv_file_magic_test_file=/usr/lib/libc.sl + ;; + esac + ;; + +interix[3-9]*) + # PIC code is broken on Interix 3.x, that's why |\.a not |_pic\.a here + lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so|\.a)$' + ;; + +irix5* | irix6* | nonstopux*) + case $LD in + *-32|*"-32 ") libmagic=32-bit;; + *-n32|*"-n32 ") libmagic=N32;; + *-64|*"-64 ") libmagic=64-bit;; + *) libmagic=never-match;; + esac + lt_cv_deplibs_check_method=pass_all + ;; + +# This must be glibc/ELF. +linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + lt_cv_deplibs_check_method=pass_all + ;; + +netbsd*) + if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then + lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so\.[0-9]+\.[0-9]+|_pic\.a)$' + else + lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so|_pic\.a)$' + fi + ;; + +newos6*) + lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (executable|dynamic lib)' + lt_cv_file_magic_cmd=/usr/bin/file + lt_cv_file_magic_test_file=/usr/lib/libnls.so + ;; + +*nto* | *qnx*) + lt_cv_deplibs_check_method=pass_all + ;; + +openbsd* | bitrig*) + if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then + lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so\.[0-9]+\.[0-9]+|\.so|_pic\.a)$' + else + lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so\.[0-9]+\.[0-9]+|_pic\.a)$' + fi + ;; + +osf3* | osf4* | osf5*) + lt_cv_deplibs_check_method=pass_all + ;; + +rdos*) + lt_cv_deplibs_check_method=pass_all + ;; + +solaris*) + lt_cv_deplibs_check_method=pass_all + ;; + +sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) + lt_cv_deplibs_check_method=pass_all + ;; + +sysv4 | sysv4.3*) + case $host_vendor in + motorola) + lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (shared object|dynamic lib) M[0-9][0-9]* Version [0-9]' + lt_cv_file_magic_test_file=`echo /usr/lib/libc.so*` + ;; + ncr) + lt_cv_deplibs_check_method=pass_all + ;; + sequent) + lt_cv_file_magic_cmd='/bin/file' + lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [LM]SB (shared object|dynamic lib )' + ;; + sni) + lt_cv_file_magic_cmd='/bin/file' + lt_cv_deplibs_check_method="file_magic ELF [0-9][0-9]*-bit [LM]SB dynamic lib" + lt_cv_file_magic_test_file=/lib/libc.so + ;; + siemens) + lt_cv_deplibs_check_method=pass_all + ;; + pc) + lt_cv_deplibs_check_method=pass_all + ;; + esac + ;; + +tpf*) + lt_cv_deplibs_check_method=pass_all + ;; +os2*) + lt_cv_deplibs_check_method=pass_all + ;; +esac + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_deplibs_check_method" >&5 +$as_echo "$lt_cv_deplibs_check_method" >&6; } + +file_magic_glob= +want_nocaseglob=no +if test "$build" = "$host"; then + case $host_os in + mingw* | pw32*) + if ( shopt | grep nocaseglob ) >/dev/null 2>&1; then + want_nocaseglob=yes + else + file_magic_glob=`echo aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ | $SED -e "s/\(..\)/s\/[\1]\/[\1]\/g;/g"` + fi + ;; + esac +fi + +file_magic_cmd=$lt_cv_file_magic_cmd +deplibs_check_method=$lt_cv_deplibs_check_method +test -z "$deplibs_check_method" && deplibs_check_method=unknown + + + + + + + + + + + + + + + + + + + + + + +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}dlltool", so it can be a program name with args. +set dummy ${ac_tool_prefix}dlltool; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_DLLTOOL+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$DLLTOOL"; then + ac_cv_prog_DLLTOOL="$DLLTOOL" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_DLLTOOL="${ac_tool_prefix}dlltool" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +DLLTOOL=$ac_cv_prog_DLLTOOL +if test -n "$DLLTOOL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $DLLTOOL" >&5 +$as_echo "$DLLTOOL" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_DLLTOOL"; then + ac_ct_DLLTOOL=$DLLTOOL + # Extract the first word of "dlltool", so it can be a program name with args. +set dummy dlltool; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_DLLTOOL+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_DLLTOOL"; then + ac_cv_prog_ac_ct_DLLTOOL="$ac_ct_DLLTOOL" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_DLLTOOL="dlltool" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_DLLTOOL=$ac_cv_prog_ac_ct_DLLTOOL +if test -n "$ac_ct_DLLTOOL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_DLLTOOL" >&5 +$as_echo "$ac_ct_DLLTOOL" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_DLLTOOL" = x; then + DLLTOOL="false" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + DLLTOOL=$ac_ct_DLLTOOL + fi +else + DLLTOOL="$ac_cv_prog_DLLTOOL" +fi + +test -z "$DLLTOOL" && DLLTOOL=dlltool + + + + + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to associate runtime and link libraries" >&5 +$as_echo_n "checking how to associate runtime and link libraries... " >&6; } +if ${lt_cv_sharedlib_from_linklib_cmd+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_sharedlib_from_linklib_cmd='unknown' + +case $host_os in +cygwin* | mingw* | pw32* | cegcc*) + # two different shell functions defined in ltmain.sh; + # decide which one to use based on capabilities of $DLLTOOL + case `$DLLTOOL --help 2>&1` in + *--identify-strict*) + lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib + ;; + *) + lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib_fallback + ;; + esac + ;; +*) + # fallback: assume linklib IS sharedlib + lt_cv_sharedlib_from_linklib_cmd=$ECHO + ;; +esac + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_sharedlib_from_linklib_cmd" >&5 +$as_echo "$lt_cv_sharedlib_from_linklib_cmd" >&6; } +sharedlib_from_linklib_cmd=$lt_cv_sharedlib_from_linklib_cmd +test -z "$sharedlib_from_linklib_cmd" && sharedlib_from_linklib_cmd=$ECHO + + + + + + + + +if test -n "$ac_tool_prefix"; then + for ac_prog in ar + do + # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. +set dummy $ac_tool_prefix$ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_AR+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$AR"; then + ac_cv_prog_AR="$AR" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_AR="$ac_tool_prefix$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +AR=$ac_cv_prog_AR +if test -n "$AR"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $AR" >&5 +$as_echo "$AR" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$AR" && break + done +fi +if test -z "$AR"; then + ac_ct_AR=$AR + for ac_prog in ar +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_AR+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_AR"; then + ac_cv_prog_ac_ct_AR="$ac_ct_AR" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_AR="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_AR=$ac_cv_prog_ac_ct_AR +if test -n "$ac_ct_AR"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_AR" >&5 +$as_echo "$ac_ct_AR" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$ac_ct_AR" && break +done + + if test "x$ac_ct_AR" = x; then + AR="false" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + AR=$ac_ct_AR + fi +fi + +: ${AR=ar} +: ${AR_FLAGS=cru} + + + + + + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for archiver @FILE support" >&5 +$as_echo_n "checking for archiver @FILE support... " >&6; } +if ${lt_cv_ar_at_file+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_ar_at_file=no + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + echo conftest.$ac_objext > conftest.lst + lt_ar_try='$AR $AR_FLAGS libconftest.a @conftest.lst >&5' + { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$lt_ar_try\""; } >&5 + (eval $lt_ar_try) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } + if test 0 -eq "$ac_status"; then + # Ensure the archiver fails upon bogus file names. + rm -f conftest.$ac_objext libconftest.a + { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$lt_ar_try\""; } >&5 + (eval $lt_ar_try) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } + if test 0 -ne "$ac_status"; then + lt_cv_ar_at_file=@ + fi + fi + rm -f conftest.* libconftest.a + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ar_at_file" >&5 +$as_echo "$lt_cv_ar_at_file" >&6; } + +if test no = "$lt_cv_ar_at_file"; then + archiver_list_spec= +else + archiver_list_spec=$lt_cv_ar_at_file +fi + + + + + + + +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}strip", so it can be a program name with args. +set dummy ${ac_tool_prefix}strip; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_STRIP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$STRIP"; then + ac_cv_prog_STRIP="$STRIP" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_STRIP="${ac_tool_prefix}strip" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +STRIP=$ac_cv_prog_STRIP +if test -n "$STRIP"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $STRIP" >&5 +$as_echo "$STRIP" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_STRIP"; then + ac_ct_STRIP=$STRIP + # Extract the first word of "strip", so it can be a program name with args. +set dummy strip; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_STRIP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_STRIP"; then + ac_cv_prog_ac_ct_STRIP="$ac_ct_STRIP" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_STRIP="strip" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_STRIP=$ac_cv_prog_ac_ct_STRIP +if test -n "$ac_ct_STRIP"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_STRIP" >&5 +$as_echo "$ac_ct_STRIP" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_STRIP" = x; then + STRIP=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + STRIP=$ac_ct_STRIP + fi +else + STRIP="$ac_cv_prog_STRIP" +fi + +test -z "$STRIP" && STRIP=: + + + + + + +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}ranlib", so it can be a program name with args. +set dummy ${ac_tool_prefix}ranlib; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_RANLIB+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$RANLIB"; then + ac_cv_prog_RANLIB="$RANLIB" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_RANLIB="${ac_tool_prefix}ranlib" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +RANLIB=$ac_cv_prog_RANLIB +if test -n "$RANLIB"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $RANLIB" >&5 +$as_echo "$RANLIB" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_RANLIB"; then + ac_ct_RANLIB=$RANLIB + # Extract the first word of "ranlib", so it can be a program name with args. +set dummy ranlib; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_RANLIB+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_RANLIB"; then + ac_cv_prog_ac_ct_RANLIB="$ac_ct_RANLIB" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_RANLIB="ranlib" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_RANLIB=$ac_cv_prog_ac_ct_RANLIB +if test -n "$ac_ct_RANLIB"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_RANLIB" >&5 +$as_echo "$ac_ct_RANLIB" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_RANLIB" = x; then + RANLIB=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + RANLIB=$ac_ct_RANLIB + fi +else + RANLIB="$ac_cv_prog_RANLIB" +fi + +test -z "$RANLIB" && RANLIB=: + + + + + + +# Determine commands to create old-style static archives. +old_archive_cmds='$AR $AR_FLAGS $oldlib$oldobjs' +old_postinstall_cmds='chmod 644 $oldlib' +old_postuninstall_cmds= + +if test -n "$RANLIB"; then + case $host_os in + bitrig* | openbsd*) + old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB -t \$tool_oldlib" + ;; + *) + old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB \$tool_oldlib" + ;; + esac + old_archive_cmds="$old_archive_cmds~\$RANLIB \$tool_oldlib" +fi + +case $host_os in + darwin*) + lock_old_archive_extraction=yes ;; + *) + lock_old_archive_extraction=no ;; +esac + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# If no C compiler was specified, use CC. +LTCC=${LTCC-"$CC"} + +# If no C compiler flags were specified, use CFLAGS. +LTCFLAGS=${LTCFLAGS-"$CFLAGS"} + +# Allow CC to be a program name with arguments. +compiler=$CC + + +# Check for command to grab the raw symbol name followed by C symbol from nm. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking command to parse $NM output from $compiler object" >&5 +$as_echo_n "checking command to parse $NM output from $compiler object... " >&6; } +if ${lt_cv_sys_global_symbol_pipe+:} false; then : + $as_echo_n "(cached) " >&6 +else + +# These are sane defaults that work on at least a few old systems. +# [They come from Ultrix. What could be older than Ultrix?!! ;)] + +# Character class describing NM global symbol codes. +symcode='[BCDEGRST]' + +# Regexp to match symbols that can be accessed directly from C. +sympat='\([_A-Za-z][_A-Za-z0-9]*\)' + +# Define system-specific variables. +case $host_os in +aix*) + symcode='[BCDT]' + ;; +cygwin* | mingw* | pw32* | cegcc*) + symcode='[ABCDGISTW]' + ;; +hpux*) + if test ia64 = "$host_cpu"; then + symcode='[ABCDEGRST]' + fi + ;; +irix* | nonstopux*) + symcode='[BCDEGRST]' + ;; +osf*) + symcode='[BCDEGQRST]' + ;; +solaris*) + symcode='[BDRT]' + ;; +sco3.2v5*) + symcode='[DT]' + ;; +sysv4.2uw2*) + symcode='[DT]' + ;; +sysv5* | sco5v6* | unixware* | OpenUNIX*) + symcode='[ABDT]' + ;; +sysv4) + symcode='[DFNSTU]' + ;; +esac + +# If we're using GNU nm, then use its standard symbol codes. +case `$NM -V 2>&1` in +*GNU* | *'with BFD'*) + symcode='[ABCDGIRSTW]' ;; +esac + +if test "$lt_cv_nm_interface" = "MS dumpbin"; then + # Gets list of data symbols to import. + lt_cv_sys_global_symbol_to_import="sed -n -e 's/^I .* \(.*\)$/\1/p'" + # Adjust the below global symbol transforms to fixup imported variables. + lt_cdecl_hook=" -e 's/^I .* \(.*\)$/extern __declspec(dllimport) char \1;/p'" + lt_c_name_hook=" -e 's/^I .* \(.*\)$/ {\"\1\", (void *) 0},/p'" + lt_c_name_lib_hook="\ + -e 's/^I .* \(lib.*\)$/ {\"\1\", (void *) 0},/p'\ + -e 's/^I .* \(.*\)$/ {\"lib\1\", (void *) 0},/p'" +else + # Disable hooks by default. + lt_cv_sys_global_symbol_to_import= + lt_cdecl_hook= + lt_c_name_hook= + lt_c_name_lib_hook= +fi + +# Transform an extracted symbol line into a proper C declaration. +# Some systems (esp. on ia64) link data and code symbols differently, +# so use this general approach. +lt_cv_sys_global_symbol_to_cdecl="sed -n"\ +$lt_cdecl_hook\ +" -e 's/^T .* \(.*\)$/extern int \1();/p'"\ +" -e 's/^$symcode$symcode* .* \(.*\)$/extern char \1;/p'" + +# Transform an extracted symbol line into symbol name and symbol address +lt_cv_sys_global_symbol_to_c_name_address="sed -n"\ +$lt_c_name_hook\ +" -e 's/^: \(.*\) .*$/ {\"\1\", (void *) 0},/p'"\ +" -e 's/^$symcode$symcode* .* \(.*\)$/ {\"\1\", (void *) \&\1},/p'" + +# Transform an extracted symbol line into symbol name with lib prefix and +# symbol address. +lt_cv_sys_global_symbol_to_c_name_address_lib_prefix="sed -n"\ +$lt_c_name_lib_hook\ +" -e 's/^: \(.*\) .*$/ {\"\1\", (void *) 0},/p'"\ +" -e 's/^$symcode$symcode* .* \(lib.*\)$/ {\"\1\", (void *) \&\1},/p'"\ +" -e 's/^$symcode$symcode* .* \(.*\)$/ {\"lib\1\", (void *) \&\1},/p'" + +# Handle CRLF in mingw tool chain +opt_cr= +case $build_os in +mingw*) + opt_cr=`$ECHO 'x\{0,1\}' | tr x '\015'` # option cr in regexp + ;; +esac + +# Try without a prefix underscore, then with it. +for ac_symprfx in "" "_"; do + + # Transform symcode, sympat, and symprfx into a raw symbol and a C symbol. + symxfrm="\\1 $ac_symprfx\\2 \\2" + + # Write the raw and C identifiers. + if test "$lt_cv_nm_interface" = "MS dumpbin"; then + # Fake it for dumpbin and say T for any non-static function, + # D for any global variable and I for any imported variable. + # Also find C++ and __fastcall symbols from MSVC++, + # which start with @ or ?. + lt_cv_sys_global_symbol_pipe="$AWK '"\ +" {last_section=section; section=\$ 3};"\ +" /^COFF SYMBOL TABLE/{for(i in hide) delete hide[i]};"\ +" /Section length .*#relocs.*(pick any)/{hide[last_section]=1};"\ +" /^ *Symbol name *: /{split(\$ 0,sn,\":\"); si=substr(sn[2],2)};"\ +" /^ *Type *: code/{print \"T\",si,substr(si,length(prfx))};"\ +" /^ *Type *: data/{print \"I\",si,substr(si,length(prfx))};"\ +" \$ 0!~/External *\|/{next};"\ +" / 0+ UNDEF /{next}; / UNDEF \([^|]\)*()/{next};"\ +" {if(hide[section]) next};"\ +" {f=\"D\"}; \$ 0~/\(\).*\|/{f=\"T\"};"\ +" {split(\$ 0,a,/\||\r/); split(a[2],s)};"\ +" s[1]~/^[@?]/{print f,s[1],s[1]; next};"\ +" s[1]~prfx {split(s[1],t,\"@\"); print f,t[1],substr(t[1],length(prfx))}"\ +" ' prfx=^$ac_symprfx" + else + lt_cv_sys_global_symbol_pipe="sed -n -e 's/^.*[ ]\($symcode$symcode*\)[ ][ ]*$ac_symprfx$sympat$opt_cr$/$symxfrm/p'" + fi + lt_cv_sys_global_symbol_pipe="$lt_cv_sys_global_symbol_pipe | sed '/ __gnu_lto/d'" + + # Check to see that the pipe works correctly. + pipe_works=no + + rm -f conftest* + cat > conftest.$ac_ext <<_LT_EOF +#ifdef __cplusplus +extern "C" { +#endif +char nm_test_var; +void nm_test_func(void); +void nm_test_func(void){} +#ifdef __cplusplus +} +#endif +int main(){nm_test_var='a';nm_test_func();return(0);} +_LT_EOF + + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + # Now try to grab the symbols. + nlist=conftest.nm + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$NM conftest.$ac_objext \| "$lt_cv_sys_global_symbol_pipe" \> $nlist\""; } >&5 + (eval $NM conftest.$ac_objext \| "$lt_cv_sys_global_symbol_pipe" \> $nlist) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && test -s "$nlist"; then + # Try sorting and uniquifying the output. + if sort "$nlist" | uniq > "$nlist"T; then + mv -f "$nlist"T "$nlist" + else + rm -f "$nlist"T + fi + + # Make sure that we snagged all the symbols we need. + if $GREP ' nm_test_var$' "$nlist" >/dev/null; then + if $GREP ' nm_test_func$' "$nlist" >/dev/null; then + cat <<_LT_EOF > conftest.$ac_ext +/* Keep this code in sync between libtool.m4, ltmain, lt_system.h, and tests. */ +#if defined _WIN32 || defined __CYGWIN__ || defined _WIN32_WCE +/* DATA imports from DLLs on WIN32 can't be const, because runtime + relocations are performed -- see ld's documentation on pseudo-relocs. */ +# define LT_DLSYM_CONST +#elif defined __osf__ +/* This system does not cope well with relocations in const data. */ +# define LT_DLSYM_CONST +#else +# define LT_DLSYM_CONST const +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +_LT_EOF + # Now generate the symbol file. + eval "$lt_cv_sys_global_symbol_to_cdecl"' < "$nlist" | $GREP -v main >> conftest.$ac_ext' + + cat <<_LT_EOF >> conftest.$ac_ext + +/* The mapping between symbol names and symbols. */ +LT_DLSYM_CONST struct { + const char *name; + void *address; +} +lt__PROGRAM__LTX_preloaded_symbols[] = +{ + { "@PROGRAM@", (void *) 0 }, +_LT_EOF + $SED "s/^$symcode$symcode* .* \(.*\)$/ {\"\1\", (void *) \&\1},/" < "$nlist" | $GREP -v main >> conftest.$ac_ext + cat <<\_LT_EOF >> conftest.$ac_ext + {0, (void *) 0} +}; + +/* This works around a problem in FreeBSD linker */ +#ifdef FREEBSD_WORKAROUND +static const void *lt_preloaded_setup() { + return lt__PROGRAM__LTX_preloaded_symbols; +} +#endif + +#ifdef __cplusplus +} +#endif +_LT_EOF + # Now try linking the two files. + mv conftest.$ac_objext conftstm.$ac_objext + lt_globsym_save_LIBS=$LIBS + lt_globsym_save_CFLAGS=$CFLAGS + LIBS=conftstm.$ac_objext + CFLAGS="$CFLAGS$lt_prog_compiler_no_builtin_flag" + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_link\""; } >&5 + (eval $ac_link) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && test -s conftest$ac_exeext; then + pipe_works=yes + fi + LIBS=$lt_globsym_save_LIBS + CFLAGS=$lt_globsym_save_CFLAGS + else + echo "cannot find nm_test_func in $nlist" >&5 + fi + else + echo "cannot find nm_test_var in $nlist" >&5 + fi + else + echo "cannot run $lt_cv_sys_global_symbol_pipe" >&5 + fi + else + echo "$progname: failed program was:" >&5 + cat conftest.$ac_ext >&5 + fi + rm -rf conftest* conftst* + + # Do not use the global_symbol_pipe unless it works. + if test yes = "$pipe_works"; then + break + else + lt_cv_sys_global_symbol_pipe= + fi +done + +fi + +if test -z "$lt_cv_sys_global_symbol_pipe"; then + lt_cv_sys_global_symbol_to_cdecl= +fi +if test -z "$lt_cv_sys_global_symbol_pipe$lt_cv_sys_global_symbol_to_cdecl"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: failed" >&5 +$as_echo "failed" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: ok" >&5 +$as_echo "ok" >&6; } +fi + +# Response file support. +if test "$lt_cv_nm_interface" = "MS dumpbin"; then + nm_file_list_spec='@' +elif $NM --help 2>/dev/null | grep '[@]FILE' >/dev/null; then + nm_file_list_spec='@' +fi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for sysroot" >&5 +$as_echo_n "checking for sysroot... " >&6; } + +# Check whether --with-sysroot was given. +if test "${with_sysroot+set}" = set; then : + withval=$with_sysroot; +else + with_sysroot=no +fi + + +lt_sysroot= +case $with_sysroot in #( + yes) + if test yes = "$GCC"; then + lt_sysroot=`$CC --print-sysroot 2>/dev/null` + fi + ;; #( + /*) + lt_sysroot=`echo "$with_sysroot" | sed -e "$sed_quote_subst"` + ;; #( + no|'') + ;; #( + *) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_sysroot" >&5 +$as_echo "$with_sysroot" >&6; } + as_fn_error $? "The sysroot must be an absolute path." "$LINENO" 5 + ;; +esac + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: ${lt_sysroot:-no}" >&5 +$as_echo "${lt_sysroot:-no}" >&6; } + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a working dd" >&5 +$as_echo_n "checking for a working dd... " >&6; } +if ${ac_cv_path_lt_DD+:} false; then : + $as_echo_n "(cached) " >&6 +else + printf 0123456789abcdef0123456789abcdef >conftest.i +cat conftest.i conftest.i >conftest2.i +: ${lt_DD:=$DD} +if test -z "$lt_DD"; then + ac_path_lt_DD_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in dd; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_lt_DD="$as_dir/$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_lt_DD" || continue +if "$ac_path_lt_DD" bs=32 count=1 conftest.out 2>/dev/null; then + cmp -s conftest.i conftest.out \ + && ac_cv_path_lt_DD="$ac_path_lt_DD" ac_path_lt_DD_found=: +fi + $ac_path_lt_DD_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_lt_DD"; then + : + fi +else + ac_cv_path_lt_DD=$lt_DD +fi + +rm -f conftest.i conftest2.i conftest.out +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_lt_DD" >&5 +$as_echo "$ac_cv_path_lt_DD" >&6; } + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to truncate binary pipes" >&5 +$as_echo_n "checking how to truncate binary pipes... " >&6; } +if ${lt_cv_truncate_bin+:} false; then : + $as_echo_n "(cached) " >&6 +else + printf 0123456789abcdef0123456789abcdef >conftest.i +cat conftest.i conftest.i >conftest2.i +lt_cv_truncate_bin= +if "$ac_cv_path_lt_DD" bs=32 count=1 conftest.out 2>/dev/null; then + cmp -s conftest.i conftest.out \ + && lt_cv_truncate_bin="$ac_cv_path_lt_DD bs=4096 count=1" +fi +rm -f conftest.i conftest2.i conftest.out +test -z "$lt_cv_truncate_bin" && lt_cv_truncate_bin="$SED -e 4q" +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_truncate_bin" >&5 +$as_echo "$lt_cv_truncate_bin" >&6; } + + + + + + + +# Calculate cc_basename. Skip known compiler wrappers and cross-prefix. +func_cc_basename () +{ + for cc_temp in $*""; do + case $cc_temp in + compile | *[\\/]compile | ccache | *[\\/]ccache ) ;; + distcc | *[\\/]distcc | purify | *[\\/]purify ) ;; + \-*) ;; + *) break;; + esac + done + func_cc_basename_result=`$ECHO "$cc_temp" | $SED "s%.*/%%; s%^$host_alias-%%"` +} + +# Check whether --enable-libtool-lock was given. +if test "${enable_libtool_lock+set}" = set; then : + enableval=$enable_libtool_lock; +fi + +test no = "$enable_libtool_lock" || enable_libtool_lock=yes + +# Some flags need to be propagated to the compiler or linker for good +# libtool support. +case $host in +ia64-*-hpux*) + # Find out what ABI is being produced by ac_compile, and set mode + # options accordingly. + echo 'int i;' > conftest.$ac_ext + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + case `/usr/bin/file conftest.$ac_objext` in + *ELF-32*) + HPUX_IA64_MODE=32 + ;; + *ELF-64*) + HPUX_IA64_MODE=64 + ;; + esac + fi + rm -rf conftest* + ;; +*-*-irix6*) + # Find out what ABI is being produced by ac_compile, and set linker + # options accordingly. + echo '#line '$LINENO' "configure"' > conftest.$ac_ext + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + if test yes = "$lt_cv_prog_gnu_ld"; then + case `/usr/bin/file conftest.$ac_objext` in + *32-bit*) + LD="${LD-ld} -melf32bsmip" + ;; + *N32*) + LD="${LD-ld} -melf32bmipn32" + ;; + *64-bit*) + LD="${LD-ld} -melf64bmip" + ;; + esac + else + case `/usr/bin/file conftest.$ac_objext` in + *32-bit*) + LD="${LD-ld} -32" + ;; + *N32*) + LD="${LD-ld} -n32" + ;; + *64-bit*) + LD="${LD-ld} -64" + ;; + esac + fi + fi + rm -rf conftest* + ;; + +mips64*-*linux*) + # Find out what ABI is being produced by ac_compile, and set linker + # options accordingly. + echo '#line '$LINENO' "configure"' > conftest.$ac_ext + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + emul=elf + case `/usr/bin/file conftest.$ac_objext` in + *32-bit*) + emul="${emul}32" + ;; + *64-bit*) + emul="${emul}64" + ;; + esac + case `/usr/bin/file conftest.$ac_objext` in + *MSB*) + emul="${emul}btsmip" + ;; + *LSB*) + emul="${emul}ltsmip" + ;; + esac + case `/usr/bin/file conftest.$ac_objext` in + *N32*) + emul="${emul}n32" + ;; + esac + LD="${LD-ld} -m $emul" + fi + rm -rf conftest* + ;; + +x86_64-*kfreebsd*-gnu|x86_64-*linux*|powerpc*-*linux*| \ +s390*-*linux*|s390*-*tpf*|sparc*-*linux*) + # Find out what ABI is being produced by ac_compile, and set linker + # options accordingly. Note that the listed cases only cover the + # situations where additional linker options are needed (such as when + # doing 32-bit compilation for a host where ld defaults to 64-bit, or + # vice versa); the common cases where no linker options are needed do + # not appear in the list. + echo 'int i;' > conftest.$ac_ext + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + case `/usr/bin/file conftest.o` in + *32-bit*) + case $host in + x86_64-*kfreebsd*-gnu) + LD="${LD-ld} -m elf_i386_fbsd" + ;; + x86_64-*linux*) + case `/usr/bin/file conftest.o` in + *x86-64*) + LD="${LD-ld} -m elf32_x86_64" + ;; + *) + LD="${LD-ld} -m elf_i386" + ;; + esac + ;; + powerpc64le-*linux*) + LD="${LD-ld} -m elf32lppclinux" + ;; + powerpc64-*linux*) + LD="${LD-ld} -m elf32ppclinux" + ;; + s390x-*linux*) + LD="${LD-ld} -m elf_s390" + ;; + sparc64-*linux*) + LD="${LD-ld} -m elf32_sparc" + ;; + esac + ;; + *64-bit*) + case $host in + x86_64-*kfreebsd*-gnu) + LD="${LD-ld} -m elf_x86_64_fbsd" + ;; + x86_64-*linux*) + LD="${LD-ld} -m elf_x86_64" + ;; + powerpcle-*linux*) + LD="${LD-ld} -m elf64lppc" + ;; + powerpc-*linux*) + LD="${LD-ld} -m elf64ppc" + ;; + s390*-*linux*|s390*-*tpf*) + LD="${LD-ld} -m elf64_s390" + ;; + sparc*-*linux*) + LD="${LD-ld} -m elf64_sparc" + ;; + esac + ;; + esac + fi + rm -rf conftest* + ;; + +*-*-sco3.2v5*) + # On SCO OpenServer 5, we need -belf to get full-featured binaries. + SAVE_CFLAGS=$CFLAGS + CFLAGS="$CFLAGS -belf" + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler needs -belf" >&5 +$as_echo_n "checking whether the C compiler needs -belf... " >&6; } +if ${lt_cv_cc_needs_belf+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + lt_cv_cc_needs_belf=yes +else + lt_cv_cc_needs_belf=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_cc_needs_belf" >&5 +$as_echo "$lt_cv_cc_needs_belf" >&6; } + if test yes != "$lt_cv_cc_needs_belf"; then + # this is probably gcc 2.8.0, egcs 1.0 or newer; no need for -belf + CFLAGS=$SAVE_CFLAGS + fi + ;; +*-*solaris*) + # Find out what ABI is being produced by ac_compile, and set linker + # options accordingly. + echo 'int i;' > conftest.$ac_ext + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + case `/usr/bin/file conftest.o` in + *64-bit*) + case $lt_cv_prog_gnu_ld in + yes*) + case $host in + i?86-*-solaris*|x86_64-*-solaris*) + LD="${LD-ld} -m elf_x86_64" + ;; + sparc*-*-solaris*) + LD="${LD-ld} -m elf64_sparc" + ;; + esac + # GNU ld 2.21 introduced _sol2 emulations. Use them if available. + if ${LD-ld} -V | grep _sol2 >/dev/null 2>&1; then + LD=${LD-ld}_sol2 + fi + ;; + *) + if ${LD-ld} -64 -r -o conftest2.o conftest.o >/dev/null 2>&1; then + LD="${LD-ld} -64" + fi + ;; + esac + ;; + esac + fi + rm -rf conftest* + ;; +esac + +need_locks=$enable_libtool_lock + +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}mt", so it can be a program name with args. +set dummy ${ac_tool_prefix}mt; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_MANIFEST_TOOL+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$MANIFEST_TOOL"; then + ac_cv_prog_MANIFEST_TOOL="$MANIFEST_TOOL" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_MANIFEST_TOOL="${ac_tool_prefix}mt" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +MANIFEST_TOOL=$ac_cv_prog_MANIFEST_TOOL +if test -n "$MANIFEST_TOOL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $MANIFEST_TOOL" >&5 +$as_echo "$MANIFEST_TOOL" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_MANIFEST_TOOL"; then + ac_ct_MANIFEST_TOOL=$MANIFEST_TOOL + # Extract the first word of "mt", so it can be a program name with args. +set dummy mt; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_MANIFEST_TOOL+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_MANIFEST_TOOL"; then + ac_cv_prog_ac_ct_MANIFEST_TOOL="$ac_ct_MANIFEST_TOOL" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_MANIFEST_TOOL="mt" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_MANIFEST_TOOL=$ac_cv_prog_ac_ct_MANIFEST_TOOL +if test -n "$ac_ct_MANIFEST_TOOL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_MANIFEST_TOOL" >&5 +$as_echo "$ac_ct_MANIFEST_TOOL" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_MANIFEST_TOOL" = x; then + MANIFEST_TOOL=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + MANIFEST_TOOL=$ac_ct_MANIFEST_TOOL + fi +else + MANIFEST_TOOL="$ac_cv_prog_MANIFEST_TOOL" +fi + +test -z "$MANIFEST_TOOL" && MANIFEST_TOOL=mt +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if $MANIFEST_TOOL is a manifest tool" >&5 +$as_echo_n "checking if $MANIFEST_TOOL is a manifest tool... " >&6; } +if ${lt_cv_path_mainfest_tool+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_path_mainfest_tool=no + echo "$as_me:$LINENO: $MANIFEST_TOOL '-?'" >&5 + $MANIFEST_TOOL '-?' 2>conftest.err > conftest.out + cat conftest.err >&5 + if $GREP 'Manifest Tool' conftest.out > /dev/null; then + lt_cv_path_mainfest_tool=yes + fi + rm -f conftest* +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_path_mainfest_tool" >&5 +$as_echo "$lt_cv_path_mainfest_tool" >&6; } +if test yes != "$lt_cv_path_mainfest_tool"; then + MANIFEST_TOOL=: +fi + + + + + + + case $host_os in + rhapsody* | darwin*) + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}dsymutil", so it can be a program name with args. +set dummy ${ac_tool_prefix}dsymutil; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_DSYMUTIL+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$DSYMUTIL"; then + ac_cv_prog_DSYMUTIL="$DSYMUTIL" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_DSYMUTIL="${ac_tool_prefix}dsymutil" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +DSYMUTIL=$ac_cv_prog_DSYMUTIL +if test -n "$DSYMUTIL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $DSYMUTIL" >&5 +$as_echo "$DSYMUTIL" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_DSYMUTIL"; then + ac_ct_DSYMUTIL=$DSYMUTIL + # Extract the first word of "dsymutil", so it can be a program name with args. +set dummy dsymutil; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_DSYMUTIL+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_DSYMUTIL"; then + ac_cv_prog_ac_ct_DSYMUTIL="$ac_ct_DSYMUTIL" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_DSYMUTIL="dsymutil" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_DSYMUTIL=$ac_cv_prog_ac_ct_DSYMUTIL +if test -n "$ac_ct_DSYMUTIL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_DSYMUTIL" >&5 +$as_echo "$ac_ct_DSYMUTIL" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_DSYMUTIL" = x; then + DSYMUTIL=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + DSYMUTIL=$ac_ct_DSYMUTIL + fi +else + DSYMUTIL="$ac_cv_prog_DSYMUTIL" +fi + + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}nmedit", so it can be a program name with args. +set dummy ${ac_tool_prefix}nmedit; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_NMEDIT+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$NMEDIT"; then + ac_cv_prog_NMEDIT="$NMEDIT" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_NMEDIT="${ac_tool_prefix}nmedit" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +NMEDIT=$ac_cv_prog_NMEDIT +if test -n "$NMEDIT"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NMEDIT" >&5 +$as_echo "$NMEDIT" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_NMEDIT"; then + ac_ct_NMEDIT=$NMEDIT + # Extract the first word of "nmedit", so it can be a program name with args. +set dummy nmedit; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_NMEDIT+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_NMEDIT"; then + ac_cv_prog_ac_ct_NMEDIT="$ac_ct_NMEDIT" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_NMEDIT="nmedit" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_NMEDIT=$ac_cv_prog_ac_ct_NMEDIT +if test -n "$ac_ct_NMEDIT"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_NMEDIT" >&5 +$as_echo "$ac_ct_NMEDIT" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_NMEDIT" = x; then + NMEDIT=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + NMEDIT=$ac_ct_NMEDIT + fi +else + NMEDIT="$ac_cv_prog_NMEDIT" +fi + + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}lipo", so it can be a program name with args. +set dummy ${ac_tool_prefix}lipo; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_LIPO+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$LIPO"; then + ac_cv_prog_LIPO="$LIPO" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_LIPO="${ac_tool_prefix}lipo" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +LIPO=$ac_cv_prog_LIPO +if test -n "$LIPO"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $LIPO" >&5 +$as_echo "$LIPO" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_LIPO"; then + ac_ct_LIPO=$LIPO + # Extract the first word of "lipo", so it can be a program name with args. +set dummy lipo; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_LIPO+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_LIPO"; then + ac_cv_prog_ac_ct_LIPO="$ac_ct_LIPO" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_LIPO="lipo" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_LIPO=$ac_cv_prog_ac_ct_LIPO +if test -n "$ac_ct_LIPO"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_LIPO" >&5 +$as_echo "$ac_ct_LIPO" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_LIPO" = x; then + LIPO=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + LIPO=$ac_ct_LIPO + fi +else + LIPO="$ac_cv_prog_LIPO" +fi + + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}otool", so it can be a program name with args. +set dummy ${ac_tool_prefix}otool; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_OTOOL+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$OTOOL"; then + ac_cv_prog_OTOOL="$OTOOL" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_OTOOL="${ac_tool_prefix}otool" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +OTOOL=$ac_cv_prog_OTOOL +if test -n "$OTOOL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $OTOOL" >&5 +$as_echo "$OTOOL" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_OTOOL"; then + ac_ct_OTOOL=$OTOOL + # Extract the first word of "otool", so it can be a program name with args. +set dummy otool; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_OTOOL+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_OTOOL"; then + ac_cv_prog_ac_ct_OTOOL="$ac_ct_OTOOL" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_OTOOL="otool" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_OTOOL=$ac_cv_prog_ac_ct_OTOOL +if test -n "$ac_ct_OTOOL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_OTOOL" >&5 +$as_echo "$ac_ct_OTOOL" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_OTOOL" = x; then + OTOOL=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + OTOOL=$ac_ct_OTOOL + fi +else + OTOOL="$ac_cv_prog_OTOOL" +fi + + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}otool64", so it can be a program name with args. +set dummy ${ac_tool_prefix}otool64; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_OTOOL64+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$OTOOL64"; then + ac_cv_prog_OTOOL64="$OTOOL64" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_OTOOL64="${ac_tool_prefix}otool64" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +OTOOL64=$ac_cv_prog_OTOOL64 +if test -n "$OTOOL64"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $OTOOL64" >&5 +$as_echo "$OTOOL64" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_OTOOL64"; then + ac_ct_OTOOL64=$OTOOL64 + # Extract the first word of "otool64", so it can be a program name with args. +set dummy otool64; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_OTOOL64+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_OTOOL64"; then + ac_cv_prog_ac_ct_OTOOL64="$ac_ct_OTOOL64" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_OTOOL64="otool64" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_OTOOL64=$ac_cv_prog_ac_ct_OTOOL64 +if test -n "$ac_ct_OTOOL64"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_OTOOL64" >&5 +$as_echo "$ac_ct_OTOOL64" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_OTOOL64" = x; then + OTOOL64=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + OTOOL64=$ac_ct_OTOOL64 + fi +else + OTOOL64="$ac_cv_prog_OTOOL64" +fi + + + + + + + + + + + + + + + + + + + + + + + + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for -single_module linker flag" >&5 +$as_echo_n "checking for -single_module linker flag... " >&6; } +if ${lt_cv_apple_cc_single_mod+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_apple_cc_single_mod=no + if test -z "$LT_MULTI_MODULE"; then + # By default we will add the -single_module flag. You can override + # by either setting the environment variable LT_MULTI_MODULE + # non-empty at configure time, or by adding -multi_module to the + # link flags. + rm -rf libconftest.dylib* + echo "int foo(void){return 1;}" > conftest.c + echo "$LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \ +-dynamiclib -Wl,-single_module conftest.c" >&5 + $LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \ + -dynamiclib -Wl,-single_module conftest.c 2>conftest.err + _lt_result=$? + # If there is a non-empty error log, and "single_module" + # appears in it, assume the flag caused a linker warning + if test -s conftest.err && $GREP single_module conftest.err; then + cat conftest.err >&5 + # Otherwise, if the output was created with a 0 exit code from + # the compiler, it worked. + elif test -f libconftest.dylib && test 0 = "$_lt_result"; then + lt_cv_apple_cc_single_mod=yes + else + cat conftest.err >&5 + fi + rm -rf libconftest.dylib* + rm -f conftest.* + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_apple_cc_single_mod" >&5 +$as_echo "$lt_cv_apple_cc_single_mod" >&6; } + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for -exported_symbols_list linker flag" >&5 +$as_echo_n "checking for -exported_symbols_list linker flag... " >&6; } +if ${lt_cv_ld_exported_symbols_list+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_ld_exported_symbols_list=no + save_LDFLAGS=$LDFLAGS + echo "_main" > conftest.sym + LDFLAGS="$LDFLAGS -Wl,-exported_symbols_list,conftest.sym" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + lt_cv_ld_exported_symbols_list=yes +else + lt_cv_ld_exported_symbols_list=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + LDFLAGS=$save_LDFLAGS + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ld_exported_symbols_list" >&5 +$as_echo "$lt_cv_ld_exported_symbols_list" >&6; } + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for -force_load linker flag" >&5 +$as_echo_n "checking for -force_load linker flag... " >&6; } +if ${lt_cv_ld_force_load+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_ld_force_load=no + cat > conftest.c << _LT_EOF +int forced_loaded() { return 2;} +_LT_EOF + echo "$LTCC $LTCFLAGS -c -o conftest.o conftest.c" >&5 + $LTCC $LTCFLAGS -c -o conftest.o conftest.c 2>&5 + echo "$AR cru libconftest.a conftest.o" >&5 + $AR cru libconftest.a conftest.o 2>&5 + echo "$RANLIB libconftest.a" >&5 + $RANLIB libconftest.a 2>&5 + cat > conftest.c << _LT_EOF +int main() { return 0;} +_LT_EOF + echo "$LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a" >&5 + $LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a 2>conftest.err + _lt_result=$? + if test -s conftest.err && $GREP force_load conftest.err; then + cat conftest.err >&5 + elif test -f conftest && test 0 = "$_lt_result" && $GREP forced_load conftest >/dev/null 2>&1; then + lt_cv_ld_force_load=yes + else + cat conftest.err >&5 + fi + rm -f conftest.err libconftest.a conftest conftest.c + rm -rf conftest.dSYM + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ld_force_load" >&5 +$as_echo "$lt_cv_ld_force_load" >&6; } + case $host_os in + rhapsody* | darwin1.[012]) + _lt_dar_allow_undefined='$wl-undefined ${wl}suppress' ;; + darwin1.*) + _lt_dar_allow_undefined='$wl-flat_namespace $wl-undefined ${wl}suppress' ;; + darwin*) # darwin 5.x on + # if running on 10.5 or later, the deployment target defaults + # to the OS version, if on x86, and 10.4, the deployment + # target defaults to 10.4. Don't you love it? + case ${MACOSX_DEPLOYMENT_TARGET-10.0},$host in + 10.0,*86*-darwin8*|10.0,*-darwin[91]*) + _lt_dar_allow_undefined='$wl-undefined ${wl}dynamic_lookup' ;; + 10.[012][,.]*) + _lt_dar_allow_undefined='$wl-flat_namespace $wl-undefined ${wl}suppress' ;; + 10.*) + _lt_dar_allow_undefined='$wl-undefined ${wl}dynamic_lookup' ;; + esac + ;; + esac + if test yes = "$lt_cv_apple_cc_single_mod"; then + _lt_dar_single_mod='$single_module' + fi + if test yes = "$lt_cv_ld_exported_symbols_list"; then + _lt_dar_export_syms=' $wl-exported_symbols_list,$output_objdir/$libname-symbols.expsym' + else + _lt_dar_export_syms='~$NMEDIT -s $output_objdir/$libname-symbols.expsym $lib' + fi + if test : != "$DSYMUTIL" && test no = "$lt_cv_ld_force_load"; then + _lt_dsymutil='~$DSYMUTIL $lib || :' + else + _lt_dsymutil= + fi + ;; + esac + +# func_munge_path_list VARIABLE PATH +# ----------------------------------- +# VARIABLE is name of variable containing _space_ separated list of +# directories to be munged by the contents of PATH, which is string +# having a format: +# "DIR[:DIR]:" +# string "DIR[ DIR]" will be prepended to VARIABLE +# ":DIR[:DIR]" +# string "DIR[ DIR]" will be appended to VARIABLE +# "DIRP[:DIRP]::[DIRA:]DIRA" +# string "DIRP[ DIRP]" will be prepended to VARIABLE and string +# "DIRA[ DIRA]" will be appended to VARIABLE +# "DIR[:DIR]" +# VARIABLE will be replaced by "DIR[ DIR]" +func_munge_path_list () +{ + case x$2 in + x) + ;; + *:) + eval $1=\"`$ECHO $2 | $SED 's/:/ /g'` \$$1\" + ;; + x:*) + eval $1=\"\$$1 `$ECHO $2 | $SED 's/:/ /g'`\" + ;; + *::*) + eval $1=\"\$$1\ `$ECHO $2 | $SED -e 's/.*:://' -e 's/:/ /g'`\" + eval $1=\"`$ECHO $2 | $SED -e 's/::.*//' -e 's/:/ /g'`\ \$$1\" + ;; + *) + eval $1=\"`$ECHO $2 | $SED 's/:/ /g'`\" + ;; + esac +} + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ANSI C header files" >&5 +$as_echo_n "checking for ANSI C header files... " >&6; } +if ${ac_cv_header_stdc+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +#include +#include + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_header_stdc=yes +else + ac_cv_header_stdc=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + +if test $ac_cv_header_stdc = yes; then + # SunOS 4.x string.h does not declare mem*, contrary to ANSI. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "memchr" >/dev/null 2>&1; then : + +else + ac_cv_header_stdc=no +fi +rm -f conftest* + +fi + +if test $ac_cv_header_stdc = yes; then + # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "free" >/dev/null 2>&1; then : + +else + ac_cv_header_stdc=no +fi +rm -f conftest* + +fi + +if test $ac_cv_header_stdc = yes; then + # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi. + if test "$cross_compiling" = yes; then : + : +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +#if ((' ' & 0x0FF) == 0x020) +# define ISLOWER(c) ('a' <= (c) && (c) <= 'z') +# define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c)) +#else +# define ISLOWER(c) \ + (('a' <= (c) && (c) <= 'i') \ + || ('j' <= (c) && (c) <= 'r') \ + || ('s' <= (c) && (c) <= 'z')) +# define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c)) +#endif + +#define XOR(e, f) (((e) && !(f)) || (!(e) && (f))) +int +main () +{ + int i; + for (i = 0; i < 256; i++) + if (XOR (islower (i), ISLOWER (i)) + || toupper (i) != TOUPPER (i)) + return 2; + return 0; +} +_ACEOF +if ac_fn_c_try_run "$LINENO"; then : + +else + ac_cv_header_stdc=no +fi +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ + conftest.$ac_objext conftest.beam conftest.$ac_ext +fi + +fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdc" >&5 +$as_echo "$ac_cv_header_stdc" >&6; } +if test $ac_cv_header_stdc = yes; then + +$as_echo "#define STDC_HEADERS 1" >>confdefs.h + +fi + +# On IRIX 5.3, sys/types and inttypes.h are conflicting. +for ac_header in sys/types.h sys/stat.h stdlib.h string.h memory.h strings.h \ + inttypes.h stdint.h unistd.h +do : + as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` +ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default +" +if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 +_ACEOF + +fi + +done + + +for ac_header in dlfcn.h +do : + ac_fn_c_check_header_compile "$LINENO" "dlfcn.h" "ac_cv_header_dlfcn_h" "$ac_includes_default +" +if test "x$ac_cv_header_dlfcn_h" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_DLFCN_H 1 +_ACEOF + +fi + +done + + + + +func_stripname_cnf () +{ + case $2 in + .*) func_stripname_result=`$ECHO "$3" | $SED "s%^$1%%; s%\\\\$2\$%%"`;; + *) func_stripname_result=`$ECHO "$3" | $SED "s%^$1%%; s%$2\$%%"`;; + esac +} # func_stripname_cnf + + + + + +# Set options + + + + enable_dlopen=no + + + enable_win32_dll=no + + + # Check whether --enable-shared was given. +if test "${enable_shared+set}" = set; then : + enableval=$enable_shared; p=${PACKAGE-default} + case $enableval in + yes) enable_shared=yes ;; + no) enable_shared=no ;; + *) + enable_shared=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, + for pkg in $enableval; do + IFS=$lt_save_ifs + if test "X$pkg" = "X$p"; then + enable_shared=yes + fi + done + IFS=$lt_save_ifs + ;; + esac +else + enable_shared=yes +fi + + + + + + + + + + # Check whether --enable-static was given. +if test "${enable_static+set}" = set; then : + enableval=$enable_static; p=${PACKAGE-default} + case $enableval in + yes) enable_static=yes ;; + no) enable_static=no ;; + *) + enable_static=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, + for pkg in $enableval; do + IFS=$lt_save_ifs + if test "X$pkg" = "X$p"; then + enable_static=yes + fi + done + IFS=$lt_save_ifs + ;; + esac +else + enable_static=yes +fi + + + + + + + + + + +# Check whether --with-pic was given. +if test "${with_pic+set}" = set; then : + withval=$with_pic; lt_p=${PACKAGE-default} + case $withval in + yes|no) pic_mode=$withval ;; + *) + pic_mode=default + # Look at the argument we got. We use all the common list separators. + lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, + for lt_pkg in $withval; do + IFS=$lt_save_ifs + if test "X$lt_pkg" = "X$lt_p"; then + pic_mode=yes + fi + done + IFS=$lt_save_ifs + ;; + esac +else + pic_mode=default +fi + + + + + + + + + # Check whether --enable-fast-install was given. +if test "${enable_fast_install+set}" = set; then : + enableval=$enable_fast_install; p=${PACKAGE-default} + case $enableval in + yes) enable_fast_install=yes ;; + no) enable_fast_install=no ;; + *) + enable_fast_install=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, + for pkg in $enableval; do + IFS=$lt_save_ifs + if test "X$pkg" = "X$p"; then + enable_fast_install=yes + fi + done + IFS=$lt_save_ifs + ;; + esac +else + enable_fast_install=yes +fi + + + + + + + + + shared_archive_member_spec= +case $host,$enable_shared in +power*-*-aix[5-9]*,yes) + { $as_echo "$as_me:${as_lineno-$LINENO}: checking which variant of shared library versioning to provide" >&5 +$as_echo_n "checking which variant of shared library versioning to provide... " >&6; } + +# Check whether --with-aix-soname was given. +if test "${with_aix_soname+set}" = set; then : + withval=$with_aix_soname; case $withval in + aix|svr4|both) + ;; + *) + as_fn_error $? "Unknown argument to --with-aix-soname" "$LINENO" 5 + ;; + esac + lt_cv_with_aix_soname=$with_aix_soname +else + if ${lt_cv_with_aix_soname+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_with_aix_soname=aix +fi + + with_aix_soname=$lt_cv_with_aix_soname +fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_aix_soname" >&5 +$as_echo "$with_aix_soname" >&6; } + if test aix != "$with_aix_soname"; then + # For the AIX way of multilib, we name the shared archive member + # based on the bitwidth used, traditionally 'shr.o' or 'shr_64.o', + # and 'shr.imp' or 'shr_64.imp', respectively, for the Import File. + # Even when GNU compilers ignore OBJECT_MODE but need '-maix64' flag, + # the AIX toolchain works better with OBJECT_MODE set (default 32). + if test 64 = "${OBJECT_MODE-32}"; then + shared_archive_member_spec=shr_64 + else + shared_archive_member_spec=shr + fi + fi + ;; +*) + with_aix_soname=aix + ;; +esac + + + + + + + + + + +# This can be used to rebuild libtool when needed +LIBTOOL_DEPS=$ltmain + +# Always use our own libtool. +LIBTOOL='$(SHELL) $(top_builddir)/libtool' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +test -z "$LN_S" && LN_S="ln -s" + + + + + + + + + + + + + + +if test -n "${ZSH_VERSION+set}"; then + setopt NO_GLOB_SUBST +fi + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for objdir" >&5 +$as_echo_n "checking for objdir... " >&6; } +if ${lt_cv_objdir+:} false; then : + $as_echo_n "(cached) " >&6 +else + rm -f .libs 2>/dev/null +mkdir .libs 2>/dev/null +if test -d .libs; then + lt_cv_objdir=.libs +else + # MS-DOS does not allow filenames that begin with a dot. + lt_cv_objdir=_libs +fi +rmdir .libs 2>/dev/null +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_objdir" >&5 +$as_echo "$lt_cv_objdir" >&6; } +objdir=$lt_cv_objdir + + + + + +cat >>confdefs.h <<_ACEOF +#define LT_OBJDIR "$lt_cv_objdir/" +_ACEOF + + + + +case $host_os in +aix3*) + # AIX sometimes has problems with the GCC collect2 program. For some + # reason, if we set the COLLECT_NAMES environment variable, the problems + # vanish in a puff of smoke. + if test set != "${COLLECT_NAMES+set}"; then + COLLECT_NAMES= + export COLLECT_NAMES + fi + ;; +esac + +# Global variables: +ofile=libtool +can_build_shared=yes + +# All known linkers require a '.a' archive for static linking (except MSVC, +# which needs '.lib'). +libext=a + +with_gnu_ld=$lt_cv_prog_gnu_ld + +old_CC=$CC +old_CFLAGS=$CFLAGS + +# Set sane defaults for various variables +test -z "$CC" && CC=cc +test -z "$LTCC" && LTCC=$CC +test -z "$LTCFLAGS" && LTCFLAGS=$CFLAGS +test -z "$LD" && LD=ld +test -z "$ac_objext" && ac_objext=o + +func_cc_basename $compiler +cc_basename=$func_cc_basename_result + + +# Only perform the check for file, if the check method requires it +test -z "$MAGIC_CMD" && MAGIC_CMD=file +case $deplibs_check_method in +file_magic*) + if test "$file_magic_cmd" = '$MAGIC_CMD'; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ${ac_tool_prefix}file" >&5 +$as_echo_n "checking for ${ac_tool_prefix}file... " >&6; } +if ${lt_cv_path_MAGIC_CMD+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $MAGIC_CMD in +[\\/*] | ?:[\\/]*) + lt_cv_path_MAGIC_CMD=$MAGIC_CMD # Let the user override the test with a path. + ;; +*) + lt_save_MAGIC_CMD=$MAGIC_CMD + lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR + ac_dummy="/usr/bin$PATH_SEPARATOR$PATH" + for ac_dir in $ac_dummy; do + IFS=$lt_save_ifs + test -z "$ac_dir" && ac_dir=. + if test -f "$ac_dir/${ac_tool_prefix}file"; then + lt_cv_path_MAGIC_CMD=$ac_dir/"${ac_tool_prefix}file" + if test -n "$file_magic_test_file"; then + case $deplibs_check_method in + "file_magic "*) + file_magic_regex=`expr "$deplibs_check_method" : "file_magic \(.*\)"` + MAGIC_CMD=$lt_cv_path_MAGIC_CMD + if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null | + $EGREP "$file_magic_regex" > /dev/null; then + : + else + cat <<_LT_EOF 1>&2 + +*** Warning: the command libtool uses to detect shared libraries, +*** $file_magic_cmd, produces output that libtool cannot recognize. +*** The result is that libtool may fail to recognize shared libraries +*** as such. This will affect the creation of libtool libraries that +*** depend on shared libraries, but programs linked with such libtool +*** libraries will work regardless of this problem. Nevertheless, you +*** may want to report the problem to your system manager and/or to +*** bug-libtool@gnu.org + +_LT_EOF + fi ;; + esac + fi + break + fi + done + IFS=$lt_save_ifs + MAGIC_CMD=$lt_save_MAGIC_CMD + ;; +esac +fi + +MAGIC_CMD=$lt_cv_path_MAGIC_CMD +if test -n "$MAGIC_CMD"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $MAGIC_CMD" >&5 +$as_echo "$MAGIC_CMD" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + + + +if test -z "$lt_cv_path_MAGIC_CMD"; then + if test -n "$ac_tool_prefix"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for file" >&5 +$as_echo_n "checking for file... " >&6; } +if ${lt_cv_path_MAGIC_CMD+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $MAGIC_CMD in +[\\/*] | ?:[\\/]*) + lt_cv_path_MAGIC_CMD=$MAGIC_CMD # Let the user override the test with a path. + ;; +*) + lt_save_MAGIC_CMD=$MAGIC_CMD + lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR + ac_dummy="/usr/bin$PATH_SEPARATOR$PATH" + for ac_dir in $ac_dummy; do + IFS=$lt_save_ifs + test -z "$ac_dir" && ac_dir=. + if test -f "$ac_dir/file"; then + lt_cv_path_MAGIC_CMD=$ac_dir/"file" + if test -n "$file_magic_test_file"; then + case $deplibs_check_method in + "file_magic "*) + file_magic_regex=`expr "$deplibs_check_method" : "file_magic \(.*\)"` + MAGIC_CMD=$lt_cv_path_MAGIC_CMD + if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null | + $EGREP "$file_magic_regex" > /dev/null; then + : + else + cat <<_LT_EOF 1>&2 + +*** Warning: the command libtool uses to detect shared libraries, +*** $file_magic_cmd, produces output that libtool cannot recognize. +*** The result is that libtool may fail to recognize shared libraries +*** as such. This will affect the creation of libtool libraries that +*** depend on shared libraries, but programs linked with such libtool +*** libraries will work regardless of this problem. Nevertheless, you +*** may want to report the problem to your system manager and/or to +*** bug-libtool@gnu.org + +_LT_EOF + fi ;; + esac + fi + break + fi + done + IFS=$lt_save_ifs + MAGIC_CMD=$lt_save_MAGIC_CMD + ;; +esac +fi + +MAGIC_CMD=$lt_cv_path_MAGIC_CMD +if test -n "$MAGIC_CMD"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $MAGIC_CMD" >&5 +$as_echo "$MAGIC_CMD" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + else + MAGIC_CMD=: + fi +fi + + fi + ;; +esac + +# Use C for the default configuration in the libtool script + +lt_save_CC=$CC +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + +# Source file extension for C test sources. +ac_ext=c + +# Object file extension for compiled C test sources. +objext=o +objext=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code="int some_variable = 0;" + +# Code to be used in simple link tests +lt_simple_link_test_code='int main(){return(0);}' + + + + + + + +# If no C compiler was specified, use CC. +LTCC=${LTCC-"$CC"} + +# If no C compiler flags were specified, use CFLAGS. +LTCFLAGS=${LTCFLAGS-"$CFLAGS"} + +# Allow CC to be a program name with arguments. +compiler=$CC + +# Save the default compiler, since it gets overwritten when the other +# tags are being tested, and _LT_TAGVAR(compiler, []) is a NOP. +compiler_DEFAULT=$CC + +# save warnings/boilerplate of simple test code +ac_outfile=conftest.$ac_objext +echo "$lt_simple_compile_test_code" >conftest.$ac_ext +eval "$ac_compile" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err +_lt_compiler_boilerplate=`cat conftest.err` +$RM conftest* + +ac_outfile=conftest.$ac_objext +echo "$lt_simple_link_test_code" >conftest.$ac_ext +eval "$ac_link" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err +_lt_linker_boilerplate=`cat conftest.err` +$RM -r conftest* + + +## CAVEAT EMPTOR: +## There is no encapsulation within the following macros, do not change +## the running order or otherwise move them around unless you know exactly +## what you are doing... +if test -n "$compiler"; then + +lt_prog_compiler_no_builtin_flag= + +if test yes = "$GCC"; then + case $cc_basename in + nvcc*) + lt_prog_compiler_no_builtin_flag=' -Xcompiler -fno-builtin' ;; + *) + lt_prog_compiler_no_builtin_flag=' -fno-builtin' ;; + esac + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -fno-rtti -fno-exceptions" >&5 +$as_echo_n "checking if $compiler supports -fno-rtti -fno-exceptions... " >&6; } +if ${lt_cv_prog_compiler_rtti_exceptions+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_rtti_exceptions=no + ac_outfile=conftest.$ac_objext + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + lt_compiler_flag="-fno-rtti -fno-exceptions" ## exclude from sc_useless_quotes_in_assignment + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + # The option is referenced via a variable to avoid confusing sed. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) + (eval "$lt_compile" 2>conftest.err) + ac_status=$? + cat conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + if (exit $ac_status) && test -s "$ac_outfile"; then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings other than the usual output. + $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then + lt_cv_prog_compiler_rtti_exceptions=yes + fi + fi + $RM conftest* + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_rtti_exceptions" >&5 +$as_echo "$lt_cv_prog_compiler_rtti_exceptions" >&6; } + +if test yes = "$lt_cv_prog_compiler_rtti_exceptions"; then + lt_prog_compiler_no_builtin_flag="$lt_prog_compiler_no_builtin_flag -fno-rtti -fno-exceptions" +else + : +fi + +fi + + + + + + + lt_prog_compiler_wl= +lt_prog_compiler_pic= +lt_prog_compiler_static= + + + if test yes = "$GCC"; then + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_static='-static' + + case $host_os in + aix*) + # All AIX code is PIC. + if test ia64 = "$host_cpu"; then + # AIX 5 now supports IA64 processor + lt_prog_compiler_static='-Bstatic' + fi + lt_prog_compiler_pic='-fPIC' + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + lt_prog_compiler_pic='-fPIC' + ;; + m68k) + # FIXME: we need at least 68020 code to build shared libraries, but + # adding the '-m68020' flag to GCC prevents building anything better, + # like '-m68040'. + lt_prog_compiler_pic='-m68020 -resident32 -malways-restore-a4' + ;; + esac + ;; + + beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) + # PIC is the default for these OSes. + ;; + + mingw* | cygwin* | pw32* | os2* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + # Although the cygwin gcc ignores -fPIC, still need this for old-style + # (--disable-auto-import) libraries + lt_prog_compiler_pic='-DDLL_EXPORT' + case $host_os in + os2*) + lt_prog_compiler_static='$wl-static' + ;; + esac + ;; + + darwin* | rhapsody*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + lt_prog_compiler_pic='-fno-common' + ;; + + haiku*) + # PIC is the default for Haiku. + # The "-static" flag exists, but is broken. + lt_prog_compiler_static= + ;; + + hpux*) + # PIC is the default for 64-bit PA HP-UX, but not for 32-bit + # PA HP-UX. On IA64 HP-UX, PIC is the default but the pic flag + # sets the default TLS model and affects inlining. + case $host_cpu in + hppa*64*) + # +Z the default + ;; + *) + lt_prog_compiler_pic='-fPIC' + ;; + esac + ;; + + interix[3-9]*) + # Interix 3.x gcc -fpic/-fPIC options generate broken code. + # Instead, we relocate shared libraries at runtime. + ;; + + msdosdjgpp*) + # Just because we use GCC doesn't mean we suddenly get shared libraries + # on systems that don't support them. + lt_prog_compiler_can_build_shared=no + enable_shared=no + ;; + + *nto* | *qnx*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + lt_prog_compiler_pic='-fPIC -shared' + ;; + + sysv4*MP*) + if test -d /usr/nec; then + lt_prog_compiler_pic=-Kconform_pic + fi + ;; + + *) + lt_prog_compiler_pic='-fPIC' + ;; + esac + + case $cc_basename in + nvcc*) # Cuda Compiler Driver 2.2 + lt_prog_compiler_wl='-Xlinker ' + if test -n "$lt_prog_compiler_pic"; then + lt_prog_compiler_pic="-Xcompiler $lt_prog_compiler_pic" + fi + ;; + esac + else + # PORTME Check for flag to pass linker flags through the system compiler. + case $host_os in + aix*) + lt_prog_compiler_wl='-Wl,' + if test ia64 = "$host_cpu"; then + # AIX 5 now supports IA64 processor + lt_prog_compiler_static='-Bstatic' + else + lt_prog_compiler_static='-bnso -bI:/lib/syscalls.exp' + fi + ;; + + darwin* | rhapsody*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + lt_prog_compiler_pic='-fno-common' + case $cc_basename in + nagfor*) + # NAG Fortran compiler + lt_prog_compiler_wl='-Wl,-Wl,,' + lt_prog_compiler_pic='-PIC' + lt_prog_compiler_static='-Bstatic' + ;; + esac + ;; + + mingw* | cygwin* | pw32* | os2* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + lt_prog_compiler_pic='-DDLL_EXPORT' + case $host_os in + os2*) + lt_prog_compiler_static='$wl-static' + ;; + esac + ;; + + hpux9* | hpux10* | hpux11*) + lt_prog_compiler_wl='-Wl,' + # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but + # not for PA HP-UX. + case $host_cpu in + hppa*64*|ia64*) + # +Z the default + ;; + *) + lt_prog_compiler_pic='+Z' + ;; + esac + # Is there a better lt_prog_compiler_static that works with the bundled CC? + lt_prog_compiler_static='$wl-a ${wl}archive' + ;; + + irix5* | irix6* | nonstopux*) + lt_prog_compiler_wl='-Wl,' + # PIC (with -KPIC) is the default. + lt_prog_compiler_static='-non_shared' + ;; + + linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + case $cc_basename in + # old Intel for x86_64, which still supported -KPIC. + ecc*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-static' + ;; + # icc used to be incompatible with GCC. + # ICC 10 doesn't accept -KPIC any more. + icc* | ifort*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-fPIC' + lt_prog_compiler_static='-static' + ;; + # Lahey Fortran 8.1. + lf95*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='--shared' + lt_prog_compiler_static='--static' + ;; + nagfor*) + # NAG Fortran compiler + lt_prog_compiler_wl='-Wl,-Wl,,' + lt_prog_compiler_pic='-PIC' + lt_prog_compiler_static='-Bstatic' + ;; + tcc*) + # Fabrice Bellard et al's Tiny C Compiler + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-fPIC' + lt_prog_compiler_static='-static' + ;; + pgcc* | pgf77* | pgf90* | pgf95* | pgfortran*) + # Portland Group compilers (*not* the Pentium gcc compiler, + # which looks to be a dead project) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-fpic' + lt_prog_compiler_static='-Bstatic' + ;; + ccc*) + lt_prog_compiler_wl='-Wl,' + # All Alpha code is PIC. + lt_prog_compiler_static='-non_shared' + ;; + xl* | bgxl* | bgf* | mpixl*) + # IBM XL C 8.0/Fortran 10.1, 11.1 on PPC and BlueGene + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-qpic' + lt_prog_compiler_static='-qstaticlink' + ;; + *) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ Ceres\ Fortran* | *Sun*Fortran*\ [1-7].* | *Sun*Fortran*\ 8.[0-3]*) + # Sun Fortran 8.3 passes all unrecognized flags to the linker + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-Bstatic' + lt_prog_compiler_wl='' + ;; + *Sun\ F* | *Sun*Fortran*) + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-Bstatic' + lt_prog_compiler_wl='-Qoption ld ' + ;; + *Sun\ C*) + # Sun C 5.9 + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-Bstatic' + lt_prog_compiler_wl='-Wl,' + ;; + *Intel*\ [CF]*Compiler*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-fPIC' + lt_prog_compiler_static='-static' + ;; + *Portland\ Group*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-fpic' + lt_prog_compiler_static='-Bstatic' + ;; + esac + ;; + esac + ;; + + newsos6) + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-Bstatic' + ;; + + *nto* | *qnx*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + lt_prog_compiler_pic='-fPIC -shared' + ;; + + osf3* | osf4* | osf5*) + lt_prog_compiler_wl='-Wl,' + # All OSF/1 code is PIC. + lt_prog_compiler_static='-non_shared' + ;; + + rdos*) + lt_prog_compiler_static='-non_shared' + ;; + + solaris*) + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-Bstatic' + case $cc_basename in + f77* | f90* | f95* | sunf77* | sunf90* | sunf95*) + lt_prog_compiler_wl='-Qoption ld ';; + *) + lt_prog_compiler_wl='-Wl,';; + esac + ;; + + sunos4*) + lt_prog_compiler_wl='-Qoption ld ' + lt_prog_compiler_pic='-PIC' + lt_prog_compiler_static='-Bstatic' + ;; + + sysv4 | sysv4.2uw2* | sysv4.3*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-Bstatic' + ;; + + sysv4*MP*) + if test -d /usr/nec; then + lt_prog_compiler_pic='-Kconform_pic' + lt_prog_compiler_static='-Bstatic' + fi + ;; + + sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-Bstatic' + ;; + + unicos*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_can_build_shared=no + ;; + + uts4*) + lt_prog_compiler_pic='-pic' + lt_prog_compiler_static='-Bstatic' + ;; + + *) + lt_prog_compiler_can_build_shared=no + ;; + esac + fi + +case $host_os in + # For platforms that do not support PIC, -DPIC is meaningless: + *djgpp*) + lt_prog_compiler_pic= + ;; + *) + lt_prog_compiler_pic="$lt_prog_compiler_pic -DPIC" + ;; +esac + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $compiler option to produce PIC" >&5 +$as_echo_n "checking for $compiler option to produce PIC... " >&6; } +if ${lt_cv_prog_compiler_pic+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_pic=$lt_prog_compiler_pic +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic" >&5 +$as_echo "$lt_cv_prog_compiler_pic" >&6; } +lt_prog_compiler_pic=$lt_cv_prog_compiler_pic + +# +# Check to make sure the PIC flag actually works. +# +if test -n "$lt_prog_compiler_pic"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler PIC flag $lt_prog_compiler_pic works" >&5 +$as_echo_n "checking if $compiler PIC flag $lt_prog_compiler_pic works... " >&6; } +if ${lt_cv_prog_compiler_pic_works+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_pic_works=no + ac_outfile=conftest.$ac_objext + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + lt_compiler_flag="$lt_prog_compiler_pic -DPIC" ## exclude from sc_useless_quotes_in_assignment + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + # The option is referenced via a variable to avoid confusing sed. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) + (eval "$lt_compile" 2>conftest.err) + ac_status=$? + cat conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + if (exit $ac_status) && test -s "$ac_outfile"; then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings other than the usual output. + $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then + lt_cv_prog_compiler_pic_works=yes + fi + fi + $RM conftest* + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic_works" >&5 +$as_echo "$lt_cv_prog_compiler_pic_works" >&6; } + +if test yes = "$lt_cv_prog_compiler_pic_works"; then + case $lt_prog_compiler_pic in + "" | " "*) ;; + *) lt_prog_compiler_pic=" $lt_prog_compiler_pic" ;; + esac +else + lt_prog_compiler_pic= + lt_prog_compiler_can_build_shared=no +fi + +fi + + + + + + + + + + + +# +# Check to make sure the static flag actually works. +# +wl=$lt_prog_compiler_wl eval lt_tmp_static_flag=\"$lt_prog_compiler_static\" +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler static flag $lt_tmp_static_flag works" >&5 +$as_echo_n "checking if $compiler static flag $lt_tmp_static_flag works... " >&6; } +if ${lt_cv_prog_compiler_static_works+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_static_works=no + save_LDFLAGS=$LDFLAGS + LDFLAGS="$LDFLAGS $lt_tmp_static_flag" + echo "$lt_simple_link_test_code" > conftest.$ac_ext + if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then + # The linker can only warn and ignore the option if not recognized + # So say no if there are warnings + if test -s conftest.err; then + # Append any errors to the config.log. + cat conftest.err 1>&5 + $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if diff conftest.exp conftest.er2 >/dev/null; then + lt_cv_prog_compiler_static_works=yes + fi + else + lt_cv_prog_compiler_static_works=yes + fi + fi + $RM -r conftest* + LDFLAGS=$save_LDFLAGS + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_static_works" >&5 +$as_echo "$lt_cv_prog_compiler_static_works" >&6; } + +if test yes = "$lt_cv_prog_compiler_static_works"; then + : +else + lt_prog_compiler_static= +fi + + + + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -c -o file.$ac_objext" >&5 +$as_echo_n "checking if $compiler supports -c -o file.$ac_objext... " >&6; } +if ${lt_cv_prog_compiler_c_o+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_c_o=no + $RM -r conftest 2>/dev/null + mkdir conftest + cd conftest + mkdir out + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + lt_compiler_flag="-o out/conftest2.$ac_objext" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) + (eval "$lt_compile" 2>out/conftest.err) + ac_status=$? + cat out/conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + if (exit $ac_status) && test -s out/conftest2.$ac_objext + then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings + $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp + $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2 + if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then + lt_cv_prog_compiler_c_o=yes + fi + fi + chmod u+w . 2>&5 + $RM conftest* + # SGI C++ compiler will create directory out/ii_files/ for + # template instantiation + test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files + $RM out/* && rmdir out + cd .. + $RM -r conftest + $RM conftest* + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_c_o" >&5 +$as_echo "$lt_cv_prog_compiler_c_o" >&6; } + + + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -c -o file.$ac_objext" >&5 +$as_echo_n "checking if $compiler supports -c -o file.$ac_objext... " >&6; } +if ${lt_cv_prog_compiler_c_o+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_c_o=no + $RM -r conftest 2>/dev/null + mkdir conftest + cd conftest + mkdir out + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + lt_compiler_flag="-o out/conftest2.$ac_objext" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) + (eval "$lt_compile" 2>out/conftest.err) + ac_status=$? + cat out/conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + if (exit $ac_status) && test -s out/conftest2.$ac_objext + then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings + $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp + $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2 + if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then + lt_cv_prog_compiler_c_o=yes + fi + fi + chmod u+w . 2>&5 + $RM conftest* + # SGI C++ compiler will create directory out/ii_files/ for + # template instantiation + test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files + $RM out/* && rmdir out + cd .. + $RM -r conftest + $RM conftest* + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_c_o" >&5 +$as_echo "$lt_cv_prog_compiler_c_o" >&6; } + + + + +hard_links=nottested +if test no = "$lt_cv_prog_compiler_c_o" && test no != "$need_locks"; then + # do not overwrite the value of need_locks provided by the user + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if we can lock with hard links" >&5 +$as_echo_n "checking if we can lock with hard links... " >&6; } + hard_links=yes + $RM conftest* + ln conftest.a conftest.b 2>/dev/null && hard_links=no + touch conftest.a + ln conftest.a conftest.b 2>&5 || hard_links=no + ln conftest.a conftest.b 2>/dev/null && hard_links=no + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $hard_links" >&5 +$as_echo "$hard_links" >&6; } + if test no = "$hard_links"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: '$CC' does not support '-c -o', so 'make -j' may be unsafe" >&5 +$as_echo "$as_me: WARNING: '$CC' does not support '-c -o', so 'make -j' may be unsafe" >&2;} + need_locks=warn + fi +else + need_locks=no +fi + + + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the $compiler linker ($LD) supports shared libraries" >&5 +$as_echo_n "checking whether the $compiler linker ($LD) supports shared libraries... " >&6; } + + runpath_var= + allow_undefined_flag= + always_export_symbols=no + archive_cmds= + archive_expsym_cmds= + compiler_needs_object=no + enable_shared_with_static_runtimes=no + export_dynamic_flag_spec= + export_symbols_cmds='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + hardcode_automatic=no + hardcode_direct=no + hardcode_direct_absolute=no + hardcode_libdir_flag_spec= + hardcode_libdir_separator= + hardcode_minus_L=no + hardcode_shlibpath_var=unsupported + inherit_rpath=no + link_all_deplibs=unknown + module_cmds= + module_expsym_cmds= + old_archive_from_new_cmds= + old_archive_from_expsyms_cmds= + thread_safe_flag_spec= + whole_archive_flag_spec= + # include_expsyms should be a list of space-separated symbols to be *always* + # included in the symbol list + include_expsyms= + # exclude_expsyms can be an extended regexp of symbols to exclude + # it will be wrapped by ' (' and ')$', so one must not match beginning or + # end of line. Example: 'a|bc|.*d.*' will exclude the symbols 'a' and 'bc', + # as well as any symbol that contains 'd'. + exclude_expsyms='_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*' + # Although _GLOBAL_OFFSET_TABLE_ is a valid symbol C name, most a.out + # platforms (ab)use it in PIC code, but their linkers get confused if + # the symbol is explicitly referenced. Since portable code cannot + # rely on this symbol name, it's probably fine to never include it in + # preloaded symbol tables. + # Exclude shared library initialization/finalization symbols. + extract_expsyms_cmds= + + case $host_os in + cygwin* | mingw* | pw32* | cegcc*) + # FIXME: the MSVC++ port hasn't been tested in a loooong time + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + if test yes != "$GCC"; then + with_gnu_ld=no + fi + ;; + interix*) + # we just hope/assume this is gcc and not c89 (= MSVC++) + with_gnu_ld=yes + ;; + openbsd* | bitrig*) + with_gnu_ld=no + ;; + esac + + ld_shlibs=yes + + # On some targets, GNU ld is compatible enough with the native linker + # that we're better off using the native interface for both. + lt_use_gnu_ld_interface=no + if test yes = "$with_gnu_ld"; then + case $host_os in + aix*) + # The AIX port of GNU ld has always aspired to compatibility + # with the native linker. However, as the warning in the GNU ld + # block says, versions before 2.19.5* couldn't really create working + # shared libraries, regardless of the interface used. + case `$LD -v 2>&1` in + *\ \(GNU\ Binutils\)\ 2.19.5*) ;; + *\ \(GNU\ Binutils\)\ 2.[2-9]*) ;; + *\ \(GNU\ Binutils\)\ [3-9]*) ;; + *) + lt_use_gnu_ld_interface=yes + ;; + esac + ;; + *) + lt_use_gnu_ld_interface=yes + ;; + esac + fi + + if test yes = "$lt_use_gnu_ld_interface"; then + # If archive_cmds runs LD, not CC, wlarc should be empty + wlarc='$wl' + + # Set some defaults for GNU ld with shared library support. These + # are reset later if shared libraries are not supported. Putting them + # here allows them to be overridden if necessary. + runpath_var=LD_RUN_PATH + hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' + export_dynamic_flag_spec='$wl--export-dynamic' + # ancient GNU ld didn't support --whole-archive et. al. + if $LD --help 2>&1 | $GREP 'no-whole-archive' > /dev/null; then + whole_archive_flag_spec=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive' + else + whole_archive_flag_spec= + fi + supports_anon_versioning=no + case `$LD -v | $SED -e 's/(^)\+)\s\+//' 2>&1` in + *GNU\ gold*) supports_anon_versioning=yes ;; + *\ [01].* | *\ 2.[0-9].* | *\ 2.10.*) ;; # catch versions < 2.11 + *\ 2.11.93.0.2\ *) supports_anon_versioning=yes ;; # RH7.3 ... + *\ 2.11.92.0.12\ *) supports_anon_versioning=yes ;; # Mandrake 8.2 ... + *\ 2.11.*) ;; # other 2.11 versions + *) supports_anon_versioning=yes ;; + esac + + # See if GNU ld supports shared libraries. + case $host_os in + aix[3-9]*) + # On AIX/PPC, the GNU linker is very broken + if test ia64 != "$host_cpu"; then + ld_shlibs=no + cat <<_LT_EOF 1>&2 + +*** Warning: the GNU linker, at least up to release 2.19, is reported +*** to be unable to reliably create shared libraries on AIX. +*** Therefore, libtool is disabling shared libraries support. If you +*** really care for shared libraries, you may want to install binutils +*** 2.20 or above, or modify your PATH so that a non-GNU linker is found. +*** You will then need to restart the configuration process. + +_LT_EOF + fi + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + archive_expsym_cmds='' + ;; + m68k) + archive_cmds='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' + hardcode_libdir_flag_spec='-L$libdir' + hardcode_minus_L=yes + ;; + esac + ;; + + beos*) + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + allow_undefined_flag=unsupported + # Joseph Beckenbach says some releases of gcc + # support --undefined. This deserves some investigation. FIXME + archive_cmds='$CC -nostart $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + else + ld_shlibs=no + fi + ;; + + cygwin* | mingw* | pw32* | cegcc*) + # _LT_TAGVAR(hardcode_libdir_flag_spec, ) is actually meaningless, + # as there is no search path for DLLs. + hardcode_libdir_flag_spec='-L$libdir' + export_dynamic_flag_spec='$wl--export-all-symbols' + allow_undefined_flag=unsupported + always_export_symbols=no + enable_shared_with_static_runtimes=yes + export_symbols_cmds='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[BCDGRS][ ]/s/.*[ ]\([^ ]*\)/\1 DATA/;s/^.*[ ]__nm__\([^ ]*\)[ ][^ ]*/\1 DATA/;/^I[ ]/d;/^[AITW][ ]/s/.* //'\'' | sort | uniq > $export_symbols' + exclude_expsyms='[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname' + + if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then + archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + # If the export-symbols file already is a .def file, use it as + # is; otherwise, prepend EXPORTS... + archive_expsym_cmds='if test DEF = "`$SED -n -e '\''s/^[ ]*//'\'' -e '\''/^\(;.*\)*$/d'\'' -e '\''s/^\(EXPORTS\|LIBRARY\)\([ ].*\)*$/DEF/p'\'' -e q $export_symbols`" ; then + cp $export_symbols $output_objdir/$soname.def; + else + echo EXPORTS > $output_objdir/$soname.def; + cat $export_symbols >> $output_objdir/$soname.def; + fi~ + $CC -shared $output_objdir/$soname.def $libobjs $deplibs $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + else + ld_shlibs=no + fi + ;; + + haiku*) + archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + link_all_deplibs=yes + ;; + + os2*) + hardcode_libdir_flag_spec='-L$libdir' + hardcode_minus_L=yes + allow_undefined_flag=unsupported + shrext_cmds=.dll + archive_cmds='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + archive_expsym_cmds='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + prefix_cmds="$SED"~ + if test EXPORTS = "`$SED 1q $export_symbols`"; then + prefix_cmds="$prefix_cmds -e 1d"; + fi~ + prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~ + cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + old_archive_From_new_cmds='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def' + enable_shared_with_static_runtimes=yes + ;; + + interix[3-9]*) + hardcode_direct=no + hardcode_shlibpath_var=no + hardcode_libdir_flag_spec='$wl-rpath,$libdir' + export_dynamic_flag_spec='$wl-E' + # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc. + # Instead, shared libraries are loaded at an image base (0x10000000 by + # default) and relocated if they conflict, which is a slow very memory + # consuming and fragmenting process. To avoid this, we pick a random, + # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link + # time. Moving up from 0x10000000 also allows more sbrk(2) space. + archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + archive_expsym_cmds='sed "s|^|_|" $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--retain-symbols-file,$output_objdir/$soname.expsym $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + ;; + + gnu* | linux* | tpf* | k*bsd*-gnu | kopensolaris*-gnu) + tmp_diet=no + if test linux-dietlibc = "$host_os"; then + case $cc_basename in + diet\ *) tmp_diet=yes;; # linux-dietlibc with static linking (!diet-dyn) + esac + fi + if $LD --help 2>&1 | $EGREP ': supported targets:.* elf' > /dev/null \ + && test no = "$tmp_diet" + then + tmp_addflag=' $pic_flag' + tmp_sharedflag='-shared' + case $cc_basename,$host_cpu in + pgcc*) # Portland Group C compiler + whole_archive_flag_spec='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + tmp_addflag=' $pic_flag' + ;; + pgf77* | pgf90* | pgf95* | pgfortran*) + # Portland Group f77 and f90 compilers + whole_archive_flag_spec='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + tmp_addflag=' $pic_flag -Mnomain' ;; + ecc*,ia64* | icc*,ia64*) # Intel C compiler on ia64 + tmp_addflag=' -i_dynamic' ;; + efc*,ia64* | ifort*,ia64*) # Intel Fortran compiler on ia64 + tmp_addflag=' -i_dynamic -nofor_main' ;; + ifc* | ifort*) # Intel Fortran compiler + tmp_addflag=' -nofor_main' ;; + lf95*) # Lahey Fortran 8.1 + whole_archive_flag_spec= + tmp_sharedflag='--shared' ;; + nagfor*) # NAGFOR 5.3 + tmp_sharedflag='-Wl,-shared' ;; + xl[cC]* | bgxl[cC]* | mpixl[cC]*) # IBM XL C 8.0 on PPC (deal with xlf below) + tmp_sharedflag='-qmkshrobj' + tmp_addflag= ;; + nvcc*) # Cuda Compiler Driver 2.2 + whole_archive_flag_spec='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + compiler_needs_object=yes + ;; + esac + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) # Sun C 5.9 + whole_archive_flag_spec='$wl--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + compiler_needs_object=yes + tmp_sharedflag='-G' ;; + *Sun\ F*) # Sun Fortran 8.3 + tmp_sharedflag='-G' ;; + esac + archive_cmds='$CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + + if test yes = "$supports_anon_versioning"; then + archive_expsym_cmds='echo "{ global:" > $output_objdir/$libname.ver~ + cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ + echo "local: *; };" >> $output_objdir/$libname.ver~ + $CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-version-script $wl$output_objdir/$libname.ver -o $lib' + fi + + case $cc_basename in + tcc*) + export_dynamic_flag_spec='-rdynamic' + ;; + xlf* | bgf* | bgxlf* | mpixlf*) + # IBM XL Fortran 10.1 on PPC cannot create shared libs itself + whole_archive_flag_spec='--whole-archive$convenience --no-whole-archive' + hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' + archive_cmds='$LD -shared $libobjs $deplibs $linker_flags -soname $soname -o $lib' + if test yes = "$supports_anon_versioning"; then + archive_expsym_cmds='echo "{ global:" > $output_objdir/$libname.ver~ + cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ + echo "local: *; };" >> $output_objdir/$libname.ver~ + $LD -shared $libobjs $deplibs $linker_flags -soname $soname -version-script $output_objdir/$libname.ver -o $lib' + fi + ;; + esac + else + ld_shlibs=no + fi + ;; + + netbsd*) + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + archive_cmds='$LD -Bshareable $libobjs $deplibs $linker_flags -o $lib' + wlarc= + else + archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + fi + ;; + + solaris*) + if $LD -v 2>&1 | $GREP 'BFD 2\.8' > /dev/null; then + ld_shlibs=no + cat <<_LT_EOF 1>&2 + +*** Warning: The releases 2.8.* of the GNU linker cannot reliably +*** create shared libraries on Solaris systems. Therefore, libtool +*** is disabling shared libraries support. We urge you to upgrade GNU +*** binutils to release 2.9.1 or newer. Another option is to modify +*** your PATH or compiler configuration so that the native linker is +*** used, and then restart. + +_LT_EOF + elif $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + else + ld_shlibs=no + fi + ;; + + sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX*) + case `$LD -v 2>&1` in + *\ [01].* | *\ 2.[0-9].* | *\ 2.1[0-5].*) + ld_shlibs=no + cat <<_LT_EOF 1>&2 + +*** Warning: Releases of the GNU linker prior to 2.16.91.0.3 cannot +*** reliably create shared libraries on SCO systems. Therefore, libtool +*** is disabling shared libraries support. We urge you to upgrade GNU +*** binutils to release 2.16.91.0.3 or newer. Another option is to modify +*** your PATH or compiler configuration so that the native linker is +*** used, and then restart. + +_LT_EOF + ;; + *) + # For security reasons, it is highly recommended that you always + # use absolute paths for naming shared libraries, and exclude the + # DT_RUNPATH tag from executables and libraries. But doing so + # requires that you compile everything twice, which is a pain. + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' + archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + archive_expsym_cmds='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + else + ld_shlibs=no + fi + ;; + esac + ;; + + sunos4*) + archive_cmds='$LD -assert pure-text -Bshareable -o $lib $libobjs $deplibs $linker_flags' + wlarc= + hardcode_direct=yes + hardcode_shlibpath_var=no + ;; + + *) + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + else + ld_shlibs=no + fi + ;; + esac + + if test no = "$ld_shlibs"; then + runpath_var= + hardcode_libdir_flag_spec= + export_dynamic_flag_spec= + whole_archive_flag_spec= + fi + else + # PORTME fill in a description of your system's linker (not GNU ld) + case $host_os in + aix3*) + allow_undefined_flag=unsupported + always_export_symbols=yes + archive_expsym_cmds='$LD -o $output_objdir/$soname $libobjs $deplibs $linker_flags -bE:$export_symbols -T512 -H512 -bM:SRE~$AR $AR_FLAGS $lib $output_objdir/$soname' + # Note: this linker hardcodes the directories in LIBPATH if there + # are no directories specified by -L. + hardcode_minus_L=yes + if test yes = "$GCC" && test -z "$lt_prog_compiler_static"; then + # Neither direct hardcoding nor static linking is supported with a + # broken collect2. + hardcode_direct=unsupported + fi + ;; + + aix[4-9]*) + if test ia64 = "$host_cpu"; then + # On IA64, the linker does run time linking by default, so we don't + # have to do anything special. + aix_use_runtimelinking=no + exp_sym_flag='-Bexport' + no_entry_flag= + else + # If we're using GNU nm, then we don't want the "-C" option. + # -C means demangle to GNU nm, but means don't demangle to AIX nm. + # Without the "-l" option, or with the "-B" option, AIX nm treats + # weak defined symbols like other global defined symbols, whereas + # GNU nm marks them as "W". + # While the 'weak' keyword is ignored in the Export File, we need + # it in the Import File for the 'aix-soname' feature, so we have + # to replace the "-B" option with "-P" for AIX nm. + if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then + export_symbols_cmds='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && (substr(\$ 3,1,1) != ".")) { if (\$ 2 == "W") { print \$ 3 " weak" } else { print \$ 3 } } }'\'' | sort -u > $export_symbols' + else + export_symbols_cmds='`func_echo_all $NM | $SED -e '\''s/B\([^B]*\)$/P\1/'\''` -PCpgl $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) && (substr(\$ 1,1,1) != ".")) { if ((\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) { print \$ 1 " weak" } else { print \$ 1 } } }'\'' | sort -u > $export_symbols' + fi + aix_use_runtimelinking=no + + # Test if we are trying to use run time linking or normal + # AIX style linking. If -brtl is somewhere in LDFLAGS, we + # have runtime linking enabled, and use it for executables. + # For shared libraries, we enable/disable runtime linking + # depending on the kind of the shared library created - + # when "with_aix_soname,aix_use_runtimelinking" is: + # "aix,no" lib.a(lib.so.V) shared, rtl:no, for executables + # "aix,yes" lib.so shared, rtl:yes, for executables + # lib.a static archive + # "both,no" lib.so.V(shr.o) shared, rtl:yes + # lib.a(lib.so.V) shared, rtl:no, for executables + # "both,yes" lib.so.V(shr.o) shared, rtl:yes, for executables + # lib.a(lib.so.V) shared, rtl:no + # "svr4,*" lib.so.V(shr.o) shared, rtl:yes, for executables + # lib.a static archive + case $host_os in aix4.[23]|aix4.[23].*|aix[5-9]*) + for ld_flag in $LDFLAGS; do + if (test x-brtl = "x$ld_flag" || test x-Wl,-brtl = "x$ld_flag"); then + aix_use_runtimelinking=yes + break + fi + done + if test svr4,no = "$with_aix_soname,$aix_use_runtimelinking"; then + # With aix-soname=svr4, we create the lib.so.V shared archives only, + # so we don't have lib.a shared libs to link our executables. + # We have to force runtime linking in this case. + aix_use_runtimelinking=yes + LDFLAGS="$LDFLAGS -Wl,-brtl" + fi + ;; + esac + + exp_sym_flag='-bexport' + no_entry_flag='-bnoentry' + fi + + # When large executables or shared objects are built, AIX ld can + # have problems creating the table of contents. If linking a library + # or program results in "error TOC overflow" add -mminimal-toc to + # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not + # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS. + + archive_cmds='' + hardcode_direct=yes + hardcode_direct_absolute=yes + hardcode_libdir_separator=':' + link_all_deplibs=yes + file_list_spec='$wl-f,' + case $with_aix_soname,$aix_use_runtimelinking in + aix,*) ;; # traditional, no import file + svr4,* | *,yes) # use import file + # The Import File defines what to hardcode. + hardcode_direct=no + hardcode_direct_absolute=no + ;; + esac + + if test yes = "$GCC"; then + case $host_os in aix4.[012]|aix4.[012].*) + # We only want to do this on AIX 4.2 and lower, the check + # below for broken collect2 doesn't work under 4.3+ + collect2name=`$CC -print-prog-name=collect2` + if test -f "$collect2name" && + strings "$collect2name" | $GREP resolve_lib_name >/dev/null + then + # We have reworked collect2 + : + else + # We have old collect2 + hardcode_direct=unsupported + # It fails to find uninstalled libraries when the uninstalled + # path is not listed in the libpath. Setting hardcode_minus_L + # to unsupported forces relinking + hardcode_minus_L=yes + hardcode_libdir_flag_spec='-L$libdir' + hardcode_libdir_separator= + fi + ;; + esac + shared_flag='-shared' + if test yes = "$aix_use_runtimelinking"; then + shared_flag="$shared_flag "'$wl-G' + fi + # Need to ensure runtime linking is disabled for the traditional + # shared library, or the linker may eventually find shared libraries + # /with/ Import File - we do not want to mix them. + shared_flag_aix='-shared' + shared_flag_svr4='-shared $wl-G' + else + # not using gcc + if test ia64 = "$host_cpu"; then + # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release + # chokes on -Wl,-G. The following line is correct: + shared_flag='-G' + else + if test yes = "$aix_use_runtimelinking"; then + shared_flag='$wl-G' + else + shared_flag='$wl-bM:SRE' + fi + shared_flag_aix='$wl-bM:SRE' + shared_flag_svr4='$wl-G' + fi + fi + + export_dynamic_flag_spec='$wl-bexpall' + # It seems that -bexpall does not export symbols beginning with + # underscore (_), so it is better to generate a list of symbols to export. + always_export_symbols=yes + if test aix,yes = "$with_aix_soname,$aix_use_runtimelinking"; then + # Warning - without using the other runtime loading flags (-brtl), + # -berok will link without error, but may produce a broken library. + allow_undefined_flag='-berok' + # Determine the default libpath from the value encoded in an + # empty executable. + if test set = "${lt_cv_aix_libpath+set}"; then + aix_libpath=$lt_cv_aix_libpath +else + if ${lt_cv_aix_libpath_+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + + lt_aix_libpath_sed=' + /Import File Strings/,/^$/ { + /^0/ { + s/^0 *\([^ ]*\) *$/\1/ + p + } + }' + lt_cv_aix_libpath_=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + # Check for a 64-bit object if we didn't find anything. + if test -z "$lt_cv_aix_libpath_"; then + lt_cv_aix_libpath_=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + fi +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + if test -z "$lt_cv_aix_libpath_"; then + lt_cv_aix_libpath_=/usr/lib:/lib + fi + +fi + + aix_libpath=$lt_cv_aix_libpath_ +fi + + hardcode_libdir_flag_spec='$wl-blibpath:$libdir:'"$aix_libpath" + archive_expsym_cmds='$CC -o $output_objdir/$soname $libobjs $deplibs $wl'$no_entry_flag' $compiler_flags `if test -n "$allow_undefined_flag"; then func_echo_all "$wl$allow_undefined_flag"; else :; fi` $wl'$exp_sym_flag:\$export_symbols' '$shared_flag + else + if test ia64 = "$host_cpu"; then + hardcode_libdir_flag_spec='$wl-R $libdir:/usr/lib:/lib' + allow_undefined_flag="-z nodefs" + archive_expsym_cmds="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\$wl$no_entry_flag"' $compiler_flags $wl$allow_undefined_flag '"\$wl$exp_sym_flag:\$export_symbols" + else + # Determine the default libpath from the value encoded in an + # empty executable. + if test set = "${lt_cv_aix_libpath+set}"; then + aix_libpath=$lt_cv_aix_libpath +else + if ${lt_cv_aix_libpath_+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + + lt_aix_libpath_sed=' + /Import File Strings/,/^$/ { + /^0/ { + s/^0 *\([^ ]*\) *$/\1/ + p + } + }' + lt_cv_aix_libpath_=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + # Check for a 64-bit object if we didn't find anything. + if test -z "$lt_cv_aix_libpath_"; then + lt_cv_aix_libpath_=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + fi +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + if test -z "$lt_cv_aix_libpath_"; then + lt_cv_aix_libpath_=/usr/lib:/lib + fi + +fi + + aix_libpath=$lt_cv_aix_libpath_ +fi + + hardcode_libdir_flag_spec='$wl-blibpath:$libdir:'"$aix_libpath" + # Warning - without using the other run time loading flags, + # -berok will link without error, but may produce a broken library. + no_undefined_flag=' $wl-bernotok' + allow_undefined_flag=' $wl-berok' + if test yes = "$with_gnu_ld"; then + # We only use this code for GNU lds that support --whole-archive. + whole_archive_flag_spec='$wl--whole-archive$convenience $wl--no-whole-archive' + else + # Exported symbols can be pulled into shared objects from archives + whole_archive_flag_spec='$convenience' + fi + archive_cmds_need_lc=yes + archive_expsym_cmds='$RM -r $output_objdir/$realname.d~$MKDIR $output_objdir/$realname.d' + # -brtl affects multiple linker settings, -berok does not and is overridden later + compiler_flags_filtered='`func_echo_all "$compiler_flags " | $SED -e "s%-brtl\\([, ]\\)%-berok\\1%g"`' + if test svr4 != "$with_aix_soname"; then + # This is similar to how AIX traditionally builds its shared libraries. + archive_expsym_cmds="$archive_expsym_cmds"'~$CC '$shared_flag_aix' -o $output_objdir/$realname.d/$soname $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$realname.d/$soname' + fi + if test aix != "$with_aix_soname"; then + archive_expsym_cmds="$archive_expsym_cmds"'~$CC '$shared_flag_svr4' -o $output_objdir/$realname.d/$shared_archive_member_spec.o $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$STRIP -e $output_objdir/$realname.d/$shared_archive_member_spec.o~( func_echo_all "#! $soname($shared_archive_member_spec.o)"; if test shr_64 = "$shared_archive_member_spec"; then func_echo_all "# 64"; else func_echo_all "# 32"; fi; cat $export_symbols ) > $output_objdir/$realname.d/$shared_archive_member_spec.imp~$AR $AR_FLAGS $output_objdir/$soname $output_objdir/$realname.d/$shared_archive_member_spec.o $output_objdir/$realname.d/$shared_archive_member_spec.imp' + else + # used by -dlpreopen to get the symbols + archive_expsym_cmds="$archive_expsym_cmds"'~$MV $output_objdir/$realname.d/$soname $output_objdir' + fi + archive_expsym_cmds="$archive_expsym_cmds"'~$RM -r $output_objdir/$realname.d' + fi + fi + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + archive_expsym_cmds='' + ;; + m68k) + archive_cmds='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' + hardcode_libdir_flag_spec='-L$libdir' + hardcode_minus_L=yes + ;; + esac + ;; + + bsdi[45]*) + export_dynamic_flag_spec=-rdynamic + ;; + + cygwin* | mingw* | pw32* | cegcc*) + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + # hardcode_libdir_flag_spec is actually meaningless, as there is + # no search path for DLLs. + case $cc_basename in + cl*) + # Native MSVC + hardcode_libdir_flag_spec=' ' + allow_undefined_flag=unsupported + always_export_symbols=yes + file_list_spec='@' + # Tell ltmain to make .lib files, not .a files. + libext=lib + # Tell ltmain to make .dll files, not .so files. + shrext_cmds=.dll + # FIXME: Setting linknames here is a bad hack. + archive_cmds='$CC -o $output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~linknames=' + archive_expsym_cmds='if test DEF = "`$SED -n -e '\''s/^[ ]*//'\'' -e '\''/^\(;.*\)*$/d'\'' -e '\''s/^\(EXPORTS\|LIBRARY\)\([ ].*\)*$/DEF/p'\'' -e q $export_symbols`" ; then + cp "$export_symbols" "$output_objdir/$soname.def"; + echo "$tool_output_objdir$soname.def" > "$output_objdir/$soname.exp"; + else + $SED -e '\''s/^/-link -EXPORT:/'\'' < $export_symbols > $output_objdir/$soname.exp; + fi~ + $CC -o $tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~ + linknames=' + # The linker will not automatically build a static lib if we build a DLL. + # _LT_TAGVAR(old_archive_from_new_cmds, )='true' + enable_shared_with_static_runtimes=yes + exclude_expsyms='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*' + export_symbols_cmds='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[BCDGRS][ ]/s/.*[ ]\([^ ]*\)/\1,DATA/'\'' | $SED -e '\''/^[AITW][ ]/s/.*[ ]//'\'' | sort | uniq > $export_symbols' + # Don't use ranlib + old_postinstall_cmds='chmod 644 $oldlib' + postlink_cmds='lt_outputfile="@OUTPUT@"~ + lt_tool_outputfile="@TOOL_OUTPUT@"~ + case $lt_outputfile in + *.exe|*.EXE) ;; + *) + lt_outputfile=$lt_outputfile.exe + lt_tool_outputfile=$lt_tool_outputfile.exe + ;; + esac~ + if test : != "$MANIFEST_TOOL" && test -f "$lt_outputfile.manifest"; then + $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1; + $RM "$lt_outputfile.manifest"; + fi' + ;; + *) + # Assume MSVC wrapper + hardcode_libdir_flag_spec=' ' + allow_undefined_flag=unsupported + # Tell ltmain to make .lib files, not .a files. + libext=lib + # Tell ltmain to make .dll files, not .so files. + shrext_cmds=.dll + # FIXME: Setting linknames here is a bad hack. + archive_cmds='$CC -o $lib $libobjs $compiler_flags `func_echo_all "$deplibs" | $SED '\''s/ -lc$//'\''` -link -dll~linknames=' + # The linker will automatically build a .lib file if we build a DLL. + old_archive_from_new_cmds='true' + # FIXME: Should let the user specify the lib program. + old_archive_cmds='lib -OUT:$oldlib$oldobjs$old_deplibs' + enable_shared_with_static_runtimes=yes + ;; + esac + ;; + + darwin* | rhapsody*) + + + archive_cmds_need_lc=no + hardcode_direct=no + hardcode_automatic=yes + hardcode_shlibpath_var=unsupported + if test yes = "$lt_cv_ld_force_load"; then + whole_archive_flag_spec='`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience $wl-force_load,$conv\"; done; func_echo_all \"$new_convenience\"`' + + else + whole_archive_flag_spec='' + fi + link_all_deplibs=yes + allow_undefined_flag=$_lt_dar_allow_undefined + case $cc_basename in + ifort*|nagfor*) _lt_dar_can_shared=yes ;; + *) _lt_dar_can_shared=$GCC ;; + esac + if test yes = "$_lt_dar_can_shared"; then + output_verbose_link_cmd=func_echo_all + archive_cmds="\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod$_lt_dsymutil" + module_cmds="\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags$_lt_dsymutil" + archive_expsym_cmds="sed 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod$_lt_dar_export_syms$_lt_dsymutil" + module_expsym_cmds="sed -e 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags$_lt_dar_export_syms$_lt_dsymutil" + + else + ld_shlibs=no + fi + + ;; + + dgux*) + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_libdir_flag_spec='-L$libdir' + hardcode_shlibpath_var=no + ;; + + # FreeBSD 2.2.[012] allows us to include c++rt0.o to get C++ constructor + # support. Future versions do this automatically, but an explicit c++rt0.o + # does not break anything, and helps significantly (at the cost of a little + # extra space). + freebsd2.2*) + archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags /usr/lib/c++rt0.o' + hardcode_libdir_flag_spec='-R$libdir' + hardcode_direct=yes + hardcode_shlibpath_var=no + ;; + + # Unfortunately, older versions of FreeBSD 2 do not have this feature. + freebsd2.*) + archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' + hardcode_direct=yes + hardcode_minus_L=yes + hardcode_shlibpath_var=no + ;; + + # FreeBSD 3 and greater uses gcc -shared to do shared libraries. + freebsd* | dragonfly*) + archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + hardcode_libdir_flag_spec='-R$libdir' + hardcode_direct=yes + hardcode_shlibpath_var=no + ;; + + hpux9*) + if test yes = "$GCC"; then + archive_cmds='$RM $output_objdir/$soname~$CC -shared $pic_flag $wl+b $wl$install_libdir -o $output_objdir/$soname $libobjs $deplibs $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' + else + archive_cmds='$RM $output_objdir/$soname~$LD -b +b $install_libdir -o $output_objdir/$soname $libobjs $deplibs $linker_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' + fi + hardcode_libdir_flag_spec='$wl+b $wl$libdir' + hardcode_libdir_separator=: + hardcode_direct=yes + + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + hardcode_minus_L=yes + export_dynamic_flag_spec='$wl-E' + ;; + + hpux10*) + if test yes,no = "$GCC,$with_gnu_ld"; then + archive_cmds='$CC -shared $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags' + else + archive_cmds='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags' + fi + if test no = "$with_gnu_ld"; then + hardcode_libdir_flag_spec='$wl+b $wl$libdir' + hardcode_libdir_separator=: + hardcode_direct=yes + hardcode_direct_absolute=yes + export_dynamic_flag_spec='$wl-E' + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + hardcode_minus_L=yes + fi + ;; + + hpux11*) + if test yes,no = "$GCC,$with_gnu_ld"; then + case $host_cpu in + hppa*64*) + archive_cmds='$CC -shared $wl+h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + ia64*) + archive_cmds='$CC -shared $pic_flag $wl+h $wl$soname $wl+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + archive_cmds='$CC -shared $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + else + case $host_cpu in + hppa*64*) + archive_cmds='$CC -b $wl+h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + ia64*) + archive_cmds='$CC -b $wl+h $wl$soname $wl+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + + # Older versions of the 11.00 compiler do not understand -b yet + # (HP92453-01 A.11.01.20 doesn't, HP92453-01 B.11.X.35175-35176.GP does) + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC understands -b" >&5 +$as_echo_n "checking if $CC understands -b... " >&6; } +if ${lt_cv_prog_compiler__b+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler__b=no + save_LDFLAGS=$LDFLAGS + LDFLAGS="$LDFLAGS -b" + echo "$lt_simple_link_test_code" > conftest.$ac_ext + if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then + # The linker can only warn and ignore the option if not recognized + # So say no if there are warnings + if test -s conftest.err; then + # Append any errors to the config.log. + cat conftest.err 1>&5 + $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if diff conftest.exp conftest.er2 >/dev/null; then + lt_cv_prog_compiler__b=yes + fi + else + lt_cv_prog_compiler__b=yes + fi + fi + $RM -r conftest* + LDFLAGS=$save_LDFLAGS + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler__b" >&5 +$as_echo "$lt_cv_prog_compiler__b" >&6; } + +if test yes = "$lt_cv_prog_compiler__b"; then + archive_cmds='$CC -b $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags' +else + archive_cmds='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags' +fi + + ;; + esac + fi + if test no = "$with_gnu_ld"; then + hardcode_libdir_flag_spec='$wl+b $wl$libdir' + hardcode_libdir_separator=: + + case $host_cpu in + hppa*64*|ia64*) + hardcode_direct=no + hardcode_shlibpath_var=no + ;; + *) + hardcode_direct=yes + hardcode_direct_absolute=yes + export_dynamic_flag_spec='$wl-E' + + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + hardcode_minus_L=yes + ;; + esac + fi + ;; + + irix5* | irix6* | nonstopux*) + if test yes = "$GCC"; then + archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + # Try to use the -exported_symbol ld option, if it does not + # work, assume that -exports_file does not work either and + # implicitly export all symbols. + # This should be the same for all languages, so no per-tag cache variable. + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the $host_os linker accepts -exported_symbol" >&5 +$as_echo_n "checking whether the $host_os linker accepts -exported_symbol... " >&6; } +if ${lt_cv_irix_exported_symbol+:} false; then : + $as_echo_n "(cached) " >&6 +else + save_LDFLAGS=$LDFLAGS + LDFLAGS="$LDFLAGS -shared $wl-exported_symbol ${wl}foo $wl-update_registry $wl/dev/null" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +int foo (void) { return 0; } +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + lt_cv_irix_exported_symbol=yes +else + lt_cv_irix_exported_symbol=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + LDFLAGS=$save_LDFLAGS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_irix_exported_symbol" >&5 +$as_echo "$lt_cv_irix_exported_symbol" >&6; } + if test yes = "$lt_cv_irix_exported_symbol"; then + archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations $wl-exports_file $wl$export_symbols -o $lib' + fi + else + archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + archive_expsym_cmds='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -exports_file $export_symbols -o $lib' + fi + archive_cmds_need_lc='no' + hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' + hardcode_libdir_separator=: + inherit_rpath=yes + link_all_deplibs=yes + ;; + + linux*) + case $cc_basename in + tcc*) + # Fabrice Bellard et al's Tiny C Compiler + ld_shlibs=yes + archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + ;; + + netbsd*) + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' # a.out + else + archive_cmds='$LD -shared -o $lib $libobjs $deplibs $linker_flags' # ELF + fi + hardcode_libdir_flag_spec='-R$libdir' + hardcode_direct=yes + hardcode_shlibpath_var=no + ;; + + newsos6) + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_direct=yes + hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' + hardcode_libdir_separator=: + hardcode_shlibpath_var=no + ;; + + *nto* | *qnx*) + ;; + + openbsd* | bitrig*) + if test -f /usr/libexec/ld.so; then + hardcode_direct=yes + hardcode_shlibpath_var=no + hardcode_direct_absolute=yes + if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then + archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags $wl-retain-symbols-file,$export_symbols' + hardcode_libdir_flag_spec='$wl-rpath,$libdir' + export_dynamic_flag_spec='$wl-E' + else + archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + hardcode_libdir_flag_spec='$wl-rpath,$libdir' + fi + else + ld_shlibs=no + fi + ;; + + os2*) + hardcode_libdir_flag_spec='-L$libdir' + hardcode_minus_L=yes + allow_undefined_flag=unsupported + shrext_cmds=.dll + archive_cmds='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + archive_expsym_cmds='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + prefix_cmds="$SED"~ + if test EXPORTS = "`$SED 1q $export_symbols`"; then + prefix_cmds="$prefix_cmds -e 1d"; + fi~ + prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~ + cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + old_archive_From_new_cmds='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def' + enable_shared_with_static_runtimes=yes + ;; + + osf3*) + if test yes = "$GCC"; then + allow_undefined_flag=' $wl-expect_unresolved $wl\*' + archive_cmds='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + else + allow_undefined_flag=' -expect_unresolved \*' + archive_cmds='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + fi + archive_cmds_need_lc='no' + hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' + hardcode_libdir_separator=: + ;; + + osf4* | osf5*) # as osf3* with the addition of -msym flag + if test yes = "$GCC"; then + allow_undefined_flag=' $wl-expect_unresolved $wl\*' + archive_cmds='$CC -shared$allow_undefined_flag $pic_flag $libobjs $deplibs $compiler_flags $wl-msym $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' + else + allow_undefined_flag=' -expect_unresolved \*' + archive_cmds='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + archive_expsym_cmds='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done; printf "%s\\n" "-hidden">> $lib.exp~ + $CC -shared$allow_undefined_flag $wl-input $wl$lib.exp $compiler_flags $libobjs $deplibs -soname $soname `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib~$RM $lib.exp' + + # Both c and cxx compiler support -rpath directly + hardcode_libdir_flag_spec='-rpath $libdir' + fi + archive_cmds_need_lc='no' + hardcode_libdir_separator=: + ;; + + solaris*) + no_undefined_flag=' -z defs' + if test yes = "$GCC"; then + wlarc='$wl' + archive_cmds='$CC -shared $pic_flag $wl-z ${wl}text $wl-h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -shared $pic_flag $wl-z ${wl}text $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp' + else + case `$CC -V 2>&1` in + *"Compilers 5.0"*) + wlarc='' + archive_cmds='$LD -G$allow_undefined_flag -h $soname -o $lib $libobjs $deplibs $linker_flags' + archive_expsym_cmds='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $LD -G$allow_undefined_flag -M $lib.exp -h $soname -o $lib $libobjs $deplibs $linker_flags~$RM $lib.exp' + ;; + *) + wlarc='$wl' + archive_cmds='$CC -G$allow_undefined_flag -h $soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -G$allow_undefined_flag -M $lib.exp -h $soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp' + ;; + esac + fi + hardcode_libdir_flag_spec='-R$libdir' + hardcode_shlibpath_var=no + case $host_os in + solaris2.[0-5] | solaris2.[0-5].*) ;; + *) + # The compiler driver will combine and reorder linker options, + # but understands '-z linker_flag'. GCC discards it without '$wl', + # but is careful enough not to reorder. + # Supported since Solaris 2.6 (maybe 2.5.1?) + if test yes = "$GCC"; then + whole_archive_flag_spec='$wl-z ${wl}allextract$convenience $wl-z ${wl}defaultextract' + else + whole_archive_flag_spec='-z allextract$convenience -z defaultextract' + fi + ;; + esac + link_all_deplibs=yes + ;; + + sunos4*) + if test sequent = "$host_vendor"; then + # Use $CC to link under sequent, because it throws in some extra .o + # files that make .init and .fini sections work. + archive_cmds='$CC -G $wl-h $soname -o $lib $libobjs $deplibs $compiler_flags' + else + archive_cmds='$LD -assert pure-text -Bstatic -o $lib $libobjs $deplibs $linker_flags' + fi + hardcode_libdir_flag_spec='-L$libdir' + hardcode_direct=yes + hardcode_minus_L=yes + hardcode_shlibpath_var=no + ;; + + sysv4) + case $host_vendor in + sni) + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_direct=yes # is this really true??? + ;; + siemens) + ## LD is ld it makes a PLAMLIB + ## CC just makes a GrossModule. + archive_cmds='$LD -G -o $lib $libobjs $deplibs $linker_flags' + reload_cmds='$CC -r -o $output$reload_objs' + hardcode_direct=no + ;; + motorola) + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_direct=no #Motorola manual says yes, but my tests say they lie + ;; + esac + runpath_var='LD_RUN_PATH' + hardcode_shlibpath_var=no + ;; + + sysv4.3*) + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_shlibpath_var=no + export_dynamic_flag_spec='-Bexport' + ;; + + sysv4*MP*) + if test -d /usr/nec; then + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_shlibpath_var=no + runpath_var=LD_RUN_PATH + hardcode_runpath_var=yes + ld_shlibs=yes + fi + ;; + + sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[01].[10]* | unixware7* | sco3.2v5.0.[024]*) + no_undefined_flag='$wl-z,text' + archive_cmds_need_lc=no + hardcode_shlibpath_var=no + runpath_var='LD_RUN_PATH' + + if test yes = "$GCC"; then + archive_cmds='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + else + archive_cmds='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + fi + ;; + + sysv5* | sco3.2v5* | sco5v6*) + # Note: We CANNOT use -z defs as we might desire, because we do not + # link with -lc, and that would cause any symbols used from libc to + # always be unresolved, which means just about no library would + # ever link correctly. If we're not using GNU ld we use -z text + # though, which does catch some bad symbols but isn't as heavy-handed + # as -z defs. + no_undefined_flag='$wl-z,text' + allow_undefined_flag='$wl-z,nodefs' + archive_cmds_need_lc=no + hardcode_shlibpath_var=no + hardcode_libdir_flag_spec='$wl-R,$libdir' + hardcode_libdir_separator=':' + link_all_deplibs=yes + export_dynamic_flag_spec='$wl-Bexport' + runpath_var='LD_RUN_PATH' + + if test yes = "$GCC"; then + archive_cmds='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + else + archive_cmds='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + fi + ;; + + uts4*) + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_libdir_flag_spec='-L$libdir' + hardcode_shlibpath_var=no + ;; + + *) + ld_shlibs=no + ;; + esac + + if test sni = "$host_vendor"; then + case $host in + sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*) + export_dynamic_flag_spec='$wl-Blargedynsym' + ;; + esac + fi + fi + +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ld_shlibs" >&5 +$as_echo "$ld_shlibs" >&6; } +test no = "$ld_shlibs" && can_build_shared=no + +with_gnu_ld=$with_gnu_ld + + + + + + + + + + + + + + + +# +# Do we need to explicitly link libc? +# +case "x$archive_cmds_need_lc" in +x|xyes) + # Assume -lc should be added + archive_cmds_need_lc=yes + + if test yes,yes = "$GCC,$enable_shared"; then + case $archive_cmds in + *'~'*) + # FIXME: we may have to deal with multi-command sequences. + ;; + '$CC '*) + # Test whether the compiler implicitly links with -lc since on some + # systems, -lgcc has to come before -lc. If gcc already passes -lc + # to ld, don't add -lc before -lgcc. + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether -lc should be explicitly linked in" >&5 +$as_echo_n "checking whether -lc should be explicitly linked in... " >&6; } +if ${lt_cv_archive_cmds_need_lc+:} false; then : + $as_echo_n "(cached) " >&6 +else + $RM conftest* + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } 2>conftest.err; then + soname=conftest + lib=conftest + libobjs=conftest.$ac_objext + deplibs= + wl=$lt_prog_compiler_wl + pic_flag=$lt_prog_compiler_pic + compiler_flags=-v + linker_flags=-v + verstring= + output_objdir=. + libname=conftest + lt_save_allow_undefined_flag=$allow_undefined_flag + allow_undefined_flag= + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$archive_cmds 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1\""; } >&5 + (eval $archive_cmds 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } + then + lt_cv_archive_cmds_need_lc=no + else + lt_cv_archive_cmds_need_lc=yes + fi + allow_undefined_flag=$lt_save_allow_undefined_flag + else + cat conftest.err 1>&5 + fi + $RM conftest* + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_archive_cmds_need_lc" >&5 +$as_echo "$lt_cv_archive_cmds_need_lc" >&6; } + archive_cmds_need_lc=$lt_cv_archive_cmds_need_lc + ;; + esac + fi + ;; +esac + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking dynamic linker characteristics" >&5 +$as_echo_n "checking dynamic linker characteristics... " >&6; } + +if test yes = "$GCC"; then + case $host_os in + darwin*) lt_awk_arg='/^libraries:/,/LR/' ;; + *) lt_awk_arg='/^libraries:/' ;; + esac + case $host_os in + mingw* | cegcc*) lt_sed_strip_eq='s|=\([A-Za-z]:\)|\1|g' ;; + *) lt_sed_strip_eq='s|=/|/|g' ;; + esac + lt_search_path_spec=`$CC -print-search-dirs | awk $lt_awk_arg | $SED -e "s/^libraries://" -e $lt_sed_strip_eq` + case $lt_search_path_spec in + *\;*) + # if the path contains ";" then we assume it to be the separator + # otherwise default to the standard path separator (i.e. ":") - it is + # assumed that no part of a normal pathname contains ";" but that should + # okay in the real world where ";" in dirpaths is itself problematic. + lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED 's/;/ /g'` + ;; + *) + lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED "s/$PATH_SEPARATOR/ /g"` + ;; + esac + # Ok, now we have the path, separated by spaces, we can step through it + # and add multilib dir if necessary... + lt_tmp_lt_search_path_spec= + lt_multi_os_dir=/`$CC $CPPFLAGS $CFLAGS $LDFLAGS -print-multi-os-directory 2>/dev/null` + # ...but if some path component already ends with the multilib dir we assume + # that all is fine and trust -print-search-dirs as is (GCC 4.2? or newer). + case "$lt_multi_os_dir; $lt_search_path_spec " in + "/; "* | "/.; "* | "/./; "* | *"$lt_multi_os_dir "* | *"$lt_multi_os_dir/ "*) + lt_multi_os_dir= + ;; + esac + for lt_sys_path in $lt_search_path_spec; do + if test -d "$lt_sys_path$lt_multi_os_dir"; then + lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path$lt_multi_os_dir" + elif test -n "$lt_multi_os_dir"; then + test -d "$lt_sys_path" && \ + lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path" + fi + done + lt_search_path_spec=`$ECHO "$lt_tmp_lt_search_path_spec" | awk ' +BEGIN {RS = " "; FS = "/|\n";} { + lt_foo = ""; + lt_count = 0; + for (lt_i = NF; lt_i > 0; lt_i--) { + if ($lt_i != "" && $lt_i != ".") { + if ($lt_i == "..") { + lt_count++; + } else { + if (lt_count == 0) { + lt_foo = "/" $lt_i lt_foo; + } else { + lt_count--; + } + } + } + } + if (lt_foo != "") { lt_freq[lt_foo]++; } + if (lt_freq[lt_foo] == 1) { print lt_foo; } +}'` + # AWK program above erroneously prepends '/' to C:/dos/paths + # for these hosts. + case $host_os in + mingw* | cegcc*) lt_search_path_spec=`$ECHO "$lt_search_path_spec" |\ + $SED 's|/\([A-Za-z]:\)|\1|g'` ;; + esac + sys_lib_search_path_spec=`$ECHO "$lt_search_path_spec" | $lt_NL2SP` +else + sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib" +fi +library_names_spec= +libname_spec='lib$name' +soname_spec= +shrext_cmds=.so +postinstall_cmds= +postuninstall_cmds= +finish_cmds= +finish_eval= +shlibpath_var= +shlibpath_overrides_runpath=unknown +version_type=none +dynamic_linker="$host_os ld.so" +sys_lib_dlsearch_path_spec="/lib /usr/lib" +need_lib_prefix=unknown +hardcode_into_libs=no + +# when you set need_version to no, make sure it does not cause -set_version +# flags to be left without arguments +need_version=unknown + + + +case $host_os in +aix3*) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname.a' + shlibpath_var=LIBPATH + + # AIX 3 has no versioning support, so we append a major version to the name. + soname_spec='$libname$release$shared_ext$major' + ;; + +aix[4-9]*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + hardcode_into_libs=yes + if test ia64 = "$host_cpu"; then + # AIX 5 supports IA64 + library_names_spec='$libname$release$shared_ext$major $libname$release$shared_ext$versuffix $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + else + # With GCC up to 2.95.x, collect2 would create an import file + # for dependence libraries. The import file would start with + # the line '#! .'. This would cause the generated library to + # depend on '.', always an invalid library. This was fixed in + # development snapshots of GCC prior to 3.0. + case $host_os in + aix4 | aix4.[01] | aix4.[01].*) + if { echo '#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 97)' + echo ' yes ' + echo '#endif'; } | $CC -E - | $GREP yes > /dev/null; then + : + else + can_build_shared=no + fi + ;; + esac + # Using Import Files as archive members, it is possible to support + # filename-based versioning of shared library archives on AIX. While + # this would work for both with and without runtime linking, it will + # prevent static linking of such archives. So we do filename-based + # shared library versioning with .so extension only, which is used + # when both runtime linking and shared linking is enabled. + # Unfortunately, runtime linking may impact performance, so we do + # not want this to be the default eventually. Also, we use the + # versioned .so libs for executables only if there is the -brtl + # linker flag in LDFLAGS as well, or --with-aix-soname=svr4 only. + # To allow for filename-based versioning support, we need to create + # libNAME.so.V as an archive file, containing: + # *) an Import File, referring to the versioned filename of the + # archive as well as the shared archive member, telling the + # bitwidth (32 or 64) of that shared object, and providing the + # list of exported symbols of that shared object, eventually + # decorated with the 'weak' keyword + # *) the shared object with the F_LOADONLY flag set, to really avoid + # it being seen by the linker. + # At run time we better use the real file rather than another symlink, + # but for link time we create the symlink libNAME.so -> libNAME.so.V + + case $with_aix_soname,$aix_use_runtimelinking in + # AIX (on Power*) has no versioning support, so currently we cannot hardcode correct + # soname into executable. Probably we can add versioning support to + # collect2, so additional links can be useful in future. + aix,yes) # traditional libtool + dynamic_linker='AIX unversionable lib.so' + # If using run time linking (on AIX 4.2 or later) use lib.so + # instead of lib.a to let people know that these are not + # typical AIX shared libraries. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + ;; + aix,no) # traditional AIX only + dynamic_linker='AIX lib.a(lib.so.V)' + # We preserve .a as extension for shared libraries through AIX4.2 + # and later when we are not doing run time linking. + library_names_spec='$libname$release.a $libname.a' + soname_spec='$libname$release$shared_ext$major' + ;; + svr4,*) # full svr4 only + dynamic_linker="AIX lib.so.V($shared_archive_member_spec.o)" + library_names_spec='$libname$release$shared_ext$major $libname$shared_ext' + # We do not specify a path in Import Files, so LIBPATH fires. + shlibpath_overrides_runpath=yes + ;; + *,yes) # both, prefer svr4 + dynamic_linker="AIX lib.so.V($shared_archive_member_spec.o), lib.a(lib.so.V)" + library_names_spec='$libname$release$shared_ext$major $libname$shared_ext' + # unpreferred sharedlib libNAME.a needs extra handling + postinstall_cmds='test -n "$linkname" || linkname="$realname"~func_stripname "" ".so" "$linkname"~$install_shared_prog "$dir/$func_stripname_result.$libext" "$destdir/$func_stripname_result.$libext"~test -z "$tstripme" || test -z "$striplib" || $striplib "$destdir/$func_stripname_result.$libext"' + postuninstall_cmds='for n in $library_names $old_library; do :; done~func_stripname "" ".so" "$n"~test "$func_stripname_result" = "$n" || func_append rmfiles " $odir/$func_stripname_result.$libext"' + # We do not specify a path in Import Files, so LIBPATH fires. + shlibpath_overrides_runpath=yes + ;; + *,no) # both, prefer aix + dynamic_linker="AIX lib.a(lib.so.V), lib.so.V($shared_archive_member_spec.o)" + library_names_spec='$libname$release.a $libname.a' + soname_spec='$libname$release$shared_ext$major' + # unpreferred sharedlib libNAME.so.V and symlink libNAME.so need extra handling + postinstall_cmds='test -z "$dlname" || $install_shared_prog $dir/$dlname $destdir/$dlname~test -z "$tstripme" || test -z "$striplib" || $striplib $destdir/$dlname~test -n "$linkname" || linkname=$realname~func_stripname "" ".a" "$linkname"~(cd "$destdir" && $LN_S -f $dlname $func_stripname_result.so)' + postuninstall_cmds='test -z "$dlname" || func_append rmfiles " $odir/$dlname"~for n in $old_library $library_names; do :; done~func_stripname "" ".a" "$n"~func_append rmfiles " $odir/$func_stripname_result.so"' + ;; + esac + shlibpath_var=LIBPATH + fi + ;; + +amigaos*) + case $host_cpu in + powerpc) + # Since July 2007 AmigaOS4 officially supports .so libraries. + # When compiling the executable, add -use-dynld -Lsobjs: to the compileline. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + ;; + m68k) + library_names_spec='$libname.ixlibrary $libname.a' + # Create ${libname}_ixlibrary.a entries in /sys/libs. + finish_eval='for lib in `ls $libdir/*.ixlibrary 2>/dev/null`; do libname=`func_echo_all "$lib" | $SED '\''s%^.*/\([^/]*\)\.ixlibrary$%\1%'\''`; $RM /sys/libs/${libname}_ixlibrary.a; $show "cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a"; cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a || exit 1; done' + ;; + esac + ;; + +beos*) + library_names_spec='$libname$shared_ext' + dynamic_linker="$host_os ld.so" + shlibpath_var=LIBRARY_PATH + ;; + +bsdi[45]*) + version_type=linux # correct to gnu/linux during the next big refactor + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib" + sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib" + # the default ld.so.conf also contains /usr/contrib/lib and + # /usr/X11R6/lib (/usr/X11 is a link to /usr/X11R6), but let us allow + # libtool to hard-code these into programs + ;; + +cygwin* | mingw* | pw32* | cegcc*) + version_type=windows + shrext_cmds=.dll + need_version=no + need_lib_prefix=no + + case $GCC,$cc_basename in + yes,*) + # gcc + library_names_spec='$libname.dll.a' + # DLL is installed to $(libdir)/../bin by postinstall_cmds + postinstall_cmds='base_file=`basename \$file`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname~ + chmod a+x \$dldir/$dlname~ + if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then + eval '\''$striplib \$dldir/$dlname'\'' || exit \$?; + fi' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $RM \$dlpath' + shlibpath_overrides_runpath=yes + + case $host_os in + cygwin*) + # Cygwin DLLs use 'cyg' prefix rather than 'lib' + soname_spec='`echo $libname | sed -e 's/^lib/cyg/'``echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' + + sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/lib/w32api" + ;; + mingw* | cegcc*) + # MinGW DLLs use traditional 'lib' prefix + soname_spec='$libname`echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' + ;; + pw32*) + # pw32 DLLs use 'pw' prefix rather than 'lib' + library_names_spec='`echo $libname | sed -e 's/^lib/pw/'``echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' + ;; + esac + dynamic_linker='Win32 ld.exe' + ;; + + *,cl*) + # Native MSVC + libname_spec='$name' + soname_spec='$libname`echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' + library_names_spec='$libname.dll.lib' + + case $build_os in + mingw*) + sys_lib_search_path_spec= + lt_save_ifs=$IFS + IFS=';' + for lt_path in $LIB + do + IFS=$lt_save_ifs + # Let DOS variable expansion print the short 8.3 style file name. + lt_path=`cd "$lt_path" 2>/dev/null && cmd //C "for %i in (".") do @echo %~si"` + sys_lib_search_path_spec="$sys_lib_search_path_spec $lt_path" + done + IFS=$lt_save_ifs + # Convert to MSYS style. + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | sed -e 's|\\\\|/|g' -e 's| \\([a-zA-Z]\\):| /\\1|g' -e 's|^ ||'` + ;; + cygwin*) + # Convert to unix form, then to dos form, then back to unix form + # but this time dos style (no spaces!) so that the unix form looks + # like /cygdrive/c/PROGRA~1:/cygdr... + sys_lib_search_path_spec=`cygpath --path --unix "$LIB"` + sys_lib_search_path_spec=`cygpath --path --dos "$sys_lib_search_path_spec" 2>/dev/null` + sys_lib_search_path_spec=`cygpath --path --unix "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` + ;; + *) + sys_lib_search_path_spec=$LIB + if $ECHO "$sys_lib_search_path_spec" | $GREP ';[c-zC-Z]:/' >/dev/null; then + # It is most probably a Windows format PATH. + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e 's/;/ /g'` + else + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` + fi + # FIXME: find the short name or the path components, as spaces are + # common. (e.g. "Program Files" -> "PROGRA~1") + ;; + esac + + # DLL is installed to $(libdir)/../bin by postinstall_cmds + postinstall_cmds='base_file=`basename \$file`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $RM \$dlpath' + shlibpath_overrides_runpath=yes + dynamic_linker='Win32 link.exe' + ;; + + *) + # Assume MSVC wrapper + library_names_spec='$libname`echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext $libname.lib' + dynamic_linker='Win32 ld.exe' + ;; + esac + # FIXME: first we should search . and the directory the executable is in + shlibpath_var=PATH + ;; + +darwin* | rhapsody*) + dynamic_linker="$host_os dyld" + version_type=darwin + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$major$shared_ext $libname$shared_ext' + soname_spec='$libname$release$major$shared_ext' + shlibpath_overrides_runpath=yes + shlibpath_var=DYLD_LIBRARY_PATH + shrext_cmds='`test .$module = .yes && echo .so || echo .dylib`' + + sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/local/lib" + sys_lib_dlsearch_path_spec='/usr/local/lib /lib /usr/lib' + ;; + +dgux*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +freebsd* | dragonfly*) + # DragonFly does not have aout. When/if they implement a new + # versioning mechanism, adjust this. + if test -x /usr/bin/objformat; then + objformat=`/usr/bin/objformat` + else + case $host_os in + freebsd[23].*) objformat=aout ;; + *) objformat=elf ;; + esac + fi + version_type=freebsd-$objformat + case $version_type in + freebsd-elf*) + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + need_version=no + need_lib_prefix=no + ;; + freebsd-*) + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + need_version=yes + ;; + esac + shlibpath_var=LD_LIBRARY_PATH + case $host_os in + freebsd2.*) + shlibpath_overrides_runpath=yes + ;; + freebsd3.[01]* | freebsdelf3.[01]*) + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + freebsd3.[2-9]* | freebsdelf3.[2-9]* | \ + freebsd4.[0-5] | freebsdelf4.[0-5] | freebsd4.1.1 | freebsdelf4.1.1) + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + *) # from 4.6 on, and DragonFly + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + esac + ;; + +haiku*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + dynamic_linker="$host_os runtime_loader" + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LIBRARY_PATH + shlibpath_overrides_runpath=no + sys_lib_dlsearch_path_spec='/boot/home/config/lib /boot/common/lib /boot/system/lib' + hardcode_into_libs=yes + ;; + +hpux9* | hpux10* | hpux11*) + # Give a soname corresponding to the major version so that dld.sl refuses to + # link against other versions. + version_type=sunos + need_lib_prefix=no + need_version=no + case $host_cpu in + ia64*) + shrext_cmds='.so' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.so" + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + if test 32 = "$HPUX_IA64_MODE"; then + sys_lib_search_path_spec="/usr/lib/hpux32 /usr/local/lib/hpux32 /usr/local/lib" + sys_lib_dlsearch_path_spec=/usr/lib/hpux32 + else + sys_lib_search_path_spec="/usr/lib/hpux64 /usr/local/lib/hpux64" + sys_lib_dlsearch_path_spec=/usr/lib/hpux64 + fi + ;; + hppa*64*) + shrext_cmds='.sl' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.sl" + shlibpath_var=LD_LIBRARY_PATH # How should we handle SHLIB_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + sys_lib_search_path_spec="/usr/lib/pa20_64 /usr/ccs/lib/pa20_64" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + *) + shrext_cmds='.sl' + dynamic_linker="$host_os dld.sl" + shlibpath_var=SHLIB_PATH + shlibpath_overrides_runpath=no # +s is required to enable SHLIB_PATH + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + ;; + esac + # HP-UX runs *really* slowly unless shared libraries are mode 555, ... + postinstall_cmds='chmod 555 $lib' + # or fails outright, so override atomically: + install_override_mode=555 + ;; + +interix[3-9]*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + dynamic_linker='Interix 3.x ld.so.1 (PE, like ELF)' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + +irix5* | irix6* | nonstopux*) + case $host_os in + nonstopux*) version_type=nonstopux ;; + *) + if test yes = "$lt_cv_prog_gnu_ld"; then + version_type=linux # correct to gnu/linux during the next big refactor + else + version_type=irix + fi ;; + esac + need_lib_prefix=no + need_version=no + soname_spec='$libname$release$shared_ext$major' + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$release$shared_ext $libname$shared_ext' + case $host_os in + irix5* | nonstopux*) + libsuff= shlibsuff= + ;; + *) + case $LD in # libtool.m4 will add one of these switches to LD + *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ") + libsuff= shlibsuff= libmagic=32-bit;; + *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ") + libsuff=32 shlibsuff=N32 libmagic=N32;; + *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ") + libsuff=64 shlibsuff=64 libmagic=64-bit;; + *) libsuff= shlibsuff= libmagic=never-match;; + esac + ;; + esac + shlibpath_var=LD_LIBRARY${shlibsuff}_PATH + shlibpath_overrides_runpath=no + sys_lib_search_path_spec="/usr/lib$libsuff /lib$libsuff /usr/local/lib$libsuff" + sys_lib_dlsearch_path_spec="/usr/lib$libsuff /lib$libsuff" + hardcode_into_libs=yes + ;; + +# No shared lib support for Linux oldld, aout, or coff. +linux*oldld* | linux*aout* | linux*coff*) + dynamic_linker=no + ;; + +linux*android*) + version_type=none # Android doesn't support versioned libraries. + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext' + soname_spec='$libname$release$shared_ext' + finish_cmds= + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + + # This implies no fast_install, which is unacceptable. + # Some rework will be needed to allow for fast_install + # before this can be enabled. + hardcode_into_libs=yes + + dynamic_linker='Android linker' + # Don't embed -rpath directories since the linker doesn't support them. + hardcode_libdir_flag_spec='-L$libdir' + ;; + +# This must be glibc/ELF. +linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -n $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + + # Some binutils ld are patched to set DT_RUNPATH + if ${lt_cv_shlibpath_overrides_runpath+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_shlibpath_overrides_runpath=no + save_LDFLAGS=$LDFLAGS + save_libdir=$libdir + eval "libdir=/foo; wl=\"$lt_prog_compiler_wl\"; \ + LDFLAGS=\"\$LDFLAGS $hardcode_libdir_flag_spec\"" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + if ($OBJDUMP -p conftest$ac_exeext) 2>/dev/null | grep "RUNPATH.*$libdir" >/dev/null; then : + lt_cv_shlibpath_overrides_runpath=yes +fi +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + LDFLAGS=$save_LDFLAGS + libdir=$save_libdir + +fi + + shlibpath_overrides_runpath=$lt_cv_shlibpath_overrides_runpath + + # This implies no fast_install, which is unacceptable. + # Some rework will be needed to allow for fast_install + # before this can be enabled. + hardcode_into_libs=yes + + # Ideally, we could use ldconfig to report *all* directores which are + # searched for libraries, however this is still not possible. Aside from not + # being certain /sbin/ldconfig is available, command + # 'ldconfig -N -X -v | grep ^/' on 64bit Fedora does not report /usr/lib64, + # even though it is searched at run-time. Try to do the best guess by + # appending ld.so.conf contents (and includes) to the search path. + if test -f /etc/ld.so.conf; then + lt_ld_extra=`awk '/^include / { system(sprintf("cd /etc; cat %s 2>/dev/null", \$2)); skip = 1; } { if (!skip) print \$0; skip = 0; }' < /etc/ld.so.conf | $SED -e 's/#.*//;/^[ ]*hwcap[ ]/d;s/[:, ]/ /g;s/=[^=]*$//;s/=[^= ]* / /g;s/"//g;/^$/d' | tr '\n' ' '` + sys_lib_dlsearch_path_spec="/lib /usr/lib $lt_ld_extra" + fi + + # We used to test for /lib/ld.so.1 and disable shared libraries on + # powerpc, because MkLinux only supported shared libraries with the + # GNU dynamic linker. Since this was broken with cross compilers, + # most powerpc-linux boxes support dynamic linking these days and + # people can always --disable-shared, the test was removed, and we + # assume the GNU/Linux dynamic linker is in use. + dynamic_linker='GNU/Linux ld.so' + ;; + +netbsd*) + version_type=sunos + need_lib_prefix=no + need_version=no + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + dynamic_linker='NetBSD (a.out) ld.so' + else + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + dynamic_linker='NetBSD ld.elf_so' + fi + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + +newsos6) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + ;; + +*nto* | *qnx*) + version_type=qnx + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + dynamic_linker='ldqnx.so' + ;; + +openbsd* | bitrig*) + version_type=sunos + sys_lib_dlsearch_path_spec=/usr/lib + need_lib_prefix=no + if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then + need_version=no + else + need_version=yes + fi + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + ;; + +os2*) + libname_spec='$name' + version_type=windows + shrext_cmds=.dll + need_version=no + need_lib_prefix=no + # OS/2 can only load a DLL with a base name of 8 characters or less. + soname_spec='`test -n "$os2dllname" && libname="$os2dllname"; + v=$($ECHO $release$versuffix | tr -d .-); + n=$($ECHO $libname | cut -b -$((8 - ${#v})) | tr . _); + $ECHO $n$v`$shared_ext' + library_names_spec='${libname}_dll.$libext' + dynamic_linker='OS/2 ld.exe' + shlibpath_var=BEGINLIBPATH + sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + postinstall_cmds='base_file=`basename \$file`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; $ECHO \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname~ + chmod a+x \$dldir/$dlname~ + if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then + eval '\''$striplib \$dldir/$dlname'\'' || exit \$?; + fi' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; $ECHO \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $RM \$dlpath' + ;; + +osf3* | osf4* | osf5*) + version_type=osf + need_lib_prefix=no + need_version=no + soname_spec='$libname$release$shared_ext$major' + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + +rdos*) + dynamic_linker=no + ;; + +solaris*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + # ldd complains unless libraries are executable + postinstall_cmds='chmod +x $lib' + ;; + +sunos4*) + version_type=sunos + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + finish_cmds='PATH="\$PATH:/usr/etc" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + if test yes = "$with_gnu_ld"; then + need_lib_prefix=no + fi + need_version=yes + ;; + +sysv4 | sysv4.3*) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + case $host_vendor in + sni) + shlibpath_overrides_runpath=no + need_lib_prefix=no + runpath_var=LD_RUN_PATH + ;; + siemens) + need_lib_prefix=no + ;; + motorola) + need_lib_prefix=no + need_version=no + shlibpath_overrides_runpath=no + sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib' + ;; + esac + ;; + +sysv4*MP*) + if test -d /usr/nec; then + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$shared_ext.$versuffix $libname$shared_ext.$major $libname$shared_ext' + soname_spec='$libname$shared_ext.$major' + shlibpath_var=LD_LIBRARY_PATH + fi + ;; + +sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) + version_type=sco + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + if test yes = "$with_gnu_ld"; then + sys_lib_search_path_spec='/usr/local/lib /usr/gnu/lib /usr/ccs/lib /usr/lib /lib' + else + sys_lib_search_path_spec='/usr/ccs/lib /usr/lib' + case $host_os in + sco3.2v5*) + sys_lib_search_path_spec="$sys_lib_search_path_spec /lib" + ;; + esac + fi + sys_lib_dlsearch_path_spec='/usr/lib' + ;; + +tpf*) + # TPF is a cross-target only. Preferred cross-host = GNU/Linux. + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + +uts4*) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +*) + dynamic_linker=no + ;; +esac +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $dynamic_linker" >&5 +$as_echo "$dynamic_linker" >&6; } +test no = "$dynamic_linker" && can_build_shared=no + +variables_saved_for_relink="PATH $shlibpath_var $runpath_var" +if test yes = "$GCC"; then + variables_saved_for_relink="$variables_saved_for_relink GCC_EXEC_PREFIX COMPILER_PATH LIBRARY_PATH" +fi + +if test set = "${lt_cv_sys_lib_search_path_spec+set}"; then + sys_lib_search_path_spec=$lt_cv_sys_lib_search_path_spec +fi + +if test set = "${lt_cv_sys_lib_dlsearch_path_spec+set}"; then + sys_lib_dlsearch_path_spec=$lt_cv_sys_lib_dlsearch_path_spec +fi + +# remember unaugmented sys_lib_dlsearch_path content for libtool script decls... +configure_time_dlsearch_path=$sys_lib_dlsearch_path_spec + +# ... but it needs LT_SYS_LIBRARY_PATH munging for other configure-time code +func_munge_path_list sys_lib_dlsearch_path_spec "$LT_SYS_LIBRARY_PATH" + +# to be used as default LT_SYS_LIBRARY_PATH value in generated libtool +configure_time_lt_sys_library_path=$LT_SYS_LIBRARY_PATH + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to hardcode library paths into programs" >&5 +$as_echo_n "checking how to hardcode library paths into programs... " >&6; } +hardcode_action= +if test -n "$hardcode_libdir_flag_spec" || + test -n "$runpath_var" || + test yes = "$hardcode_automatic"; then + + # We can hardcode non-existent directories. + if test no != "$hardcode_direct" && + # If the only mechanism to avoid hardcoding is shlibpath_var, we + # have to relink, otherwise we might link with an installed library + # when we should be linking with a yet-to-be-installed one + ## test no != "$_LT_TAGVAR(hardcode_shlibpath_var, )" && + test no != "$hardcode_minus_L"; then + # Linking always hardcodes the temporary library directory. + hardcode_action=relink + else + # We can link without hardcoding, and we can hardcode nonexisting dirs. + hardcode_action=immediate + fi +else + # We cannot hardcode anything, or else we can only hardcode existing + # directories. + hardcode_action=unsupported +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $hardcode_action" >&5 +$as_echo "$hardcode_action" >&6; } + +if test relink = "$hardcode_action" || + test yes = "$inherit_rpath"; then + # Fast installation is not supported + enable_fast_install=no +elif test yes = "$shlibpath_overrides_runpath" || + test no = "$enable_shared"; then + # Fast installation is not necessary + enable_fast_install=needless +fi + + + + + + + if test yes != "$enable_dlopen"; then + enable_dlopen=unknown + enable_dlopen_self=unknown + enable_dlopen_self_static=unknown +else + lt_cv_dlopen=no + lt_cv_dlopen_libs= + + case $host_os in + beos*) + lt_cv_dlopen=load_add_on + lt_cv_dlopen_libs= + lt_cv_dlopen_self=yes + ;; + + mingw* | pw32* | cegcc*) + lt_cv_dlopen=LoadLibrary + lt_cv_dlopen_libs= + ;; + + cygwin*) + lt_cv_dlopen=dlopen + lt_cv_dlopen_libs= + ;; + + darwin*) + # if libdl is installed we need to link against it + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5 +$as_echo_n "checking for dlopen in -ldl... " >&6; } +if ${ac_cv_lib_dl_dlopen+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-ldl $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char dlopen (); +int +main () +{ +return dlopen (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_dl_dlopen=yes +else + ac_cv_lib_dl_dlopen=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5 +$as_echo "$ac_cv_lib_dl_dlopen" >&6; } +if test "x$ac_cv_lib_dl_dlopen" = xyes; then : + lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-ldl +else + + lt_cv_dlopen=dyld + lt_cv_dlopen_libs= + lt_cv_dlopen_self=yes + +fi + + ;; + + tpf*) + # Don't try to run any link tests for TPF. We know it's impossible + # because TPF is a cross-compiler, and we know how we open DSOs. + lt_cv_dlopen=dlopen + lt_cv_dlopen_libs= + lt_cv_dlopen_self=no + ;; + + *) + ac_fn_c_check_func "$LINENO" "shl_load" "ac_cv_func_shl_load" +if test "x$ac_cv_func_shl_load" = xyes; then : + lt_cv_dlopen=shl_load +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for shl_load in -ldld" >&5 +$as_echo_n "checking for shl_load in -ldld... " >&6; } +if ${ac_cv_lib_dld_shl_load+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-ldld $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char shl_load (); +int +main () +{ +return shl_load (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_dld_shl_load=yes +else + ac_cv_lib_dld_shl_load=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dld_shl_load" >&5 +$as_echo "$ac_cv_lib_dld_shl_load" >&6; } +if test "x$ac_cv_lib_dld_shl_load" = xyes; then : + lt_cv_dlopen=shl_load lt_cv_dlopen_libs=-ldld +else + ac_fn_c_check_func "$LINENO" "dlopen" "ac_cv_func_dlopen" +if test "x$ac_cv_func_dlopen" = xyes; then : + lt_cv_dlopen=dlopen +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5 +$as_echo_n "checking for dlopen in -ldl... " >&6; } +if ${ac_cv_lib_dl_dlopen+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-ldl $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char dlopen (); +int +main () +{ +return dlopen (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_dl_dlopen=yes +else + ac_cv_lib_dl_dlopen=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5 +$as_echo "$ac_cv_lib_dl_dlopen" >&6; } +if test "x$ac_cv_lib_dl_dlopen" = xyes; then : + lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-ldl +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -lsvld" >&5 +$as_echo_n "checking for dlopen in -lsvld... " >&6; } +if ${ac_cv_lib_svld_dlopen+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lsvld $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char dlopen (); +int +main () +{ +return dlopen (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_svld_dlopen=yes +else + ac_cv_lib_svld_dlopen=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_svld_dlopen" >&5 +$as_echo "$ac_cv_lib_svld_dlopen" >&6; } +if test "x$ac_cv_lib_svld_dlopen" = xyes; then : + lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-lsvld +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dld_link in -ldld" >&5 +$as_echo_n "checking for dld_link in -ldld... " >&6; } +if ${ac_cv_lib_dld_dld_link+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-ldld $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char dld_link (); +int +main () +{ +return dld_link (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_dld_dld_link=yes +else + ac_cv_lib_dld_dld_link=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dld_dld_link" >&5 +$as_echo "$ac_cv_lib_dld_dld_link" >&6; } +if test "x$ac_cv_lib_dld_dld_link" = xyes; then : + lt_cv_dlopen=dld_link lt_cv_dlopen_libs=-ldld +fi + + +fi + + +fi + + +fi + + +fi + + +fi + + ;; + esac + + if test no = "$lt_cv_dlopen"; then + enable_dlopen=no + else + enable_dlopen=yes + fi + + case $lt_cv_dlopen in + dlopen) + save_CPPFLAGS=$CPPFLAGS + test yes = "$ac_cv_header_dlfcn_h" && CPPFLAGS="$CPPFLAGS -DHAVE_DLFCN_H" + + save_LDFLAGS=$LDFLAGS + wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $export_dynamic_flag_spec\" + + save_LIBS=$LIBS + LIBS="$lt_cv_dlopen_libs $LIBS" + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether a program can dlopen itself" >&5 +$as_echo_n "checking whether a program can dlopen itself... " >&6; } +if ${lt_cv_dlopen_self+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test yes = "$cross_compiling"; then : + lt_cv_dlopen_self=cross +else + lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 + lt_status=$lt_dlunknown + cat > conftest.$ac_ext <<_LT_EOF +#line $LINENO "configure" +#include "confdefs.h" + +#if HAVE_DLFCN_H +#include +#endif + +#include + +#ifdef RTLD_GLOBAL +# define LT_DLGLOBAL RTLD_GLOBAL +#else +# ifdef DL_GLOBAL +# define LT_DLGLOBAL DL_GLOBAL +# else +# define LT_DLGLOBAL 0 +# endif +#endif + +/* We may have to define LT_DLLAZY_OR_NOW in the command line if we + find out it does not work in some platform. */ +#ifndef LT_DLLAZY_OR_NOW +# ifdef RTLD_LAZY +# define LT_DLLAZY_OR_NOW RTLD_LAZY +# else +# ifdef DL_LAZY +# define LT_DLLAZY_OR_NOW DL_LAZY +# else +# ifdef RTLD_NOW +# define LT_DLLAZY_OR_NOW RTLD_NOW +# else +# ifdef DL_NOW +# define LT_DLLAZY_OR_NOW DL_NOW +# else +# define LT_DLLAZY_OR_NOW 0 +# endif +# endif +# endif +# endif +#endif + +/* When -fvisibility=hidden is used, assume the code has been annotated + correspondingly for the symbols needed. */ +#if defined __GNUC__ && (((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)) || (__GNUC__ > 3)) +int fnord () __attribute__((visibility("default"))); +#endif + +int fnord () { return 42; } +int main () +{ + void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW); + int status = $lt_dlunknown; + + if (self) + { + if (dlsym (self,"fnord")) status = $lt_dlno_uscore; + else + { + if (dlsym( self,"_fnord")) status = $lt_dlneed_uscore; + else puts (dlerror ()); + } + /* dlclose (self); */ + } + else + puts (dlerror ()); + + return status; +} +_LT_EOF + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_link\""; } >&5 + (eval $ac_link) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && test -s "conftest$ac_exeext" 2>/dev/null; then + (./conftest; exit; ) >&5 2>/dev/null + lt_status=$? + case x$lt_status in + x$lt_dlno_uscore) lt_cv_dlopen_self=yes ;; + x$lt_dlneed_uscore) lt_cv_dlopen_self=yes ;; + x$lt_dlunknown|x*) lt_cv_dlopen_self=no ;; + esac + else : + # compilation failed + lt_cv_dlopen_self=no + fi +fi +rm -fr conftest* + + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_dlopen_self" >&5 +$as_echo "$lt_cv_dlopen_self" >&6; } + + if test yes = "$lt_cv_dlopen_self"; then + wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $lt_prog_compiler_static\" + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether a statically linked program can dlopen itself" >&5 +$as_echo_n "checking whether a statically linked program can dlopen itself... " >&6; } +if ${lt_cv_dlopen_self_static+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test yes = "$cross_compiling"; then : + lt_cv_dlopen_self_static=cross +else + lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 + lt_status=$lt_dlunknown + cat > conftest.$ac_ext <<_LT_EOF +#line $LINENO "configure" +#include "confdefs.h" + +#if HAVE_DLFCN_H +#include +#endif + +#include + +#ifdef RTLD_GLOBAL +# define LT_DLGLOBAL RTLD_GLOBAL +#else +# ifdef DL_GLOBAL +# define LT_DLGLOBAL DL_GLOBAL +# else +# define LT_DLGLOBAL 0 +# endif +#endif + +/* We may have to define LT_DLLAZY_OR_NOW in the command line if we + find out it does not work in some platform. */ +#ifndef LT_DLLAZY_OR_NOW +# ifdef RTLD_LAZY +# define LT_DLLAZY_OR_NOW RTLD_LAZY +# else +# ifdef DL_LAZY +# define LT_DLLAZY_OR_NOW DL_LAZY +# else +# ifdef RTLD_NOW +# define LT_DLLAZY_OR_NOW RTLD_NOW +# else +# ifdef DL_NOW +# define LT_DLLAZY_OR_NOW DL_NOW +# else +# define LT_DLLAZY_OR_NOW 0 +# endif +# endif +# endif +# endif +#endif + +/* When -fvisibility=hidden is used, assume the code has been annotated + correspondingly for the symbols needed. */ +#if defined __GNUC__ && (((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)) || (__GNUC__ > 3)) +int fnord () __attribute__((visibility("default"))); +#endif + +int fnord () { return 42; } +int main () +{ + void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW); + int status = $lt_dlunknown; + + if (self) + { + if (dlsym (self,"fnord")) status = $lt_dlno_uscore; + else + { + if (dlsym( self,"_fnord")) status = $lt_dlneed_uscore; + else puts (dlerror ()); + } + /* dlclose (self); */ + } + else + puts (dlerror ()); + + return status; +} +_LT_EOF + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_link\""; } >&5 + (eval $ac_link) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && test -s "conftest$ac_exeext" 2>/dev/null; then + (./conftest; exit; ) >&5 2>/dev/null + lt_status=$? + case x$lt_status in + x$lt_dlno_uscore) lt_cv_dlopen_self_static=yes ;; + x$lt_dlneed_uscore) lt_cv_dlopen_self_static=yes ;; + x$lt_dlunknown|x*) lt_cv_dlopen_self_static=no ;; + esac + else : + # compilation failed + lt_cv_dlopen_self_static=no + fi +fi +rm -fr conftest* + + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_dlopen_self_static" >&5 +$as_echo "$lt_cv_dlopen_self_static" >&6; } + fi + + CPPFLAGS=$save_CPPFLAGS + LDFLAGS=$save_LDFLAGS + LIBS=$save_LIBS + ;; + esac + + case $lt_cv_dlopen_self in + yes|no) enable_dlopen_self=$lt_cv_dlopen_self ;; + *) enable_dlopen_self=unknown ;; + esac + + case $lt_cv_dlopen_self_static in + yes|no) enable_dlopen_self_static=$lt_cv_dlopen_self_static ;; + *) enable_dlopen_self_static=unknown ;; + esac +fi + + + + + + + + + + + + + + + + + +striplib= +old_striplib= +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether stripping libraries is possible" >&5 +$as_echo_n "checking whether stripping libraries is possible... " >&6; } +if test -n "$STRIP" && $STRIP -V 2>&1 | $GREP "GNU strip" >/dev/null; then + test -z "$old_striplib" && old_striplib="$STRIP --strip-debug" + test -z "$striplib" && striplib="$STRIP --strip-unneeded" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +else +# FIXME - insert some real tests, host_os isn't really good enough + case $host_os in + darwin*) + if test -n "$STRIP"; then + striplib="$STRIP -x" + old_striplib="$STRIP -S" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + fi + ;; + *) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + ;; + esac +fi + + + + + + + + + + + + + # Report what library types will actually be built + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if libtool supports shared libraries" >&5 +$as_echo_n "checking if libtool supports shared libraries... " >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $can_build_shared" >&5 +$as_echo "$can_build_shared" >&6; } + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build shared libraries" >&5 +$as_echo_n "checking whether to build shared libraries... " >&6; } + test no = "$can_build_shared" && enable_shared=no + + # On AIX, shared libraries and static libraries use the same namespace, and + # are all built from PIC. + case $host_os in + aix3*) + test yes = "$enable_shared" && enable_static=no + if test -n "$RANLIB"; then + archive_cmds="$archive_cmds~\$RANLIB \$lib" + postinstall_cmds='$RANLIB $lib' + fi + ;; + + aix[4-9]*) + if test ia64 != "$host_cpu"; then + case $enable_shared,$with_aix_soname,$aix_use_runtimelinking in + yes,aix,yes) ;; # shared object as lib.so file only + yes,svr4,*) ;; # shared object as lib.so archive member only + yes,*) enable_static=no ;; # shared object in lib.a archive as well + esac + fi + ;; + esac + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $enable_shared" >&5 +$as_echo "$enable_shared" >&6; } + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build static libraries" >&5 +$as_echo_n "checking whether to build static libraries... " >&6; } + # Make sure either enable_shared or enable_static is yes. + test yes = "$enable_shared" || enable_static=yes + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $enable_static" >&5 +$as_echo "$enable_static" >&6; } + + + + +fi +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +CC=$lt_save_CC + + if test -n "$CXX" && ( test no != "$CXX" && + ( (test g++ = "$CXX" && `g++ -v >/dev/null 2>&1` ) || + (test g++ != "$CXX"))); then + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C++ preprocessor" >&5 +$as_echo_n "checking how to run the C++ preprocessor... " >&6; } +if test -z "$CXXCPP"; then + if ${ac_cv_prog_CXXCPP+:} false; then : + $as_echo_n "(cached) " >&6 +else + # Double quotes because CXXCPP needs to be expanded + for CXXCPP in "$CXX -E" "/lib/cpp" + do + ac_preproc_ok=false +for ac_cxx_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer to if __STDC__ is defined, since + # exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifdef __STDC__ +# include +#else +# include +#endif + Syntax error +_ACEOF +if ac_fn_cxx_try_cpp "$LINENO"; then : + +else + # Broken: fails on valid input. +continue +fi +rm -f conftest.err conftest.i conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +_ACEOF +if ac_fn_cxx_try_cpp "$LINENO"; then : + # Broken: success on invalid input. +continue +else + # Passes both tests. +ac_preproc_ok=: +break +fi +rm -f conftest.err conftest.i conftest.$ac_ext + +done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok; then : + break +fi + + done + ac_cv_prog_CXXCPP=$CXXCPP + +fi + CXXCPP=$ac_cv_prog_CXXCPP +else + ac_cv_prog_CXXCPP=$CXXCPP +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CXXCPP" >&5 +$as_echo "$CXXCPP" >&6; } +ac_preproc_ok=false +for ac_cxx_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer to if __STDC__ is defined, since + # exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifdef __STDC__ +# include +#else +# include +#endif + Syntax error +_ACEOF +if ac_fn_cxx_try_cpp "$LINENO"; then : + +else + # Broken: fails on valid input. +continue +fi +rm -f conftest.err conftest.i conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +_ACEOF +if ac_fn_cxx_try_cpp "$LINENO"; then : + # Broken: success on invalid input. +continue +else + # Passes both tests. +ac_preproc_ok=: +break +fi +rm -f conftest.err conftest.i conftest.$ac_ext + +done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok; then : + +else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "C++ preprocessor \"$CXXCPP\" fails sanity check +See \`config.log' for more details" "$LINENO" 5; } +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +else + _lt_caught_CXX_error=yes +fi + +ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + +archive_cmds_need_lc_CXX=no +allow_undefined_flag_CXX= +always_export_symbols_CXX=no +archive_expsym_cmds_CXX= +compiler_needs_object_CXX=no +export_dynamic_flag_spec_CXX= +hardcode_direct_CXX=no +hardcode_direct_absolute_CXX=no +hardcode_libdir_flag_spec_CXX= +hardcode_libdir_separator_CXX= +hardcode_minus_L_CXX=no +hardcode_shlibpath_var_CXX=unsupported +hardcode_automatic_CXX=no +inherit_rpath_CXX=no +module_cmds_CXX= +module_expsym_cmds_CXX= +link_all_deplibs_CXX=unknown +old_archive_cmds_CXX=$old_archive_cmds +reload_flag_CXX=$reload_flag +reload_cmds_CXX=$reload_cmds +no_undefined_flag_CXX= +whole_archive_flag_spec_CXX= +enable_shared_with_static_runtimes_CXX=no + +# Source file extension for C++ test sources. +ac_ext=cpp + +# Object file extension for compiled C++ test sources. +objext=o +objext_CXX=$objext + +# No sense in running all these tests if we already determined that +# the CXX compiler isn't working. Some variables (like enable_shared) +# are currently assumed to apply to all compilers on this platform, +# and will be corrupted by setting them based on a non-working compiler. +if test yes != "$_lt_caught_CXX_error"; then + # Code to be used in simple compile tests + lt_simple_compile_test_code="int some_variable = 0;" + + # Code to be used in simple link tests + lt_simple_link_test_code='int main(int, char *[]) { return(0); }' + + # ltmain only uses $CC for tagged configurations so make sure $CC is set. + + + + + + +# If no C compiler was specified, use CC. +LTCC=${LTCC-"$CC"} + +# If no C compiler flags were specified, use CFLAGS. +LTCFLAGS=${LTCFLAGS-"$CFLAGS"} + +# Allow CC to be a program name with arguments. +compiler=$CC + + + # save warnings/boilerplate of simple test code + ac_outfile=conftest.$ac_objext +echo "$lt_simple_compile_test_code" >conftest.$ac_ext +eval "$ac_compile" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err +_lt_compiler_boilerplate=`cat conftest.err` +$RM conftest* + + ac_outfile=conftest.$ac_objext +echo "$lt_simple_link_test_code" >conftest.$ac_ext +eval "$ac_link" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err +_lt_linker_boilerplate=`cat conftest.err` +$RM -r conftest* + + + # Allow CC to be a program name with arguments. + lt_save_CC=$CC + lt_save_CFLAGS=$CFLAGS + lt_save_LD=$LD + lt_save_GCC=$GCC + GCC=$GXX + lt_save_with_gnu_ld=$with_gnu_ld + lt_save_path_LD=$lt_cv_path_LD + if test -n "${lt_cv_prog_gnu_ldcxx+set}"; then + lt_cv_prog_gnu_ld=$lt_cv_prog_gnu_ldcxx + else + $as_unset lt_cv_prog_gnu_ld + fi + if test -n "${lt_cv_path_LDCXX+set}"; then + lt_cv_path_LD=$lt_cv_path_LDCXX + else + $as_unset lt_cv_path_LD + fi + test -z "${LDCXX+set}" || LD=$LDCXX + CC=${CXX-"c++"} + CFLAGS=$CXXFLAGS + compiler=$CC + compiler_CXX=$CC + func_cc_basename $compiler +cc_basename=$func_cc_basename_result + + + if test -n "$compiler"; then + # We don't want -fno-exception when compiling C++ code, so set the + # no_builtin_flag separately + if test yes = "$GXX"; then + lt_prog_compiler_no_builtin_flag_CXX=' -fno-builtin' + else + lt_prog_compiler_no_builtin_flag_CXX= + fi + + if test yes = "$GXX"; then + # Set up default GNU C++ configuration + + + +# Check whether --with-gnu-ld was given. +if test "${with_gnu_ld+set}" = set; then : + withval=$with_gnu_ld; test no = "$withval" || with_gnu_ld=yes +else + with_gnu_ld=no +fi + +ac_prog=ld +if test yes = "$GCC"; then + # Check if gcc -print-prog-name=ld gives a path. + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ld used by $CC" >&5 +$as_echo_n "checking for ld used by $CC... " >&6; } + case $host in + *-*-mingw*) + # gcc leaves a trailing carriage return, which upsets mingw + ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;; + *) + ac_prog=`($CC -print-prog-name=ld) 2>&5` ;; + esac + case $ac_prog in + # Accept absolute paths. + [\\/]* | ?:[\\/]*) + re_direlt='/[^/][^/]*/\.\./' + # Canonicalize the pathname of ld + ac_prog=`$ECHO "$ac_prog"| $SED 's%\\\\%/%g'` + while $ECHO "$ac_prog" | $GREP "$re_direlt" > /dev/null 2>&1; do + ac_prog=`$ECHO $ac_prog| $SED "s%$re_direlt%/%"` + done + test -z "$LD" && LD=$ac_prog + ;; + "") + # If it fails, then pretend we aren't using GCC. + ac_prog=ld + ;; + *) + # If it is relative, then search for the first ld in PATH. + with_gnu_ld=unknown + ;; + esac +elif test yes = "$with_gnu_ld"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for GNU ld" >&5 +$as_echo_n "checking for GNU ld... " >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for non-GNU ld" >&5 +$as_echo_n "checking for non-GNU ld... " >&6; } +fi +if ${lt_cv_path_LD+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -z "$LD"; then + lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR + for ac_dir in $PATH; do + IFS=$lt_save_ifs + test -z "$ac_dir" && ac_dir=. + if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then + lt_cv_path_LD=$ac_dir/$ac_prog + # Check to see if the program is GNU ld. I'd rather use --version, + # but apparently some variants of GNU ld only accept -v. + # Break only if it was the GNU/non-GNU ld that we prefer. + case `"$lt_cv_path_LD" -v 2>&1 &5 +$as_echo "$LD" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi +test -z "$LD" && as_fn_error $? "no acceptable ld found in \$PATH" "$LINENO" 5 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if the linker ($LD) is GNU ld" >&5 +$as_echo_n "checking if the linker ($LD) is GNU ld... " >&6; } +if ${lt_cv_prog_gnu_ld+:} false; then : + $as_echo_n "(cached) " >&6 +else + # I'd rather use --version here, but apparently some GNU lds only accept -v. +case `$LD -v 2>&1 &5 +$as_echo "$lt_cv_prog_gnu_ld" >&6; } +with_gnu_ld=$lt_cv_prog_gnu_ld + + + + + + + + # Check if GNU C++ uses GNU ld as the underlying linker, since the + # archiving commands below assume that GNU ld is being used. + if test yes = "$with_gnu_ld"; then + archive_cmds_CXX='$CC $pic_flag -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' + archive_expsym_cmds_CXX='$CC $pic_flag -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + + hardcode_libdir_flag_spec_CXX='$wl-rpath $wl$libdir' + export_dynamic_flag_spec_CXX='$wl--export-dynamic' + + # If archive_cmds runs LD, not CC, wlarc should be empty + # XXX I think wlarc can be eliminated in ltcf-cxx, but I need to + # investigate it a little bit more. (MM) + wlarc='$wl' + + # ancient GNU ld didn't support --whole-archive et. al. + if eval "`$CC -print-prog-name=ld` --help 2>&1" | + $GREP 'no-whole-archive' > /dev/null; then + whole_archive_flag_spec_CXX=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive' + else + whole_archive_flag_spec_CXX= + fi + else + with_gnu_ld=no + wlarc= + + # A generic and very simple default shared library creation + # command for GNU C++ for the case where it uses the native + # linker, instead of GNU ld. If possible, this setting should + # overridden to take advantage of the native linker features on + # the platform it is being used on. + archive_cmds_CXX='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $lib' + fi + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"' + + else + GXX=no + with_gnu_ld=no + wlarc= + fi + + # PORTME: fill in a description of your system's C++ link characteristics + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the $compiler linker ($LD) supports shared libraries" >&5 +$as_echo_n "checking whether the $compiler linker ($LD) supports shared libraries... " >&6; } + ld_shlibs_CXX=yes + case $host_os in + aix3*) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + aix[4-9]*) + if test ia64 = "$host_cpu"; then + # On IA64, the linker does run time linking by default, so we don't + # have to do anything special. + aix_use_runtimelinking=no + exp_sym_flag='-Bexport' + no_entry_flag= + else + aix_use_runtimelinking=no + + # Test if we are trying to use run time linking or normal + # AIX style linking. If -brtl is somewhere in LDFLAGS, we + # have runtime linking enabled, and use it for executables. + # For shared libraries, we enable/disable runtime linking + # depending on the kind of the shared library created - + # when "with_aix_soname,aix_use_runtimelinking" is: + # "aix,no" lib.a(lib.so.V) shared, rtl:no, for executables + # "aix,yes" lib.so shared, rtl:yes, for executables + # lib.a static archive + # "both,no" lib.so.V(shr.o) shared, rtl:yes + # lib.a(lib.so.V) shared, rtl:no, for executables + # "both,yes" lib.so.V(shr.o) shared, rtl:yes, for executables + # lib.a(lib.so.V) shared, rtl:no + # "svr4,*" lib.so.V(shr.o) shared, rtl:yes, for executables + # lib.a static archive + case $host_os in aix4.[23]|aix4.[23].*|aix[5-9]*) + for ld_flag in $LDFLAGS; do + case $ld_flag in + *-brtl*) + aix_use_runtimelinking=yes + break + ;; + esac + done + if test svr4,no = "$with_aix_soname,$aix_use_runtimelinking"; then + # With aix-soname=svr4, we create the lib.so.V shared archives only, + # so we don't have lib.a shared libs to link our executables. + # We have to force runtime linking in this case. + aix_use_runtimelinking=yes + LDFLAGS="$LDFLAGS -Wl,-brtl" + fi + ;; + esac + + exp_sym_flag='-bexport' + no_entry_flag='-bnoentry' + fi + + # When large executables or shared objects are built, AIX ld can + # have problems creating the table of contents. If linking a library + # or program results in "error TOC overflow" add -mminimal-toc to + # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not + # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS. + + archive_cmds_CXX='' + hardcode_direct_CXX=yes + hardcode_direct_absolute_CXX=yes + hardcode_libdir_separator_CXX=':' + link_all_deplibs_CXX=yes + file_list_spec_CXX='$wl-f,' + case $with_aix_soname,$aix_use_runtimelinking in + aix,*) ;; # no import file + svr4,* | *,yes) # use import file + # The Import File defines what to hardcode. + hardcode_direct_CXX=no + hardcode_direct_absolute_CXX=no + ;; + esac + + if test yes = "$GXX"; then + case $host_os in aix4.[012]|aix4.[012].*) + # We only want to do this on AIX 4.2 and lower, the check + # below for broken collect2 doesn't work under 4.3+ + collect2name=`$CC -print-prog-name=collect2` + if test -f "$collect2name" && + strings "$collect2name" | $GREP resolve_lib_name >/dev/null + then + # We have reworked collect2 + : + else + # We have old collect2 + hardcode_direct_CXX=unsupported + # It fails to find uninstalled libraries when the uninstalled + # path is not listed in the libpath. Setting hardcode_minus_L + # to unsupported forces relinking + hardcode_minus_L_CXX=yes + hardcode_libdir_flag_spec_CXX='-L$libdir' + hardcode_libdir_separator_CXX= + fi + esac + shared_flag='-shared' + if test yes = "$aix_use_runtimelinking"; then + shared_flag=$shared_flag' $wl-G' + fi + # Need to ensure runtime linking is disabled for the traditional + # shared library, or the linker may eventually find shared libraries + # /with/ Import File - we do not want to mix them. + shared_flag_aix='-shared' + shared_flag_svr4='-shared $wl-G' + else + # not using gcc + if test ia64 = "$host_cpu"; then + # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release + # chokes on -Wl,-G. The following line is correct: + shared_flag='-G' + else + if test yes = "$aix_use_runtimelinking"; then + shared_flag='$wl-G' + else + shared_flag='$wl-bM:SRE' + fi + shared_flag_aix='$wl-bM:SRE' + shared_flag_svr4='$wl-G' + fi + fi + + export_dynamic_flag_spec_CXX='$wl-bexpall' + # It seems that -bexpall does not export symbols beginning with + # underscore (_), so it is better to generate a list of symbols to + # export. + always_export_symbols_CXX=yes + if test aix,yes = "$with_aix_soname,$aix_use_runtimelinking"; then + # Warning - without using the other runtime loading flags (-brtl), + # -berok will link without error, but may produce a broken library. + # The "-G" linker flag allows undefined symbols. + no_undefined_flag_CXX='-bernotok' + # Determine the default libpath from the value encoded in an empty + # executable. + if test set = "${lt_cv_aix_libpath+set}"; then + aix_libpath=$lt_cv_aix_libpath +else + if ${lt_cv_aix_libpath__CXX+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_link "$LINENO"; then : + + lt_aix_libpath_sed=' + /Import File Strings/,/^$/ { + /^0/ { + s/^0 *\([^ ]*\) *$/\1/ + p + } + }' + lt_cv_aix_libpath__CXX=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + # Check for a 64-bit object if we didn't find anything. + if test -z "$lt_cv_aix_libpath__CXX"; then + lt_cv_aix_libpath__CXX=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + fi +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + if test -z "$lt_cv_aix_libpath__CXX"; then + lt_cv_aix_libpath__CXX=/usr/lib:/lib + fi + +fi + + aix_libpath=$lt_cv_aix_libpath__CXX +fi + + hardcode_libdir_flag_spec_CXX='$wl-blibpath:$libdir:'"$aix_libpath" + + archive_expsym_cmds_CXX='$CC -o $output_objdir/$soname $libobjs $deplibs $wl'$no_entry_flag' $compiler_flags `if test -n "$allow_undefined_flag"; then func_echo_all "$wl$allow_undefined_flag"; else :; fi` $wl'$exp_sym_flag:\$export_symbols' '$shared_flag + else + if test ia64 = "$host_cpu"; then + hardcode_libdir_flag_spec_CXX='$wl-R $libdir:/usr/lib:/lib' + allow_undefined_flag_CXX="-z nodefs" + archive_expsym_cmds_CXX="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\$wl$no_entry_flag"' $compiler_flags $wl$allow_undefined_flag '"\$wl$exp_sym_flag:\$export_symbols" + else + # Determine the default libpath from the value encoded in an + # empty executable. + if test set = "${lt_cv_aix_libpath+set}"; then + aix_libpath=$lt_cv_aix_libpath +else + if ${lt_cv_aix_libpath__CXX+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_link "$LINENO"; then : + + lt_aix_libpath_sed=' + /Import File Strings/,/^$/ { + /^0/ { + s/^0 *\([^ ]*\) *$/\1/ + p + } + }' + lt_cv_aix_libpath__CXX=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + # Check for a 64-bit object if we didn't find anything. + if test -z "$lt_cv_aix_libpath__CXX"; then + lt_cv_aix_libpath__CXX=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + fi +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + if test -z "$lt_cv_aix_libpath__CXX"; then + lt_cv_aix_libpath__CXX=/usr/lib:/lib + fi + +fi + + aix_libpath=$lt_cv_aix_libpath__CXX +fi + + hardcode_libdir_flag_spec_CXX='$wl-blibpath:$libdir:'"$aix_libpath" + # Warning - without using the other run time loading flags, + # -berok will link without error, but may produce a broken library. + no_undefined_flag_CXX=' $wl-bernotok' + allow_undefined_flag_CXX=' $wl-berok' + if test yes = "$with_gnu_ld"; then + # We only use this code for GNU lds that support --whole-archive. + whole_archive_flag_spec_CXX='$wl--whole-archive$convenience $wl--no-whole-archive' + else + # Exported symbols can be pulled into shared objects from archives + whole_archive_flag_spec_CXX='$convenience' + fi + archive_cmds_need_lc_CXX=yes + archive_expsym_cmds_CXX='$RM -r $output_objdir/$realname.d~$MKDIR $output_objdir/$realname.d' + # -brtl affects multiple linker settings, -berok does not and is overridden later + compiler_flags_filtered='`func_echo_all "$compiler_flags " | $SED -e "s%-brtl\\([, ]\\)%-berok\\1%g"`' + if test svr4 != "$with_aix_soname"; then + # This is similar to how AIX traditionally builds its shared + # libraries. Need -bnortl late, we may have -brtl in LDFLAGS. + archive_expsym_cmds_CXX="$archive_expsym_cmds_CXX"'~$CC '$shared_flag_aix' -o $output_objdir/$realname.d/$soname $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$realname.d/$soname' + fi + if test aix != "$with_aix_soname"; then + archive_expsym_cmds_CXX="$archive_expsym_cmds_CXX"'~$CC '$shared_flag_svr4' -o $output_objdir/$realname.d/$shared_archive_member_spec.o $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$STRIP -e $output_objdir/$realname.d/$shared_archive_member_spec.o~( func_echo_all "#! $soname($shared_archive_member_spec.o)"; if test shr_64 = "$shared_archive_member_spec"; then func_echo_all "# 64"; else func_echo_all "# 32"; fi; cat $export_symbols ) > $output_objdir/$realname.d/$shared_archive_member_spec.imp~$AR $AR_FLAGS $output_objdir/$soname $output_objdir/$realname.d/$shared_archive_member_spec.o $output_objdir/$realname.d/$shared_archive_member_spec.imp' + else + # used by -dlpreopen to get the symbols + archive_expsym_cmds_CXX="$archive_expsym_cmds_CXX"'~$MV $output_objdir/$realname.d/$soname $output_objdir' + fi + archive_expsym_cmds_CXX="$archive_expsym_cmds_CXX"'~$RM -r $output_objdir/$realname.d' + fi + fi + ;; + + beos*) + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + allow_undefined_flag_CXX=unsupported + # Joseph Beckenbach says some releases of gcc + # support --undefined. This deserves some investigation. FIXME + archive_cmds_CXX='$CC -nostart $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + else + ld_shlibs_CXX=no + fi + ;; + + chorus*) + case $cc_basename in + *) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + esac + ;; + + cygwin* | mingw* | pw32* | cegcc*) + case $GXX,$cc_basename in + ,cl* | no,cl*) + # Native MSVC + # hardcode_libdir_flag_spec is actually meaningless, as there is + # no search path for DLLs. + hardcode_libdir_flag_spec_CXX=' ' + allow_undefined_flag_CXX=unsupported + always_export_symbols_CXX=yes + file_list_spec_CXX='@' + # Tell ltmain to make .lib files, not .a files. + libext=lib + # Tell ltmain to make .dll files, not .so files. + shrext_cmds=.dll + # FIXME: Setting linknames here is a bad hack. + archive_cmds_CXX='$CC -o $output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~linknames=' + archive_expsym_cmds_CXX='if test DEF = "`$SED -n -e '\''s/^[ ]*//'\'' -e '\''/^\(;.*\)*$/d'\'' -e '\''s/^\(EXPORTS\|LIBRARY\)\([ ].*\)*$/DEF/p'\'' -e q $export_symbols`" ; then + cp "$export_symbols" "$output_objdir/$soname.def"; + echo "$tool_output_objdir$soname.def" > "$output_objdir/$soname.exp"; + else + $SED -e '\''s/^/-link -EXPORT:/'\'' < $export_symbols > $output_objdir/$soname.exp; + fi~ + $CC -o $tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~ + linknames=' + # The linker will not automatically build a static lib if we build a DLL. + # _LT_TAGVAR(old_archive_from_new_cmds, CXX)='true' + enable_shared_with_static_runtimes_CXX=yes + # Don't use ranlib + old_postinstall_cmds_CXX='chmod 644 $oldlib' + postlink_cmds_CXX='lt_outputfile="@OUTPUT@"~ + lt_tool_outputfile="@TOOL_OUTPUT@"~ + case $lt_outputfile in + *.exe|*.EXE) ;; + *) + lt_outputfile=$lt_outputfile.exe + lt_tool_outputfile=$lt_tool_outputfile.exe + ;; + esac~ + func_to_tool_file "$lt_outputfile"~ + if test : != "$MANIFEST_TOOL" && test -f "$lt_outputfile.manifest"; then + $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1; + $RM "$lt_outputfile.manifest"; + fi' + ;; + *) + # g++ + # _LT_TAGVAR(hardcode_libdir_flag_spec, CXX) is actually meaningless, + # as there is no search path for DLLs. + hardcode_libdir_flag_spec_CXX='-L$libdir' + export_dynamic_flag_spec_CXX='$wl--export-all-symbols' + allow_undefined_flag_CXX=unsupported + always_export_symbols_CXX=no + enable_shared_with_static_runtimes_CXX=yes + + if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then + archive_cmds_CXX='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + # If the export-symbols file already is a .def file, use it as + # is; otherwise, prepend EXPORTS... + archive_expsym_cmds_CXX='if test DEF = "`$SED -n -e '\''s/^[ ]*//'\'' -e '\''/^\(;.*\)*$/d'\'' -e '\''s/^\(EXPORTS\|LIBRARY\)\([ ].*\)*$/DEF/p'\'' -e q $export_symbols`" ; then + cp $export_symbols $output_objdir/$soname.def; + else + echo EXPORTS > $output_objdir/$soname.def; + cat $export_symbols >> $output_objdir/$soname.def; + fi~ + $CC -shared -nostdlib $output_objdir/$soname.def $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + else + ld_shlibs_CXX=no + fi + ;; + esac + ;; + darwin* | rhapsody*) + + + archive_cmds_need_lc_CXX=no + hardcode_direct_CXX=no + hardcode_automatic_CXX=yes + hardcode_shlibpath_var_CXX=unsupported + if test yes = "$lt_cv_ld_force_load"; then + whole_archive_flag_spec_CXX='`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience $wl-force_load,$conv\"; done; func_echo_all \"$new_convenience\"`' + + else + whole_archive_flag_spec_CXX='' + fi + link_all_deplibs_CXX=yes + allow_undefined_flag_CXX=$_lt_dar_allow_undefined + case $cc_basename in + ifort*|nagfor*) _lt_dar_can_shared=yes ;; + *) _lt_dar_can_shared=$GCC ;; + esac + if test yes = "$_lt_dar_can_shared"; then + output_verbose_link_cmd=func_echo_all + archive_cmds_CXX="\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod$_lt_dsymutil" + module_cmds_CXX="\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags$_lt_dsymutil" + archive_expsym_cmds_CXX="sed 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod$_lt_dar_export_syms$_lt_dsymutil" + module_expsym_cmds_CXX="sed -e 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags$_lt_dar_export_syms$_lt_dsymutil" + if test yes != "$lt_cv_apple_cc_single_mod"; then + archive_cmds_CXX="\$CC -r -keep_private_externs -nostdlib -o \$lib-master.o \$libobjs~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$lib-master.o \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring$_lt_dsymutil" + archive_expsym_cmds_CXX="sed 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC -r -keep_private_externs -nostdlib -o \$lib-master.o \$libobjs~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$lib-master.o \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring$_lt_dar_export_syms$_lt_dsymutil" + fi + + else + ld_shlibs_CXX=no + fi + + ;; + + os2*) + hardcode_libdir_flag_spec_CXX='-L$libdir' + hardcode_minus_L_CXX=yes + allow_undefined_flag_CXX=unsupported + shrext_cmds=.dll + archive_cmds_CXX='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + archive_expsym_cmds_CXX='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + prefix_cmds="$SED"~ + if test EXPORTS = "`$SED 1q $export_symbols`"; then + prefix_cmds="$prefix_cmds -e 1d"; + fi~ + prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~ + cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + old_archive_From_new_cmds_CXX='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def' + enable_shared_with_static_runtimes_CXX=yes + ;; + + dgux*) + case $cc_basename in + ec++*) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + ghcx*) + # Green Hills C++ Compiler + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + *) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + esac + ;; + + freebsd2.*) + # C++ shared libraries reported to be fairly broken before + # switch to ELF + ld_shlibs_CXX=no + ;; + + freebsd-elf*) + archive_cmds_need_lc_CXX=no + ;; + + freebsd* | dragonfly*) + # FreeBSD 3 and later use GNU C++ and GNU ld with standard ELF + # conventions + ld_shlibs_CXX=yes + ;; + + haiku*) + archive_cmds_CXX='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + link_all_deplibs_CXX=yes + ;; + + hpux9*) + hardcode_libdir_flag_spec_CXX='$wl+b $wl$libdir' + hardcode_libdir_separator_CXX=: + export_dynamic_flag_spec_CXX='$wl-E' + hardcode_direct_CXX=yes + hardcode_minus_L_CXX=yes # Not in the search PATH, + # but as the default + # location of the library. + + case $cc_basename in + CC*) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + aCC*) + archive_cmds_CXX='$RM $output_objdir/$soname~$CC -b $wl+b $wl$install_libdir -o $output_objdir/$soname $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | $EGREP "\-L"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' + ;; + *) + if test yes = "$GXX"; then + archive_cmds_CXX='$RM $output_objdir/$soname~$CC -shared -nostdlib $pic_flag $wl+b $wl$install_libdir -o $output_objdir/$soname $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' + else + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + fi + ;; + esac + ;; + + hpux10*|hpux11*) + if test no = "$with_gnu_ld"; then + hardcode_libdir_flag_spec_CXX='$wl+b $wl$libdir' + hardcode_libdir_separator_CXX=: + + case $host_cpu in + hppa*64*|ia64*) + ;; + *) + export_dynamic_flag_spec_CXX='$wl-E' + ;; + esac + fi + case $host_cpu in + hppa*64*|ia64*) + hardcode_direct_CXX=no + hardcode_shlibpath_var_CXX=no + ;; + *) + hardcode_direct_CXX=yes + hardcode_direct_absolute_CXX=yes + hardcode_minus_L_CXX=yes # Not in the search PATH, + # but as the default + # location of the library. + ;; + esac + + case $cc_basename in + CC*) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + aCC*) + case $host_cpu in + hppa*64*) + archive_cmds_CXX='$CC -b $wl+h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + ia64*) + archive_cmds_CXX='$CC -b $wl+h $wl$soname $wl+nodefaultrpath -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + *) + archive_cmds_CXX='$CC -b $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + esac + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | $GREP "\-L"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' + ;; + *) + if test yes = "$GXX"; then + if test no = "$with_gnu_ld"; then + case $host_cpu in + hppa*64*) + archive_cmds_CXX='$CC -shared -nostdlib -fPIC $wl+h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + ia64*) + archive_cmds_CXX='$CC -shared -nostdlib $pic_flag $wl+h $wl$soname $wl+nodefaultrpath -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + *) + archive_cmds_CXX='$CC -shared -nostdlib $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + esac + fi + else + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + fi + ;; + esac + ;; + + interix[3-9]*) + hardcode_direct_CXX=no + hardcode_shlibpath_var_CXX=no + hardcode_libdir_flag_spec_CXX='$wl-rpath,$libdir' + export_dynamic_flag_spec_CXX='$wl-E' + # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc. + # Instead, shared libraries are loaded at an image base (0x10000000 by + # default) and relocated if they conflict, which is a slow very memory + # consuming and fragmenting process. To avoid this, we pick a random, + # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link + # time. Moving up from 0x10000000 also allows more sbrk(2) space. + archive_cmds_CXX='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + archive_expsym_cmds_CXX='sed "s|^|_|" $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--retain-symbols-file,$output_objdir/$soname.expsym $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + ;; + irix5* | irix6*) + case $cc_basename in + CC*) + # SGI C++ + archive_cmds_CXX='$CC -shared -all -multigot $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + + # Archives containing C++ object files must be created using + # "CC -ar", where "CC" is the IRIX C++ compiler. This is + # necessary to make sure instantiated templates are included + # in the archive. + old_archive_cmds_CXX='$CC -ar -WR,-u -o $oldlib $oldobjs' + ;; + *) + if test yes = "$GXX"; then + if test no = "$with_gnu_ld"; then + archive_cmds_CXX='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + else + archive_cmds_CXX='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` -o $lib' + fi + fi + link_all_deplibs_CXX=yes + ;; + esac + hardcode_libdir_flag_spec_CXX='$wl-rpath $wl$libdir' + hardcode_libdir_separator_CXX=: + inherit_rpath_CXX=yes + ;; + + linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + case $cc_basename in + KCC*) + # Kuck and Associates, Inc. (KAI) C++ Compiler + + # KCC will only create a shared library if the output file + # ends with ".so" (or ".sl" for HP-UX), so rename the library + # to its proper name (with version) after linking. + archive_cmds_CXX='tempext=`echo $shared_ext | $SED -e '\''s/\([^()0-9A-Za-z{}]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\$tempext\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib' + archive_expsym_cmds_CXX='tempext=`echo $shared_ext | $SED -e '\''s/\([^()0-9A-Za-z{}]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\$tempext\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib $wl-retain-symbols-file,$export_symbols; mv \$templib $lib' + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1 | $GREP "ld"`; rm -f libconftest$shared_ext; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' + + hardcode_libdir_flag_spec_CXX='$wl-rpath,$libdir' + export_dynamic_flag_spec_CXX='$wl--export-dynamic' + + # Archives containing C++ object files must be created using + # "CC -Bstatic", where "CC" is the KAI C++ compiler. + old_archive_cmds_CXX='$CC -Bstatic -o $oldlib $oldobjs' + ;; + icpc* | ecpc* ) + # Intel C++ + with_gnu_ld=yes + # version 8.0 and above of icpc choke on multiply defined symbols + # if we add $predep_objects and $postdep_objects, however 7.1 and + # earlier do not add the objects themselves. + case `$CC -V 2>&1` in + *"Version 7."*) + archive_cmds_CXX='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' + archive_expsym_cmds_CXX='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + ;; + *) # Version 8.0 or newer + tmp_idyn= + case $host_cpu in + ia64*) tmp_idyn=' -i_dynamic';; + esac + archive_cmds_CXX='$CC -shared'"$tmp_idyn"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + archive_expsym_cmds_CXX='$CC -shared'"$tmp_idyn"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + ;; + esac + archive_cmds_need_lc_CXX=no + hardcode_libdir_flag_spec_CXX='$wl-rpath,$libdir' + export_dynamic_flag_spec_CXX='$wl--export-dynamic' + whole_archive_flag_spec_CXX='$wl--whole-archive$convenience $wl--no-whole-archive' + ;; + pgCC* | pgcpp*) + # Portland Group C++ compiler + case `$CC -V` in + *pgCC\ [1-5].* | *pgcpp\ [1-5].*) + prelink_cmds_CXX='tpldir=Template.dir~ + rm -rf $tpldir~ + $CC --prelink_objects --instantiation_dir $tpldir $objs $libobjs $compile_deplibs~ + compile_command="$compile_command `find $tpldir -name \*.o | sort | $NL2SP`"' + old_archive_cmds_CXX='tpldir=Template.dir~ + rm -rf $tpldir~ + $CC --prelink_objects --instantiation_dir $tpldir $oldobjs$old_deplibs~ + $AR $AR_FLAGS $oldlib$oldobjs$old_deplibs `find $tpldir -name \*.o | sort | $NL2SP`~ + $RANLIB $oldlib' + archive_cmds_CXX='tpldir=Template.dir~ + rm -rf $tpldir~ + $CC --prelink_objects --instantiation_dir $tpldir $predep_objects $libobjs $deplibs $convenience $postdep_objects~ + $CC -shared $pic_flag $predep_objects $libobjs $deplibs `find $tpldir -name \*.o | sort | $NL2SP` $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' + archive_expsym_cmds_CXX='tpldir=Template.dir~ + rm -rf $tpldir~ + $CC --prelink_objects --instantiation_dir $tpldir $predep_objects $libobjs $deplibs $convenience $postdep_objects~ + $CC -shared $pic_flag $predep_objects $libobjs $deplibs `find $tpldir -name \*.o | sort | $NL2SP` $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + ;; + *) # Version 6 and above use weak symbols + archive_cmds_CXX='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' + archive_expsym_cmds_CXX='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + ;; + esac + + hardcode_libdir_flag_spec_CXX='$wl--rpath $wl$libdir' + export_dynamic_flag_spec_CXX='$wl--export-dynamic' + whole_archive_flag_spec_CXX='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + ;; + cxx*) + # Compaq C++ + archive_cmds_CXX='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' + archive_expsym_cmds_CXX='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib $wl-retain-symbols-file $wl$export_symbols' + + runpath_var=LD_RUN_PATH + hardcode_libdir_flag_spec_CXX='-rpath $libdir' + hardcode_libdir_separator_CXX=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP "ld"`; templist=`func_echo_all "$templist" | $SED "s/\(^.*ld.*\)\( .*ld .*$\)/\1/"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "X$list" | $Xsed' + ;; + xl* | mpixl* | bgxl*) + # IBM XL 8.0 on PPC, with GNU ld + hardcode_libdir_flag_spec_CXX='$wl-rpath $wl$libdir' + export_dynamic_flag_spec_CXX='$wl--export-dynamic' + archive_cmds_CXX='$CC -qmkshrobj $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + if test yes = "$supports_anon_versioning"; then + archive_expsym_cmds_CXX='echo "{ global:" > $output_objdir/$libname.ver~ + cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ + echo "local: *; };" >> $output_objdir/$libname.ver~ + $CC -qmkshrobj $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-version-script $wl$output_objdir/$libname.ver -o $lib' + fi + ;; + *) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) + # Sun C++ 5.9 + no_undefined_flag_CXX=' -zdefs' + archive_cmds_CXX='$CC -G$allow_undefined_flag -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + archive_expsym_cmds_CXX='$CC -G$allow_undefined_flag -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-retain-symbols-file $wl$export_symbols' + hardcode_libdir_flag_spec_CXX='-R$libdir' + whole_archive_flag_spec_CXX='$wl--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + compiler_needs_object_CXX=yes + + # Not sure whether something based on + # $CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1 + # would be better. + output_verbose_link_cmd='func_echo_all' + + # Archives containing C++ object files must be created using + # "CC -xar", where "CC" is the Sun C++ compiler. This is + # necessary to make sure instantiated templates are included + # in the archive. + old_archive_cmds_CXX='$CC -xar -o $oldlib $oldobjs' + ;; + esac + ;; + esac + ;; + + lynxos*) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + + m88k*) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + + mvs*) + case $cc_basename in + cxx*) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + *) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + esac + ;; + + netbsd*) + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + archive_cmds_CXX='$LD -Bshareable -o $lib $predep_objects $libobjs $deplibs $postdep_objects $linker_flags' + wlarc= + hardcode_libdir_flag_spec_CXX='-R$libdir' + hardcode_direct_CXX=yes + hardcode_shlibpath_var_CXX=no + fi + # Workaround some broken pre-1.5 toolchains + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP conftest.$objext | $SED -e "s:-lgcc -lc -lgcc::"' + ;; + + *nto* | *qnx*) + ld_shlibs_CXX=yes + ;; + + openbsd* | bitrig*) + if test -f /usr/libexec/ld.so; then + hardcode_direct_CXX=yes + hardcode_shlibpath_var_CXX=no + hardcode_direct_absolute_CXX=yes + archive_cmds_CXX='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $lib' + hardcode_libdir_flag_spec_CXX='$wl-rpath,$libdir' + if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`"; then + archive_expsym_cmds_CXX='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-retain-symbols-file,$export_symbols -o $lib' + export_dynamic_flag_spec_CXX='$wl-E' + whole_archive_flag_spec_CXX=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive' + fi + output_verbose_link_cmd=func_echo_all + else + ld_shlibs_CXX=no + fi + ;; + + osf3* | osf4* | osf5*) + case $cc_basename in + KCC*) + # Kuck and Associates, Inc. (KAI) C++ Compiler + + # KCC will only create a shared library if the output file + # ends with ".so" (or ".sl" for HP-UX), so rename the library + # to its proper name (with version) after linking. + archive_cmds_CXX='tempext=`echo $shared_ext | $SED -e '\''s/\([^()0-9A-Za-z{}]\)/\\\\\1/g'\''`; templib=`echo "$lib" | $SED -e "s/\$tempext\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib' + + hardcode_libdir_flag_spec_CXX='$wl-rpath,$libdir' + hardcode_libdir_separator_CXX=: + + # Archives containing C++ object files must be created using + # the KAI C++ compiler. + case $host in + osf3*) old_archive_cmds_CXX='$CC -Bstatic -o $oldlib $oldobjs' ;; + *) old_archive_cmds_CXX='$CC -o $oldlib $oldobjs' ;; + esac + ;; + RCC*) + # Rational C++ 2.4.1 + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + cxx*) + case $host in + osf3*) + allow_undefined_flag_CXX=' $wl-expect_unresolved $wl\*' + archive_cmds_CXX='$CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $soname `test -n "$verstring" && func_echo_all "$wl-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + hardcode_libdir_flag_spec_CXX='$wl-rpath $wl$libdir' + ;; + *) + allow_undefined_flag_CXX=' -expect_unresolved \*' + archive_cmds_CXX='$CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + archive_expsym_cmds_CXX='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done~ + echo "-hidden">> $lib.exp~ + $CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -msym -soname $soname $wl-input $wl$lib.exp `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib~ + $RM $lib.exp' + hardcode_libdir_flag_spec_CXX='-rpath $libdir' + ;; + esac + + hardcode_libdir_separator_CXX=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP "ld" | $GREP -v "ld:"`; templist=`func_echo_all "$templist" | $SED "s/\(^.*ld.*\)\( .*ld.*$\)/\1/"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' + ;; + *) + if test yes,no = "$GXX,$with_gnu_ld"; then + allow_undefined_flag_CXX=' $wl-expect_unresolved $wl\*' + case $host in + osf3*) + archive_cmds_CXX='$CC -shared -nostdlib $allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + ;; + *) + archive_cmds_CXX='$CC -shared $pic_flag -nostdlib $allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-msym $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + ;; + esac + + hardcode_libdir_flag_spec_CXX='$wl-rpath $wl$libdir' + hardcode_libdir_separator_CXX=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"' + + else + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + fi + ;; + esac + ;; + + psos*) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + + sunos4*) + case $cc_basename in + CC*) + # Sun C++ 4.x + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + lcc*) + # Lucid + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + *) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + esac + ;; + + solaris*) + case $cc_basename in + CC* | sunCC*) + # Sun C++ 4.2, 5.x and Centerline C++ + archive_cmds_need_lc_CXX=yes + no_undefined_flag_CXX=' -zdefs' + archive_cmds_CXX='$CC -G$allow_undefined_flag -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + archive_expsym_cmds_CXX='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -G$allow_undefined_flag $wl-M $wl$lib.exp -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp' + + hardcode_libdir_flag_spec_CXX='-R$libdir' + hardcode_shlibpath_var_CXX=no + case $host_os in + solaris2.[0-5] | solaris2.[0-5].*) ;; + *) + # The compiler driver will combine and reorder linker options, + # but understands '-z linker_flag'. + # Supported since Solaris 2.6 (maybe 2.5.1?) + whole_archive_flag_spec_CXX='-z allextract$convenience -z defaultextract' + ;; + esac + link_all_deplibs_CXX=yes + + output_verbose_link_cmd='func_echo_all' + + # Archives containing C++ object files must be created using + # "CC -xar", where "CC" is the Sun C++ compiler. This is + # necessary to make sure instantiated templates are included + # in the archive. + old_archive_cmds_CXX='$CC -xar -o $oldlib $oldobjs' + ;; + gcx*) + # Green Hills C++ Compiler + archive_cmds_CXX='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-h $wl$soname -o $lib' + + # The C++ compiler must be used to create the archive. + old_archive_cmds_CXX='$CC $LDFLAGS -archive -o $oldlib $oldobjs' + ;; + *) + # GNU C++ compiler with Solaris linker + if test yes,no = "$GXX,$with_gnu_ld"; then + no_undefined_flag_CXX=' $wl-z ${wl}defs' + if $CC --version | $GREP -v '^2\.7' > /dev/null; then + archive_cmds_CXX='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-h $wl$soname -o $lib' + archive_expsym_cmds_CXX='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -shared $pic_flag -nostdlib $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp' + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"' + else + # g++ 2.7 appears to require '-G' NOT '-shared' on this + # platform. + archive_cmds_CXX='$CC -G -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-h $wl$soname -o $lib' + archive_expsym_cmds_CXX='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -G -nostdlib $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp' + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -G $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"' + fi + + hardcode_libdir_flag_spec_CXX='$wl-R $wl$libdir' + case $host_os in + solaris2.[0-5] | solaris2.[0-5].*) ;; + *) + whole_archive_flag_spec_CXX='$wl-z ${wl}allextract$convenience $wl-z ${wl}defaultextract' + ;; + esac + fi + ;; + esac + ;; + + sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[01].[10]* | unixware7* | sco3.2v5.0.[024]*) + no_undefined_flag_CXX='$wl-z,text' + archive_cmds_need_lc_CXX=no + hardcode_shlibpath_var_CXX=no + runpath_var='LD_RUN_PATH' + + case $cc_basename in + CC*) + archive_cmds_CXX='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds_CXX='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + archive_cmds_CXX='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds_CXX='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + ;; + + sysv5* | sco3.2v5* | sco5v6*) + # Note: We CANNOT use -z defs as we might desire, because we do not + # link with -lc, and that would cause any symbols used from libc to + # always be unresolved, which means just about no library would + # ever link correctly. If we're not using GNU ld we use -z text + # though, which does catch some bad symbols but isn't as heavy-handed + # as -z defs. + no_undefined_flag_CXX='$wl-z,text' + allow_undefined_flag_CXX='$wl-z,nodefs' + archive_cmds_need_lc_CXX=no + hardcode_shlibpath_var_CXX=no + hardcode_libdir_flag_spec_CXX='$wl-R,$libdir' + hardcode_libdir_separator_CXX=':' + link_all_deplibs_CXX=yes + export_dynamic_flag_spec_CXX='$wl-Bexport' + runpath_var='LD_RUN_PATH' + + case $cc_basename in + CC*) + archive_cmds_CXX='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds_CXX='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + old_archive_cmds_CXX='$CC -Tprelink_objects $oldobjs~ + '"$old_archive_cmds_CXX" + reload_cmds_CXX='$CC -Tprelink_objects $reload_objs~ + '"$reload_cmds_CXX" + ;; + *) + archive_cmds_CXX='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds_CXX='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + ;; + + tandem*) + case $cc_basename in + NCC*) + # NonStop-UX NCC 3.20 + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + *) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + esac + ;; + + vxworks*) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + + *) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + esac + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ld_shlibs_CXX" >&5 +$as_echo "$ld_shlibs_CXX" >&6; } + test no = "$ld_shlibs_CXX" && can_build_shared=no + + GCC_CXX=$GXX + LD_CXX=$LD + + ## CAVEAT EMPTOR: + ## There is no encapsulation within the following macros, do not change + ## the running order or otherwise move them around unless you know exactly + ## what you are doing... + # Dependencies to place before and after the object being linked: +predep_objects_CXX= +postdep_objects_CXX= +predeps_CXX= +postdeps_CXX= +compiler_lib_search_path_CXX= + +cat > conftest.$ac_ext <<_LT_EOF +class Foo +{ +public: + Foo (void) { a = 0; } +private: + int a; +}; +_LT_EOF + + +_lt_libdeps_save_CFLAGS=$CFLAGS +case "$CC $CFLAGS " in #( +*\ -flto*\ *) CFLAGS="$CFLAGS -fno-lto" ;; +*\ -fwhopr*\ *) CFLAGS="$CFLAGS -fno-whopr" ;; +*\ -fuse-linker-plugin*\ *) CFLAGS="$CFLAGS -fno-use-linker-plugin" ;; +esac + +if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + # Parse the compiler output and extract the necessary + # objects, libraries and library flags. + + # Sentinel used to keep track of whether or not we are before + # the conftest object file. + pre_test_object_deps_done=no + + for p in `eval "$output_verbose_link_cmd"`; do + case $prev$p in + + -L* | -R* | -l*) + # Some compilers place space between "-{L,R}" and the path. + # Remove the space. + if test x-L = "$p" || + test x-R = "$p"; then + prev=$p + continue + fi + + # Expand the sysroot to ease extracting the directories later. + if test -z "$prev"; then + case $p in + -L*) func_stripname_cnf '-L' '' "$p"; prev=-L; p=$func_stripname_result ;; + -R*) func_stripname_cnf '-R' '' "$p"; prev=-R; p=$func_stripname_result ;; + -l*) func_stripname_cnf '-l' '' "$p"; prev=-l; p=$func_stripname_result ;; + esac + fi + case $p in + =*) func_stripname_cnf '=' '' "$p"; p=$lt_sysroot$func_stripname_result ;; + esac + if test no = "$pre_test_object_deps_done"; then + case $prev in + -L | -R) + # Internal compiler library paths should come after those + # provided the user. The postdeps already come after the + # user supplied libs so there is no need to process them. + if test -z "$compiler_lib_search_path_CXX"; then + compiler_lib_search_path_CXX=$prev$p + else + compiler_lib_search_path_CXX="${compiler_lib_search_path_CXX} $prev$p" + fi + ;; + # The "-l" case would never come before the object being + # linked, so don't bother handling this case. + esac + else + if test -z "$postdeps_CXX"; then + postdeps_CXX=$prev$p + else + postdeps_CXX="${postdeps_CXX} $prev$p" + fi + fi + prev= + ;; + + *.lto.$objext) ;; # Ignore GCC LTO objects + *.$objext) + # This assumes that the test object file only shows up + # once in the compiler output. + if test "$p" = "conftest.$objext"; then + pre_test_object_deps_done=yes + continue + fi + + if test no = "$pre_test_object_deps_done"; then + if test -z "$predep_objects_CXX"; then + predep_objects_CXX=$p + else + predep_objects_CXX="$predep_objects_CXX $p" + fi + else + if test -z "$postdep_objects_CXX"; then + postdep_objects_CXX=$p + else + postdep_objects_CXX="$postdep_objects_CXX $p" + fi + fi + ;; + + *) ;; # Ignore the rest. + + esac + done + + # Clean up. + rm -f a.out a.exe +else + echo "libtool.m4: error: problem compiling CXX test program" +fi + +$RM -f confest.$objext +CFLAGS=$_lt_libdeps_save_CFLAGS + +# PORTME: override above test on systems where it is broken +case $host_os in +interix[3-9]*) + # Interix 3.5 installs completely hosed .la files for C++, so rather than + # hack all around it, let's just trust "g++" to DTRT. + predep_objects_CXX= + postdep_objects_CXX= + postdeps_CXX= + ;; +esac + + +case " $postdeps_CXX " in +*" -lc "*) archive_cmds_need_lc_CXX=no ;; +esac + compiler_lib_search_dirs_CXX= +if test -n "${compiler_lib_search_path_CXX}"; then + compiler_lib_search_dirs_CXX=`echo " ${compiler_lib_search_path_CXX}" | $SED -e 's! -L! !g' -e 's!^ !!'` +fi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + lt_prog_compiler_wl_CXX= +lt_prog_compiler_pic_CXX= +lt_prog_compiler_static_CXX= + + + # C++ specific cases for pic, static, wl, etc. + if test yes = "$GXX"; then + lt_prog_compiler_wl_CXX='-Wl,' + lt_prog_compiler_static_CXX='-static' + + case $host_os in + aix*) + # All AIX code is PIC. + if test ia64 = "$host_cpu"; then + # AIX 5 now supports IA64 processor + lt_prog_compiler_static_CXX='-Bstatic' + fi + lt_prog_compiler_pic_CXX='-fPIC' + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + lt_prog_compiler_pic_CXX='-fPIC' + ;; + m68k) + # FIXME: we need at least 68020 code to build shared libraries, but + # adding the '-m68020' flag to GCC prevents building anything better, + # like '-m68040'. + lt_prog_compiler_pic_CXX='-m68020 -resident32 -malways-restore-a4' + ;; + esac + ;; + + beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) + # PIC is the default for these OSes. + ;; + mingw* | cygwin* | os2* | pw32* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + # Although the cygwin gcc ignores -fPIC, still need this for old-style + # (--disable-auto-import) libraries + lt_prog_compiler_pic_CXX='-DDLL_EXPORT' + case $host_os in + os2*) + lt_prog_compiler_static_CXX='$wl-static' + ;; + esac + ;; + darwin* | rhapsody*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + lt_prog_compiler_pic_CXX='-fno-common' + ;; + *djgpp*) + # DJGPP does not support shared libraries at all + lt_prog_compiler_pic_CXX= + ;; + haiku*) + # PIC is the default for Haiku. + # The "-static" flag exists, but is broken. + lt_prog_compiler_static_CXX= + ;; + interix[3-9]*) + # Interix 3.x gcc -fpic/-fPIC options generate broken code. + # Instead, we relocate shared libraries at runtime. + ;; + sysv4*MP*) + if test -d /usr/nec; then + lt_prog_compiler_pic_CXX=-Kconform_pic + fi + ;; + hpux*) + # PIC is the default for 64-bit PA HP-UX, but not for 32-bit + # PA HP-UX. On IA64 HP-UX, PIC is the default but the pic flag + # sets the default TLS model and affects inlining. + case $host_cpu in + hppa*64*) + ;; + *) + lt_prog_compiler_pic_CXX='-fPIC' + ;; + esac + ;; + *qnx* | *nto*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + lt_prog_compiler_pic_CXX='-fPIC -shared' + ;; + *) + lt_prog_compiler_pic_CXX='-fPIC' + ;; + esac + else + case $host_os in + aix[4-9]*) + # All AIX code is PIC. + if test ia64 = "$host_cpu"; then + # AIX 5 now supports IA64 processor + lt_prog_compiler_static_CXX='-Bstatic' + else + lt_prog_compiler_static_CXX='-bnso -bI:/lib/syscalls.exp' + fi + ;; + chorus*) + case $cc_basename in + cxch68*) + # Green Hills C++ Compiler + # _LT_TAGVAR(lt_prog_compiler_static, CXX)="--no_auto_instantiation -u __main -u __premain -u _abort -r $COOL_DIR/lib/libOrb.a $MVME_DIR/lib/CC/libC.a $MVME_DIR/lib/classix/libcx.s.a" + ;; + esac + ;; + mingw* | cygwin* | os2* | pw32* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + lt_prog_compiler_pic_CXX='-DDLL_EXPORT' + ;; + dgux*) + case $cc_basename in + ec++*) + lt_prog_compiler_pic_CXX='-KPIC' + ;; + ghcx*) + # Green Hills C++ Compiler + lt_prog_compiler_pic_CXX='-pic' + ;; + *) + ;; + esac + ;; + freebsd* | dragonfly*) + # FreeBSD uses GNU C++ + ;; + hpux9* | hpux10* | hpux11*) + case $cc_basename in + CC*) + lt_prog_compiler_wl_CXX='-Wl,' + lt_prog_compiler_static_CXX='$wl-a ${wl}archive' + if test ia64 != "$host_cpu"; then + lt_prog_compiler_pic_CXX='+Z' + fi + ;; + aCC*) + lt_prog_compiler_wl_CXX='-Wl,' + lt_prog_compiler_static_CXX='$wl-a ${wl}archive' + case $host_cpu in + hppa*64*|ia64*) + # +Z the default + ;; + *) + lt_prog_compiler_pic_CXX='+Z' + ;; + esac + ;; + *) + ;; + esac + ;; + interix*) + # This is c89, which is MS Visual C++ (no shared libs) + # Anyone wants to do a port? + ;; + irix5* | irix6* | nonstopux*) + case $cc_basename in + CC*) + lt_prog_compiler_wl_CXX='-Wl,' + lt_prog_compiler_static_CXX='-non_shared' + # CC pic flag -KPIC is the default. + ;; + *) + ;; + esac + ;; + linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + case $cc_basename in + KCC*) + # KAI C++ Compiler + lt_prog_compiler_wl_CXX='--backend -Wl,' + lt_prog_compiler_pic_CXX='-fPIC' + ;; + ecpc* ) + # old Intel C++ for x86_64, which still supported -KPIC. + lt_prog_compiler_wl_CXX='-Wl,' + lt_prog_compiler_pic_CXX='-KPIC' + lt_prog_compiler_static_CXX='-static' + ;; + icpc* ) + # Intel C++, used to be incompatible with GCC. + # ICC 10 doesn't accept -KPIC any more. + lt_prog_compiler_wl_CXX='-Wl,' + lt_prog_compiler_pic_CXX='-fPIC' + lt_prog_compiler_static_CXX='-static' + ;; + pgCC* | pgcpp*) + # Portland Group C++ compiler + lt_prog_compiler_wl_CXX='-Wl,' + lt_prog_compiler_pic_CXX='-fpic' + lt_prog_compiler_static_CXX='-Bstatic' + ;; + cxx*) + # Compaq C++ + # Make sure the PIC flag is empty. It appears that all Alpha + # Linux and Compaq Tru64 Unix objects are PIC. + lt_prog_compiler_pic_CXX= + lt_prog_compiler_static_CXX='-non_shared' + ;; + xlc* | xlC* | bgxl[cC]* | mpixl[cC]*) + # IBM XL 8.0, 9.0 on PPC and BlueGene + lt_prog_compiler_wl_CXX='-Wl,' + lt_prog_compiler_pic_CXX='-qpic' + lt_prog_compiler_static_CXX='-qstaticlink' + ;; + *) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) + # Sun C++ 5.9 + lt_prog_compiler_pic_CXX='-KPIC' + lt_prog_compiler_static_CXX='-Bstatic' + lt_prog_compiler_wl_CXX='-Qoption ld ' + ;; + esac + ;; + esac + ;; + lynxos*) + ;; + m88k*) + ;; + mvs*) + case $cc_basename in + cxx*) + lt_prog_compiler_pic_CXX='-W c,exportall' + ;; + *) + ;; + esac + ;; + netbsd*) + ;; + *qnx* | *nto*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + lt_prog_compiler_pic_CXX='-fPIC -shared' + ;; + osf3* | osf4* | osf5*) + case $cc_basename in + KCC*) + lt_prog_compiler_wl_CXX='--backend -Wl,' + ;; + RCC*) + # Rational C++ 2.4.1 + lt_prog_compiler_pic_CXX='-pic' + ;; + cxx*) + # Digital/Compaq C++ + lt_prog_compiler_wl_CXX='-Wl,' + # Make sure the PIC flag is empty. It appears that all Alpha + # Linux and Compaq Tru64 Unix objects are PIC. + lt_prog_compiler_pic_CXX= + lt_prog_compiler_static_CXX='-non_shared' + ;; + *) + ;; + esac + ;; + psos*) + ;; + solaris*) + case $cc_basename in + CC* | sunCC*) + # Sun C++ 4.2, 5.x and Centerline C++ + lt_prog_compiler_pic_CXX='-KPIC' + lt_prog_compiler_static_CXX='-Bstatic' + lt_prog_compiler_wl_CXX='-Qoption ld ' + ;; + gcx*) + # Green Hills C++ Compiler + lt_prog_compiler_pic_CXX='-PIC' + ;; + *) + ;; + esac + ;; + sunos4*) + case $cc_basename in + CC*) + # Sun C++ 4.x + lt_prog_compiler_pic_CXX='-pic' + lt_prog_compiler_static_CXX='-Bstatic' + ;; + lcc*) + # Lucid + lt_prog_compiler_pic_CXX='-pic' + ;; + *) + ;; + esac + ;; + sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) + case $cc_basename in + CC*) + lt_prog_compiler_wl_CXX='-Wl,' + lt_prog_compiler_pic_CXX='-KPIC' + lt_prog_compiler_static_CXX='-Bstatic' + ;; + esac + ;; + tandem*) + case $cc_basename in + NCC*) + # NonStop-UX NCC 3.20 + lt_prog_compiler_pic_CXX='-KPIC' + ;; + *) + ;; + esac + ;; + vxworks*) + ;; + *) + lt_prog_compiler_can_build_shared_CXX=no + ;; + esac + fi + +case $host_os in + # For platforms that do not support PIC, -DPIC is meaningless: + *djgpp*) + lt_prog_compiler_pic_CXX= + ;; + *) + lt_prog_compiler_pic_CXX="$lt_prog_compiler_pic_CXX -DPIC" + ;; +esac + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $compiler option to produce PIC" >&5 +$as_echo_n "checking for $compiler option to produce PIC... " >&6; } +if ${lt_cv_prog_compiler_pic_CXX+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_pic_CXX=$lt_prog_compiler_pic_CXX +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic_CXX" >&5 +$as_echo "$lt_cv_prog_compiler_pic_CXX" >&6; } +lt_prog_compiler_pic_CXX=$lt_cv_prog_compiler_pic_CXX + +# +# Check to make sure the PIC flag actually works. +# +if test -n "$lt_prog_compiler_pic_CXX"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler PIC flag $lt_prog_compiler_pic_CXX works" >&5 +$as_echo_n "checking if $compiler PIC flag $lt_prog_compiler_pic_CXX works... " >&6; } +if ${lt_cv_prog_compiler_pic_works_CXX+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_pic_works_CXX=no + ac_outfile=conftest.$ac_objext + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + lt_compiler_flag="$lt_prog_compiler_pic_CXX -DPIC" ## exclude from sc_useless_quotes_in_assignment + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + # The option is referenced via a variable to avoid confusing sed. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) + (eval "$lt_compile" 2>conftest.err) + ac_status=$? + cat conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + if (exit $ac_status) && test -s "$ac_outfile"; then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings other than the usual output. + $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then + lt_cv_prog_compiler_pic_works_CXX=yes + fi + fi + $RM conftest* + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic_works_CXX" >&5 +$as_echo "$lt_cv_prog_compiler_pic_works_CXX" >&6; } + +if test yes = "$lt_cv_prog_compiler_pic_works_CXX"; then + case $lt_prog_compiler_pic_CXX in + "" | " "*) ;; + *) lt_prog_compiler_pic_CXX=" $lt_prog_compiler_pic_CXX" ;; + esac +else + lt_prog_compiler_pic_CXX= + lt_prog_compiler_can_build_shared_CXX=no +fi + +fi + + + + + +# +# Check to make sure the static flag actually works. +# +wl=$lt_prog_compiler_wl_CXX eval lt_tmp_static_flag=\"$lt_prog_compiler_static_CXX\" +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler static flag $lt_tmp_static_flag works" >&5 +$as_echo_n "checking if $compiler static flag $lt_tmp_static_flag works... " >&6; } +if ${lt_cv_prog_compiler_static_works_CXX+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_static_works_CXX=no + save_LDFLAGS=$LDFLAGS + LDFLAGS="$LDFLAGS $lt_tmp_static_flag" + echo "$lt_simple_link_test_code" > conftest.$ac_ext + if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then + # The linker can only warn and ignore the option if not recognized + # So say no if there are warnings + if test -s conftest.err; then + # Append any errors to the config.log. + cat conftest.err 1>&5 + $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if diff conftest.exp conftest.er2 >/dev/null; then + lt_cv_prog_compiler_static_works_CXX=yes + fi + else + lt_cv_prog_compiler_static_works_CXX=yes + fi + fi + $RM -r conftest* + LDFLAGS=$save_LDFLAGS + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_static_works_CXX" >&5 +$as_echo "$lt_cv_prog_compiler_static_works_CXX" >&6; } + +if test yes = "$lt_cv_prog_compiler_static_works_CXX"; then + : +else + lt_prog_compiler_static_CXX= +fi + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -c -o file.$ac_objext" >&5 +$as_echo_n "checking if $compiler supports -c -o file.$ac_objext... " >&6; } +if ${lt_cv_prog_compiler_c_o_CXX+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_c_o_CXX=no + $RM -r conftest 2>/dev/null + mkdir conftest + cd conftest + mkdir out + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + lt_compiler_flag="-o out/conftest2.$ac_objext" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) + (eval "$lt_compile" 2>out/conftest.err) + ac_status=$? + cat out/conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + if (exit $ac_status) && test -s out/conftest2.$ac_objext + then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings + $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp + $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2 + if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then + lt_cv_prog_compiler_c_o_CXX=yes + fi + fi + chmod u+w . 2>&5 + $RM conftest* + # SGI C++ compiler will create directory out/ii_files/ for + # template instantiation + test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files + $RM out/* && rmdir out + cd .. + $RM -r conftest + $RM conftest* + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_c_o_CXX" >&5 +$as_echo "$lt_cv_prog_compiler_c_o_CXX" >&6; } + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -c -o file.$ac_objext" >&5 +$as_echo_n "checking if $compiler supports -c -o file.$ac_objext... " >&6; } +if ${lt_cv_prog_compiler_c_o_CXX+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_c_o_CXX=no + $RM -r conftest 2>/dev/null + mkdir conftest + cd conftest + mkdir out + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + lt_compiler_flag="-o out/conftest2.$ac_objext" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) + (eval "$lt_compile" 2>out/conftest.err) + ac_status=$? + cat out/conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + if (exit $ac_status) && test -s out/conftest2.$ac_objext + then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings + $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp + $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2 + if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then + lt_cv_prog_compiler_c_o_CXX=yes + fi + fi + chmod u+w . 2>&5 + $RM conftest* + # SGI C++ compiler will create directory out/ii_files/ for + # template instantiation + test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files + $RM out/* && rmdir out + cd .. + $RM -r conftest + $RM conftest* + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_c_o_CXX" >&5 +$as_echo "$lt_cv_prog_compiler_c_o_CXX" >&6; } + + + + +hard_links=nottested +if test no = "$lt_cv_prog_compiler_c_o_CXX" && test no != "$need_locks"; then + # do not overwrite the value of need_locks provided by the user + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if we can lock with hard links" >&5 +$as_echo_n "checking if we can lock with hard links... " >&6; } + hard_links=yes + $RM conftest* + ln conftest.a conftest.b 2>/dev/null && hard_links=no + touch conftest.a + ln conftest.a conftest.b 2>&5 || hard_links=no + ln conftest.a conftest.b 2>/dev/null && hard_links=no + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $hard_links" >&5 +$as_echo "$hard_links" >&6; } + if test no = "$hard_links"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: '$CC' does not support '-c -o', so 'make -j' may be unsafe" >&5 +$as_echo "$as_me: WARNING: '$CC' does not support '-c -o', so 'make -j' may be unsafe" >&2;} + need_locks=warn + fi +else + need_locks=no +fi + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the $compiler linker ($LD) supports shared libraries" >&5 +$as_echo_n "checking whether the $compiler linker ($LD) supports shared libraries... " >&6; } + + export_symbols_cmds_CXX='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + exclude_expsyms_CXX='_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*' + case $host_os in + aix[4-9]*) + # If we're using GNU nm, then we don't want the "-C" option. + # -C means demangle to GNU nm, but means don't demangle to AIX nm. + # Without the "-l" option, or with the "-B" option, AIX nm treats + # weak defined symbols like other global defined symbols, whereas + # GNU nm marks them as "W". + # While the 'weak' keyword is ignored in the Export File, we need + # it in the Import File for the 'aix-soname' feature, so we have + # to replace the "-B" option with "-P" for AIX nm. + if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then + export_symbols_cmds_CXX='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && (substr(\$ 3,1,1) != ".")) { if (\$ 2 == "W") { print \$ 3 " weak" } else { print \$ 3 } } }'\'' | sort -u > $export_symbols' + else + export_symbols_cmds_CXX='`func_echo_all $NM | $SED -e '\''s/B\([^B]*\)$/P\1/'\''` -PCpgl $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) && (substr(\$ 1,1,1) != ".")) { if ((\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) { print \$ 1 " weak" } else { print \$ 1 } } }'\'' | sort -u > $export_symbols' + fi + ;; + pw32*) + export_symbols_cmds_CXX=$ltdll_cmds + ;; + cygwin* | mingw* | cegcc*) + case $cc_basename in + cl*) + exclude_expsyms_CXX='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*' + ;; + *) + export_symbols_cmds_CXX='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[BCDGRS][ ]/s/.*[ ]\([^ ]*\)/\1 DATA/;s/^.*[ ]__nm__\([^ ]*\)[ ][^ ]*/\1 DATA/;/^I[ ]/d;/^[AITW][ ]/s/.* //'\'' | sort | uniq > $export_symbols' + exclude_expsyms_CXX='[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname' + ;; + esac + ;; + *) + export_symbols_cmds_CXX='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + ;; + esac + +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ld_shlibs_CXX" >&5 +$as_echo "$ld_shlibs_CXX" >&6; } +test no = "$ld_shlibs_CXX" && can_build_shared=no + +with_gnu_ld_CXX=$with_gnu_ld + + + + + + +# +# Do we need to explicitly link libc? +# +case "x$archive_cmds_need_lc_CXX" in +x|xyes) + # Assume -lc should be added + archive_cmds_need_lc_CXX=yes + + if test yes,yes = "$GCC,$enable_shared"; then + case $archive_cmds_CXX in + *'~'*) + # FIXME: we may have to deal with multi-command sequences. + ;; + '$CC '*) + # Test whether the compiler implicitly links with -lc since on some + # systems, -lgcc has to come before -lc. If gcc already passes -lc + # to ld, don't add -lc before -lgcc. + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether -lc should be explicitly linked in" >&5 +$as_echo_n "checking whether -lc should be explicitly linked in... " >&6; } +if ${lt_cv_archive_cmds_need_lc_CXX+:} false; then : + $as_echo_n "(cached) " >&6 +else + $RM conftest* + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } 2>conftest.err; then + soname=conftest + lib=conftest + libobjs=conftest.$ac_objext + deplibs= + wl=$lt_prog_compiler_wl_CXX + pic_flag=$lt_prog_compiler_pic_CXX + compiler_flags=-v + linker_flags=-v + verstring= + output_objdir=. + libname=conftest + lt_save_allow_undefined_flag=$allow_undefined_flag_CXX + allow_undefined_flag_CXX= + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$archive_cmds_CXX 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1\""; } >&5 + (eval $archive_cmds_CXX 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } + then + lt_cv_archive_cmds_need_lc_CXX=no + else + lt_cv_archive_cmds_need_lc_CXX=yes + fi + allow_undefined_flag_CXX=$lt_save_allow_undefined_flag + else + cat conftest.err 1>&5 + fi + $RM conftest* + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_archive_cmds_need_lc_CXX" >&5 +$as_echo "$lt_cv_archive_cmds_need_lc_CXX" >&6; } + archive_cmds_need_lc_CXX=$lt_cv_archive_cmds_need_lc_CXX + ;; + esac + fi + ;; +esac + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking dynamic linker characteristics" >&5 +$as_echo_n "checking dynamic linker characteristics... " >&6; } + +library_names_spec= +libname_spec='lib$name' +soname_spec= +shrext_cmds=.so +postinstall_cmds= +postuninstall_cmds= +finish_cmds= +finish_eval= +shlibpath_var= +shlibpath_overrides_runpath=unknown +version_type=none +dynamic_linker="$host_os ld.so" +sys_lib_dlsearch_path_spec="/lib /usr/lib" +need_lib_prefix=unknown +hardcode_into_libs=no + +# when you set need_version to no, make sure it does not cause -set_version +# flags to be left without arguments +need_version=unknown + + + +case $host_os in +aix3*) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname.a' + shlibpath_var=LIBPATH + + # AIX 3 has no versioning support, so we append a major version to the name. + soname_spec='$libname$release$shared_ext$major' + ;; + +aix[4-9]*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + hardcode_into_libs=yes + if test ia64 = "$host_cpu"; then + # AIX 5 supports IA64 + library_names_spec='$libname$release$shared_ext$major $libname$release$shared_ext$versuffix $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + else + # With GCC up to 2.95.x, collect2 would create an import file + # for dependence libraries. The import file would start with + # the line '#! .'. This would cause the generated library to + # depend on '.', always an invalid library. This was fixed in + # development snapshots of GCC prior to 3.0. + case $host_os in + aix4 | aix4.[01] | aix4.[01].*) + if { echo '#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 97)' + echo ' yes ' + echo '#endif'; } | $CC -E - | $GREP yes > /dev/null; then + : + else + can_build_shared=no + fi + ;; + esac + # Using Import Files as archive members, it is possible to support + # filename-based versioning of shared library archives on AIX. While + # this would work for both with and without runtime linking, it will + # prevent static linking of such archives. So we do filename-based + # shared library versioning with .so extension only, which is used + # when both runtime linking and shared linking is enabled. + # Unfortunately, runtime linking may impact performance, so we do + # not want this to be the default eventually. Also, we use the + # versioned .so libs for executables only if there is the -brtl + # linker flag in LDFLAGS as well, or --with-aix-soname=svr4 only. + # To allow for filename-based versioning support, we need to create + # libNAME.so.V as an archive file, containing: + # *) an Import File, referring to the versioned filename of the + # archive as well as the shared archive member, telling the + # bitwidth (32 or 64) of that shared object, and providing the + # list of exported symbols of that shared object, eventually + # decorated with the 'weak' keyword + # *) the shared object with the F_LOADONLY flag set, to really avoid + # it being seen by the linker. + # At run time we better use the real file rather than another symlink, + # but for link time we create the symlink libNAME.so -> libNAME.so.V + + case $with_aix_soname,$aix_use_runtimelinking in + # AIX (on Power*) has no versioning support, so currently we cannot hardcode correct + # soname into executable. Probably we can add versioning support to + # collect2, so additional links can be useful in future. + aix,yes) # traditional libtool + dynamic_linker='AIX unversionable lib.so' + # If using run time linking (on AIX 4.2 or later) use lib.so + # instead of lib.a to let people know that these are not + # typical AIX shared libraries. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + ;; + aix,no) # traditional AIX only + dynamic_linker='AIX lib.a(lib.so.V)' + # We preserve .a as extension for shared libraries through AIX4.2 + # and later when we are not doing run time linking. + library_names_spec='$libname$release.a $libname.a' + soname_spec='$libname$release$shared_ext$major' + ;; + svr4,*) # full svr4 only + dynamic_linker="AIX lib.so.V($shared_archive_member_spec.o)" + library_names_spec='$libname$release$shared_ext$major $libname$shared_ext' + # We do not specify a path in Import Files, so LIBPATH fires. + shlibpath_overrides_runpath=yes + ;; + *,yes) # both, prefer svr4 + dynamic_linker="AIX lib.so.V($shared_archive_member_spec.o), lib.a(lib.so.V)" + library_names_spec='$libname$release$shared_ext$major $libname$shared_ext' + # unpreferred sharedlib libNAME.a needs extra handling + postinstall_cmds='test -n "$linkname" || linkname="$realname"~func_stripname "" ".so" "$linkname"~$install_shared_prog "$dir/$func_stripname_result.$libext" "$destdir/$func_stripname_result.$libext"~test -z "$tstripme" || test -z "$striplib" || $striplib "$destdir/$func_stripname_result.$libext"' + postuninstall_cmds='for n in $library_names $old_library; do :; done~func_stripname "" ".so" "$n"~test "$func_stripname_result" = "$n" || func_append rmfiles " $odir/$func_stripname_result.$libext"' + # We do not specify a path in Import Files, so LIBPATH fires. + shlibpath_overrides_runpath=yes + ;; + *,no) # both, prefer aix + dynamic_linker="AIX lib.a(lib.so.V), lib.so.V($shared_archive_member_spec.o)" + library_names_spec='$libname$release.a $libname.a' + soname_spec='$libname$release$shared_ext$major' + # unpreferred sharedlib libNAME.so.V and symlink libNAME.so need extra handling + postinstall_cmds='test -z "$dlname" || $install_shared_prog $dir/$dlname $destdir/$dlname~test -z "$tstripme" || test -z "$striplib" || $striplib $destdir/$dlname~test -n "$linkname" || linkname=$realname~func_stripname "" ".a" "$linkname"~(cd "$destdir" && $LN_S -f $dlname $func_stripname_result.so)' + postuninstall_cmds='test -z "$dlname" || func_append rmfiles " $odir/$dlname"~for n in $old_library $library_names; do :; done~func_stripname "" ".a" "$n"~func_append rmfiles " $odir/$func_stripname_result.so"' + ;; + esac + shlibpath_var=LIBPATH + fi + ;; + +amigaos*) + case $host_cpu in + powerpc) + # Since July 2007 AmigaOS4 officially supports .so libraries. + # When compiling the executable, add -use-dynld -Lsobjs: to the compileline. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + ;; + m68k) + library_names_spec='$libname.ixlibrary $libname.a' + # Create ${libname}_ixlibrary.a entries in /sys/libs. + finish_eval='for lib in `ls $libdir/*.ixlibrary 2>/dev/null`; do libname=`func_echo_all "$lib" | $SED '\''s%^.*/\([^/]*\)\.ixlibrary$%\1%'\''`; $RM /sys/libs/${libname}_ixlibrary.a; $show "cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a"; cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a || exit 1; done' + ;; + esac + ;; + +beos*) + library_names_spec='$libname$shared_ext' + dynamic_linker="$host_os ld.so" + shlibpath_var=LIBRARY_PATH + ;; + +bsdi[45]*) + version_type=linux # correct to gnu/linux during the next big refactor + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib" + sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib" + # the default ld.so.conf also contains /usr/contrib/lib and + # /usr/X11R6/lib (/usr/X11 is a link to /usr/X11R6), but let us allow + # libtool to hard-code these into programs + ;; + +cygwin* | mingw* | pw32* | cegcc*) + version_type=windows + shrext_cmds=.dll + need_version=no + need_lib_prefix=no + + case $GCC,$cc_basename in + yes,*) + # gcc + library_names_spec='$libname.dll.a' + # DLL is installed to $(libdir)/../bin by postinstall_cmds + postinstall_cmds='base_file=`basename \$file`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname~ + chmod a+x \$dldir/$dlname~ + if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then + eval '\''$striplib \$dldir/$dlname'\'' || exit \$?; + fi' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $RM \$dlpath' + shlibpath_overrides_runpath=yes + + case $host_os in + cygwin*) + # Cygwin DLLs use 'cyg' prefix rather than 'lib' + soname_spec='`echo $libname | sed -e 's/^lib/cyg/'``echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' + + ;; + mingw* | cegcc*) + # MinGW DLLs use traditional 'lib' prefix + soname_spec='$libname`echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' + ;; + pw32*) + # pw32 DLLs use 'pw' prefix rather than 'lib' + library_names_spec='`echo $libname | sed -e 's/^lib/pw/'``echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' + ;; + esac + dynamic_linker='Win32 ld.exe' + ;; + + *,cl*) + # Native MSVC + libname_spec='$name' + soname_spec='$libname`echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' + library_names_spec='$libname.dll.lib' + + case $build_os in + mingw*) + sys_lib_search_path_spec= + lt_save_ifs=$IFS + IFS=';' + for lt_path in $LIB + do + IFS=$lt_save_ifs + # Let DOS variable expansion print the short 8.3 style file name. + lt_path=`cd "$lt_path" 2>/dev/null && cmd //C "for %i in (".") do @echo %~si"` + sys_lib_search_path_spec="$sys_lib_search_path_spec $lt_path" + done + IFS=$lt_save_ifs + # Convert to MSYS style. + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | sed -e 's|\\\\|/|g' -e 's| \\([a-zA-Z]\\):| /\\1|g' -e 's|^ ||'` + ;; + cygwin*) + # Convert to unix form, then to dos form, then back to unix form + # but this time dos style (no spaces!) so that the unix form looks + # like /cygdrive/c/PROGRA~1:/cygdr... + sys_lib_search_path_spec=`cygpath --path --unix "$LIB"` + sys_lib_search_path_spec=`cygpath --path --dos "$sys_lib_search_path_spec" 2>/dev/null` + sys_lib_search_path_spec=`cygpath --path --unix "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` + ;; + *) + sys_lib_search_path_spec=$LIB + if $ECHO "$sys_lib_search_path_spec" | $GREP ';[c-zC-Z]:/' >/dev/null; then + # It is most probably a Windows format PATH. + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e 's/;/ /g'` + else + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` + fi + # FIXME: find the short name or the path components, as spaces are + # common. (e.g. "Program Files" -> "PROGRA~1") + ;; + esac + + # DLL is installed to $(libdir)/../bin by postinstall_cmds + postinstall_cmds='base_file=`basename \$file`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $RM \$dlpath' + shlibpath_overrides_runpath=yes + dynamic_linker='Win32 link.exe' + ;; + + *) + # Assume MSVC wrapper + library_names_spec='$libname`echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext $libname.lib' + dynamic_linker='Win32 ld.exe' + ;; + esac + # FIXME: first we should search . and the directory the executable is in + shlibpath_var=PATH + ;; + +darwin* | rhapsody*) + dynamic_linker="$host_os dyld" + version_type=darwin + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$major$shared_ext $libname$shared_ext' + soname_spec='$libname$release$major$shared_ext' + shlibpath_overrides_runpath=yes + shlibpath_var=DYLD_LIBRARY_PATH + shrext_cmds='`test .$module = .yes && echo .so || echo .dylib`' + + sys_lib_dlsearch_path_spec='/usr/local/lib /lib /usr/lib' + ;; + +dgux*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +freebsd* | dragonfly*) + # DragonFly does not have aout. When/if they implement a new + # versioning mechanism, adjust this. + if test -x /usr/bin/objformat; then + objformat=`/usr/bin/objformat` + else + case $host_os in + freebsd[23].*) objformat=aout ;; + *) objformat=elf ;; + esac + fi + version_type=freebsd-$objformat + case $version_type in + freebsd-elf*) + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + need_version=no + need_lib_prefix=no + ;; + freebsd-*) + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + need_version=yes + ;; + esac + shlibpath_var=LD_LIBRARY_PATH + case $host_os in + freebsd2.*) + shlibpath_overrides_runpath=yes + ;; + freebsd3.[01]* | freebsdelf3.[01]*) + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + freebsd3.[2-9]* | freebsdelf3.[2-9]* | \ + freebsd4.[0-5] | freebsdelf4.[0-5] | freebsd4.1.1 | freebsdelf4.1.1) + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + *) # from 4.6 on, and DragonFly + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + esac + ;; + +haiku*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + dynamic_linker="$host_os runtime_loader" + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LIBRARY_PATH + shlibpath_overrides_runpath=no + sys_lib_dlsearch_path_spec='/boot/home/config/lib /boot/common/lib /boot/system/lib' + hardcode_into_libs=yes + ;; + +hpux9* | hpux10* | hpux11*) + # Give a soname corresponding to the major version so that dld.sl refuses to + # link against other versions. + version_type=sunos + need_lib_prefix=no + need_version=no + case $host_cpu in + ia64*) + shrext_cmds='.so' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.so" + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + if test 32 = "$HPUX_IA64_MODE"; then + sys_lib_search_path_spec="/usr/lib/hpux32 /usr/local/lib/hpux32 /usr/local/lib" + sys_lib_dlsearch_path_spec=/usr/lib/hpux32 + else + sys_lib_search_path_spec="/usr/lib/hpux64 /usr/local/lib/hpux64" + sys_lib_dlsearch_path_spec=/usr/lib/hpux64 + fi + ;; + hppa*64*) + shrext_cmds='.sl' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.sl" + shlibpath_var=LD_LIBRARY_PATH # How should we handle SHLIB_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + sys_lib_search_path_spec="/usr/lib/pa20_64 /usr/ccs/lib/pa20_64" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + *) + shrext_cmds='.sl' + dynamic_linker="$host_os dld.sl" + shlibpath_var=SHLIB_PATH + shlibpath_overrides_runpath=no # +s is required to enable SHLIB_PATH + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + ;; + esac + # HP-UX runs *really* slowly unless shared libraries are mode 555, ... + postinstall_cmds='chmod 555 $lib' + # or fails outright, so override atomically: + install_override_mode=555 + ;; + +interix[3-9]*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + dynamic_linker='Interix 3.x ld.so.1 (PE, like ELF)' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + +irix5* | irix6* | nonstopux*) + case $host_os in + nonstopux*) version_type=nonstopux ;; + *) + if test yes = "$lt_cv_prog_gnu_ld"; then + version_type=linux # correct to gnu/linux during the next big refactor + else + version_type=irix + fi ;; + esac + need_lib_prefix=no + need_version=no + soname_spec='$libname$release$shared_ext$major' + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$release$shared_ext $libname$shared_ext' + case $host_os in + irix5* | nonstopux*) + libsuff= shlibsuff= + ;; + *) + case $LD in # libtool.m4 will add one of these switches to LD + *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ") + libsuff= shlibsuff= libmagic=32-bit;; + *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ") + libsuff=32 shlibsuff=N32 libmagic=N32;; + *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ") + libsuff=64 shlibsuff=64 libmagic=64-bit;; + *) libsuff= shlibsuff= libmagic=never-match;; + esac + ;; + esac + shlibpath_var=LD_LIBRARY${shlibsuff}_PATH + shlibpath_overrides_runpath=no + sys_lib_search_path_spec="/usr/lib$libsuff /lib$libsuff /usr/local/lib$libsuff" + sys_lib_dlsearch_path_spec="/usr/lib$libsuff /lib$libsuff" + hardcode_into_libs=yes + ;; + +# No shared lib support for Linux oldld, aout, or coff. +linux*oldld* | linux*aout* | linux*coff*) + dynamic_linker=no + ;; + +linux*android*) + version_type=none # Android doesn't support versioned libraries. + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext' + soname_spec='$libname$release$shared_ext' + finish_cmds= + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + + # This implies no fast_install, which is unacceptable. + # Some rework will be needed to allow for fast_install + # before this can be enabled. + hardcode_into_libs=yes + + dynamic_linker='Android linker' + # Don't embed -rpath directories since the linker doesn't support them. + hardcode_libdir_flag_spec_CXX='-L$libdir' + ;; + +# This must be glibc/ELF. +linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -n $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + + # Some binutils ld are patched to set DT_RUNPATH + if ${lt_cv_shlibpath_overrides_runpath+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_shlibpath_overrides_runpath=no + save_LDFLAGS=$LDFLAGS + save_libdir=$libdir + eval "libdir=/foo; wl=\"$lt_prog_compiler_wl_CXX\"; \ + LDFLAGS=\"\$LDFLAGS $hardcode_libdir_flag_spec_CXX\"" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_link "$LINENO"; then : + if ($OBJDUMP -p conftest$ac_exeext) 2>/dev/null | grep "RUNPATH.*$libdir" >/dev/null; then : + lt_cv_shlibpath_overrides_runpath=yes +fi +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + LDFLAGS=$save_LDFLAGS + libdir=$save_libdir + +fi + + shlibpath_overrides_runpath=$lt_cv_shlibpath_overrides_runpath + + # This implies no fast_install, which is unacceptable. + # Some rework will be needed to allow for fast_install + # before this can be enabled. + hardcode_into_libs=yes + + # Ideally, we could use ldconfig to report *all* directores which are + # searched for libraries, however this is still not possible. Aside from not + # being certain /sbin/ldconfig is available, command + # 'ldconfig -N -X -v | grep ^/' on 64bit Fedora does not report /usr/lib64, + # even though it is searched at run-time. Try to do the best guess by + # appending ld.so.conf contents (and includes) to the search path. + if test -f /etc/ld.so.conf; then + lt_ld_extra=`awk '/^include / { system(sprintf("cd /etc; cat %s 2>/dev/null", \$2)); skip = 1; } { if (!skip) print \$0; skip = 0; }' < /etc/ld.so.conf | $SED -e 's/#.*//;/^[ ]*hwcap[ ]/d;s/[:, ]/ /g;s/=[^=]*$//;s/=[^= ]* / /g;s/"//g;/^$/d' | tr '\n' ' '` + sys_lib_dlsearch_path_spec="/lib /usr/lib $lt_ld_extra" + fi + + # We used to test for /lib/ld.so.1 and disable shared libraries on + # powerpc, because MkLinux only supported shared libraries with the + # GNU dynamic linker. Since this was broken with cross compilers, + # most powerpc-linux boxes support dynamic linking these days and + # people can always --disable-shared, the test was removed, and we + # assume the GNU/Linux dynamic linker is in use. + dynamic_linker='GNU/Linux ld.so' + ;; + +netbsd*) + version_type=sunos + need_lib_prefix=no + need_version=no + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + dynamic_linker='NetBSD (a.out) ld.so' + else + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + dynamic_linker='NetBSD ld.elf_so' + fi + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + +newsos6) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + ;; + +*nto* | *qnx*) + version_type=qnx + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + dynamic_linker='ldqnx.so' + ;; + +openbsd* | bitrig*) + version_type=sunos + sys_lib_dlsearch_path_spec=/usr/lib + need_lib_prefix=no + if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then + need_version=no + else + need_version=yes + fi + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + ;; + +os2*) + libname_spec='$name' + version_type=windows + shrext_cmds=.dll + need_version=no + need_lib_prefix=no + # OS/2 can only load a DLL with a base name of 8 characters or less. + soname_spec='`test -n "$os2dllname" && libname="$os2dllname"; + v=$($ECHO $release$versuffix | tr -d .-); + n=$($ECHO $libname | cut -b -$((8 - ${#v})) | tr . _); + $ECHO $n$v`$shared_ext' + library_names_spec='${libname}_dll.$libext' + dynamic_linker='OS/2 ld.exe' + shlibpath_var=BEGINLIBPATH + sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + postinstall_cmds='base_file=`basename \$file`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; $ECHO \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname~ + chmod a+x \$dldir/$dlname~ + if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then + eval '\''$striplib \$dldir/$dlname'\'' || exit \$?; + fi' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; $ECHO \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $RM \$dlpath' + ;; + +osf3* | osf4* | osf5*) + version_type=osf + need_lib_prefix=no + need_version=no + soname_spec='$libname$release$shared_ext$major' + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + +rdos*) + dynamic_linker=no + ;; + +solaris*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + # ldd complains unless libraries are executable + postinstall_cmds='chmod +x $lib' + ;; + +sunos4*) + version_type=sunos + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + finish_cmds='PATH="\$PATH:/usr/etc" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + if test yes = "$with_gnu_ld"; then + need_lib_prefix=no + fi + need_version=yes + ;; + +sysv4 | sysv4.3*) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + case $host_vendor in + sni) + shlibpath_overrides_runpath=no + need_lib_prefix=no + runpath_var=LD_RUN_PATH + ;; + siemens) + need_lib_prefix=no + ;; + motorola) + need_lib_prefix=no + need_version=no + shlibpath_overrides_runpath=no + sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib' + ;; + esac + ;; + +sysv4*MP*) + if test -d /usr/nec; then + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$shared_ext.$versuffix $libname$shared_ext.$major $libname$shared_ext' + soname_spec='$libname$shared_ext.$major' + shlibpath_var=LD_LIBRARY_PATH + fi + ;; + +sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) + version_type=sco + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + if test yes = "$with_gnu_ld"; then + sys_lib_search_path_spec='/usr/local/lib /usr/gnu/lib /usr/ccs/lib /usr/lib /lib' + else + sys_lib_search_path_spec='/usr/ccs/lib /usr/lib' + case $host_os in + sco3.2v5*) + sys_lib_search_path_spec="$sys_lib_search_path_spec /lib" + ;; + esac + fi + sys_lib_dlsearch_path_spec='/usr/lib' + ;; + +tpf*) + # TPF is a cross-target only. Preferred cross-host = GNU/Linux. + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + +uts4*) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +*) + dynamic_linker=no + ;; +esac +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $dynamic_linker" >&5 +$as_echo "$dynamic_linker" >&6; } +test no = "$dynamic_linker" && can_build_shared=no + +variables_saved_for_relink="PATH $shlibpath_var $runpath_var" +if test yes = "$GCC"; then + variables_saved_for_relink="$variables_saved_for_relink GCC_EXEC_PREFIX COMPILER_PATH LIBRARY_PATH" +fi + +if test set = "${lt_cv_sys_lib_search_path_spec+set}"; then + sys_lib_search_path_spec=$lt_cv_sys_lib_search_path_spec +fi + +if test set = "${lt_cv_sys_lib_dlsearch_path_spec+set}"; then + sys_lib_dlsearch_path_spec=$lt_cv_sys_lib_dlsearch_path_spec +fi + +# remember unaugmented sys_lib_dlsearch_path content for libtool script decls... +configure_time_dlsearch_path=$sys_lib_dlsearch_path_spec + +# ... but it needs LT_SYS_LIBRARY_PATH munging for other configure-time code +func_munge_path_list sys_lib_dlsearch_path_spec "$LT_SYS_LIBRARY_PATH" + +# to be used as default LT_SYS_LIBRARY_PATH value in generated libtool +configure_time_lt_sys_library_path=$LT_SYS_LIBRARY_PATH + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to hardcode library paths into programs" >&5 +$as_echo_n "checking how to hardcode library paths into programs... " >&6; } +hardcode_action_CXX= +if test -n "$hardcode_libdir_flag_spec_CXX" || + test -n "$runpath_var_CXX" || + test yes = "$hardcode_automatic_CXX"; then + + # We can hardcode non-existent directories. + if test no != "$hardcode_direct_CXX" && + # If the only mechanism to avoid hardcoding is shlibpath_var, we + # have to relink, otherwise we might link with an installed library + # when we should be linking with a yet-to-be-installed one + ## test no != "$_LT_TAGVAR(hardcode_shlibpath_var, CXX)" && + test no != "$hardcode_minus_L_CXX"; then + # Linking always hardcodes the temporary library directory. + hardcode_action_CXX=relink + else + # We can link without hardcoding, and we can hardcode nonexisting dirs. + hardcode_action_CXX=immediate + fi +else + # We cannot hardcode anything, or else we can only hardcode existing + # directories. + hardcode_action_CXX=unsupported +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $hardcode_action_CXX" >&5 +$as_echo "$hardcode_action_CXX" >&6; } + +if test relink = "$hardcode_action_CXX" || + test yes = "$inherit_rpath_CXX"; then + # Fast installation is not supported + enable_fast_install=no +elif test yes = "$shlibpath_overrides_runpath" || + test no = "$enable_shared"; then + # Fast installation is not necessary + enable_fast_install=needless +fi + + + + + + + + fi # test -n "$compiler" + + CC=$lt_save_CC + CFLAGS=$lt_save_CFLAGS + LDCXX=$LD + LD=$lt_save_LD + GCC=$lt_save_GCC + with_gnu_ld=$lt_save_with_gnu_ld + lt_cv_path_LDCXX=$lt_cv_path_LD + lt_cv_path_LD=$lt_save_path_LD + lt_cv_prog_gnu_ldcxx=$lt_cv_prog_gnu_ld + lt_cv_prog_gnu_ld=$lt_save_with_gnu_ld +fi # test yes != "$_lt_caught_CXX_error" + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + + + + + + + + + + + + + ac_config_commands="$ac_config_commands libtool" + + + + +# Only expand once: + + + + +############################################################################### +# Checking for needed base libraries +############################################################################### + +$as_echo +$as_echo "Checking for posix thread support:" + + + + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +ax_pthread_ok=no + +# We used to check for pthread.h first, but this fails if pthread.h +# requires special compiler flags (e.g. on True64 or Sequent). +# It gets checked for in the link test anyway. + +# First of all, check if the user has set any of the PTHREAD_LIBS, +# etcetera environment variables, and if threads linking works using +# them: +if test "x$PTHREAD_LIBS$PTHREAD_CFLAGS" != "x"; then + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + save_LIBS="$LIBS" + LIBS="$PTHREAD_LIBS $LIBS" + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS" >&5 +$as_echo_n "checking for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS... " >&6; } + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char pthread_join (); +int +main () +{ +return pthread_join (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ax_pthread_ok=yes +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_pthread_ok" >&5 +$as_echo "$ax_pthread_ok" >&6; } + if test "x$ax_pthread_ok" = "xno"; then + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" + fi + LIBS="$save_LIBS" + CFLAGS="$save_CFLAGS" +fi + +# We must check for the threads library under a number of different +# names; the ordering is very important because some systems +# (e.g. DEC) have both -lpthread and -lpthreads, where one of the +# libraries is broken (non-POSIX). + +# Create a list of thread flags to try. Items starting with a "-" are +# C compiler flags, and other items are library names, except for "none" +# which indicates that we try without any flags at all, and "pthread-config" +# which is a program returning the flags for the Pth emulation library. + +ax_pthread_flags="pthreads none -Kthread -kthread lthread -lpthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" + +# The ordering *is* (sometimes) important. Some notes on the +# individual items follow: + +# pthreads: AIX (must check this before -lpthread) +# none: in case threads are in libc; should be tried before -Kthread and +# other compiler flags to prevent continual compiler warnings +# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) +# -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) +# lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) +# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads) +# -pthreads: Solaris/gcc +# -mthreads: Mingw32/gcc, Lynx/gcc +# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it +# doesn't hurt to check since this sometimes defines pthreads too; +# also defines -D_REENTRANT) +# ... -mt is also the pthreads flag for HP/aCC +# pthread: Linux, etcetera +# --thread-safe: KAI C++ +# pthread-config: use pthread-config program (for GNU Pth library) + +case "${host_cpu}-${host_os}" in + *solaris*) + + # On Solaris (at least, for some versions), libc contains stubbed + # (non-functional) versions of the pthreads routines, so link-based + # tests will erroneously succeed. (We need to link with -pthreads/-mt/ + # -lpthread.) (The stubs are missing pthread_cleanup_push, or rather + # a function called by this macro, so we could check for that, but + # who knows whether they'll stub that too in a future libc.) So, + # we'll just look for -pthreads and -lpthread first: + + ax_pthread_flags="-pthreads pthread -mt -pthread $ax_pthread_flags" + ;; +esac + +if test "x$ax_pthread_ok" = "xno"; then +for flag in $ax_pthread_flags; do + + case $flag in + none) + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether pthreads work without any flags" >&5 +$as_echo_n "checking whether pthreads work without any flags... " >&6; } + ;; + + -*) + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether pthreads work with $flag" >&5 +$as_echo_n "checking whether pthreads work with $flag... " >&6; } + PTHREAD_CFLAGS="$flag" + ;; + + pthread-config) + # Extract the first word of "pthread-config", so it can be a program name with args. +set dummy pthread-config; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ax_pthread_config+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ax_pthread_config"; then + ac_cv_prog_ax_pthread_config="$ax_pthread_config" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ax_pthread_config="yes" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + test -z "$ac_cv_prog_ax_pthread_config" && ac_cv_prog_ax_pthread_config="no" +fi +fi +ax_pthread_config=$ac_cv_prog_ax_pthread_config +if test -n "$ax_pthread_config"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_pthread_config" >&5 +$as_echo "$ax_pthread_config" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + if test "x$ax_pthread_config" = "xno"; then continue; fi + PTHREAD_CFLAGS="`pthread-config --cflags`" + PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" + ;; + + *) + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for the pthreads library -l$flag" >&5 +$as_echo_n "checking for the pthreads library -l$flag... " >&6; } + PTHREAD_LIBS="-l$flag" + ;; + esac + + save_LIBS="$LIBS" + save_CFLAGS="$CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + + # Check for various functions. We must include pthread.h, + # since some functions may be macros. (On the Sequent, we + # need a special flag -Kthread to make this header compile.) + # We check for pthread_join because it is in -lpthread on IRIX + # while pthread_create is in libc. We check for pthread_attr_init + # due to DEC craziness with -lpthreads. We check for + # pthread_cleanup_push because it is one of the few pthread + # functions on Solaris that doesn't have a non-functional libc stub. + # We try pthread_create on general principles. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main () +{ +pthread_t th; pthread_join(th, 0); + pthread_attr_init(0); pthread_cleanup_push(0, 0); + pthread_create(0,0,0,0); pthread_cleanup_pop(0); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ax_pthread_ok=yes +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + + LIBS="$save_LIBS" + CFLAGS="$save_CFLAGS" + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_pthread_ok" >&5 +$as_echo "$ax_pthread_ok" >&6; } + if test "x$ax_pthread_ok" = "xyes"; then + break; + fi + + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" +done +fi + +# Various other checks: +if test "x$ax_pthread_ok" = "xyes"; then + save_LIBS="$LIBS" + LIBS="$PTHREAD_LIBS $LIBS" + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + + # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for joinable pthread attribute" >&5 +$as_echo_n "checking for joinable pthread attribute... " >&6; } + attr_name=unknown + for attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main () +{ +int attr=$attr; return attr; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + attr_name=$attr; break +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + done + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $attr_name" >&5 +$as_echo "$attr_name" >&6; } + if test "x$attr_name" != "xPTHREAD_CREATE_JOINABLE"; then + +cat >>confdefs.h <<_ACEOF +#define PTHREAD_CREATE_JOINABLE $attr_name +_ACEOF + + fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if more special flags are required for pthreads" >&5 +$as_echo_n "checking if more special flags are required for pthreads... " >&6; } + flag=no + case "${host_cpu}-${host_os}" in + *-aix* | *-freebsd* | *-darwin*) flag="-D_THREAD_SAFE";; + *solaris* | *-osf* | *-hpux*) flag="-D_REENTRANT";; + esac + { $as_echo "$as_me:${as_lineno-$LINENO}: result: ${flag}" >&5 +$as_echo "${flag}" >&6; } + if test "x$flag" != "xno"; then + PTHREAD_CFLAGS="$flag $PTHREAD_CFLAGS" + fi + + LIBS="$save_LIBS" + CFLAGS="$save_CFLAGS" + + # More AIX lossage: must compile with xlc_r or cc_r + if test "x$GCC" != "xyes"; then + for ac_prog in xlc_r cc_r +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_PTHREAD_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$PTHREAD_CC"; then + ac_cv_prog_PTHREAD_CC="$PTHREAD_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_PTHREAD_CC="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +PTHREAD_CC=$ac_cv_prog_PTHREAD_CC +if test -n "$PTHREAD_CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PTHREAD_CC" >&5 +$as_echo "$PTHREAD_CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$PTHREAD_CC" && break +done +test -n "$PTHREAD_CC" || PTHREAD_CC="${CC}" + + else + PTHREAD_CC=$CC + fi + + # The next part tries to detect GCC inconsistency with -shared on some + # architectures and systems. The problem is that in certain + # configurations, when -shared is specified, GCC "forgets" to + # internally use various flags which are still necessary. + + # + # Prepare the flags + # + save_LDFLAGS="$LDFLAGS" + save_CFLAGS="$CFLAGS" + save_LIBS="$LIBS" + save_CC="$CC" + + # Try with the flags determined by the earlier checks. + # + # -Wl,-z,defs forces link-time symbol resolution, so that the + # linking checks with -shared actually have any value + # + # FIXME: -fPIC is required for -shared on many architectures, + # so we specify it here, but the right way would probably be to + # properly detect whether it is actually required. + CFLAGS="-shared -fPIC -Wl,-z,defs $CFLAGS $PTHREAD_CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + CC="$PTHREAD_CC" + + # In order not to create several levels of indentation, we test + # the value of "$done" until we find the cure or run out of ideas. + done="no" + + # First, make sure the CFLAGS we added are actually accepted by our + # compiler. If not (and OS X's ld, for instance, does not accept -z), + # then we can't do this test. + if test x"$done" = xno; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to check for GCC pthread/shared inconsistencies" >&5 +$as_echo_n "checking whether to check for GCC pthread/shared inconsistencies... " >&6; } + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + +else + done=yes +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + + if test "x$done" = xyes ; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + fi + fi + + if test x"$done" = xyes; then + done="no" + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether -pthread is sufficient with -shared" >&5 +$as_echo_n "checking whether -pthread is sufficient with -shared... " >&6; } + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main () +{ +pthread_t th; pthread_join(th, 0); + pthread_attr_init(0); pthread_cleanup_push(0, 0); + pthread_create(0,0,0,0); pthread_cleanup_pop(0); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + done=yes +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + + if test "x$done" = xyes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + fi + fi + + # + # Linux gcc on some architectures such as mips/mipsel forgets + # about -lpthread + # + if test x"$done" = xno; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether -lpthread fixes that" >&5 +$as_echo_n "checking whether -lpthread fixes that... " >&6; } + LIBS="-lpthread $PTHREAD_LIBS $save_LIBS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main () +{ +pthread_t th; pthread_join(th, 0); + pthread_attr_init(0); pthread_cleanup_push(0, 0); + pthread_create(0,0,0,0); pthread_cleanup_pop(0); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + done=yes +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + + if test "x$done" = xyes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + PTHREAD_LIBS="-lpthread $PTHREAD_LIBS" + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + fi + fi + # + # FreeBSD 4.10 gcc forgets to use -lc_r instead of -lc + # + if test x"$done" = xno; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether -lc_r fixes that" >&5 +$as_echo_n "checking whether -lc_r fixes that... " >&6; } + LIBS="-lc_r $PTHREAD_LIBS $save_LIBS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main () +{ +pthread_t th; pthread_join(th, 0); + pthread_attr_init(0); pthread_cleanup_push(0, 0); + pthread_create(0,0,0,0); pthread_cleanup_pop(0); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + done=yes +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + + if test "x$done" = xyes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + PTHREAD_LIBS="-lc_r $PTHREAD_LIBS" + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + fi + fi + if test x"$done" = xno; then + # OK, we have run out of ideas + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Impossible to determine how to use pthreads with shared libraries" >&5 +$as_echo "$as_me: WARNING: Impossible to determine how to use pthreads with shared libraries" >&2;} + + # so it's not safe to assume that we may use pthreads + acx_pthread_ok=no + fi + + CFLAGS="$save_CFLAGS" + LIBS="$save_LIBS" + CC="$save_CC" + +else + PTHREAD_CC="$CC" +fi + + + + + +# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: +if test "x$ax_pthread_ok" = "xyes"; then + +$as_echo "#define HAVE_PTHREAD 1" >>confdefs.h + + : +else + ax_pthread_ok=no + +fi +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + +LIBS="$PTHREAD_LIBS $LIBS" +CFLAGS="$PTHREAD_CFLAGS $CFLAGS" +CC="$PTHREAD_CC" +CXXFLAGS="$CXXFLAGS -ftemplate-depth=120" + +$as_echo "Checking for visibility support:" +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for __attribute__((visibility(\"hidden\")))" >&5 +$as_echo_n "checking for __attribute__((visibility(\"hidden\")))... " >&6; } +if ${ac_cv_hidden_visibility_attribute+:} false; then : + $as_echo_n "(cached) " >&6 +else + + echo 'int __attribute__ ((visibility ("hidden"))) foo (void) { return 1; }' > visibility_conftest.c + ac_cv_hidden_visibility_attribute=no + if { ac_try='${CC-cc} -fvisibility=hidden -S visibility_conftest.c -o visibility_conftest.s 1>&5' + { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_try\""; } >&5 + (eval $ac_try) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; }; + then + $as_echo "found" + ac_cv_hidden_visibility_attribute=yes + fi + rm -f visibility_conftest.* + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_hidden_visibility_attribute" >&5 +$as_echo "$ac_cv_hidden_visibility_attribute" >&6; } + +$as_echo +$as_echo "Checking for boost libraries:" + + + +# Check whether --with-boost was given. +if test "${with_boost+set}" = set; then : + withval=$with_boost; + if test "$withval" = "no"; then + want_boost="no" + elif test "$withval" = "yes"; then + want_boost="yes" + ac_boost_path="" + else + want_boost="yes" + ac_boost_path="$withval" + fi + +else + want_boost="yes" +fi + + + + +# Check whether --with-boost-libdir was given. +if test "${with_boost_libdir+set}" = set; then : + withval=$with_boost_libdir; + if test -d "$withval" + then + ac_boost_lib_path="$withval" + else + as_fn_error $? "--with-boost-libdir expected directory name" "$LINENO" 5 + fi + +else + ac_boost_lib_path="" + +fi + + +if test "x$want_boost" = "xyes"; then + boost_lib_version_req=1.53 + boost_lib_version_req_shorten=`expr $boost_lib_version_req : '\([0-9]*\.[0-9]*\)'` + boost_lib_version_req_major=`expr $boost_lib_version_req : '\([0-9]*\)'` + boost_lib_version_req_minor=`expr $boost_lib_version_req : '[0-9]*\.\([0-9]*\)'` + boost_lib_version_req_sub_minor=`expr $boost_lib_version_req : '[0-9]*\.[0-9]*\.\([0-9]*\)'` + if test "x$boost_lib_version_req_sub_minor" = "x" ; then + boost_lib_version_req_sub_minor="0" + fi + WANT_BOOST_VERSION=`expr $boost_lib_version_req_major \* 100000 \+ $boost_lib_version_req_minor \* 100 \+ $boost_lib_version_req_sub_minor` + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for boostlib >= $boost_lib_version_req" >&5 +$as_echo_n "checking for boostlib >= $boost_lib_version_req... " >&6; } + succeeded=no + + libsubdirs="lib" + ax_arch=`uname -m` + case $ax_arch in + x86_64) + libsubdirs="lib64 libx32 lib lib64" + ;; + ppc64|s390x|sparc64|aarch64|ppc64le) + libsubdirs="lib64 lib lib64 ppc64le" + ;; + esac + + + libsubdirs="lib/${host_cpu}-${host_os} $libsubdirs" + + case ${host_cpu} in + i?86) + libsubdirs="lib/i386-${host_os} $libsubdirs" + ;; + esac + + if test "$ac_boost_path" != ""; then + BOOST_CPPFLAGS="-I$ac_boost_path/include" + for ac_boost_path_tmp in $libsubdirs; do + if test -d "$ac_boost_path"/"$ac_boost_path_tmp" ; then + BOOST_LDFLAGS="-L$ac_boost_path/$ac_boost_path_tmp" + break + fi + done + elif test "$cross_compiling" != yes; then + for ac_boost_path_tmp in /usr /usr/local /opt /opt/local ; do + if test -d "$ac_boost_path_tmp/include/boost" && test -r "$ac_boost_path_tmp/include/boost"; then + for libsubdir in $libsubdirs ; do + if ls "$ac_boost_path_tmp/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi + done + BOOST_LDFLAGS="-L$ac_boost_path_tmp/$libsubdir" + BOOST_CPPFLAGS="-I$ac_boost_path_tmp/include" + break; + fi + done + fi + + if test "$ac_boost_lib_path" != ""; then + BOOST_LDFLAGS="-L$ac_boost_lib_path" + fi + + CPPFLAGS_SAVED="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" + export CPPFLAGS + + LDFLAGS_SAVED="$LDFLAGS" + LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" + export LDFLAGS + + + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #include + +int +main () +{ + + #if BOOST_VERSION >= $WANT_BOOST_VERSION + // Everything is okay + #else + # error Boost version is too old + #endif + + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + succeeded=yes + found_system=yes + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + + if test "x$succeeded" != "xyes"; then + CPPFLAGS="$CPPFLAGS_SAVED" + LDFLAGS="$LDFLAGS_SAVED" + BOOST_CPPFLAGS= + BOOST_LDFLAGS= + _version=0 + if test "$ac_boost_path" != ""; then + if test -d "$ac_boost_path" && test -r "$ac_boost_path"; then + for i in `ls -d $ac_boost_path/include/boost-* 2>/dev/null`; do + _version_tmp=`echo $i | sed "s#$ac_boost_path##" | sed 's/\/include\/boost-//' | sed 's/_/./'` + V_CHECK=`expr $_version_tmp \> $_version` + if test "$V_CHECK" = "1" ; then + _version=$_version_tmp + fi + VERSION_UNDERSCORE=`echo $_version | sed 's/\./_/'` + BOOST_CPPFLAGS="-I$ac_boost_path/include/boost-$VERSION_UNDERSCORE" + done + if test -z "$BOOST_CPPFLAGS"; then + if test -d "$ac_boost_path/boost" && test -r "$ac_boost_path/boost"; then + BOOST_CPPFLAGS="-I$ac_boost_path" + fi + fi + fi + else + if test "$cross_compiling" != yes; then + for ac_boost_path in /usr /usr/local /opt /opt/local ; do + if test -d "$ac_boost_path" && test -r "$ac_boost_path"; then + for i in `ls -d $ac_boost_path/include/boost-* 2>/dev/null`; do + _version_tmp=`echo $i | sed "s#$ac_boost_path##" | sed 's/\/include\/boost-//' | sed 's/_/./'` + V_CHECK=`expr $_version_tmp \> $_version` + if test "$V_CHECK" = "1" ; then + _version=$_version_tmp + best_path=$ac_boost_path + fi + done + fi + done + + VERSION_UNDERSCORE=`echo $_version | sed 's/\./_/'` + BOOST_CPPFLAGS="-I$best_path/include/boost-$VERSION_UNDERSCORE" + if test "$ac_boost_lib_path" = ""; then + for libsubdir in $libsubdirs ; do + if ls "$best_path/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi + done + BOOST_LDFLAGS="-L$best_path/$libsubdir" + fi + fi + + if test "x$BOOST_ROOT" != "x"; then + for libsubdir in $libsubdirs ; do + if ls "$BOOST_ROOT/stage/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi + done + if test -d "$BOOST_ROOT" && test -r "$BOOST_ROOT" && test -d "$BOOST_ROOT/stage/$libsubdir" && test -r "$BOOST_ROOT/stage/$libsubdir"; then + version_dir=`expr //$BOOST_ROOT : '.*/\(.*\)'` + stage_version=`echo $version_dir | sed 's/boost_//' | sed 's/_/./g'` + stage_version_shorten=`expr $stage_version : '\([0-9]*\.[0-9]*\)'` + V_CHECK=`expr $stage_version_shorten \>\= $_version` + if test "$V_CHECK" = "1" -a "$ac_boost_lib_path" = "" ; then + { $as_echo "$as_me:${as_lineno-$LINENO}: We will use a staged boost library from $BOOST_ROOT" >&5 +$as_echo "$as_me: We will use a staged boost library from $BOOST_ROOT" >&6;} + BOOST_CPPFLAGS="-I$BOOST_ROOT" + BOOST_LDFLAGS="-L$BOOST_ROOT/stage/$libsubdir" + fi + fi + fi + fi + + CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" + export CPPFLAGS + LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" + export LDFLAGS + + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #include + +int +main () +{ + + #if BOOST_VERSION >= $WANT_BOOST_VERSION + // Everything is okay + #else + # error Boost version is too old + #endif + + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + succeeded=yes + found_system=yes + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + fi + + if test "$succeeded" != "yes" ; then + if test "$_version" = "0" ; then + { $as_echo "$as_me:${as_lineno-$LINENO}: We could not detect the boost libraries (version $boost_lib_version_req_shorten or higher). If you have a staged boost library (still not installed) please specify \$BOOST_ROOT in your environment and do not give a PATH to --with-boost option. If you are sure you have boost installed, then check your version number looking in . See http://randspringer.de/boost for more documentation." >&5 +$as_echo "$as_me: We could not detect the boost libraries (version $boost_lib_version_req_shorten or higher). If you have a staged boost library (still not installed) please specify \$BOOST_ROOT in your environment and do not give a PATH to --with-boost option. If you are sure you have boost installed, then check your version number looking in . See http://randspringer.de/boost for more documentation." >&6;} + else + { $as_echo "$as_me:${as_lineno-$LINENO}: Your boost libraries seems to old (version $_version)." >&5 +$as_echo "$as_me: Your boost libraries seems to old (version $_version)." >&6;} + fi + # execute ACTION-IF-NOT-FOUND (if present): + : + else + + + +$as_echo "#define HAVE_BOOST /**/" >>confdefs.h + + # execute ACTION-IF-FOUND (if present): + : + fi + + CPPFLAGS="$CPPFLAGS_SAVED" + LDFLAGS="$LDFLAGS_SAVED" +fi + + + + + +# Check whether --with-boost-system was given. +if test "${with_boost_system+set}" = set; then : + withval=$with_boost_system; + if test "x$withval" = "xno"; then + want_boost="no" + elif test "x$withval" = "xyes"; then + want_boost="yes" + ax_boost_user_system_lib="" + else + want_boost="yes" + ax_boost_user_system_lib="$withval" + fi + +else + want_boost="yes" + +fi + + + if test "x$want_boost" = "xyes"; then + + + CPPFLAGS_SAVED="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" + export CPPFLAGS + + LDFLAGS_SAVED="$LDFLAGS" + LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" + export LDFLAGS + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the Boost::System library is available" >&5 +$as_echo_n "checking whether the Boost::System library is available... " >&6; } +if ${ax_cv_boost_system+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + + CXXFLAGS_SAVE=$CXXFLAGS + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main () +{ +boost::system::system_category + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + ax_cv_boost_system=yes +else + ax_cv_boost_system=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + CXXFLAGS=$CXXFLAGS_SAVE + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_boost_system" >&5 +$as_echo "$ax_cv_boost_system" >&6; } + if test "x$ax_cv_boost_system" = "xyes"; then + + + +$as_echo "#define HAVE_BOOST_SYSTEM 1" >>confdefs.h + + BOOSTLIBDIR=`echo $BOOST_LDFLAGS | sed -e 's/[^\/]*//'` + + LDFLAGS_SAVE=$LDFLAGS + if test "x$ax_boost_user_system_lib" = "x"; then + for libextension in `ls $BOOSTLIBDIR/libboost_system*.{so,dylib,a}* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^lib\(boost_system.*\)\.so.*$;\1;' -e 's;^lib\(boost_system.*\)\.a*$;\1;' -e 's;^lib\(boost_system.*\)\.dylib$;\1;'` ; do + ax_lib=${libextension} + as_ac_Lib=`$as_echo "ac_cv_lib_$ax_lib''_exit" | $as_tr_sh` +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for exit in -l$ax_lib" >&5 +$as_echo_n "checking for exit in -l$ax_lib... " >&6; } +if eval \${$as_ac_Lib+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-l$ax_lib $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char exit (); +int +main () +{ +return exit (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + eval "$as_ac_Lib=yes" +else + eval "$as_ac_Lib=no" +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +eval ac_res=\$$as_ac_Lib + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : + BOOST_SYSTEM_LIB="-l$ax_lib"; link_system="yes"; break +else + link_system="no" +fi + + done + if test "x$link_system" != "xyes"; then + for libextension in `ls $BOOSTLIBDIR/boost_system*.{dll,a}* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^\(boost_system.*\)\.dll.*$;\1;' -e 's;^\(boost_system.*\)\.a*$;\1;'` ; do + ax_lib=${libextension} + as_ac_Lib=`$as_echo "ac_cv_lib_$ax_lib''_exit" | $as_tr_sh` +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for exit in -l$ax_lib" >&5 +$as_echo_n "checking for exit in -l$ax_lib... " >&6; } +if eval \${$as_ac_Lib+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-l$ax_lib $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char exit (); +int +main () +{ +return exit (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + eval "$as_ac_Lib=yes" +else + eval "$as_ac_Lib=no" +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +eval ac_res=\$$as_ac_Lib + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : + BOOST_SYSTEM_LIB="-l$ax_lib"; link_system="yes"; break +else + link_system="no" +fi + + done + fi + + else + for ax_lib in $ax_boost_user_system_lib boost_system-$ax_boost_user_system_lib; do + as_ac_Lib=`$as_echo "ac_cv_lib_$ax_lib''_exit" | $as_tr_sh` +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for exit in -l$ax_lib" >&5 +$as_echo_n "checking for exit in -l$ax_lib... " >&6; } +if eval \${$as_ac_Lib+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-l$ax_lib $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char exit (); +int +main () +{ +return exit (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + eval "$as_ac_Lib=yes" +else + eval "$as_ac_Lib=no" +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +eval ac_res=\$$as_ac_Lib + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : + BOOST_SYSTEM_LIB="-l$ax_lib"; link_system="yes"; break +else + link_system="no" +fi + + done + + fi + if test "x$link_system" = "xno"; then + as_fn_error $? "Could not link against $ax_lib !" "$LINENO" 5 + fi + fi + + CPPFLAGS="$CPPFLAGS_SAVED" + LDFLAGS="$LDFLAGS_SAVED" + fi + +if test -z "$BOOST_SYSTEM_LIB"; then : + as_fn_error $? "Boost.System library not found. Try using --with-boost-system=lib" "$LINENO" 5 +fi + + + +# Check whether --with-boost-chrono was given. +if test "${with_boost_chrono+set}" = set; then : + withval=$with_boost_chrono; + if test "$withval" = "no"; then + want_boost="no" + elif test "$withval" = "yes"; then + want_boost="yes" + ax_boost_user_chrono_lib="" + else + want_boost="yes" + ax_boost_user_chrono_lib="$withval" + fi + +else + want_boost="yes" + +fi + + + if test "x$want_boost" = "xyes"; then + + + CPPFLAGS_SAVED="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" + export CPPFLAGS + + LDFLAGS_SAVED="$LDFLAGS" + LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" + export LDFLAGS + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the Boost::Chrono library is available" >&5 +$as_echo_n "checking whether the Boost::Chrono library is available... " >&6; } +if ${ax_cv_boost_chrono+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + + CXXFLAGS_SAVE=$CXXFLAGS + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main () +{ +boost::chrono::system_clock::time_point time; + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + ax_cv_boost_chrono=yes +else + ax_cv_boost_chrono=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + CXXFLAGS=$CXXFLAGS_SAVE + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_boost_chrono" >&5 +$as_echo "$ax_cv_boost_chrono" >&6; } + if test "x$ax_cv_boost_chrono" = "xyes"; then + + + +$as_echo "#define HAVE_BOOST_CHRONO /**/" >>confdefs.h + + BOOSTLIBDIR=`echo $BOOST_LDFLAGS | sed -e 's/[^\/]*//'` + + LDFLAGS_SAVE=$LDFLAGS + if test "x$ax_boost_user_chrono_lib" = "x"; then + for libextension in `ls $BOOSTLIBDIR/libboost_chrono*.so* $BOOSTLIBDIR/libboost_chrono*.dylib* $BOOSTLIBDIR/libboost_chrono*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^lib\(boost_chrono.*\)\.so.*$;\1;' -e 's;^lib\(boost_chrono.*\)\.dylib.*$;\1;' -e 's;^lib\(boost_chrono.*\)\.a.*$;\1;'` ; do + ax_lib=${libextension} + as_ac_Lib=`$as_echo "ac_cv_lib_$ax_lib''_exit" | $as_tr_sh` +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for exit in -l$ax_lib" >&5 +$as_echo_n "checking for exit in -l$ax_lib... " >&6; } +if eval \${$as_ac_Lib+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-l$ax_lib $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char exit (); +int +main () +{ +return exit (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + eval "$as_ac_Lib=yes" +else + eval "$as_ac_Lib=no" +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +eval ac_res=\$$as_ac_Lib + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : + BOOST_CHRONO_LIB="-l$ax_lib"; link_chrono="yes"; break +else + link_chrono="no" +fi + + done + if test "x$link_chrono" != "xyes"; then + for libextension in `ls $BOOSTLIBDIR/boost_chrono*.dll* $BOOSTLIBDIR/boost_chrono*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^\(boost_chrono.*\)\.dll.*$;\1;' -e 's;^\(boost_chrono.*\)\.a.*$;\1;'` ; do + ax_lib=${libextension} + as_ac_Lib=`$as_echo "ac_cv_lib_$ax_lib''_exit" | $as_tr_sh` +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for exit in -l$ax_lib" >&5 +$as_echo_n "checking for exit in -l$ax_lib... " >&6; } +if eval \${$as_ac_Lib+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-l$ax_lib $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char exit (); +int +main () +{ +return exit (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + eval "$as_ac_Lib=yes" +else + eval "$as_ac_Lib=no" +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +eval ac_res=\$$as_ac_Lib + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : + BOOST_CHRONO_LIB="-l$ax_lib"; link_chrono="yes"; break +else + link_chrono="no" +fi + + done + fi + + else + for ax_lib in $ax_boost_user_chrono_lib boost_chrono-$ax_boost_user_chrono_lib; do + as_ac_Lib=`$as_echo "ac_cv_lib_$ax_lib''_exit" | $as_tr_sh` +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for exit in -l$ax_lib" >&5 +$as_echo_n "checking for exit in -l$ax_lib... " >&6; } +if eval \${$as_ac_Lib+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-l$ax_lib $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char exit (); +int +main () +{ +return exit (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + eval "$as_ac_Lib=yes" +else + eval "$as_ac_Lib=no" +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +eval ac_res=\$$as_ac_Lib + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : + BOOST_CHRONO_LIB="-l$ax_lib"; link_chrono="yes"; break +else + link_chrono="no" +fi + + done + + fi + if test "x$ax_lib" = "x"; then + as_fn_error $? "Could not find a version of the library!" "$LINENO" 5 + fi + if test "x$link_chrono" = "xno"; then + as_fn_error $? "Could not link against $ax_lib !" "$LINENO" 5 + fi + fi + + CPPFLAGS="$CPPFLAGS_SAVED" + LDFLAGS="$LDFLAGS_SAVED" + fi + +if test -z "$BOOST_CHRONO_LIB"; then : + as_fn_error $? "Boost.Chrono library not found. Try using --with-boost-chrono=lib" "$LINENO" 5 +fi + + + +# Check whether --with-boost-random was given. +if test "${with_boost_random+set}" = set; then : + withval=$with_boost_random; + if test "$withval" = "no"; then + want_boost="no" + elif test "$withval" = "yes"; then + want_boost="yes" + ax_boost_user_random_lib="" + else + want_boost="yes" + ax_boost_user_random_lib="$withval" + fi + +else + want_boost="yes" + +fi + + + if test "x$want_boost" = "xyes"; then + + + + CPPFLAGS_SAVED="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" + export CPPFLAGS + + LDFLAGS_SAVED="$LDFLAGS" + LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" + export LDFLAGS + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the Boost::Random library is available" >&5 +$as_echo_n "checking whether the Boost::Random library is available... " >&6; } +if ${ax_cv_boost_random+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + + CXXFLAGS_SAVE=$CXXFLAGS + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main () +{ +boost::random::random_device()(); + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + ax_cv_boost_random=yes +else + ax_cv_boost_random=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + CXXFLAGS=$CXXFLAGS_SAVE + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_boost_random" >&5 +$as_echo "$ax_cv_boost_random" >&6; } + + if test "x$ax_cv_boost_random" = "xyes"; then + + + +$as_echo "#define HAVE_BOOST_RANDOM /**/" >>confdefs.h + + BOOSTLIBDIR=`echo $BOOST_LDFLAGS | sed -e 's/[^\/]*//'` + + if test "x$ax_boost_user_random_lib" = "x"; then + for libextension in `ls $BOOSTLIBDIR/libboost_random*.so* $BOOSTLIBDIR/libboost_random*.dylib* $BOOSTLIBDIR/libboost_random*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^lib\(boost_random.*\)\.so.*$;\1;' -e 's;^lib\(boost_random.*\)\.dylib.*$;\1;' -e 's;^lib\(boost_random.*\)\.a.*$;\1;'` ; do + ax_lib=${libextension} + as_ac_Lib=`$as_echo "ac_cv_lib_$ax_lib''_exit" | $as_tr_sh` +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for exit in -l$ax_lib" >&5 +$as_echo_n "checking for exit in -l$ax_lib... " >&6; } +if eval \${$as_ac_Lib+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-l$ax_lib $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char exit (); +int +main () +{ +return exit (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + eval "$as_ac_Lib=yes" +else + eval "$as_ac_Lib=no" +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +eval ac_res=\$$as_ac_Lib + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : + BOOST_RANDOM_LIB="-l$ax_lib"; link_random="yes"; break +else + link_random="no" +fi + + done + + if test "x$link_random" != "xyes"; then + for libextension in `ls $BOOSTLIBDIR/boost_random*.dll* $BOOSTLIBDIR/boost_random*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^\(boost_random.*\)\.dll.*$;\1;' -e 's;^\(boost_random.*\)\.a.*$;\1;'` ; do + ax_lib=${libextension} + as_ac_Lib=`$as_echo "ac_cv_lib_$ax_lib''_exit" | $as_tr_sh` +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for exit in -l$ax_lib" >&5 +$as_echo_n "checking for exit in -l$ax_lib... " >&6; } +if eval \${$as_ac_Lib+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-l$ax_lib $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char exit (); +int +main () +{ +return exit (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + eval "$as_ac_Lib=yes" +else + eval "$as_ac_Lib=no" +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +eval ac_res=\$$as_ac_Lib + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : + BOOST_RANDOM_LIB="-l$ax_lib"; link_random="yes"; break +else + link_random="no" +fi + + done + fi + + else + for ax_lib in $ax_boost_user_random_lib boost_random-$ax_boost_user_random_lib; do + as_ac_Lib=`$as_echo "ac_cv_lib_$ax_lib''_exit" | $as_tr_sh` +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for exit in -l$ax_lib" >&5 +$as_echo_n "checking for exit in -l$ax_lib... " >&6; } +if eval \${$as_ac_Lib+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-l$ax_lib $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char exit (); +int +main () +{ +return exit (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + eval "$as_ac_Lib=yes" +else + eval "$as_ac_Lib=no" +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +eval ac_res=\$$as_ac_Lib + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : + BOOST_RANDOM_LIB="-l$ax_lib"; link_random="yes"; break +else + link_random="no" +fi + + done + fi + + if test "x$ax_lib" = "x"; then + as_fn_error $? "Could not find a version of the library!" "$LINENO" 5 + fi + + if test "x$link_random" = "xno"; then + as_fn_error $? "Could not link against $ax_lib !" "$LINENO" 5 + fi + fi + + CPPFLAGS="$CPPFLAGS_SAVED" + LDFLAGS="$LDFLAGS_SAVED" + fi + +if test -z "$BOOST_RANDOM_LIB"; then : + as_fn_error $? "Boost.Random library not found. Try using --with-boost-random=lib" "$LINENO" 5 +fi + +CPPFLAGS="$BOOST_CPPFLAGS $CPPFLAGS" +LDFLAGS="$BOOST_LDFLAGS $LDFLAGS" +LIBS="$BOOST_CHRONO_LIB $BOOST_RANDOM_LIB $LIBS" + +############################################################################### +# Checking for functions and other stuffs +############################################################################### + + +$as_echo +$as_echo "Checking for pkg-config:" + + + +if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}pkg-config", so it can be a program name with args. +set dummy ${ac_tool_prefix}pkg-config; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_PKG_CONFIG+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $PKG_CONFIG in + [\\/]* | ?:[\\/]*) + ac_cv_path_PKG_CONFIG="$PKG_CONFIG" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + ;; +esac +fi +PKG_CONFIG=$ac_cv_path_PKG_CONFIG +if test -n "$PKG_CONFIG"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PKG_CONFIG" >&5 +$as_echo "$PKG_CONFIG" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_path_PKG_CONFIG"; then + ac_pt_PKG_CONFIG=$PKG_CONFIG + # Extract the first word of "pkg-config", so it can be a program name with args. +set dummy pkg-config; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_ac_pt_PKG_CONFIG+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $ac_pt_PKG_CONFIG in + [\\/]* | ?:[\\/]*) + ac_cv_path_ac_pt_PKG_CONFIG="$ac_pt_PKG_CONFIG" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_ac_pt_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + ;; +esac +fi +ac_pt_PKG_CONFIG=$ac_cv_path_ac_pt_PKG_CONFIG +if test -n "$ac_pt_PKG_CONFIG"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_pt_PKG_CONFIG" >&5 +$as_echo "$ac_pt_PKG_CONFIG" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_pt_PKG_CONFIG" = x; then + PKG_CONFIG="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + PKG_CONFIG=$ac_pt_PKG_CONFIG + fi +else + PKG_CONFIG="$ac_cv_path_PKG_CONFIG" +fi + +fi +if test -n "$PKG_CONFIG"; then + _pkg_min_version=0.20 + { $as_echo "$as_me:${as_lineno-$LINENO}: checking pkg-config is at least version $_pkg_min_version" >&5 +$as_echo_n "checking pkg-config is at least version $_pkg_min_version... " >&6; } + if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + PKG_CONFIG="" + fi + +fi + +$as_echo +$as_echo "Checking for functions and headers:" + +# Check whether --enable-largefile was given. +if test "${enable_largefile+set}" = set; then : + enableval=$enable_largefile; +fi + +if test "$enable_largefile" != no; then + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for special C compiler options needed for large files" >&5 +$as_echo_n "checking for special C compiler options needed for large files... " >&6; } +if ${ac_cv_sys_largefile_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_cv_sys_largefile_CC=no + if test "$GCC" != yes; then + ac_save_CC=$CC + while :; do + # IRIX 6.2 and later do not support large files by default, + # so use the C compiler's -n32 option if that helps. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + /* Check that off_t can represent 2**63 - 1 correctly. + We can't simply define LARGE_OFF_T to be 9223372036854775807, + since some C++ compilers masquerading as C compilers + incorrectly reject 9223372036854775807. */ +#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62)) + int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 + && LARGE_OFF_T % 2147483647 == 1) + ? 1 : -1]; +int +main () +{ + + ; + return 0; +} +_ACEOF + if ac_fn_c_try_compile "$LINENO"; then : + break +fi +rm -f core conftest.err conftest.$ac_objext + CC="$CC -n32" + if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_sys_largefile_CC=' -n32'; break +fi +rm -f core conftest.err conftest.$ac_objext + break + done + CC=$ac_save_CC + rm -f conftest.$ac_ext + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sys_largefile_CC" >&5 +$as_echo "$ac_cv_sys_largefile_CC" >&6; } + if test "$ac_cv_sys_largefile_CC" != no; then + CC=$CC$ac_cv_sys_largefile_CC + fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for _FILE_OFFSET_BITS value needed for large files" >&5 +$as_echo_n "checking for _FILE_OFFSET_BITS value needed for large files... " >&6; } +if ${ac_cv_sys_file_offset_bits+:} false; then : + $as_echo_n "(cached) " >&6 +else + while :; do + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + /* Check that off_t can represent 2**63 - 1 correctly. + We can't simply define LARGE_OFF_T to be 9223372036854775807, + since some C++ compilers masquerading as C compilers + incorrectly reject 9223372036854775807. */ +#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62)) + int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 + && LARGE_OFF_T % 2147483647 == 1) + ? 1 : -1]; +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_sys_file_offset_bits=no; break +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#define _FILE_OFFSET_BITS 64 +#include + /* Check that off_t can represent 2**63 - 1 correctly. + We can't simply define LARGE_OFF_T to be 9223372036854775807, + since some C++ compilers masquerading as C compilers + incorrectly reject 9223372036854775807. */ +#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62)) + int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 + && LARGE_OFF_T % 2147483647 == 1) + ? 1 : -1]; +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_sys_file_offset_bits=64; break +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_cv_sys_file_offset_bits=unknown + break +done +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sys_file_offset_bits" >&5 +$as_echo "$ac_cv_sys_file_offset_bits" >&6; } +case $ac_cv_sys_file_offset_bits in #( + no | unknown) ;; + *) +cat >>confdefs.h <<_ACEOF +#define _FILE_OFFSET_BITS $ac_cv_sys_file_offset_bits +_ACEOF +;; +esac +rm -rf conftest* + if test $ac_cv_sys_file_offset_bits = unknown; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for _LARGE_FILES value needed for large files" >&5 +$as_echo_n "checking for _LARGE_FILES value needed for large files... " >&6; } +if ${ac_cv_sys_large_files+:} false; then : + $as_echo_n "(cached) " >&6 +else + while :; do + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + /* Check that off_t can represent 2**63 - 1 correctly. + We can't simply define LARGE_OFF_T to be 9223372036854775807, + since some C++ compilers masquerading as C compilers + incorrectly reject 9223372036854775807. */ +#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62)) + int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 + && LARGE_OFF_T % 2147483647 == 1) + ? 1 : -1]; +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_sys_large_files=no; break +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#define _LARGE_FILES 1 +#include + /* Check that off_t can represent 2**63 - 1 correctly. + We can't simply define LARGE_OFF_T to be 9223372036854775807, + since some C++ compilers masquerading as C compilers + incorrectly reject 9223372036854775807. */ +#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62)) + int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 + && LARGE_OFF_T % 2147483647 == 1) + ? 1 : -1]; +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_sys_large_files=1; break +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_cv_sys_large_files=unknown + break +done +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sys_large_files" >&5 +$as_echo "$ac_cv_sys_large_files" >&6; } +case $ac_cv_sys_large_files in #( + no | unknown) ;; + *) +cat >>confdefs.h <<_ACEOF +#define _LARGE_FILES $ac_cv_sys_large_files +_ACEOF +;; +esac +rm -rf conftest* + fi + + +fi + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ln -s works" >&5 +$as_echo_n "checking whether ln -s works... " >&6; } +LN_S=$as_ln_s +if test "$LN_S" = "ln -s"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no, using $LN_S" >&5 +$as_echo "no, using $LN_S" >&6; } +fi + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} sets \$(MAKE)" >&5 +$as_echo_n "checking whether ${MAKE-make} sets \$(MAKE)... " >&6; } +set x ${MAKE-make} +ac_make=`$as_echo "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'` +if eval \${ac_cv_prog_make_${ac_make}_set+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat >conftest.make <<\_ACEOF +SHELL = /bin/sh +all: + @echo '@@@%%%=$(MAKE)=@@@%%%' +_ACEOF +# GNU make sometimes prints "make[1]: Entering ...", which would confuse us. +case `${MAKE-make} -f conftest.make 2>/dev/null` in + *@@@%%%=?*=@@@%%%*) + eval ac_cv_prog_make_${ac_make}_set=yes;; + *) + eval ac_cv_prog_make_${ac_make}_set=no;; +esac +rm -f conftest.make +fi +if eval test \$ac_cv_prog_make_${ac_make}_set = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + SET_MAKE= +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + SET_MAKE="MAKE=${MAKE-make}" +fi + + +for ac_func in clock_gettime +do : + ac_fn_c_check_func "$LINENO" "clock_gettime" "ac_cv_func_clock_gettime" +if test "x$ac_cv_func_clock_gettime" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_CLOCK_GETTIME 1 +_ACEOF + +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for clock_gettime in -lrt" >&5 +$as_echo_n "checking for clock_gettime in -lrt... " >&6; } +if ${ac_cv_lib_rt_clock_gettime+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lrt $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char clock_gettime (); +int +main () +{ +return clock_gettime (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_rt_clock_gettime=yes +else + ac_cv_lib_rt_clock_gettime=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_rt_clock_gettime" >&5 +$as_echo "$ac_cv_lib_rt_clock_gettime" >&6; } +if test "x$ac_cv_lib_rt_clock_gettime" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBRT 1 +_ACEOF + + LIBS="-lrt $LIBS" + +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for clock_gettime in -lposix4" >&5 +$as_echo_n "checking for clock_gettime in -lposix4... " >&6; } +if ${ac_cv_lib_posix4_clock_gettime+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lposix4 $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char clock_gettime (); +int +main () +{ +return clock_gettime (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_posix4_clock_gettime=yes +else + ac_cv_lib_posix4_clock_gettime=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_posix4_clock_gettime" >&5 +$as_echo "$ac_cv_lib_posix4_clock_gettime" >&6; } +if test "x$ac_cv_lib_posix4_clock_gettime" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBPOSIX4 1 +_ACEOF + + LIBS="-lposix4 $LIBS" + +else + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: clock_gettime function not found." >&5 +$as_echo "$as_me: WARNING: clock_gettime function not found." >&2;} +fi + +fi + + +fi +done + + + +COMPILETIME_OPTIONS="" + + +############################################################################### +# Setting configure options +############################################################################### + +# Check whether --enable-logging was given. +if test "${enable_logging+set}" = set; then : + enableval=$enable_logging; ARG_ENABLE_LOGGING=$enableval +else + ARG_ENABLE_LOGGING=no + +fi + + +# Check whether --enable-debug was given. +if test "${enable_debug+set}" = set; then : + enableval=$enable_debug; + ARG_ENABLE_DEBUG=$enableval + ac_arg_enable_debug=$enableval + +else + + ARG_ENABLE_DEBUG=no + ac_arg_enable_debug=no + + +fi + + +# Check whether --enable-dht was given. +if test "${enable_dht+set}" = set; then : + enableval=$enable_dht; ARG_ENABLE_DHT=$enableval +else + ARG_ENABLE_DHT=yes + +fi + + +# Check whether --enable-encryption was given. +if test "${enable_encryption+set}" = set; then : + enableval=$enable_encryption; ARG_ENABLE_ENCRYPTION=$enableval +else + ARG_ENABLE_ENCRYPTION=yes + +fi + + +# Check whether --enable-export-all was given. +if test "${enable_export_all+set}" = set; then : + enableval=$enable_export_all; ARG_ENABLE_FULL_EXPORT=$enableval +else + ARG_ENABLE_FULL_EXPORT=no + +fi + + +# Check whether --enable-pool-allocators was given. +if test "${enable_pool_allocators+set}" = set; then : + enableval=$enable_pool_allocators; ARG_ENABLE_POOL_ALLOC=$enableval +else + ARG_ENABLE_POOL_ALLOC=yes + +fi + + +# Check whether --enable-invariant-checks was given. +if test "${enable_invariant_checks+set}" = set; then : + enableval=$enable_invariant_checks; ARG_ENABLE_INVARIANT=$enableval +else + + if test "x$ac_arg_enable_debug" = "xyes"; then : + ARG_ENABLE_INVARIANT=yes +else + ARG_ENABLE_INVARIANT=no +fi + + +fi + + +# Check whether --enable-deprecated-functions was given. +if test "${enable_deprecated_functions+set}" = set; then : + enableval=$enable_deprecated_functions; ARG_ENABLE_DEPRECATED=$enableval +else + ARG_ENABLE_DEPRECATED=yes + +fi + + +# Check whether --enable-disk-stats was given. +if test "${enable_disk_stats+set}" = set; then : + enableval=$enable_disk_stats; ARG_ENABLE_DISK_STATS=$enableval +else + ARG_ENABLE_DISK_STATS=no + +fi + + +# Check whether --enable-examples was given. +if test "${enable_examples+set}" = set; then : + enableval=$enable_examples; ARG_ENABLE_EXAMPLES=$enableval +else + ARG_ENABLE_EXAMPLES=no + +fi + + +# Check whether --enable-tests was given. +if test "${enable_tests+set}" = set; then : + enableval=$enable_tests; ARG_ENABLE_TESTS=$enableval +else + ARG_ENABLE_TESTS=no + +fi + + +# Check whether --enable-python-binding was given. +if test "${enable_python_binding+set}" = set; then : + enableval=$enable_python_binding; ARG_ENABLE_PYTHON_BINDING=$enableval +else + ARG_ENABLE_PYTHON_BINDING=no + +fi + + + +# Check whether --with-libiconv was given. +if test "${with_libiconv+set}" = set; then : + withval=$with_libiconv; ARG_WITH_LIBICONV=$withval +else + ARG_WITH_LIBICONV=no + +fi + + +############################################################################### +# Checking configure options +############################################################################### + +$as_echo +$as_echo "Checking build options:" + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether deprecated functions should be enabled" >&5 +$as_echo_n "checking whether deprecated functions should be enabled... " >&6; } +case "$ARG_ENABLE_DEPRECATED" in #( + "yes"|"on") : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + ;; #( + "no"|"off") : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +$as_echo "#define TORRENT_NO_DEPRECATE 1" >>confdefs.h + + COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DTORRENT_NO_DEPRECATE " + ;; #( + *) : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ARG_ENABLE_DEPRECATED" >&5 +$as_echo "$ARG_ENABLE_DEPRECATED" >&6; } + as_fn_error $? "Unknown option \"$ARG_ENABLE_DEPRECATED\". Use either \"yes\" or \"no\"." "$LINENO" 5 + ;; +esac + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether debug build should be enabled" >&5 +$as_echo_n "checking whether debug build should be enabled... " >&6; } +case "$ARG_ENABLE_DEBUG" in #( + "yes") : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + +$as_echo "#define TORRENT_DEBUG 1" >>confdefs.h + + COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DTORRENT_DEBUG " + DEBUGFLAGS="-g" + ;; #( + "no") : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +$as_echo "#define NDEBUG 1" >>confdefs.h + + #COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DNDEBUG " + DEBUGFLAGS="-Os" + ;; #( + *) : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ARG_ENABLE_DEBUG" >&5 +$as_echo "$ARG_ENABLE_DEBUG" >&6; } + as_fn_error $? "Unknown option \"$ARG_ENABLE_DEBUG\". Use either \"yes\" or \"no\"." "$LINENO" 5 + ;; +esac + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether invariant check should be enabled" >&5 +$as_echo_n "checking whether invariant check should be enabled... " >&6; } #depends: $ac_arg_enable_debug +case "$ARG_ENABLE_INVARIANT" in #( + "yes"|"on") : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + if test "x$ac_arg_enable_debug" = "xno"; then : + as_fn_error $? "invariant-checks: this setting only affects debug build. Try using --enable-debug." "$LINENO" 5 +fi + ;; #( + "no"|"off") : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + if test "x$ac_arg_enable_debug" = "xyes"; then : + +$as_echo "#define TORRENT_DISABLE_INVARIANT_CHECKS 1" >>confdefs.h + +fi + ;; #( + "full") : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: full" >&5 +$as_echo "full" >&6; } + if test "x$ac_arg_enable_debug" = "xyes"; then : + +$as_echo "#define TORRENT_EXPENSIVE_INVARIANT_CHECKS 1" >>confdefs.h + +else + as_fn_error $? "invariant-checks: this setting only affects debug build. Try using --enable-debug." "$LINENO" 5 +fi + ;; #( + *) : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ARG_ENABLE_INVARIANT" >&5 +$as_echo "$ARG_ENABLE_INVARIANT" >&6; } + as_fn_error $? "Unknown option \"$ARG_ENABLE_INVARIANT\". Use either \"yes\", \"no\" or \"full\"." "$LINENO" 5 + ;; +esac + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether logging to disk should be enabled" >&5 +$as_echo_n "checking whether logging to disk should be enabled... " >&6; } +case "$ARG_ENABLE_LOGGING" in #( + "yes"|"default") : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + ;; #( + "no"|"none") : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +$as_echo "#define TORRENT_DISABLE_LOGGING 1" >>confdefs.h + + COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DTORRENT_DISABLE_LOGGING " + ;; #( + *) : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ARG_ENABLE_LOGGING" >&5 +$as_echo "$ARG_ENABLE_LOGGING" >&6; } + as_fn_error $? "Unknown option \"$ARG_ENABLE_LOGGING\". Use either \"yes\" or \"no\"" "$LINENO" 5 + ;; +esac + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether disk activity logging should be enabled" >&5 +$as_echo_n "checking whether disk activity logging should be enabled... " >&6; } +case "$ARG_ENABLE_DISK_STATS" in #( + "yes"|"on") : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + +$as_echo "#define TORRENT_DISK_STATS 1" >>confdefs.h + + ;; #( + "no"|"off") : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + ;; #( + *) : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ARG_ENABLE_DISK_STATS" >&5 +$as_echo "$ARG_ENABLE_DISK_STATS" >&6; } + as_fn_error $? "Unknown option \"$ARG_ENABLE_DISK_STATS\". Use either \"yes\" or \"no\"." "$LINENO" 5 + ;; +esac + +$as_echo +$as_echo "Checking features to be enabled:" + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether encryption support should be enabled" >&5 +$as_echo_n "checking whether encryption support should be enabled... " >&6; } +case "$ARG_ENABLE_ENCRYPTION" in #( + "yes"|"on") : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: encryption support: now checking for the OpenSSL library..." >&5 +$as_echo "$as_me: encryption support: now checking for the OpenSSL library..." >&6;} + + + found=false + +# Check whether --with-openssl was given. +if test "${with_openssl+set}" = set; then : + withval=$with_openssl; + case "$withval" in + "" | y | ye | yes | n | no) + as_fn_error $? "Invalid --with-openssl value" "$LINENO" 5 + ;; + *) ssldirs="$withval" + ;; + esac + +else + + # if pkg-config is installed and openssl has installed a .pc file, + # then use that information and don't search ssldirs + # Extract the first word of "pkg-config", so it can be a program name with args. +set dummy pkg-config; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_PKG_CONFIG+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $PKG_CONFIG in + [\\/]* | ?:[\\/]*) + ac_cv_path_PKG_CONFIG="$PKG_CONFIG" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + ;; +esac +fi +PKG_CONFIG=$ac_cv_path_PKG_CONFIG +if test -n "$PKG_CONFIG"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PKG_CONFIG" >&5 +$as_echo "$PKG_CONFIG" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + if test x"$PKG_CONFIG" != x""; then + OPENSSL_LDFLAGS=`$PKG_CONFIG openssl --libs-only-L 2>/dev/null` + if test $? = 0; then + OPENSSL_LIBS=`$PKG_CONFIG openssl --libs-only-l 2>/dev/null` + OPENSSL_INCLUDES=`$PKG_CONFIG openssl --cflags-only-I 2>/dev/null` + found=true + fi + fi + + # no such luck; use some default ssldirs + if ! $found; then + ssldirs="/usr/local/ssl /usr/lib/ssl /usr/ssl /usr/pkg /usr/local /usr" + fi + + +fi + + + + # note that we #include , so the OpenSSL headers have to be in + # an 'openssl' subdirectory + + if ! $found; then + OPENSSL_INCLUDES= + for ssldir in $ssldirs; do + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for openssl/ssl.h in $ssldir" >&5 +$as_echo_n "checking for openssl/ssl.h in $ssldir... " >&6; } + if test -f "$ssldir/include/openssl/ssl.h"; then + OPENSSL_INCLUDES="-I$ssldir/include" + OPENSSL_LDFLAGS="-L$ssldir/lib" + OPENSSL_LIBS="-lssl -lcrypto" + found=true + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + break + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + fi + done + + # if the file wasn't found, well, go ahead and try the link anyway -- maybe + # it will just work! + fi + + # try the preprocessor and linker with our new flags, + # being careful not to pollute the global LIBS, LDFLAGS, and CPPFLAGS + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiling and linking against OpenSSL works" >&5 +$as_echo_n "checking whether compiling and linking against OpenSSL works... " >&6; } + echo "Trying link with OPENSSL_LDFLAGS=$OPENSSL_LDFLAGS;" \ + "OPENSSL_LIBS=$OPENSSL_LIBS; OPENSSL_INCLUDES=$OPENSSL_INCLUDES" >&5 + + save_LIBS="$LIBS" + save_LDFLAGS="$LDFLAGS" + save_CPPFLAGS="$CPPFLAGS" + LDFLAGS="$LDFLAGS $OPENSSL_LDFLAGS" + LIBS="$OPENSSL_LIBS $LIBS" + CPPFLAGS="$OPENSSL_INCLUDES $CPPFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main () +{ +SSL_new(NULL) + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +$as_echo "#define TORRENT_USE_OPENSSL 1" >>confdefs.h + + COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DTORRENT_USE_OPENSSL " + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + + as_fn_error $? "OpenSSL library not found. Try using --with-openssl=DIR or disabling encryption at all." "$LINENO" 5 + + +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + CPPFLAGS="$save_CPPFLAGS" + LDFLAGS="$save_LDFLAGS" + LIBS="$save_LIBS" + + + + + + ;; #( + "no"|"off") : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +$as_echo "#define TORRENT_DISABLE_ENCRYPTION 1" >>confdefs.h + + COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DTORRENT_DISABLE_ENCRYPTION " + ;; #( + *) : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ARG_ENABLE_ENCRYPTION" >&5 +$as_echo "$ARG_ENABLE_ENCRYPTION" >&6; } + as_fn_error $? "Unknown option \"$ARG_ENABLE_ENCRYPTION\". Use either \"yes\" or \"no\"." "$LINENO" 5 + ;; +esac + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether dht support should be enabled" >&5 +$as_echo_n "checking whether dht support should be enabled... " >&6; } +case "$ARG_ENABLE_DHT" in #( + "yes"|"on") : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + ;; #( + "no"|"off") : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +$as_echo "#define TORRENT_DISABLE_DHT 1" >>confdefs.h + + COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DTORRENT_DISABLE_DHT " + ;; #( + "logging") : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: logging" >&5 +$as_echo "logging" >&6; } + +$as_echo "#define TORRENT_DHT_VERBOSE_LOGGING 1" >>confdefs.h + + COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DTORRENT_DHT_VERBOSE_LOGGING " + ;; #( + *) : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ARG_ENABLE_DHT" >&5 +$as_echo "$ARG_ENABLE_DHT" >&6; } + as_fn_error $? "Unknown option \"$ARG_ENABLE_DHT\". Use either \"yes\", \"no\" or \"logging\"." "$LINENO" 5 + ;; +esac + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether pool allocators should be enabled" >&5 +$as_echo_n "checking whether pool allocators should be enabled... " >&6; } +case "$ARG_ENABLE_POOL_ALLOC" in #( + "yes"|"on") : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + ;; #( + "no"|"off") : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +$as_echo "#define TORRENT_DISABLE_POOL_ALLOCATOR 1" >>confdefs.h + + ;; #( + *) : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ARG_ENABLE_POOL_ALLOC" >&5 +$as_echo "$ARG_ENABLE_POOL_ALLOC" >&6; } + as_fn_error $? "Unknown option \"$ARG_ENABLE_POOL_ALLOC\". Use either \"yes\" or \"no\"." "$LINENO" 5 + ;; +esac + +if test "x$ac_cv_hidden_visibility_attribute" = "xyes"; then : + + if test "x$ARG_ENABLE_FULL_EXPORT" = "xno"; then : + + CXXFLAGS="$CXXFLAGS -fvisibility=hidden -fvisibility-inlines-hidden" + CFLAGS="$CFLAGS -fvisibility=hidden" + LDFLAGS="$LDFLAGS -fvisibility=hidden -fvisibility-inlines-hidden" + +fi + +fi + +$as_echo +$as_echo "Checking for extra build files:" + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether example files should be built" >&5 +$as_echo_n "checking whether example files should be built... " >&6; } +case "$ARG_ENABLE_EXAMPLES" in #( + "yes") : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } ;; #( + "no") : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } ;; #( + *) : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ARG_ENABLE_EXAMPLES" >&5 +$as_echo "$ARG_ENABLE_EXAMPLES" >&6; } + as_fn_error $? "Unknown option \"$ARG_ENABLE_EXAMPLES\". Use either \"yes\" or \"no\"." "$LINENO" 5 + ;; +esac + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether test files should be built" >&5 +$as_echo_n "checking whether test files should be built... " >&6; } +case "$ARG_ENABLE_TESTS" in #( + "yes") : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + +$as_echo "#define TORRENT_EXPORT_EXTRA 1" >>confdefs.h + + COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DTORRENT_EXPORT_EXTRA " + ;; #( + "no") : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } ;; #( + *) : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ARG_ENABLE_TESTS" >&5 +$as_echo "$ARG_ENABLE_TESTS" >&6; } + as_fn_error $? "Unknown option \"$ARG_ENABLE_TESTS\". Use either \"yes\" or \"no\"." "$LINENO" 5 + ;; +esac + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether python bindings should be built" >&5 +$as_echo_n "checking whether python bindings should be built... " >&6; } +case "$ARG_ENABLE_PYTHON_BINDING" in #( + "yes") : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + + + + + + + if test -n "$PYTHON"; then + # If the user set $PYTHON, use it and don't search something else. + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $PYTHON version is >= 2.4" >&5 +$as_echo_n "checking whether $PYTHON version is >= 2.4... " >&6; } + prog="import sys +# split strings by '.' and convert to numeric. Append some zeros +# because we need at least 4 digits for the hex conversion. +# map returns an iterator in Python 3.0 and a list in 2.x +minver = list(map(int, '2.4'.split('.'))) + [0, 0, 0] +minverhex = 0 +# xrange is not present in Python 3.0 and range returns an iterator +for i in list(range(0, 4)): minverhex = (minverhex << 8) + minver[i] +sys.exit(sys.hexversion < minverhex)" + if { echo "$as_me:$LINENO: $PYTHON -c "$prog"" >&5 + ($PYTHON -c "$prog") >&5 2>&5 + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); }; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + as_fn_error $? "Python interpreter is too old" "$LINENO" 5 +fi + am_display_PYTHON=$PYTHON + else + # Otherwise, try each interpreter until we find one that satisfies + # VERSION. + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for a Python interpreter with version >= 2.4" >&5 +$as_echo_n "checking for a Python interpreter with version >= 2.4... " >&6; } +if ${am_cv_pathless_PYTHON+:} false; then : + $as_echo_n "(cached) " >&6 +else + + for am_cv_pathless_PYTHON in python python2 python3 python3.3 python3.2 python3.1 python3.0 python2.7 python2.6 python2.5 python2.4 python2.3 python2.2 python2.1 python2.0 none; do + test "$am_cv_pathless_PYTHON" = none && break + prog="import sys +# split strings by '.' and convert to numeric. Append some zeros +# because we need at least 4 digits for the hex conversion. +# map returns an iterator in Python 3.0 and a list in 2.x +minver = list(map(int, '2.4'.split('.'))) + [0, 0, 0] +minverhex = 0 +# xrange is not present in Python 3.0 and range returns an iterator +for i in list(range(0, 4)): minverhex = (minverhex << 8) + minver[i] +sys.exit(sys.hexversion < minverhex)" + if { echo "$as_me:$LINENO: $am_cv_pathless_PYTHON -c "$prog"" >&5 + ($am_cv_pathless_PYTHON -c "$prog") >&5 2>&5 + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); }; then : + break +fi + done +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_pathless_PYTHON" >&5 +$as_echo "$am_cv_pathless_PYTHON" >&6; } + # Set $PYTHON to the absolute path of $am_cv_pathless_PYTHON. + if test "$am_cv_pathless_PYTHON" = none; then + PYTHON=: + else + # Extract the first word of "$am_cv_pathless_PYTHON", so it can be a program name with args. +set dummy $am_cv_pathless_PYTHON; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_PYTHON+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $PYTHON in + [\\/]* | ?:[\\/]*) + ac_cv_path_PYTHON="$PYTHON" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_PYTHON="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + ;; +esac +fi +PYTHON=$ac_cv_path_PYTHON +if test -n "$PYTHON"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PYTHON" >&5 +$as_echo "$PYTHON" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + fi + am_display_PYTHON=$am_cv_pathless_PYTHON + fi + + + if test "$PYTHON" = :; then + as_fn_error $? "Python interpreter not found." "$LINENO" 5 + else + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $am_display_PYTHON version" >&5 +$as_echo_n "checking for $am_display_PYTHON version... " >&6; } +if ${am_cv_python_version+:} false; then : + $as_echo_n "(cached) " >&6 +else + am_cv_python_version=`$PYTHON -c "import sys; sys.stdout.write(sys.version[:3])"` +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_python_version" >&5 +$as_echo "$am_cv_python_version" >&6; } + PYTHON_VERSION=$am_cv_python_version + + + + PYTHON_PREFIX='${prefix}' + + PYTHON_EXEC_PREFIX='${exec_prefix}' + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $am_display_PYTHON platform" >&5 +$as_echo_n "checking for $am_display_PYTHON platform... " >&6; } +if ${am_cv_python_platform+:} false; then : + $as_echo_n "(cached) " >&6 +else + am_cv_python_platform=`$PYTHON -c "import sys; sys.stdout.write(sys.platform)"` +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_python_platform" >&5 +$as_echo "$am_cv_python_platform" >&6; } + PYTHON_PLATFORM=$am_cv_python_platform + + + # Just factor out some code duplication. + am_python_setup_sysconfig="\ +import sys +# Prefer sysconfig over distutils.sysconfig, for better compatibility +# with python 3.x. See automake bug#10227. +try: + import sysconfig +except ImportError: + can_use_sysconfig = 0 +else: + can_use_sysconfig = 1 +# Can't use sysconfig in CPython 2.7, since it's broken in virtualenvs: +# +try: + from platform import python_implementation + if python_implementation() == 'CPython' and sys.version[:3] == '2.7': + can_use_sysconfig = 0 +except ImportError: + pass" + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $am_display_PYTHON script directory" >&5 +$as_echo_n "checking for $am_display_PYTHON script directory... " >&6; } +if ${am_cv_python_pythondir+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test "x$prefix" = xNONE + then + am_py_prefix=$ac_default_prefix + else + am_py_prefix=$prefix + fi + am_cv_python_pythondir=`$PYTHON -c " +$am_python_setup_sysconfig +if can_use_sysconfig: + sitedir = sysconfig.get_path('purelib', vars={'base':'$am_py_prefix'}) +else: + from distutils import sysconfig + sitedir = sysconfig.get_python_lib(0, 0, prefix='$am_py_prefix') +sys.stdout.write(sitedir)"` + case $am_cv_python_pythondir in + $am_py_prefix*) + am__strip_prefix=`echo "$am_py_prefix" | sed 's|.|.|g'` + am_cv_python_pythondir=`echo "$am_cv_python_pythondir" | sed "s,^$am__strip_prefix,$PYTHON_PREFIX,"` + ;; + *) + case $am_py_prefix in + /usr|/System*) ;; + *) + am_cv_python_pythondir=$PYTHON_PREFIX/lib/python$PYTHON_VERSION/site-packages + ;; + esac + ;; + esac + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_python_pythondir" >&5 +$as_echo "$am_cv_python_pythondir" >&6; } + pythondir=$am_cv_python_pythondir + + + + pkgpythondir=\${pythondir}/$PACKAGE + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $am_display_PYTHON extension module directory" >&5 +$as_echo_n "checking for $am_display_PYTHON extension module directory... " >&6; } +if ${am_cv_python_pyexecdir+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test "x$exec_prefix" = xNONE + then + am_py_exec_prefix=$am_py_prefix + else + am_py_exec_prefix=$exec_prefix + fi + am_cv_python_pyexecdir=`$PYTHON -c " +$am_python_setup_sysconfig +if can_use_sysconfig: + sitedir = sysconfig.get_path('platlib', vars={'platbase':'$am_py_prefix'}) +else: + from distutils import sysconfig + sitedir = sysconfig.get_python_lib(1, 0, prefix='$am_py_prefix') +sys.stdout.write(sitedir)"` + case $am_cv_python_pyexecdir in + $am_py_exec_prefix*) + am__strip_prefix=`echo "$am_py_exec_prefix" | sed 's|.|.|g'` + am_cv_python_pyexecdir=`echo "$am_cv_python_pyexecdir" | sed "s,^$am__strip_prefix,$PYTHON_EXEC_PREFIX,"` + ;; + *) + case $am_py_exec_prefix in + /usr|/System*) ;; + *) + am_cv_python_pyexecdir=$PYTHON_EXEC_PREFIX/lib/python$PYTHON_VERSION/site-packages + ;; + esac + ;; + esac + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_python_pyexecdir" >&5 +$as_echo "$am_cv_python_pyexecdir" >&6; } + pyexecdir=$am_cv_python_pyexecdir + + + + pkgpyexecdir=\${pyexecdir}/$PACKAGE + + + + fi + + + + # + # Allow the use of a (user set) custom python version + # + + + # Extract the first word of "python[$PYTHON_VERSION]", so it can be a program name with args. +set dummy python$PYTHON_VERSION; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_PYTHON+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $PYTHON in + [\\/]* | ?:[\\/]*) + ac_cv_path_PYTHON="$PYTHON" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_PYTHON="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + ;; +esac +fi +PYTHON=$ac_cv_path_PYTHON +if test -n "$PYTHON"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PYTHON" >&5 +$as_echo "$PYTHON" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + if test -z "$PYTHON"; then + as_fn_error $? "Cannot find python$PYTHON_VERSION in your system path" "$LINENO" 5 + PYTHON_VERSION="" + fi + + # + # Check for a version of Python >= 2.1.0 + # + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for a version of Python >= '2.1.0'" >&5 +$as_echo_n "checking for a version of Python >= '2.1.0'... " >&6; } + ac_supports_python_ver=`$PYTHON -c "import sys; \ + ver = sys.version.split ()[0]; \ + print (ver >= '2.1.0')"` + if test "$ac_supports_python_ver" != "True"; then + if test -z "$PYTHON_NOVERSIONCHECK"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? " +This version of the AC_PYTHON_DEVEL macro +doesn't work properly with versions of Python before +2.1.0. You may need to re-run configure, setting the +variables PYTHON_CPPFLAGS, PYTHON_LIBS, PYTHON_SITE_PKG, +PYTHON_EXTRA_LIBS and PYTHON_EXTRA_LDFLAGS by hand. +Moreover, to disable this check, set PYTHON_NOVERSIONCHECK +to something else than an empty string. + +See \`config.log' for more details" "$LINENO" 5; } + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: skip at user request" >&5 +$as_echo "skip at user request" >&6; } + fi + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + fi + + # + # if the macro parameter ``version'' is set, honour it + # + if test -n ">= '2.4'"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for a version of Python >= '2.4'" >&5 +$as_echo_n "checking for a version of Python >= '2.4'... " >&6; } + ac_supports_python_ver=`$PYTHON -c "import sys; \ + ver = sys.version.split ()[0]; \ + print (ver >= '2.4')"` + if test "$ac_supports_python_ver" = "True"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + as_fn_error $? "this package requires Python >= '2.4'. +If you have it installed, but it isn't the default Python +interpreter in your system path, please pass the PYTHON_VERSION +variable to configure. See \`\`configure --help'' for reference. +" "$LINENO" 5 + PYTHON_VERSION="" + fi + fi + + # + # Check if you have distutils, else fail + # + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for the distutils Python package" >&5 +$as_echo_n "checking for the distutils Python package... " >&6; } + ac_distutils_result=`$PYTHON -c "import distutils" 2>&1 | grep -v '^\[0-9]\{1,\} refs\'` + if test -z "$ac_distutils_result"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + as_fn_error $? "cannot import Python module \"distutils\". +Please check your Python installation. The error was: +$ac_distutils_result" "$LINENO" 5 + PYTHON_VERSION="" + fi + + # + # Check for Python include path + # + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Python include path" >&5 +$as_echo_n "checking for Python include path... " >&6; } + if test -z "$PYTHON_CPPFLAGS"; then + python_path=`$PYTHON -c "import distutils.sysconfig; \ + print (distutils.sysconfig.get_python_inc ());"` + plat_python_path=`$PYTHON -c "import distutils.sysconfig; \ + print (distutils.sysconfig.get_python_inc (plat_specific=1));"` + if test -n "${python_path}"; then + if test "${plat_python_path}" != "${python_path}"; then + python_path="-I$python_path -I$plat_python_path" + else + python_path="-I$python_path" + fi + fi + PYTHON_CPPFLAGS=$python_path + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PYTHON_CPPFLAGS" >&5 +$as_echo "$PYTHON_CPPFLAGS" >&6; } + + + # + # Check for Python library path + # + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Python library path" >&5 +$as_echo_n "checking for Python library path... " >&6; } + if test -z "$PYTHON_LIBS"; then + # (makes two attempts to ensure we've got a version number + # from the interpreter) + ac_python_version=`cat<>confdefs.h <<_ACEOF +#define HAVE_PYTHON "$ac_python_version" +_ACEOF + + + # First, the library directory: + ac_python_libdir=`cat<&5 +$as_echo "$PYTHON_LIBS" >&6; } + + + # + # Check for site packages + # + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Python site-packages path" >&5 +$as_echo_n "checking for Python site-packages path... " >&6; } + if test -z "$PYTHON_SITE_PKG"; then + PYTHON_SITE_PKG=`$PYTHON -c "import distutils.sysconfig; \ + print (distutils.sysconfig.get_python_lib(0,0));"` + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PYTHON_SITE_PKG" >&5 +$as_echo "$PYTHON_SITE_PKG" >&6; } + + + # + # libraries which must be linked in when embedding + # + { $as_echo "$as_me:${as_lineno-$LINENO}: checking python extra libraries" >&5 +$as_echo_n "checking python extra libraries... " >&6; } + if test -z "$PYTHON_EXTRA_LDFLAGS"; then + PYTHON_EXTRA_LDFLAGS=`$PYTHON -c "import distutils.sysconfig; \ + conf = distutils.sysconfig.get_config_var; \ + print (conf('LIBS') + ' ' + conf('SYSLIBS'))"` + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PYTHON_EXTRA_LDFLAGS" >&5 +$as_echo "$PYTHON_EXTRA_LDFLAGS" >&6; } + + + # + # linking flags needed when embedding + # + { $as_echo "$as_me:${as_lineno-$LINENO}: checking python extra linking flags" >&5 +$as_echo_n "checking python extra linking flags... " >&6; } + if test -z "$PYTHON_EXTRA_LIBS"; then + PYTHON_EXTRA_LIBS=`$PYTHON -c "import distutils.sysconfig; \ + conf = distutils.sysconfig.get_config_var; \ + print (conf('LINKFORSHARED'))"` + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PYTHON_EXTRA_LIBS" >&5 +$as_echo "$PYTHON_EXTRA_LIBS" >&6; } + + + # + # final check to see if everything compiles alright + # + { $as_echo "$as_me:${as_lineno-$LINENO}: checking consistency of all components of python development environment" >&5 +$as_echo_n "checking consistency of all components of python development environment... " >&6; } + # save current global flags + ac_save_LIBS="$LIBS" + ac_save_LDFLAGS="$LDFLAGS" + ac_save_CPPFLAGS="$CPPFLAGS" + LIBS="$ac_save_LIBS $PYTHON_LIBS $PYTHON_EXTRA_LIBS $PYTHON_EXTRA_LIBS" + LDFLAGS="$ac_save_LDFLAGS $PYTHON_EXTRA_LDFLAGS" + CPPFLAGS="$ac_save_CPPFLAGS $PYTHON_CPPFLAGS" + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #include +int +main () +{ +Py_Initialize(); + ; + return 0; +} + +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + pythonexists=yes +else + pythonexists=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + # turn back to default flags + CPPFLAGS="$ac_save_CPPFLAGS" + LIBS="$ac_save_LIBS" + LDFLAGS="$ac_save_LDFLAGS" + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $pythonexists" >&5 +$as_echo "$pythonexists" >&6; } + + if test ! "x$pythonexists" = "xyes"; then + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? " + Could not link test program to Python. Maybe the main Python library has been + installed in some non-standard library path. If so, pass it to configure, + via the LIBS environment variable. + Example: ./configure LIBS=\"-L/usr/non-standard-path/python/lib\" + ============================================================================ + ERROR! + You probably have to install the development version of the Python package + for your distribution. The exact name of this package varies among them. + ============================================================================ + +See \`config.log' for more details" "$LINENO" 5; } + PYTHON_VERSION="" + fi + + # + # all done! + # + + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + +ax_boost_python_save_CPPFLAGS="$CPPFLAGS" +ax_boost_python_save_LDFLAGS="$LDFLAGS" +ax_boost_python_save_LIBS="$LIBS" +if test "x$PYTHON_CPPFLAGS" != "x"; then + CPPFLAGS="$PYTHON_CPPFLAGS $CPPFLAGS" +fi + +# Versions of AX_PYTHON_DEVEL() before serial 18 provided PYTHON_LDFLAGS +# instead of PYTHON_LIBS, so this is just here for compatibility. +if test "x$PYTHON_LDFLAGS" != "x"; then + LDFLAGS="$PYTHON_LDFLAGS $LDFLAGS" +fi + +# Note: Only versions of AX_PYTHON_DEVEL() since serial 18 provide PYTHON_LIBS +# instead of PYTHON_LDFLAGS. +if test "x$PYTHON_LIBS" != "x"; then + LIBS="$PYTHON_LIBS $LIBS" +fi + +if test "x$BOOST_CPPFLAGS" != "x"; then + CPPFLAGS="$BOOST_CPPFLAGS $CPPFLAGS" +fi +if test "x$BOOST_LDFLAGS" != "x"; then + LDFLAGS="$BOOST_LDFLAGS $LDFLAGS" +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the Boost::Python library is available" >&5 +$as_echo_n "checking whether the Boost::Python library is available... " >&6; } +if ${ac_cv_boost_python+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#include +BOOST_PYTHON_MODULE(test) { throw "Boost::Python test."; } +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + ac_cv_boost_python=yes +else + ac_cv_boost_python=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_boost_python" >&5 +$as_echo "$ac_cv_boost_python" >&6; } +if test "$ac_cv_boost_python" = "yes"; then + +$as_echo "#define HAVE_BOOST_PYTHON /**/" >>confdefs.h + + ax_python_lib=boost_python + +# Check whether --with-boost-python was given. +if test "${with_boost_python+set}" = set; then : + withval=$with_boost_python; if test "x$with_boost_python" != "xno" -a "x$with_boost_python" != "xyes"; then + ax_python_lib=$with_boost_python + ax_boost_python_lib=boost_python-$with_boost_python + fi +fi + + BOOSTLIBDIR=`echo $BOOST_LDFLAGS | sed -e 's/[^\/]*//'` + for ax_lib in $ax_python_lib $ax_boost_python_lib `ls $BOOSTLIBDIR/libboost_python*.so* $BOOSTLIBDIR/libboost_python*.dylib* $BOOSTLIBDIR/libboost_python*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^lib\(boost_python.*\)\.so.*$;\1;' -e 's;^lib\(boost_python.*\)\.dylib.*$;\1;' -e 's;^lib\(boost_python.*\)\.a.*$;\1;' ` boost_python boost_python3; do + as_ax_Lib=`$as_echo "ax_cv_lib_$ax_lib''_main" | $as_tr_sh` + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $ax_lib is the correct library" >&5 +$as_echo_n "checking whether $ax_lib is the correct library... " >&6; } +if eval \${$as_ax_Lib+:} false; then : + $as_echo_n "(cached) " >&6 +else + LIBS="-l$ax_lib $ax_boost_python_save_LIBS $PYTHON_LIBS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_link "$LINENO"; then : + eval "$as_ax_Lib=yes" +else + eval "$as_ax_Lib=no" +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +fi +eval ac_res=\$$as_ax_Lib + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } + if eval test \"x\$"$as_ax_Lib"\" = x"yes"; then : + BOOST_PYTHON_LIB=$ax_lib break +fi + done + +fi +CPPFLAGS="$ax_boost_python_save_CPPFLAGS" +LDFLAGS="$ax_boost_python_save_LDFLAGS" +LIBS="$ax_boost_python_save_LIBS" +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + if test -z "$BOOST_PYTHON_LIB"; then : + as_fn_error $? "Boost.Python library not found. Try using --with-boost-python=lib." "$LINENO" 5 +fi + + BOOST_PYTHON_LIB="-l$BOOST_PYTHON_LIB" + ;; #( + "no") : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + ;; #( + *) : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ARG_ENABLE_PYTHON_BINDING" >&5 +$as_echo "$ARG_ENABLE_PYTHON_BINDING" >&6; } + as_fn_error $? "Unknown option \"$ARG_ENABLE_PYTHON_BINDING\". Use either \"yes\" or \"no\"." "$LINENO" 5 + ;; +esac + +$as_echo +$as_echo "Checking for external libraries:" + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to link against system libiconv" >&5 +$as_echo_n "checking whether to link against system libiconv... " >&6; } + + if test "X$prefix" = "XNONE"; then + acl_final_prefix="$ac_default_prefix" + else + acl_final_prefix="$prefix" + fi + if test "X$exec_prefix" = "XNONE"; then + acl_final_exec_prefix='${prefix}' + else + acl_final_exec_prefix="$exec_prefix" + fi + acl_save_prefix="$prefix" + prefix="$acl_final_prefix" + eval acl_final_exec_prefix=\"$acl_final_exec_prefix\" + prefix="$acl_save_prefix" + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for shared library run path origin" >&5 +$as_echo_n "checking for shared library run path origin... " >&6; } +if ${acl_cv_rpath+:} false; then : + $as_echo_n "(cached) " >&6 +else + + CC="$CC" GCC="$GCC" LDFLAGS="$LDFLAGS" LD="$LD" with_gnu_ld="$with_gnu_ld" \ + ${CONFIG_SHELL-/bin/sh} "$ac_aux_dir/config.rpath" "$host" > conftest.sh + . ./conftest.sh + rm -f ./conftest.sh + acl_cv_rpath=done + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $acl_cv_rpath" >&5 +$as_echo "$acl_cv_rpath" >&6; } + wl="$acl_cv_wl" + libext="$acl_cv_libext" + shlibext="$acl_cv_shlibext" + hardcode_libdir_flag_spec="$acl_cv_hardcode_libdir_flag_spec" + hardcode_libdir_separator="$acl_cv_hardcode_libdir_separator" + hardcode_direct="$acl_cv_hardcode_direct" + hardcode_minus_L="$acl_cv_hardcode_minus_L" + + + + + + + + use_additional=yes + + acl_save_prefix="$prefix" + prefix="$acl_final_prefix" + acl_save_exec_prefix="$exec_prefix" + exec_prefix="$acl_final_exec_prefix" + + eval additional_includedir=\"$includedir\" + eval additional_libdir=\"$libdir\" + + exec_prefix="$acl_save_exec_prefix" + prefix="$acl_save_prefix" + + +# Check whether --with-libiconv-prefix was given. +if test "${with_libiconv_prefix+set}" = set; then : + withval=$with_libiconv_prefix; + if test "X$withval" = "Xno"; then + use_additional=no + else + if test "X$withval" = "X"; then + + acl_save_prefix="$prefix" + prefix="$acl_final_prefix" + acl_save_exec_prefix="$exec_prefix" + exec_prefix="$acl_final_exec_prefix" + + eval additional_includedir=\"$includedir\" + eval additional_libdir=\"$libdir\" + + exec_prefix="$acl_save_exec_prefix" + prefix="$acl_save_prefix" + + else + additional_includedir="$withval/include" + additional_libdir="$withval/lib" + fi + fi + +fi + + LIBICONV= + LTLIBICONV= + INCICONV= + rpathdirs= + ltrpathdirs= + names_already_handled= + names_next_round='iconv ' + while test -n "$names_next_round"; do + names_this_round="$names_next_round" + names_next_round= + for name in $names_this_round; do + already_handled= + for n in $names_already_handled; do + if test "$n" = "$name"; then + already_handled=yes + break + fi + done + if test -z "$already_handled"; then + names_already_handled="$names_already_handled $name" + uppername=`echo "$name" | sed -e 'y|abcdefghijklmnopqrstuvwxyz./-|ABCDEFGHIJKLMNOPQRSTUVWXYZ___|'` + eval value=\"\$HAVE_LIB$uppername\" + if test -n "$value"; then + if test "$value" = yes; then + eval value=\"\$LIB$uppername\" + test -z "$value" || LIBICONV="${LIBICONV}${LIBICONV:+ }$value" + eval value=\"\$LTLIB$uppername\" + test -z "$value" || LTLIBICONV="${LTLIBICONV}${LTLIBICONV:+ }$value" + else + : + fi + else + found_dir= + found_la= + found_so= + found_a= + if test $use_additional = yes; then + if test -n "$shlibext" && test -f "$additional_libdir/lib$name.$shlibext"; then + found_dir="$additional_libdir" + found_so="$additional_libdir/lib$name.$shlibext" + if test -f "$additional_libdir/lib$name.la"; then + found_la="$additional_libdir/lib$name.la" + fi + else + if test -f "$additional_libdir/lib$name.$libext"; then + found_dir="$additional_libdir" + found_a="$additional_libdir/lib$name.$libext" + if test -f "$additional_libdir/lib$name.la"; then + found_la="$additional_libdir/lib$name.la" + fi + fi + fi + fi + if test "X$found_dir" = "X"; then + for x in $LDFLAGS $LTLIBICONV; do + + acl_save_prefix="$prefix" + prefix="$acl_final_prefix" + acl_save_exec_prefix="$exec_prefix" + exec_prefix="$acl_final_exec_prefix" + eval x=\"$x\" + exec_prefix="$acl_save_exec_prefix" + prefix="$acl_save_prefix" + + case "$x" in + -L*) + dir=`echo "X$x" | sed -e 's/^X-L//'` + if test -n "$shlibext" && test -f "$dir/lib$name.$shlibext"; then + found_dir="$dir" + found_so="$dir/lib$name.$shlibext" + if test -f "$dir/lib$name.la"; then + found_la="$dir/lib$name.la" + fi + else + if test -f "$dir/lib$name.$libext"; then + found_dir="$dir" + found_a="$dir/lib$name.$libext" + if test -f "$dir/lib$name.la"; then + found_la="$dir/lib$name.la" + fi + fi + fi + ;; + esac + if test "X$found_dir" != "X"; then + break + fi + done + fi + if test "X$found_dir" != "X"; then + LTLIBICONV="${LTLIBICONV}${LTLIBICONV:+ }-L$found_dir -l$name" + if test "X$found_so" != "X"; then + if test "$enable_rpath" = no || test "X$found_dir" = "X/usr/lib"; then + LIBICONV="${LIBICONV}${LIBICONV:+ }$found_so" + else + haveit= + for x in $ltrpathdirs; do + if test "X$x" = "X$found_dir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + ltrpathdirs="$ltrpathdirs $found_dir" + fi + if test "$hardcode_direct" = yes; then + LIBICONV="${LIBICONV}${LIBICONV:+ }$found_so" + else + if test -n "$hardcode_libdir_flag_spec" && test "$hardcode_minus_L" = no; then + LIBICONV="${LIBICONV}${LIBICONV:+ }$found_so" + haveit= + for x in $rpathdirs; do + if test "X$x" = "X$found_dir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + rpathdirs="$rpathdirs $found_dir" + fi + else + haveit= + for x in $LDFLAGS $LIBICONV; do + + acl_save_prefix="$prefix" + prefix="$acl_final_prefix" + acl_save_exec_prefix="$exec_prefix" + exec_prefix="$acl_final_exec_prefix" + eval x=\"$x\" + exec_prefix="$acl_save_exec_prefix" + prefix="$acl_save_prefix" + + if test "X$x" = "X-L$found_dir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + LIBICONV="${LIBICONV}${LIBICONV:+ }-L$found_dir" + fi + if test "$hardcode_minus_L" != no; then + LIBICONV="${LIBICONV}${LIBICONV:+ }$found_so" + else + LIBICONV="${LIBICONV}${LIBICONV:+ }-l$name" + fi + fi + fi + fi + else + if test "X$found_a" != "X"; then + LIBICONV="${LIBICONV}${LIBICONV:+ }$found_a" + else + LIBICONV="${LIBICONV}${LIBICONV:+ }-L$found_dir -l$name" + fi + fi + additional_includedir= + case "$found_dir" in + */lib | */lib/) + basedir=`echo "X$found_dir" | sed -e 's,^X,,' -e 's,/lib/*$,,'` + additional_includedir="$basedir/include" + ;; + esac + if test "X$additional_includedir" != "X"; then + if test "X$additional_includedir" != "X/usr/include"; then + haveit= + if test "X$additional_includedir" = "X/usr/local/include"; then + if test -n "$GCC"; then + case $host_os in + linux* | gnu* | k*bsd*-gnu) haveit=yes;; + esac + fi + fi + if test -z "$haveit"; then + for x in $CPPFLAGS $INCICONV; do + + acl_save_prefix="$prefix" + prefix="$acl_final_prefix" + acl_save_exec_prefix="$exec_prefix" + exec_prefix="$acl_final_exec_prefix" + eval x=\"$x\" + exec_prefix="$acl_save_exec_prefix" + prefix="$acl_save_prefix" + + if test "X$x" = "X-I$additional_includedir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + if test -d "$additional_includedir"; then + INCICONV="${INCICONV}${INCICONV:+ }-I$additional_includedir" + fi + fi + fi + fi + fi + if test -n "$found_la"; then + save_libdir="$libdir" + case "$found_la" in + */* | *\\*) . "$found_la" ;; + *) . "./$found_la" ;; + esac + libdir="$save_libdir" + for dep in $dependency_libs; do + case "$dep" in + -L*) + additional_libdir=`echo "X$dep" | sed -e 's/^X-L//'` + if test "X$additional_libdir" != "X/usr/lib"; then + haveit= + if test "X$additional_libdir" = "X/usr/local/lib"; then + if test -n "$GCC"; then + case $host_os in + linux* | gnu* | k*bsd*-gnu) haveit=yes;; + esac + fi + fi + if test -z "$haveit"; then + haveit= + for x in $LDFLAGS $LIBICONV; do + + acl_save_prefix="$prefix" + prefix="$acl_final_prefix" + acl_save_exec_prefix="$exec_prefix" + exec_prefix="$acl_final_exec_prefix" + eval x=\"$x\" + exec_prefix="$acl_save_exec_prefix" + prefix="$acl_save_prefix" + + if test "X$x" = "X-L$additional_libdir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + if test -d "$additional_libdir"; then + LIBICONV="${LIBICONV}${LIBICONV:+ }-L$additional_libdir" + fi + fi + haveit= + for x in $LDFLAGS $LTLIBICONV; do + + acl_save_prefix="$prefix" + prefix="$acl_final_prefix" + acl_save_exec_prefix="$exec_prefix" + exec_prefix="$acl_final_exec_prefix" + eval x=\"$x\" + exec_prefix="$acl_save_exec_prefix" + prefix="$acl_save_prefix" + + if test "X$x" = "X-L$additional_libdir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + if test -d "$additional_libdir"; then + LTLIBICONV="${LTLIBICONV}${LTLIBICONV:+ }-L$additional_libdir" + fi + fi + fi + fi + ;; + -R*) + dir=`echo "X$dep" | sed -e 's/^X-R//'` + if test "$enable_rpath" != no; then + haveit= + for x in $rpathdirs; do + if test "X$x" = "X$dir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + rpathdirs="$rpathdirs $dir" + fi + haveit= + for x in $ltrpathdirs; do + if test "X$x" = "X$dir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + ltrpathdirs="$ltrpathdirs $dir" + fi + fi + ;; + -l*) + names_next_round="$names_next_round "`echo "X$dep" | sed -e 's/^X-l//'` + ;; + *.la) + names_next_round="$names_next_round "`echo "X$dep" | sed -e 's,^X.*/,,' -e 's,^lib,,' -e 's,\.la$,,'` + ;; + *) + LIBICONV="${LIBICONV}${LIBICONV:+ }$dep" + LTLIBICONV="${LTLIBICONV}${LTLIBICONV:+ }$dep" + ;; + esac + done + fi + else + LIBICONV="${LIBICONV}${LIBICONV:+ }-l$name" + LTLIBICONV="${LTLIBICONV}${LTLIBICONV:+ }-l$name" + fi + fi + fi + done + done + if test "X$rpathdirs" != "X"; then + if test -n "$hardcode_libdir_separator"; then + alldirs= + for found_dir in $rpathdirs; do + alldirs="${alldirs}${alldirs:+$hardcode_libdir_separator}$found_dir" + done + acl_save_libdir="$libdir" + libdir="$alldirs" + eval flag=\"$hardcode_libdir_flag_spec\" + libdir="$acl_save_libdir" + LIBICONV="${LIBICONV}${LIBICONV:+ }$flag" + else + for found_dir in $rpathdirs; do + acl_save_libdir="$libdir" + libdir="$found_dir" + eval flag=\"$hardcode_libdir_flag_spec\" + libdir="$acl_save_libdir" + LIBICONV="${LIBICONV}${LIBICONV:+ }$flag" + done + fi + fi + if test "X$ltrpathdirs" != "X"; then + for found_dir in $ltrpathdirs; do + LTLIBICONV="${LTLIBICONV}${LTLIBICONV:+ }-R$found_dir" + done + fi + + +case "$ARG_WITH_LIBICONV" in #( + "yes") : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + + + + + am_save_CPPFLAGS="$CPPFLAGS" + + for element in $INCICONV; do + haveit= + for x in $CPPFLAGS; do + + acl_save_prefix="$prefix" + prefix="$acl_final_prefix" + acl_save_exec_prefix="$exec_prefix" + exec_prefix="$acl_final_exec_prefix" + eval x=\"$x\" + exec_prefix="$acl_save_exec_prefix" + prefix="$acl_save_prefix" + + if test "X$x" = "X$element"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + CPPFLAGS="${CPPFLAGS}${CPPFLAGS:+ }$element" + fi + done + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for iconv" >&5 +$as_echo_n "checking for iconv... " >&6; } +if ${am_cv_func_iconv+:} false; then : + $as_echo_n "(cached) " >&6 +else + + am_cv_func_iconv="no, consider installing GNU libiconv" + am_cv_lib_iconv=no + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +int +main () +{ +iconv_t cd = iconv_open("",""); + iconv(cd,NULL,NULL,NULL,NULL); + iconv_close(cd); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + am_cv_func_iconv=yes +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + if test "$am_cv_func_iconv" != yes; then + am_save_LIBS="$LIBS" + LIBS="$LIBS $LIBICONV" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +int +main () +{ +iconv_t cd = iconv_open("",""); + iconv(cd,NULL,NULL,NULL,NULL); + iconv_close(cd); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + am_cv_lib_iconv=yes + am_cv_func_iconv=yes +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + LIBS="$am_save_LIBS" + fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_func_iconv" >&5 +$as_echo "$am_cv_func_iconv" >&6; } + if test "$am_cv_func_iconv" = yes; then + +$as_echo "#define HAVE_ICONV 1" >>confdefs.h + + fi + if test "$am_cv_lib_iconv" = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to link with libiconv" >&5 +$as_echo_n "checking how to link with libiconv... " >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $LIBICONV" >&5 +$as_echo "$LIBICONV" >&6; } + else + CPPFLAGS="$am_save_CPPFLAGS" + LIBICONV= + LTLIBICONV= + fi + + + + if test "$am_cv_func_iconv" = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for iconv declaration" >&5 +$as_echo_n "checking for iconv declaration... " >&6; } + if ${am_cv_proto_iconv+:} false; then : + $as_echo_n "(cached) " >&6 +else + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#include +#include +extern +#ifdef __cplusplus +"C" +#endif +#if defined(__STDC__) || defined(__cplusplus) +size_t iconv (iconv_t cd, char * *inbuf, size_t *inbytesleft, char * *outbuf, size_t *outbytesleft); +#else +size_t iconv(); +#endif + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + am_cv_proto_iconv_arg1="" +else + am_cv_proto_iconv_arg1="const" +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + am_cv_proto_iconv="extern size_t iconv (iconv_t cd, $am_cv_proto_iconv_arg1 char * *inbuf, size_t *inbytesleft, char * *outbuf, size_t *outbytesleft);" +fi + + am_cv_proto_iconv=`echo "$am_cv_proto_iconv" | tr -s ' ' | sed -e 's/( /(/'` + { $as_echo "$as_me:${as_lineno-$LINENO}: result: ${ac_t:- + }$am_cv_proto_iconv" >&5 +$as_echo "${ac_t:- + }$am_cv_proto_iconv" >&6; } + +cat >>confdefs.h <<_ACEOF +#define ICONV_CONST $am_cv_proto_iconv_arg1 +_ACEOF + + fi + + if test "x$am_cv_func_iconv" = "xyes"; then : + + ICONV_LIBS=$LTLIBICONV + + LIBS="$ICONV_LIBS $LIBS" + +else + + as_fn_error $? "Iconv library not found." "$LINENO" 5 + +fi + ;; #( + "no") : + + ARG_WITH_LIBICONV="no" + ;; #( + *) : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ARG_WITH_LIBICONV" >&5 +$as_echo "$ARG_WITH_LIBICONV" >&6; } + as_fn_error $? "Unknown option \"$ARG_WITH_LIBICONV\". Use either \"yes\" or \"no\"." "$LINENO" 5 + ;; +esac + +############################################################################### +# Setting conditional variables for Makefiles +############################################################################### + + if test "x$ARG_ENABLE_DHT" = "xyes" -o "x$ARG_ENABLE_DHT" = "xlogging" ; then + ENABLE_DHT_TRUE= + ENABLE_DHT_FALSE='#' +else + ENABLE_DHT_TRUE='#' + ENABLE_DHT_FALSE= +fi + + if test "x$ARG_ENABLE_EXAMPLES" = "xyes"; then + ENABLE_EXAMPLES_TRUE= + ENABLE_EXAMPLES_FALSE='#' +else + ENABLE_EXAMPLES_TRUE='#' + ENABLE_EXAMPLES_FALSE= +fi + + if test "x$ARG_ENABLE_TESTS" = "xyes"; then + ENABLE_TESTS_TRUE= + ENABLE_TESTS_FALSE='#' +else + ENABLE_TESTS_TRUE='#' + ENABLE_TESTS_FALSE= +fi + + if test "x$ARG_ENABLE_PYTHON_BINDING" = "xyes"; then + ENABLE_PYTHON_BINDING_TRUE= + ENABLE_PYTHON_BINDING_FALSE='#' +else + ENABLE_PYTHON_BINDING_TRUE='#' + ENABLE_PYTHON_BINDING_FALSE= +fi + + + if test "x$ARG_ENABLE_ENCRYPTION" = "xyes" -o "x$ARG_ENABLE_ENCRYPTION" = "xon" ; then + WITH_OPENSSL_TRUE= + WITH_OPENSSL_FALSE='#' +else + WITH_OPENSSL_TRUE='#' + WITH_OPENSSL_FALSE= +fi + + + +############################################################################### +# Other useful stuff +############################################################################### + +# this works around a bug in asio in boost-1.39 +# see: https://svn.boost.org/trac/boost/ticket/3095 + +$as_echo "#define BOOST_ASIO_HASH_MAP_BUCKETS 1021" >>confdefs.h + +COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DBOOST_ASIO_HASH_MAP_BUCKETS=1021 " + + +$as_echo "#define BOOST_EXCEPTION_DISABLE 1" >>confdefs.h + +COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DBOOST_EXCEPTION_DISABLE " + + +$as_echo "#define BOOST_ASIO_ENABLE_CANCELIO 1" >>confdefs.h + +COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DBOOST_ASIO_ENABLE_CANCELIO " + + +if test "x$PYTHON_INSTALL_PARAMS" = "x"; then : + PYTHON_INSTALL_PARAMS='--prefix=$(DESTDIR)$(prefix)' +fi + +if test "x$enable_shared" = "xyes"; then : + +$as_echo "#define TORRENT_BUILDING_SHARED 1" >>confdefs.h + + COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DTORRENT_LINKING_SHARED " +fi + + + + + + +# Try to guess real svn revision if any, fallback to hardcoded otherwise +SVN_REVISION=`svn info 2>/dev/null | sed -n -e '/^URL\:.*libtorrent.svn.sourceforge.net/,$!d;s,^Revision\: \([0-9]*\)$,\1,p'` +if test -z "$SVN_REVISION"; then : + SVN_REVISION=`sed -n -e 's/^#define LIBTORRENT_REVISION \"\$Rev\: \([0-9]*\) \$\" $/\1/p' include/libtorrent/version.hpp` +fi + + +############################################################################### +# Generating Makefiles +############################################################################### + +$as_echo +$as_echo "Generating Makefiles:" + +ac_config_files="$ac_config_files Makefile src/Makefile include/libtorrent/Makefile examples/Makefile test/Makefile tools/Makefile bindings/Makefile bindings/python/Makefile bindings/python/link_flags bindings/python/compile_flags libtorrent-rasterbar.pc libtorrent-rasterbar-cmake.pc" + + +cat >confcache <<\_ACEOF +# This file is a shell script that caches the results of configure +# tests run on this system so they can be shared between configure +# scripts and configure runs, see configure's option --config-cache. +# It is not useful on other systems. If it contains results you don't +# want to keep, you may remove or edit it. +# +# config.status only pays attention to the cache file if you give it +# the --recheck option to rerun configure. +# +# `ac_cv_env_foo' variables (set or unset) will be overridden when +# loading this file, other *unset* `ac_cv_foo' will be assigned the +# following values. + +_ACEOF + +# The following way of writing the cache mishandles newlines in values, +# but we know of no workaround that is simple, portable, and efficient. +# So, we kill variables containing newlines. +# Ultrix sh set writes to stderr and can't be redirected directly, +# and sets the high bit in the cache file unless we assign to the vars. +( + for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do + eval ac_val=\$$ac_var + case $ac_val in #( + *${as_nl}*) + case $ac_var in #( + *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + esac + case $ac_var in #( + _ | IFS | as_nl) ;; #( + BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( + *) { eval $ac_var=; unset $ac_var;} ;; + esac ;; + esac + done + + (set) 2>&1 | + case $as_nl`(ac_space=' '; set) 2>&1` in #( + *${as_nl}ac_space=\ *) + # `set' does not quote correctly, so add quotes: double-quote + # substitution turns \\\\ into \\, and sed turns \\ into \. + sed -n \ + "s/'/'\\\\''/g; + s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" + ;; #( + *) + # `set' quotes correctly as required by POSIX, so do not add quotes. + sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" + ;; + esac | + sort +) | + sed ' + /^ac_cv_env_/b end + t clear + :clear + s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/ + t end + s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ + :end' >>confcache +if diff "$cache_file" confcache >/dev/null 2>&1; then :; else + if test -w "$cache_file"; then + if test "x$cache_file" != "x/dev/null"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 +$as_echo "$as_me: updating cache $cache_file" >&6;} + if test ! -f "$cache_file" || test -h "$cache_file"; then + cat confcache >"$cache_file" + else + case $cache_file in #( + */* | ?:*) + mv -f confcache "$cache_file"$$ && + mv -f "$cache_file"$$ "$cache_file" ;; #( + *) + mv -f confcache "$cache_file" ;; + esac + fi + fi + else + { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 +$as_echo "$as_me: not updating unwritable cache $cache_file" >&6;} + fi +fi +rm -f confcache + +test "x$prefix" = xNONE && prefix=$ac_default_prefix +# Let make expand exec_prefix. +test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' + +# Transform confdefs.h into DEFS. +# Protect against shell expansion while executing Makefile rules. +# Protect against Makefile macro expansion. +# +# If the first sed substitution is executed (which looks for macros that +# take arguments), then branch to the quote section. Otherwise, +# look for a macro that doesn't take arguments. +ac_script=' +:mline +/\\$/{ + N + s,\\\n,, + b mline +} +t clear +:clear +s/^[ ]*#[ ]*define[ ][ ]*\([^ (][^ (]*([^)]*)\)[ ]*\(.*\)/-D\1=\2/g +t quote +s/^[ ]*#[ ]*define[ ][ ]*\([^ ][^ ]*\)[ ]*\(.*\)/-D\1=\2/g +t quote +b any +:quote +s/[ `~#$^&*(){}\\|;'\''"<>?]/\\&/g +s/\[/\\&/g +s/\]/\\&/g +s/\$/$$/g +H +:any +${ + g + s/^\n// + s/\n/ /g + p +} +' +DEFS=`sed -n "$ac_script" confdefs.h` + + +ac_libobjs= +ac_ltlibobjs= +U= +for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue + # 1. Remove the extension, and $U if already installed. + ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' + ac_i=`$as_echo "$ac_i" | sed "$ac_script"` + # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR + # will be set to the directory where LIBOBJS objects are built. + as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext" + as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo' +done +LIBOBJS=$ac_libobjs + +LTLIBOBJS=$ac_ltlibobjs + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking that generated files are newer than configure" >&5 +$as_echo_n "checking that generated files are newer than configure... " >&6; } + if test -n "$am_sleep_pid"; then + # Hide warnings about reused PIDs. + wait $am_sleep_pid 2>/dev/null + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: result: done" >&5 +$as_echo "done" >&6; } +if test -z "${AMDEP_TRUE}" && test -z "${AMDEP_FALSE}"; then + as_fn_error $? "conditional \"AMDEP\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${am__fastdepCC_TRUE}" && test -z "${am__fastdepCC_FALSE}"; then + as_fn_error $? "conditional \"am__fastdepCC\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${am__fastdepCXX_TRUE}" && test -z "${am__fastdepCXX_FALSE}"; then + as_fn_error $? "conditional \"am__fastdepCXX\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi + if test -n "$EXEEXT"; then + am__EXEEXT_TRUE= + am__EXEEXT_FALSE='#' +else + am__EXEEXT_TRUE='#' + am__EXEEXT_FALSE= +fi + +if test -z "${ENABLE_DHT_TRUE}" && test -z "${ENABLE_DHT_FALSE}"; then + as_fn_error $? "conditional \"ENABLE_DHT\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${ENABLE_EXAMPLES_TRUE}" && test -z "${ENABLE_EXAMPLES_FALSE}"; then + as_fn_error $? "conditional \"ENABLE_EXAMPLES\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${ENABLE_TESTS_TRUE}" && test -z "${ENABLE_TESTS_FALSE}"; then + as_fn_error $? "conditional \"ENABLE_TESTS\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${ENABLE_PYTHON_BINDING_TRUE}" && test -z "${ENABLE_PYTHON_BINDING_FALSE}"; then + as_fn_error $? "conditional \"ENABLE_PYTHON_BINDING\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${WITH_OPENSSL_TRUE}" && test -z "${WITH_OPENSSL_FALSE}"; then + as_fn_error $? "conditional \"WITH_OPENSSL\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi + +: "${CONFIG_STATUS=./config.status}" +ac_write_fail=0 +ac_clean_files_save=$ac_clean_files +ac_clean_files="$ac_clean_files $CONFIG_STATUS" +{ $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 +$as_echo "$as_me: creating $CONFIG_STATUS" >&6;} +as_write_fail=0 +cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1 +#! $SHELL +# Generated by $as_me. +# Run this file to recreate the current configuration. +# Compiler output produced by configure, useful for debugging +# configure, is in config.log if it exists. + +debug=false +ac_cs_recheck=false +ac_cs_silent=false + +SHELL=\${CONFIG_SHELL-$SHELL} +export SHELL +_ASEOF +cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1 +## -------------------- ## +## M4sh Initialization. ## +## -------------------- ## + +# Be more Bourne compatible +DUALCASE=1; export DUALCASE # for MKS sh +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else + case `(set -o) 2>/dev/null` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi + + +as_nl=' +' +export as_nl +# Printing a long string crashes Solaris 7 /usr/bin/printf. +as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo +# Prefer a ksh shell builtin over an external printf program on Solaris, +# but without wasting forks for bash or zsh. +if test -z "$BASH_VERSION$ZSH_VERSION" \ + && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='print -r --' + as_echo_n='print -rn --' +elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='printf %s\n' + as_echo_n='printf %s' +else + if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then + as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' + as_echo_n='/usr/ucb/echo -n' + else + as_echo_body='eval expr "X$1" : "X\\(.*\\)"' + as_echo_n_body='eval + arg=$1; + case $arg in #( + *"$as_nl"*) + expr "X$arg" : "X\\(.*\\)$as_nl"; + arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; + esac; + expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" + ' + export as_echo_n_body + as_echo_n='sh -c $as_echo_n_body as_echo' + fi + export as_echo_body + as_echo='sh -c $as_echo_body as_echo' +fi + +# The user is always right. +if test "${PATH_SEPARATOR+set}" != set; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } +fi + + +# IFS +# We need space, tab and new line, in precisely that order. Quoting is +# there to prevent editors from complaining about space-tab. +# (If _AS_PATH_WALK were called with IFS unset, it would disable word +# splitting by setting IFS to empty value.) +IFS=" "" $as_nl" + +# Find who we are. Look in the path if we contain no directory separator. +as_myself= +case $0 in #(( + *[\\/]* ) as_myself=$0 ;; + *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break + done +IFS=$as_save_IFS + + ;; +esac +# We did not find ourselves, most probably we were run as `sh COMMAND' +# in which case we are not to be found in the path. +if test "x$as_myself" = x; then + as_myself=$0 +fi +if test ! -f "$as_myself"; then + $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + exit 1 +fi + +# Unset variables that we do not need and which cause bugs (e.g. in +# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" +# suppresses any "Segmentation fault" message there. '((' could +# trigger a bug in pdksh 5.2.14. +for as_var in BASH_ENV ENV MAIL MAILPATH +do eval test x\${$as_var+set} = xset \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done +PS1='$ ' +PS2='> ' +PS4='+ ' + +# NLS nuisances. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# CDPATH. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + + +# as_fn_error STATUS ERROR [LINENO LOG_FD] +# ---------------------------------------- +# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are +# provided, also output the error to LOG_FD, referencing LINENO. Then exit the +# script with STATUS, using 1 if that was 0. +as_fn_error () +{ + as_status=$1; test $as_status -eq 0 && as_status=1 + if test "$4"; then + as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + fi + $as_echo "$as_me: error: $2" >&2 + as_fn_exit $as_status +} # as_fn_error + + +# as_fn_set_status STATUS +# ----------------------- +# Set $? to STATUS, without forking. +as_fn_set_status () +{ + return $1 +} # as_fn_set_status + +# as_fn_exit STATUS +# ----------------- +# Exit the shell with STATUS, even in a "trap 0" or "set -e" context. +as_fn_exit () +{ + set +e + as_fn_set_status $1 + exit $1 +} # as_fn_exit + +# as_fn_unset VAR +# --------------- +# Portably unset VAR. +as_fn_unset () +{ + { eval $1=; unset $1;} +} +as_unset=as_fn_unset +# as_fn_append VAR VALUE +# ---------------------- +# Append the text in VALUE to the end of the definition contained in VAR. Take +# advantage of any shell optimizations that allow amortized linear growth over +# repeated appends, instead of the typical quadratic growth present in naive +# implementations. +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : + eval 'as_fn_append () + { + eval $1+=\$2 + }' +else + as_fn_append () + { + eval $1=\$$1\$2 + } +fi # as_fn_append + +# as_fn_arith ARG... +# ------------------ +# Perform arithmetic evaluation on the ARGs, and store the result in the +# global $as_val. Take advantage of shells that can avoid forks. The arguments +# must be portable across $(()) and expr. +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : + eval 'as_fn_arith () + { + as_val=$(( $* )) + }' +else + as_fn_arith () + { + as_val=`expr "$@" || test $? -eq 1` + } +fi # as_fn_arith + + +if expr a : '\(a\)' >/dev/null 2>&1 && + test "X`expr 00001 : '.*\(...\)'`" = X001; then + as_expr=expr +else + as_expr=false +fi + +if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then + as_basename=basename +else + as_basename=false +fi + +if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then + as_dirname=dirname +else + as_dirname=false +fi + +as_me=`$as_basename -- "$0" || +$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ + X"$0" : 'X\(//\)$' \| \ + X"$0" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X/"$0" | + sed '/^.*\/\([^/][^/]*\)\/*$/{ + s//\1/ + q + } + /^X\/\(\/\/\)$/{ + s//\1/ + q + } + /^X\/\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + +# Avoid depending upon Character Ranges. +as_cr_letters='abcdefghijklmnopqrstuvwxyz' +as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' +as_cr_Letters=$as_cr_letters$as_cr_LETTERS +as_cr_digits='0123456789' +as_cr_alnum=$as_cr_Letters$as_cr_digits + +ECHO_C= ECHO_N= ECHO_T= +case `echo -n x` in #((((( +-n*) + case `echo 'xy\c'` in + *c*) ECHO_T=' ';; # ECHO_T is single tab character. + xy) ECHO_C='\c';; + *) echo `echo ksh88 bug on AIX 6.1` > /dev/null + ECHO_T=' ';; + esac;; +*) + ECHO_N='-n';; +esac + +rm -f conf$$ conf$$.exe conf$$.file +if test -d conf$$.dir; then + rm -f conf$$.dir/conf$$.file +else + rm -f conf$$.dir + mkdir conf$$.dir 2>/dev/null +fi +if (echo >conf$$.file) 2>/dev/null; then + if ln -s conf$$.file conf$$ 2>/dev/null; then + as_ln_s='ln -s' + # ... but there are two gotchas: + # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. + # In both cases, we have to default to `cp -pR'. + ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || + as_ln_s='cp -pR' + elif ln conf$$.file conf$$ 2>/dev/null; then + as_ln_s=ln + else + as_ln_s='cp -pR' + fi +else + as_ln_s='cp -pR' +fi +rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file +rmdir conf$$.dir 2>/dev/null + + +# as_fn_mkdir_p +# ------------- +# Create "$as_dir" as a directory, including parents if necessary. +as_fn_mkdir_p () +{ + + case $as_dir in #( + -*) as_dir=./$as_dir;; + esac + test -d "$as_dir" || eval $as_mkdir_p || { + as_dirs= + while :; do + case $as_dir in #( + *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *) as_qdir=$as_dir;; + esac + as_dirs="'$as_qdir' $as_dirs" + as_dir=`$as_dirname -- "$as_dir" || +$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_dir" : 'X\(//\)[^/]' \| \ + X"$as_dir" : 'X\(//\)$' \| \ + X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_dir" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + test -d "$as_dir" && break + done + test -z "$as_dirs" || eval "mkdir $as_dirs" + } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" + + +} # as_fn_mkdir_p +if mkdir -p . 2>/dev/null; then + as_mkdir_p='mkdir -p "$as_dir"' +else + test -d ./-p && rmdir ./-p + as_mkdir_p=false +fi + + +# as_fn_executable_p FILE +# ----------------------- +# Test if FILE is an executable regular file. +as_fn_executable_p () +{ + test -f "$1" && test -x "$1" +} # as_fn_executable_p +as_test_x='test -x' +as_executable_p=as_fn_executable_p + +# Sed expression to map a string onto a valid CPP name. +as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" + +# Sed expression to map a string onto a valid variable name. +as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" + + +exec 6>&1 +## ----------------------------------- ## +## Main body of $CONFIG_STATUS script. ## +## ----------------------------------- ## +_ASEOF +test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1 + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# Save the log message, to keep $0 and so on meaningful, and to +# report actual input values of CONFIG_FILES etc. instead of their +# values after options handling. +ac_log=" +This file was extended by libtorrent-rasterbar $as_me 1.1.1, which was +generated by GNU Autoconf 2.69. Invocation command line was + + CONFIG_FILES = $CONFIG_FILES + CONFIG_HEADERS = $CONFIG_HEADERS + CONFIG_LINKS = $CONFIG_LINKS + CONFIG_COMMANDS = $CONFIG_COMMANDS + $ $0 $@ + +on `(hostname || uname -n) 2>/dev/null | sed 1q` +" + +_ACEOF + +case $ac_config_files in *" +"*) set x $ac_config_files; shift; ac_config_files=$*;; +esac + + + +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +# Files that config.status was made for. +config_files="$ac_config_files" +config_commands="$ac_config_commands" + +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +ac_cs_usage="\ +\`$as_me' instantiates files and other configuration actions +from templates according to the current configuration. Unless the files +and actions are specified as TAGs, all are instantiated by default. + +Usage: $0 [OPTION]... [TAG]... + + -h, --help print this help, then exit + -V, --version print version number and configuration settings, then exit + --config print configuration, then exit + -q, --quiet, --silent + do not print progress messages + -d, --debug don't remove temporary files + --recheck update $as_me by reconfiguring in the same conditions + --file=FILE[:TEMPLATE] + instantiate the configuration file FILE + +Configuration files: +$config_files + +Configuration commands: +$config_commands + +Report bugs to . +libtorrent-rasterbar home page: ." + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" +ac_cs_version="\\ +libtorrent-rasterbar config.status 1.1.1 +configured by $0, generated by GNU Autoconf 2.69, + with options \\"\$ac_cs_config\\" + +Copyright (C) 2012 Free Software Foundation, Inc. +This config.status script is free software; the Free Software Foundation +gives unlimited permission to copy, distribute and modify it." + +ac_pwd='$ac_pwd' +srcdir='$srcdir' +INSTALL='$INSTALL' +MKDIR_P='$MKDIR_P' +AWK='$AWK' +test -n "\$AWK" || AWK=awk +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# The default lists apply if the user does not specify any file. +ac_need_defaults=: +while test $# != 0 +do + case $1 in + --*=?*) + ac_option=`expr "X$1" : 'X\([^=]*\)='` + ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'` + ac_shift=: + ;; + --*=) + ac_option=`expr "X$1" : 'X\([^=]*\)='` + ac_optarg= + ac_shift=: + ;; + *) + ac_option=$1 + ac_optarg=$2 + ac_shift=shift + ;; + esac + + case $ac_option in + # Handling of the options. + -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) + ac_cs_recheck=: ;; + --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) + $as_echo "$ac_cs_version"; exit ;; + --config | --confi | --conf | --con | --co | --c ) + $as_echo "$ac_cs_config"; exit ;; + --debug | --debu | --deb | --de | --d | -d ) + debug=: ;; + --file | --fil | --fi | --f ) + $ac_shift + case $ac_optarg in + *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; + '') as_fn_error $? "missing file argument" ;; + esac + as_fn_append CONFIG_FILES " '$ac_optarg'" + ac_need_defaults=false;; + --he | --h | --help | --hel | -h ) + $as_echo "$ac_cs_usage"; exit ;; + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil | --si | --s) + ac_cs_silent=: ;; + + # This is an error. + -*) as_fn_error $? "unrecognized option: \`$1' +Try \`$0 --help' for more information." ;; + + *) as_fn_append ac_config_targets " $1" + ac_need_defaults=false ;; + + esac + shift +done + +ac_configure_extra_args= + +if $ac_cs_silent; then + exec 6>/dev/null + ac_configure_extra_args="$ac_configure_extra_args --silent" +fi + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +if \$ac_cs_recheck; then + set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion + shift + \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6 + CONFIG_SHELL='$SHELL' + export CONFIG_SHELL + exec "\$@" +fi + +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +exec 5>>config.log +{ + echo + sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX +## Running $as_me. ## +_ASBOX + $as_echo "$ac_log" +} >&5 + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +# +# INIT-COMMANDS +# +AMDEP_TRUE="$AMDEP_TRUE" ac_aux_dir="$ac_aux_dir" + + +# The HP-UX ksh and POSIX shell print the target directory to stdout +# if CDPATH is set. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + +sed_quote_subst='$sed_quote_subst' +double_quote_subst='$double_quote_subst' +delay_variable_subst='$delay_variable_subst' +macro_version='`$ECHO "$macro_version" | $SED "$delay_single_quote_subst"`' +macro_revision='`$ECHO "$macro_revision" | $SED "$delay_single_quote_subst"`' +enable_shared='`$ECHO "$enable_shared" | $SED "$delay_single_quote_subst"`' +enable_static='`$ECHO "$enable_static" | $SED "$delay_single_quote_subst"`' +pic_mode='`$ECHO "$pic_mode" | $SED "$delay_single_quote_subst"`' +enable_fast_install='`$ECHO "$enable_fast_install" | $SED "$delay_single_quote_subst"`' +shared_archive_member_spec='`$ECHO "$shared_archive_member_spec" | $SED "$delay_single_quote_subst"`' +SHELL='`$ECHO "$SHELL" | $SED "$delay_single_quote_subst"`' +ECHO='`$ECHO "$ECHO" | $SED "$delay_single_quote_subst"`' +PATH_SEPARATOR='`$ECHO "$PATH_SEPARATOR" | $SED "$delay_single_quote_subst"`' +host_alias='`$ECHO "$host_alias" | $SED "$delay_single_quote_subst"`' +host='`$ECHO "$host" | $SED "$delay_single_quote_subst"`' +host_os='`$ECHO "$host_os" | $SED "$delay_single_quote_subst"`' +build_alias='`$ECHO "$build_alias" | $SED "$delay_single_quote_subst"`' +build='`$ECHO "$build" | $SED "$delay_single_quote_subst"`' +build_os='`$ECHO "$build_os" | $SED "$delay_single_quote_subst"`' +SED='`$ECHO "$SED" | $SED "$delay_single_quote_subst"`' +Xsed='`$ECHO "$Xsed" | $SED "$delay_single_quote_subst"`' +GREP='`$ECHO "$GREP" | $SED "$delay_single_quote_subst"`' +EGREP='`$ECHO "$EGREP" | $SED "$delay_single_quote_subst"`' +FGREP='`$ECHO "$FGREP" | $SED "$delay_single_quote_subst"`' +LD='`$ECHO "$LD" | $SED "$delay_single_quote_subst"`' +NM='`$ECHO "$NM" | $SED "$delay_single_quote_subst"`' +LN_S='`$ECHO "$LN_S" | $SED "$delay_single_quote_subst"`' +max_cmd_len='`$ECHO "$max_cmd_len" | $SED "$delay_single_quote_subst"`' +ac_objext='`$ECHO "$ac_objext" | $SED "$delay_single_quote_subst"`' +exeext='`$ECHO "$exeext" | $SED "$delay_single_quote_subst"`' +lt_unset='`$ECHO "$lt_unset" | $SED "$delay_single_quote_subst"`' +lt_SP2NL='`$ECHO "$lt_SP2NL" | $SED "$delay_single_quote_subst"`' +lt_NL2SP='`$ECHO "$lt_NL2SP" | $SED "$delay_single_quote_subst"`' +lt_cv_to_host_file_cmd='`$ECHO "$lt_cv_to_host_file_cmd" | $SED "$delay_single_quote_subst"`' +lt_cv_to_tool_file_cmd='`$ECHO "$lt_cv_to_tool_file_cmd" | $SED "$delay_single_quote_subst"`' +reload_flag='`$ECHO "$reload_flag" | $SED "$delay_single_quote_subst"`' +reload_cmds='`$ECHO "$reload_cmds" | $SED "$delay_single_quote_subst"`' +OBJDUMP='`$ECHO "$OBJDUMP" | $SED "$delay_single_quote_subst"`' +deplibs_check_method='`$ECHO "$deplibs_check_method" | $SED "$delay_single_quote_subst"`' +file_magic_cmd='`$ECHO "$file_magic_cmd" | $SED "$delay_single_quote_subst"`' +file_magic_glob='`$ECHO "$file_magic_glob" | $SED "$delay_single_quote_subst"`' +want_nocaseglob='`$ECHO "$want_nocaseglob" | $SED "$delay_single_quote_subst"`' +DLLTOOL='`$ECHO "$DLLTOOL" | $SED "$delay_single_quote_subst"`' +sharedlib_from_linklib_cmd='`$ECHO "$sharedlib_from_linklib_cmd" | $SED "$delay_single_quote_subst"`' +AR='`$ECHO "$AR" | $SED "$delay_single_quote_subst"`' +AR_FLAGS='`$ECHO "$AR_FLAGS" | $SED "$delay_single_quote_subst"`' +archiver_list_spec='`$ECHO "$archiver_list_spec" | $SED "$delay_single_quote_subst"`' +STRIP='`$ECHO "$STRIP" | $SED "$delay_single_quote_subst"`' +RANLIB='`$ECHO "$RANLIB" | $SED "$delay_single_quote_subst"`' +old_postinstall_cmds='`$ECHO "$old_postinstall_cmds" | $SED "$delay_single_quote_subst"`' +old_postuninstall_cmds='`$ECHO "$old_postuninstall_cmds" | $SED "$delay_single_quote_subst"`' +old_archive_cmds='`$ECHO "$old_archive_cmds" | $SED "$delay_single_quote_subst"`' +lock_old_archive_extraction='`$ECHO "$lock_old_archive_extraction" | $SED "$delay_single_quote_subst"`' +CC='`$ECHO "$CC" | $SED "$delay_single_quote_subst"`' +CFLAGS='`$ECHO "$CFLAGS" | $SED "$delay_single_quote_subst"`' +compiler='`$ECHO "$compiler" | $SED "$delay_single_quote_subst"`' +GCC='`$ECHO "$GCC" | $SED "$delay_single_quote_subst"`' +lt_cv_sys_global_symbol_pipe='`$ECHO "$lt_cv_sys_global_symbol_pipe" | $SED "$delay_single_quote_subst"`' +lt_cv_sys_global_symbol_to_cdecl='`$ECHO "$lt_cv_sys_global_symbol_to_cdecl" | $SED "$delay_single_quote_subst"`' +lt_cv_sys_global_symbol_to_import='`$ECHO "$lt_cv_sys_global_symbol_to_import" | $SED "$delay_single_quote_subst"`' +lt_cv_sys_global_symbol_to_c_name_address='`$ECHO "$lt_cv_sys_global_symbol_to_c_name_address" | $SED "$delay_single_quote_subst"`' +lt_cv_sys_global_symbol_to_c_name_address_lib_prefix='`$ECHO "$lt_cv_sys_global_symbol_to_c_name_address_lib_prefix" | $SED "$delay_single_quote_subst"`' +lt_cv_nm_interface='`$ECHO "$lt_cv_nm_interface" | $SED "$delay_single_quote_subst"`' +nm_file_list_spec='`$ECHO "$nm_file_list_spec" | $SED "$delay_single_quote_subst"`' +lt_sysroot='`$ECHO "$lt_sysroot" | $SED "$delay_single_quote_subst"`' +lt_cv_truncate_bin='`$ECHO "$lt_cv_truncate_bin" | $SED "$delay_single_quote_subst"`' +objdir='`$ECHO "$objdir" | $SED "$delay_single_quote_subst"`' +MAGIC_CMD='`$ECHO "$MAGIC_CMD" | $SED "$delay_single_quote_subst"`' +lt_prog_compiler_no_builtin_flag='`$ECHO "$lt_prog_compiler_no_builtin_flag" | $SED "$delay_single_quote_subst"`' +lt_prog_compiler_pic='`$ECHO "$lt_prog_compiler_pic" | $SED "$delay_single_quote_subst"`' +lt_prog_compiler_wl='`$ECHO "$lt_prog_compiler_wl" | $SED "$delay_single_quote_subst"`' +lt_prog_compiler_static='`$ECHO "$lt_prog_compiler_static" | $SED "$delay_single_quote_subst"`' +lt_cv_prog_compiler_c_o='`$ECHO "$lt_cv_prog_compiler_c_o" | $SED "$delay_single_quote_subst"`' +need_locks='`$ECHO "$need_locks" | $SED "$delay_single_quote_subst"`' +MANIFEST_TOOL='`$ECHO "$MANIFEST_TOOL" | $SED "$delay_single_quote_subst"`' +DSYMUTIL='`$ECHO "$DSYMUTIL" | $SED "$delay_single_quote_subst"`' +NMEDIT='`$ECHO "$NMEDIT" | $SED "$delay_single_quote_subst"`' +LIPO='`$ECHO "$LIPO" | $SED "$delay_single_quote_subst"`' +OTOOL='`$ECHO "$OTOOL" | $SED "$delay_single_quote_subst"`' +OTOOL64='`$ECHO "$OTOOL64" | $SED "$delay_single_quote_subst"`' +libext='`$ECHO "$libext" | $SED "$delay_single_quote_subst"`' +shrext_cmds='`$ECHO "$shrext_cmds" | $SED "$delay_single_quote_subst"`' +extract_expsyms_cmds='`$ECHO "$extract_expsyms_cmds" | $SED "$delay_single_quote_subst"`' +archive_cmds_need_lc='`$ECHO "$archive_cmds_need_lc" | $SED "$delay_single_quote_subst"`' +enable_shared_with_static_runtimes='`$ECHO "$enable_shared_with_static_runtimes" | $SED "$delay_single_quote_subst"`' +export_dynamic_flag_spec='`$ECHO "$export_dynamic_flag_spec" | $SED "$delay_single_quote_subst"`' +whole_archive_flag_spec='`$ECHO "$whole_archive_flag_spec" | $SED "$delay_single_quote_subst"`' +compiler_needs_object='`$ECHO "$compiler_needs_object" | $SED "$delay_single_quote_subst"`' +old_archive_from_new_cmds='`$ECHO "$old_archive_from_new_cmds" | $SED "$delay_single_quote_subst"`' +old_archive_from_expsyms_cmds='`$ECHO "$old_archive_from_expsyms_cmds" | $SED "$delay_single_quote_subst"`' +archive_cmds='`$ECHO "$archive_cmds" | $SED "$delay_single_quote_subst"`' +archive_expsym_cmds='`$ECHO "$archive_expsym_cmds" | $SED "$delay_single_quote_subst"`' +module_cmds='`$ECHO "$module_cmds" | $SED "$delay_single_quote_subst"`' +module_expsym_cmds='`$ECHO "$module_expsym_cmds" | $SED "$delay_single_quote_subst"`' +with_gnu_ld='`$ECHO "$with_gnu_ld" | $SED "$delay_single_quote_subst"`' +allow_undefined_flag='`$ECHO "$allow_undefined_flag" | $SED "$delay_single_quote_subst"`' +no_undefined_flag='`$ECHO "$no_undefined_flag" | $SED "$delay_single_quote_subst"`' +hardcode_libdir_flag_spec='`$ECHO "$hardcode_libdir_flag_spec" | $SED "$delay_single_quote_subst"`' +hardcode_libdir_separator='`$ECHO "$hardcode_libdir_separator" | $SED "$delay_single_quote_subst"`' +hardcode_direct='`$ECHO "$hardcode_direct" | $SED "$delay_single_quote_subst"`' +hardcode_direct_absolute='`$ECHO "$hardcode_direct_absolute" | $SED "$delay_single_quote_subst"`' +hardcode_minus_L='`$ECHO "$hardcode_minus_L" | $SED "$delay_single_quote_subst"`' +hardcode_shlibpath_var='`$ECHO "$hardcode_shlibpath_var" | $SED "$delay_single_quote_subst"`' +hardcode_automatic='`$ECHO "$hardcode_automatic" | $SED "$delay_single_quote_subst"`' +inherit_rpath='`$ECHO "$inherit_rpath" | $SED "$delay_single_quote_subst"`' +link_all_deplibs='`$ECHO "$link_all_deplibs" | $SED "$delay_single_quote_subst"`' +always_export_symbols='`$ECHO "$always_export_symbols" | $SED "$delay_single_quote_subst"`' +export_symbols_cmds='`$ECHO "$export_symbols_cmds" | $SED "$delay_single_quote_subst"`' +exclude_expsyms='`$ECHO "$exclude_expsyms" | $SED "$delay_single_quote_subst"`' +include_expsyms='`$ECHO "$include_expsyms" | $SED "$delay_single_quote_subst"`' +prelink_cmds='`$ECHO "$prelink_cmds" | $SED "$delay_single_quote_subst"`' +postlink_cmds='`$ECHO "$postlink_cmds" | $SED "$delay_single_quote_subst"`' +file_list_spec='`$ECHO "$file_list_spec" | $SED "$delay_single_quote_subst"`' +variables_saved_for_relink='`$ECHO "$variables_saved_for_relink" | $SED "$delay_single_quote_subst"`' +need_lib_prefix='`$ECHO "$need_lib_prefix" | $SED "$delay_single_quote_subst"`' +need_version='`$ECHO "$need_version" | $SED "$delay_single_quote_subst"`' +version_type='`$ECHO "$version_type" | $SED "$delay_single_quote_subst"`' +runpath_var='`$ECHO "$runpath_var" | $SED "$delay_single_quote_subst"`' +shlibpath_var='`$ECHO "$shlibpath_var" | $SED "$delay_single_quote_subst"`' +shlibpath_overrides_runpath='`$ECHO "$shlibpath_overrides_runpath" | $SED "$delay_single_quote_subst"`' +libname_spec='`$ECHO "$libname_spec" | $SED "$delay_single_quote_subst"`' +library_names_spec='`$ECHO "$library_names_spec" | $SED "$delay_single_quote_subst"`' +soname_spec='`$ECHO "$soname_spec" | $SED "$delay_single_quote_subst"`' +install_override_mode='`$ECHO "$install_override_mode" | $SED "$delay_single_quote_subst"`' +postinstall_cmds='`$ECHO "$postinstall_cmds" | $SED "$delay_single_quote_subst"`' +postuninstall_cmds='`$ECHO "$postuninstall_cmds" | $SED "$delay_single_quote_subst"`' +finish_cmds='`$ECHO "$finish_cmds" | $SED "$delay_single_quote_subst"`' +finish_eval='`$ECHO "$finish_eval" | $SED "$delay_single_quote_subst"`' +hardcode_into_libs='`$ECHO "$hardcode_into_libs" | $SED "$delay_single_quote_subst"`' +sys_lib_search_path_spec='`$ECHO "$sys_lib_search_path_spec" | $SED "$delay_single_quote_subst"`' +configure_time_dlsearch_path='`$ECHO "$configure_time_dlsearch_path" | $SED "$delay_single_quote_subst"`' +configure_time_lt_sys_library_path='`$ECHO "$configure_time_lt_sys_library_path" | $SED "$delay_single_quote_subst"`' +hardcode_action='`$ECHO "$hardcode_action" | $SED "$delay_single_quote_subst"`' +enable_dlopen='`$ECHO "$enable_dlopen" | $SED "$delay_single_quote_subst"`' +enable_dlopen_self='`$ECHO "$enable_dlopen_self" | $SED "$delay_single_quote_subst"`' +enable_dlopen_self_static='`$ECHO "$enable_dlopen_self_static" | $SED "$delay_single_quote_subst"`' +old_striplib='`$ECHO "$old_striplib" | $SED "$delay_single_quote_subst"`' +striplib='`$ECHO "$striplib" | $SED "$delay_single_quote_subst"`' +compiler_lib_search_dirs='`$ECHO "$compiler_lib_search_dirs" | $SED "$delay_single_quote_subst"`' +predep_objects='`$ECHO "$predep_objects" | $SED "$delay_single_quote_subst"`' +postdep_objects='`$ECHO "$postdep_objects" | $SED "$delay_single_quote_subst"`' +predeps='`$ECHO "$predeps" | $SED "$delay_single_quote_subst"`' +postdeps='`$ECHO "$postdeps" | $SED "$delay_single_quote_subst"`' +compiler_lib_search_path='`$ECHO "$compiler_lib_search_path" | $SED "$delay_single_quote_subst"`' +LD_CXX='`$ECHO "$LD_CXX" | $SED "$delay_single_quote_subst"`' +reload_flag_CXX='`$ECHO "$reload_flag_CXX" | $SED "$delay_single_quote_subst"`' +reload_cmds_CXX='`$ECHO "$reload_cmds_CXX" | $SED "$delay_single_quote_subst"`' +old_archive_cmds_CXX='`$ECHO "$old_archive_cmds_CXX" | $SED "$delay_single_quote_subst"`' +compiler_CXX='`$ECHO "$compiler_CXX" | $SED "$delay_single_quote_subst"`' +GCC_CXX='`$ECHO "$GCC_CXX" | $SED "$delay_single_quote_subst"`' +lt_prog_compiler_no_builtin_flag_CXX='`$ECHO "$lt_prog_compiler_no_builtin_flag_CXX" | $SED "$delay_single_quote_subst"`' +lt_prog_compiler_pic_CXX='`$ECHO "$lt_prog_compiler_pic_CXX" | $SED "$delay_single_quote_subst"`' +lt_prog_compiler_wl_CXX='`$ECHO "$lt_prog_compiler_wl_CXX" | $SED "$delay_single_quote_subst"`' +lt_prog_compiler_static_CXX='`$ECHO "$lt_prog_compiler_static_CXX" | $SED "$delay_single_quote_subst"`' +lt_cv_prog_compiler_c_o_CXX='`$ECHO "$lt_cv_prog_compiler_c_o_CXX" | $SED "$delay_single_quote_subst"`' +archive_cmds_need_lc_CXX='`$ECHO "$archive_cmds_need_lc_CXX" | $SED "$delay_single_quote_subst"`' +enable_shared_with_static_runtimes_CXX='`$ECHO "$enable_shared_with_static_runtimes_CXX" | $SED "$delay_single_quote_subst"`' +export_dynamic_flag_spec_CXX='`$ECHO "$export_dynamic_flag_spec_CXX" | $SED "$delay_single_quote_subst"`' +whole_archive_flag_spec_CXX='`$ECHO "$whole_archive_flag_spec_CXX" | $SED "$delay_single_quote_subst"`' +compiler_needs_object_CXX='`$ECHO "$compiler_needs_object_CXX" | $SED "$delay_single_quote_subst"`' +old_archive_from_new_cmds_CXX='`$ECHO "$old_archive_from_new_cmds_CXX" | $SED "$delay_single_quote_subst"`' +old_archive_from_expsyms_cmds_CXX='`$ECHO "$old_archive_from_expsyms_cmds_CXX" | $SED "$delay_single_quote_subst"`' +archive_cmds_CXX='`$ECHO "$archive_cmds_CXX" | $SED "$delay_single_quote_subst"`' +archive_expsym_cmds_CXX='`$ECHO "$archive_expsym_cmds_CXX" | $SED "$delay_single_quote_subst"`' +module_cmds_CXX='`$ECHO "$module_cmds_CXX" | $SED "$delay_single_quote_subst"`' +module_expsym_cmds_CXX='`$ECHO "$module_expsym_cmds_CXX" | $SED "$delay_single_quote_subst"`' +with_gnu_ld_CXX='`$ECHO "$with_gnu_ld_CXX" | $SED "$delay_single_quote_subst"`' +allow_undefined_flag_CXX='`$ECHO "$allow_undefined_flag_CXX" | $SED "$delay_single_quote_subst"`' +no_undefined_flag_CXX='`$ECHO "$no_undefined_flag_CXX" | $SED "$delay_single_quote_subst"`' +hardcode_libdir_flag_spec_CXX='`$ECHO "$hardcode_libdir_flag_spec_CXX" | $SED "$delay_single_quote_subst"`' +hardcode_libdir_separator_CXX='`$ECHO "$hardcode_libdir_separator_CXX" | $SED "$delay_single_quote_subst"`' +hardcode_direct_CXX='`$ECHO "$hardcode_direct_CXX" | $SED "$delay_single_quote_subst"`' +hardcode_direct_absolute_CXX='`$ECHO "$hardcode_direct_absolute_CXX" | $SED "$delay_single_quote_subst"`' +hardcode_minus_L_CXX='`$ECHO "$hardcode_minus_L_CXX" | $SED "$delay_single_quote_subst"`' +hardcode_shlibpath_var_CXX='`$ECHO "$hardcode_shlibpath_var_CXX" | $SED "$delay_single_quote_subst"`' +hardcode_automatic_CXX='`$ECHO "$hardcode_automatic_CXX" | $SED "$delay_single_quote_subst"`' +inherit_rpath_CXX='`$ECHO "$inherit_rpath_CXX" | $SED "$delay_single_quote_subst"`' +link_all_deplibs_CXX='`$ECHO "$link_all_deplibs_CXX" | $SED "$delay_single_quote_subst"`' +always_export_symbols_CXX='`$ECHO "$always_export_symbols_CXX" | $SED "$delay_single_quote_subst"`' +export_symbols_cmds_CXX='`$ECHO "$export_symbols_cmds_CXX" | $SED "$delay_single_quote_subst"`' +exclude_expsyms_CXX='`$ECHO "$exclude_expsyms_CXX" | $SED "$delay_single_quote_subst"`' +include_expsyms_CXX='`$ECHO "$include_expsyms_CXX" | $SED "$delay_single_quote_subst"`' +prelink_cmds_CXX='`$ECHO "$prelink_cmds_CXX" | $SED "$delay_single_quote_subst"`' +postlink_cmds_CXX='`$ECHO "$postlink_cmds_CXX" | $SED "$delay_single_quote_subst"`' +file_list_spec_CXX='`$ECHO "$file_list_spec_CXX" | $SED "$delay_single_quote_subst"`' +hardcode_action_CXX='`$ECHO "$hardcode_action_CXX" | $SED "$delay_single_quote_subst"`' +compiler_lib_search_dirs_CXX='`$ECHO "$compiler_lib_search_dirs_CXX" | $SED "$delay_single_quote_subst"`' +predep_objects_CXX='`$ECHO "$predep_objects_CXX" | $SED "$delay_single_quote_subst"`' +postdep_objects_CXX='`$ECHO "$postdep_objects_CXX" | $SED "$delay_single_quote_subst"`' +predeps_CXX='`$ECHO "$predeps_CXX" | $SED "$delay_single_quote_subst"`' +postdeps_CXX='`$ECHO "$postdeps_CXX" | $SED "$delay_single_quote_subst"`' +compiler_lib_search_path_CXX='`$ECHO "$compiler_lib_search_path_CXX" | $SED "$delay_single_quote_subst"`' + +LTCC='$LTCC' +LTCFLAGS='$LTCFLAGS' +compiler='$compiler_DEFAULT' + +# A function that is used when there is no print builtin or printf. +func_fallback_echo () +{ + eval 'cat <<_LTECHO_EOF +\$1 +_LTECHO_EOF' +} + +# Quote evaled strings. +for var in SHELL \ +ECHO \ +PATH_SEPARATOR \ +SED \ +GREP \ +EGREP \ +FGREP \ +LD \ +NM \ +LN_S \ +lt_SP2NL \ +lt_NL2SP \ +reload_flag \ +OBJDUMP \ +deplibs_check_method \ +file_magic_cmd \ +file_magic_glob \ +want_nocaseglob \ +DLLTOOL \ +sharedlib_from_linklib_cmd \ +AR \ +AR_FLAGS \ +archiver_list_spec \ +STRIP \ +RANLIB \ +CC \ +CFLAGS \ +compiler \ +lt_cv_sys_global_symbol_pipe \ +lt_cv_sys_global_symbol_to_cdecl \ +lt_cv_sys_global_symbol_to_import \ +lt_cv_sys_global_symbol_to_c_name_address \ +lt_cv_sys_global_symbol_to_c_name_address_lib_prefix \ +lt_cv_nm_interface \ +nm_file_list_spec \ +lt_cv_truncate_bin \ +lt_prog_compiler_no_builtin_flag \ +lt_prog_compiler_pic \ +lt_prog_compiler_wl \ +lt_prog_compiler_static \ +lt_cv_prog_compiler_c_o \ +need_locks \ +MANIFEST_TOOL \ +DSYMUTIL \ +NMEDIT \ +LIPO \ +OTOOL \ +OTOOL64 \ +shrext_cmds \ +export_dynamic_flag_spec \ +whole_archive_flag_spec \ +compiler_needs_object \ +with_gnu_ld \ +allow_undefined_flag \ +no_undefined_flag \ +hardcode_libdir_flag_spec \ +hardcode_libdir_separator \ +exclude_expsyms \ +include_expsyms \ +file_list_spec \ +variables_saved_for_relink \ +libname_spec \ +library_names_spec \ +soname_spec \ +install_override_mode \ +finish_eval \ +old_striplib \ +striplib \ +compiler_lib_search_dirs \ +predep_objects \ +postdep_objects \ +predeps \ +postdeps \ +compiler_lib_search_path \ +LD_CXX \ +reload_flag_CXX \ +compiler_CXX \ +lt_prog_compiler_no_builtin_flag_CXX \ +lt_prog_compiler_pic_CXX \ +lt_prog_compiler_wl_CXX \ +lt_prog_compiler_static_CXX \ +lt_cv_prog_compiler_c_o_CXX \ +export_dynamic_flag_spec_CXX \ +whole_archive_flag_spec_CXX \ +compiler_needs_object_CXX \ +with_gnu_ld_CXX \ +allow_undefined_flag_CXX \ +no_undefined_flag_CXX \ +hardcode_libdir_flag_spec_CXX \ +hardcode_libdir_separator_CXX \ +exclude_expsyms_CXX \ +include_expsyms_CXX \ +file_list_spec_CXX \ +compiler_lib_search_dirs_CXX \ +predep_objects_CXX \ +postdep_objects_CXX \ +predeps_CXX \ +postdeps_CXX \ +compiler_lib_search_path_CXX; do + case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in + *[\\\\\\\`\\"\\\$]*) + eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED \\"\\\$sed_quote_subst\\"\\\`\\\\\\"" ## exclude from sc_prohibit_nested_quotes + ;; + *) + eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\"" + ;; + esac +done + +# Double-quote double-evaled strings. +for var in reload_cmds \ +old_postinstall_cmds \ +old_postuninstall_cmds \ +old_archive_cmds \ +extract_expsyms_cmds \ +old_archive_from_new_cmds \ +old_archive_from_expsyms_cmds \ +archive_cmds \ +archive_expsym_cmds \ +module_cmds \ +module_expsym_cmds \ +export_symbols_cmds \ +prelink_cmds \ +postlink_cmds \ +postinstall_cmds \ +postuninstall_cmds \ +finish_cmds \ +sys_lib_search_path_spec \ +configure_time_dlsearch_path \ +configure_time_lt_sys_library_path \ +reload_cmds_CXX \ +old_archive_cmds_CXX \ +old_archive_from_new_cmds_CXX \ +old_archive_from_expsyms_cmds_CXX \ +archive_cmds_CXX \ +archive_expsym_cmds_CXX \ +module_cmds_CXX \ +module_expsym_cmds_CXX \ +export_symbols_cmds_CXX \ +prelink_cmds_CXX \ +postlink_cmds_CXX; do + case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in + *[\\\\\\\`\\"\\\$]*) + eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED -e \\"\\\$double_quote_subst\\" -e \\"\\\$sed_quote_subst\\" -e \\"\\\$delay_variable_subst\\"\\\`\\\\\\"" ## exclude from sc_prohibit_nested_quotes + ;; + *) + eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\"" + ;; + esac +done + +ac_aux_dir='$ac_aux_dir' + +# See if we are running on zsh, and set the options that allow our +# commands through without removal of \ escapes INIT. +if test -n "\${ZSH_VERSION+set}"; then + setopt NO_GLOB_SUBST +fi + + + PACKAGE='$PACKAGE' + VERSION='$VERSION' + RM='$RM' + ofile='$ofile' + + + + + + +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 + +# Handling of arguments. +for ac_config_target in $ac_config_targets +do + case $ac_config_target in + "depfiles") CONFIG_COMMANDS="$CONFIG_COMMANDS depfiles" ;; + "libtool") CONFIG_COMMANDS="$CONFIG_COMMANDS libtool" ;; + "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; + "src/Makefile") CONFIG_FILES="$CONFIG_FILES src/Makefile" ;; + "include/libtorrent/Makefile") CONFIG_FILES="$CONFIG_FILES include/libtorrent/Makefile" ;; + "examples/Makefile") CONFIG_FILES="$CONFIG_FILES examples/Makefile" ;; + "test/Makefile") CONFIG_FILES="$CONFIG_FILES test/Makefile" ;; + "tools/Makefile") CONFIG_FILES="$CONFIG_FILES tools/Makefile" ;; + "bindings/Makefile") CONFIG_FILES="$CONFIG_FILES bindings/Makefile" ;; + "bindings/python/Makefile") CONFIG_FILES="$CONFIG_FILES bindings/python/Makefile" ;; + "bindings/python/link_flags") CONFIG_FILES="$CONFIG_FILES bindings/python/link_flags" ;; + "bindings/python/compile_flags") CONFIG_FILES="$CONFIG_FILES bindings/python/compile_flags" ;; + "libtorrent-rasterbar.pc") CONFIG_FILES="$CONFIG_FILES libtorrent-rasterbar.pc" ;; + "libtorrent-rasterbar-cmake.pc") CONFIG_FILES="$CONFIG_FILES libtorrent-rasterbar-cmake.pc" ;; + + *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; + esac +done + + +# If the user did not use the arguments to specify the items to instantiate, +# then the envvar interface is used. Set only those that are not. +# We use the long form for the default assignment because of an extremely +# bizarre bug on SunOS 4.1.3. +if $ac_need_defaults; then + test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files + test "${CONFIG_COMMANDS+set}" = set || CONFIG_COMMANDS=$config_commands +fi + +# Have a temporary directory for convenience. Make it in the build tree +# simply because there is no reason against having it here, and in addition, +# creating and moving files from /tmp can sometimes cause problems. +# Hook for its removal unless debugging. +# Note that there is a small window in which the directory will not be cleaned: +# after its creation but before its name has been assigned to `$tmp'. +$debug || +{ + tmp= ac_tmp= + trap 'exit_status=$? + : "${ac_tmp:=$tmp}" + { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status +' 0 + trap 'as_fn_exit 1' 1 2 13 15 +} +# Create a (secure) tmp directory for tmp files. + +{ + tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` && + test -d "$tmp" +} || +{ + tmp=./conf$$-$RANDOM + (umask 077 && mkdir "$tmp") +} || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5 +ac_tmp=$tmp + +# Set up the scripts for CONFIG_FILES section. +# No need to generate them if there are no CONFIG_FILES. +# This happens for instance with `./config.status config.h'. +if test -n "$CONFIG_FILES"; then + + +ac_cr=`echo X | tr X '\015'` +# On cygwin, bash can eat \r inside `` if the user requested igncr. +# But we know of no other shell where ac_cr would be empty at this +# point, so we can use a bashism as a fallback. +if test "x$ac_cr" = x; then + eval ac_cr=\$\'\\r\' +fi +ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' /dev/null` +if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then + ac_cs_awk_cr='\\r' +else + ac_cs_awk_cr=$ac_cr +fi + +echo 'BEGIN {' >"$ac_tmp/subs1.awk" && +_ACEOF + + +{ + echo "cat >conf$$subs.awk <<_ACEOF" && + echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' && + echo "_ACEOF" +} >conf$$subs.sh || + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 +ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'` +ac_delim='%!_!# ' +for ac_last_try in false false false false false :; do + . ./conf$$subs.sh || + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 + + ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X` + if test $ac_delim_n = $ac_delim_num; then + break + elif $ac_last_try; then + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 + else + ac_delim="$ac_delim!$ac_delim _$ac_delim!! " + fi +done +rm -f conf$$subs.sh + +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK && +_ACEOF +sed -n ' +h +s/^/S["/; s/!.*/"]=/ +p +g +s/^[^!]*!// +:repl +t repl +s/'"$ac_delim"'$// +t delim +:nl +h +s/\(.\{148\}\)..*/\1/ +t more1 +s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/ +p +n +b repl +:more1 +s/["\\]/\\&/g; s/^/"/; s/$/"\\/ +p +g +s/.\{148\}// +t nl +:delim +h +s/\(.\{148\}\)..*/\1/ +t more2 +s/["\\]/\\&/g; s/^/"/; s/$/"/ +p +b +:more2 +s/["\\]/\\&/g; s/^/"/; s/$/"\\/ +p +g +s/.\{148\}// +t delim +' >$CONFIG_STATUS || ac_write_fail=1 +rm -f conf$$subs.awk +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +_ACAWK +cat >>"\$ac_tmp/subs1.awk" <<_ACAWK && + for (key in S) S_is_set[key] = 1 + FS = "" + +} +{ + line = $ 0 + nfields = split(line, field, "@") + substed = 0 + len = length(field[1]) + for (i = 2; i < nfields; i++) { + key = field[i] + keylen = length(key) + if (S_is_set[key]) { + value = S[key] + line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3) + len += length(value) + length(field[++i]) + substed = 1 + } else + len += 1 + keylen + } + + print line +} + +_ACAWK +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then + sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g" +else + cat +fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \ + || as_fn_error $? "could not setup config files machinery" "$LINENO" 5 +_ACEOF + +# VPATH may cause trouble with some makes, so we remove sole $(srcdir), +# ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and +# trailing colons and then remove the whole line if VPATH becomes empty +# (actually we leave an empty line to preserve line numbers). +if test "x$srcdir" = x.; then + ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{ +h +s/// +s/^/:/ +s/[ ]*$/:/ +s/:\$(srcdir):/:/g +s/:\${srcdir}:/:/g +s/:@srcdir@:/:/g +s/^:*// +s/:*$// +x +s/\(=[ ]*\).*/\1/ +G +s/\n// +s/^[^=]*=[ ]*$// +}' +fi + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +fi # test -n "$CONFIG_FILES" + + +eval set X " :F $CONFIG_FILES :C $CONFIG_COMMANDS" +shift +for ac_tag +do + case $ac_tag in + :[FHLC]) ac_mode=$ac_tag; continue;; + esac + case $ac_mode$ac_tag in + :[FHL]*:*);; + :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;; + :[FH]-) ac_tag=-:-;; + :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; + esac + ac_save_IFS=$IFS + IFS=: + set x $ac_tag + IFS=$ac_save_IFS + shift + ac_file=$1 + shift + + case $ac_mode in + :L) ac_source=$1;; + :[FH]) + ac_file_inputs= + for ac_f + do + case $ac_f in + -) ac_f="$ac_tmp/stdin";; + *) # Look for the file first in the build tree, then in the source tree + # (if the path is not absolute). The absolute path cannot be DOS-style, + # because $ac_f cannot contain `:'. + test -f "$ac_f" || + case $ac_f in + [\\/$]*) false;; + *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; + esac || + as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;; + esac + case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac + as_fn_append ac_file_inputs " '$ac_f'" + done + + # Let's still pretend it is `configure' which instantiates (i.e., don't + # use $as_me), people would be surprised to read: + # /* config.h. Generated by config.status. */ + configure_input='Generated from '` + $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' + `' by configure.' + if test x"$ac_file" != x-; then + configure_input="$ac_file. $configure_input" + { $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 +$as_echo "$as_me: creating $ac_file" >&6;} + fi + # Neutralize special characters interpreted by sed in replacement strings. + case $configure_input in #( + *\&* | *\|* | *\\* ) + ac_sed_conf_input=`$as_echo "$configure_input" | + sed 's/[\\\\&|]/\\\\&/g'`;; #( + *) ac_sed_conf_input=$configure_input;; + esac + + case $ac_tag in + *:-:* | *:-) cat >"$ac_tmp/stdin" \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; + esac + ;; + esac + + ac_dir=`$as_dirname -- "$ac_file" || +$as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$ac_file" : 'X\(//\)[^/]' \| \ + X"$ac_file" : 'X\(//\)$' \| \ + X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$ac_file" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + as_dir="$ac_dir"; as_fn_mkdir_p + ac_builddir=. + +case "$ac_dir" in +.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; +*) + ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` + # A ".." for each directory in $ac_dir_suffix. + ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + case $ac_top_builddir_sub in + "") ac_top_builddir_sub=. ac_top_build_prefix= ;; + *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; + esac ;; +esac +ac_abs_top_builddir=$ac_pwd +ac_abs_builddir=$ac_pwd$ac_dir_suffix +# for backward compatibility: +ac_top_builddir=$ac_top_build_prefix + +case $srcdir in + .) # We are building in place. + ac_srcdir=. + ac_top_srcdir=$ac_top_builddir_sub + ac_abs_top_srcdir=$ac_pwd ;; + [\\/]* | ?:[\\/]* ) # Absolute name. + ac_srcdir=$srcdir$ac_dir_suffix; + ac_top_srcdir=$srcdir + ac_abs_top_srcdir=$srcdir ;; + *) # Relative name. + ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix + ac_top_srcdir=$ac_top_build_prefix$srcdir + ac_abs_top_srcdir=$ac_pwd/$srcdir ;; +esac +ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix + + + case $ac_mode in + :F) + # + # CONFIG_FILE + # + + case $INSTALL in + [\\/$]* | ?:[\\/]* ) ac_INSTALL=$INSTALL ;; + *) ac_INSTALL=$ac_top_build_prefix$INSTALL ;; + esac + ac_MKDIR_P=$MKDIR_P + case $MKDIR_P in + [\\/$]* | ?:[\\/]* ) ;; + */*) ac_MKDIR_P=$ac_top_build_prefix$MKDIR_P ;; + esac +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# If the template does not know about datarootdir, expand it. +# FIXME: This hack should be removed a few years after 2.60. +ac_datarootdir_hack=; ac_datarootdir_seen= +ac_sed_dataroot=' +/datarootdir/ { + p + q +} +/@datadir@/p +/@docdir@/p +/@infodir@/p +/@localedir@/p +/@mandir@/p' +case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in +*datarootdir*) ac_datarootdir_seen=yes;; +*@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 +$as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 + ac_datarootdir_hack=' + s&@datadir@&$datadir&g + s&@docdir@&$docdir&g + s&@infodir@&$infodir&g + s&@localedir@&$localedir&g + s&@mandir@&$mandir&g + s&\\\${datarootdir}&$datarootdir&g' ;; +esac +_ACEOF + +# Neutralize VPATH when `$srcdir' = `.'. +# Shell code in configure.ac might set extrasub. +# FIXME: do we really want to maintain this feature? +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +ac_sed_extra="$ac_vpsub +$extrasub +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +:t +/@[a-zA-Z_][a-zA-Z_0-9]*@/!b +s|@configure_input@|$ac_sed_conf_input|;t t +s&@top_builddir@&$ac_top_builddir_sub&;t t +s&@top_build_prefix@&$ac_top_build_prefix&;t t +s&@srcdir@&$ac_srcdir&;t t +s&@abs_srcdir@&$ac_abs_srcdir&;t t +s&@top_srcdir@&$ac_top_srcdir&;t t +s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t +s&@builddir@&$ac_builddir&;t t +s&@abs_builddir@&$ac_abs_builddir&;t t +s&@abs_top_builddir@&$ac_abs_top_builddir&;t t +s&@INSTALL@&$ac_INSTALL&;t t +s&@MKDIR_P@&$ac_MKDIR_P&;t t +$ac_datarootdir_hack +" +eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \ + >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + +test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && + { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } && + { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \ + "$ac_tmp/out"`; test -z "$ac_out"; } && + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir' +which seems to be undefined. Please make sure it is defined" >&5 +$as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' +which seems to be undefined. Please make sure it is defined" >&2;} + + rm -f "$ac_tmp/stdin" + case $ac_file in + -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";; + *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";; + esac \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + ;; + + + :C) { $as_echo "$as_me:${as_lineno-$LINENO}: executing $ac_file commands" >&5 +$as_echo "$as_me: executing $ac_file commands" >&6;} + ;; + esac + + + case $ac_file$ac_mode in + "depfiles":C) test x"$AMDEP_TRUE" != x"" || { + # Older Autoconf quotes --file arguments for eval, but not when files + # are listed without --file. Let's play safe and only enable the eval + # if we detect the quoting. + case $CONFIG_FILES in + *\'*) eval set x "$CONFIG_FILES" ;; + *) set x $CONFIG_FILES ;; + esac + shift + for mf + do + # Strip MF so we end up with the name of the file. + mf=`echo "$mf" | sed -e 's/:.*$//'` + # Check whether this is an Automake generated Makefile or not. + # We used to match only the files named 'Makefile.in', but + # some people rename them; so instead we look at the file content. + # Grep'ing the first line is not enough: some people post-process + # each Makefile.in and add a new line on top of each file to say so. + # Grep'ing the whole file is not good either: AIX grep has a line + # limit of 2048, but all sed's we know have understand at least 4000. + if sed -n 's,^#.*generated by automake.*,X,p' "$mf" | grep X >/dev/null 2>&1; then + dirpart=`$as_dirname -- "$mf" || +$as_expr X"$mf" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$mf" : 'X\(//\)[^/]' \| \ + X"$mf" : 'X\(//\)$' \| \ + X"$mf" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$mf" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + else + continue + fi + # Extract the definition of DEPDIR, am__include, and am__quote + # from the Makefile without running 'make'. + DEPDIR=`sed -n 's/^DEPDIR = //p' < "$mf"` + test -z "$DEPDIR" && continue + am__include=`sed -n 's/^am__include = //p' < "$mf"` + test -z "$am__include" && continue + am__quote=`sed -n 's/^am__quote = //p' < "$mf"` + # Find all dependency output files, they are included files with + # $(DEPDIR) in their names. We invoke sed twice because it is the + # simplest approach to changing $(DEPDIR) to its actual value in the + # expansion. + for file in `sed -n " + s/^$am__include $am__quote\(.*(DEPDIR).*\)$am__quote"'$/\1/p' <"$mf" | \ + sed -e 's/\$(DEPDIR)/'"$DEPDIR"'/g'`; do + # Make sure the directory exists. + test -f "$dirpart/$file" && continue + fdir=`$as_dirname -- "$file" || +$as_expr X"$file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$file" : 'X\(//\)[^/]' \| \ + X"$file" : 'X\(//\)$' \| \ + X"$file" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$file" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + as_dir=$dirpart/$fdir; as_fn_mkdir_p + # echo "creating $dirpart/$file" + echo '# dummy' > "$dirpart/$file" + done + done +} + ;; + "libtool":C) + + # See if we are running on zsh, and set the options that allow our + # commands through without removal of \ escapes. + if test -n "${ZSH_VERSION+set}"; then + setopt NO_GLOB_SUBST + fi + + cfgfile=${ofile}T + trap "$RM \"$cfgfile\"; exit 1" 1 2 15 + $RM "$cfgfile" + + cat <<_LT_EOF >> "$cfgfile" +#! $SHELL +# Generated automatically by $as_me ($PACKAGE) $VERSION +# Libtool was configured on host `(hostname || uname -n) 2>/dev/null | sed 1q`: +# NOTE: Changes made to this file will be lost: look at ltmain.sh. + +# Provide generalized library-building support services. +# Written by Gordon Matzigkeit, 1996 + +# Copyright (C) 2014 Free Software Foundation, Inc. +# This is free software; see the source for copying conditions. There is NO +# warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +# GNU Libtool is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of of the License, or +# (at your option) any later version. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program or library that is built +# using GNU Libtool, you may include this file under the same +# distribution terms that you use for the rest of that program. +# +# GNU Libtool is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +# The names of the tagged configurations supported by this script. +available_tags='CXX ' + +# Configured defaults for sys_lib_dlsearch_path munging. +: \${LT_SYS_LIBRARY_PATH="$configure_time_lt_sys_library_path"} + +# ### BEGIN LIBTOOL CONFIG + +# Which release of libtool.m4 was used? +macro_version=$macro_version +macro_revision=$macro_revision + +# Whether or not to build shared libraries. +build_libtool_libs=$enable_shared + +# Whether or not to build static libraries. +build_old_libs=$enable_static + +# What type of objects to build. +pic_mode=$pic_mode + +# Whether or not to optimize for fast installation. +fast_install=$enable_fast_install + +# Shared archive member basename,for filename based shared library versioning on AIX. +shared_archive_member_spec=$shared_archive_member_spec + +# Shell to use when invoking shell scripts. +SHELL=$lt_SHELL + +# An echo program that protects backslashes. +ECHO=$lt_ECHO + +# The PATH separator for the build system. +PATH_SEPARATOR=$lt_PATH_SEPARATOR + +# The host system. +host_alias=$host_alias +host=$host +host_os=$host_os + +# The build system. +build_alias=$build_alias +build=$build +build_os=$build_os + +# A sed program that does not truncate output. +SED=$lt_SED + +# Sed that helps us avoid accidentally triggering echo(1) options like -n. +Xsed="\$SED -e 1s/^X//" + +# A grep program that handles long lines. +GREP=$lt_GREP + +# An ERE matcher. +EGREP=$lt_EGREP + +# A literal string matcher. +FGREP=$lt_FGREP + +# A BSD- or MS-compatible name lister. +NM=$lt_NM + +# Whether we need soft or hard links. +LN_S=$lt_LN_S + +# What is the maximum length of a command? +max_cmd_len=$max_cmd_len + +# Object file suffix (normally "o"). +objext=$ac_objext + +# Executable file suffix (normally ""). +exeext=$exeext + +# whether the shell understands "unset". +lt_unset=$lt_unset + +# turn spaces into newlines. +SP2NL=$lt_lt_SP2NL + +# turn newlines into spaces. +NL2SP=$lt_lt_NL2SP + +# convert \$build file names to \$host format. +to_host_file_cmd=$lt_cv_to_host_file_cmd + +# convert \$build files to toolchain format. +to_tool_file_cmd=$lt_cv_to_tool_file_cmd + +# An object symbol dumper. +OBJDUMP=$lt_OBJDUMP + +# Method to check whether dependent libraries are shared objects. +deplibs_check_method=$lt_deplibs_check_method + +# Command to use when deplibs_check_method = "file_magic". +file_magic_cmd=$lt_file_magic_cmd + +# How to find potential files when deplibs_check_method = "file_magic". +file_magic_glob=$lt_file_magic_glob + +# Find potential files using nocaseglob when deplibs_check_method = "file_magic". +want_nocaseglob=$lt_want_nocaseglob + +# DLL creation program. +DLLTOOL=$lt_DLLTOOL + +# Command to associate shared and link libraries. +sharedlib_from_linklib_cmd=$lt_sharedlib_from_linklib_cmd + +# The archiver. +AR=$lt_AR + +# Flags to create an archive. +AR_FLAGS=$lt_AR_FLAGS + +# How to feed a file listing to the archiver. +archiver_list_spec=$lt_archiver_list_spec + +# A symbol stripping program. +STRIP=$lt_STRIP + +# Commands used to install an old-style archive. +RANLIB=$lt_RANLIB +old_postinstall_cmds=$lt_old_postinstall_cmds +old_postuninstall_cmds=$lt_old_postuninstall_cmds + +# Whether to use a lock for old archive extraction. +lock_old_archive_extraction=$lock_old_archive_extraction + +# A C compiler. +LTCC=$lt_CC + +# LTCC compiler flags. +LTCFLAGS=$lt_CFLAGS + +# Take the output of nm and produce a listing of raw symbols and C names. +global_symbol_pipe=$lt_lt_cv_sys_global_symbol_pipe + +# Transform the output of nm in a proper C declaration. +global_symbol_to_cdecl=$lt_lt_cv_sys_global_symbol_to_cdecl + +# Transform the output of nm into a list of symbols to manually relocate. +global_symbol_to_import=$lt_lt_cv_sys_global_symbol_to_import + +# Transform the output of nm in a C name address pair. +global_symbol_to_c_name_address=$lt_lt_cv_sys_global_symbol_to_c_name_address + +# Transform the output of nm in a C name address pair when lib prefix is needed. +global_symbol_to_c_name_address_lib_prefix=$lt_lt_cv_sys_global_symbol_to_c_name_address_lib_prefix + +# The name lister interface. +nm_interface=$lt_lt_cv_nm_interface + +# Specify filename containing input files for \$NM. +nm_file_list_spec=$lt_nm_file_list_spec + +# The root where to search for dependent libraries,and where our libraries should be installed. +lt_sysroot=$lt_sysroot + +# Command to truncate a binary pipe. +lt_truncate_bin=$lt_lt_cv_truncate_bin + +# The name of the directory that contains temporary libtool files. +objdir=$objdir + +# Used to examine libraries when file_magic_cmd begins with "file". +MAGIC_CMD=$MAGIC_CMD + +# Must we lock files when doing compilation? +need_locks=$lt_need_locks + +# Manifest tool. +MANIFEST_TOOL=$lt_MANIFEST_TOOL + +# Tool to manipulate archived DWARF debug symbol files on Mac OS X. +DSYMUTIL=$lt_DSYMUTIL + +# Tool to change global to local symbols on Mac OS X. +NMEDIT=$lt_NMEDIT + +# Tool to manipulate fat objects and archives on Mac OS X. +LIPO=$lt_LIPO + +# ldd/readelf like tool for Mach-O binaries on Mac OS X. +OTOOL=$lt_OTOOL + +# ldd/readelf like tool for 64 bit Mach-O binaries on Mac OS X 10.4. +OTOOL64=$lt_OTOOL64 + +# Old archive suffix (normally "a"). +libext=$libext + +# Shared library suffix (normally ".so"). +shrext_cmds=$lt_shrext_cmds + +# The commands to extract the exported symbol list from a shared archive. +extract_expsyms_cmds=$lt_extract_expsyms_cmds + +# Variables whose values should be saved in libtool wrapper scripts and +# restored at link time. +variables_saved_for_relink=$lt_variables_saved_for_relink + +# Do we need the "lib" prefix for modules? +need_lib_prefix=$need_lib_prefix + +# Do we need a version for libraries? +need_version=$need_version + +# Library versioning type. +version_type=$version_type + +# Shared library runtime path variable. +runpath_var=$runpath_var + +# Shared library path variable. +shlibpath_var=$shlibpath_var + +# Is shlibpath searched before the hard-coded library search path? +shlibpath_overrides_runpath=$shlibpath_overrides_runpath + +# Format of library name prefix. +libname_spec=$lt_libname_spec + +# List of archive names. First name is the real one, the rest are links. +# The last name is the one that the linker finds with -lNAME +library_names_spec=$lt_library_names_spec + +# The coded name of the library, if different from the real name. +soname_spec=$lt_soname_spec + +# Permission mode override for installation of shared libraries. +install_override_mode=$lt_install_override_mode + +# Command to use after installation of a shared archive. +postinstall_cmds=$lt_postinstall_cmds + +# Command to use after uninstallation of a shared archive. +postuninstall_cmds=$lt_postuninstall_cmds + +# Commands used to finish a libtool library installation in a directory. +finish_cmds=$lt_finish_cmds + +# As "finish_cmds", except a single script fragment to be evaled but +# not shown. +finish_eval=$lt_finish_eval + +# Whether we should hardcode library paths into libraries. +hardcode_into_libs=$hardcode_into_libs + +# Compile-time system search path for libraries. +sys_lib_search_path_spec=$lt_sys_lib_search_path_spec + +# Detected run-time system search path for libraries. +sys_lib_dlsearch_path_spec=$lt_configure_time_dlsearch_path + +# Explicit LT_SYS_LIBRARY_PATH set during ./configure time. +configure_time_lt_sys_library_path=$lt_configure_time_lt_sys_library_path + +# Whether dlopen is supported. +dlopen_support=$enable_dlopen + +# Whether dlopen of programs is supported. +dlopen_self=$enable_dlopen_self + +# Whether dlopen of statically linked programs is supported. +dlopen_self_static=$enable_dlopen_self_static + +# Commands to strip libraries. +old_striplib=$lt_old_striplib +striplib=$lt_striplib + + +# The linker used to build libraries. +LD=$lt_LD + +# How to create reloadable object files. +reload_flag=$lt_reload_flag +reload_cmds=$lt_reload_cmds + +# Commands used to build an old-style archive. +old_archive_cmds=$lt_old_archive_cmds + +# A language specific compiler. +CC=$lt_compiler + +# Is the compiler the GNU compiler? +with_gcc=$GCC + +# Compiler flag to turn off builtin functions. +no_builtin_flag=$lt_lt_prog_compiler_no_builtin_flag + +# Additional compiler flags for building library objects. +pic_flag=$lt_lt_prog_compiler_pic + +# How to pass a linker flag through the compiler. +wl=$lt_lt_prog_compiler_wl + +# Compiler flag to prevent dynamic linking. +link_static_flag=$lt_lt_prog_compiler_static + +# Does compiler simultaneously support -c and -o options? +compiler_c_o=$lt_lt_cv_prog_compiler_c_o + +# Whether or not to add -lc for building shared libraries. +build_libtool_need_lc=$archive_cmds_need_lc + +# Whether or not to disallow shared libs when runtime libs are static. +allow_libtool_libs_with_static_runtimes=$enable_shared_with_static_runtimes + +# Compiler flag to allow reflexive dlopens. +export_dynamic_flag_spec=$lt_export_dynamic_flag_spec + +# Compiler flag to generate shared objects directly from archives. +whole_archive_flag_spec=$lt_whole_archive_flag_spec + +# Whether the compiler copes with passing no objects directly. +compiler_needs_object=$lt_compiler_needs_object + +# Create an old-style archive from a shared archive. +old_archive_from_new_cmds=$lt_old_archive_from_new_cmds + +# Create a temporary old-style archive to link instead of a shared archive. +old_archive_from_expsyms_cmds=$lt_old_archive_from_expsyms_cmds + +# Commands used to build a shared archive. +archive_cmds=$lt_archive_cmds +archive_expsym_cmds=$lt_archive_expsym_cmds + +# Commands used to build a loadable module if different from building +# a shared archive. +module_cmds=$lt_module_cmds +module_expsym_cmds=$lt_module_expsym_cmds + +# Whether we are building with GNU ld or not. +with_gnu_ld=$lt_with_gnu_ld + +# Flag that allows shared libraries with undefined symbols to be built. +allow_undefined_flag=$lt_allow_undefined_flag + +# Flag that enforces no undefined symbols. +no_undefined_flag=$lt_no_undefined_flag + +# Flag to hardcode \$libdir into a binary during linking. +# This must work even if \$libdir does not exist +hardcode_libdir_flag_spec=$lt_hardcode_libdir_flag_spec + +# Whether we need a single "-rpath" flag with a separated argument. +hardcode_libdir_separator=$lt_hardcode_libdir_separator + +# Set to "yes" if using DIR/libNAME\$shared_ext during linking hardcodes +# DIR into the resulting binary. +hardcode_direct=$hardcode_direct + +# Set to "yes" if using DIR/libNAME\$shared_ext during linking hardcodes +# DIR into the resulting binary and the resulting library dependency is +# "absolute",i.e impossible to change by setting \$shlibpath_var if the +# library is relocated. +hardcode_direct_absolute=$hardcode_direct_absolute + +# Set to "yes" if using the -LDIR flag during linking hardcodes DIR +# into the resulting binary. +hardcode_minus_L=$hardcode_minus_L + +# Set to "yes" if using SHLIBPATH_VAR=DIR during linking hardcodes DIR +# into the resulting binary. +hardcode_shlibpath_var=$hardcode_shlibpath_var + +# Set to "yes" if building a shared library automatically hardcodes DIR +# into the library and all subsequent libraries and executables linked +# against it. +hardcode_automatic=$hardcode_automatic + +# Set to yes if linker adds runtime paths of dependent libraries +# to runtime path list. +inherit_rpath=$inherit_rpath + +# Whether libtool must link a program against all its dependency libraries. +link_all_deplibs=$link_all_deplibs + +# Set to "yes" if exported symbols are required. +always_export_symbols=$always_export_symbols + +# The commands to list exported symbols. +export_symbols_cmds=$lt_export_symbols_cmds + +# Symbols that should not be listed in the preloaded symbols. +exclude_expsyms=$lt_exclude_expsyms + +# Symbols that must always be exported. +include_expsyms=$lt_include_expsyms + +# Commands necessary for linking programs (against libraries) with templates. +prelink_cmds=$lt_prelink_cmds + +# Commands necessary for finishing linking programs. +postlink_cmds=$lt_postlink_cmds + +# Specify filename containing input files. +file_list_spec=$lt_file_list_spec + +# How to hardcode a shared library path into an executable. +hardcode_action=$hardcode_action + +# The directories searched by this compiler when creating a shared library. +compiler_lib_search_dirs=$lt_compiler_lib_search_dirs + +# Dependencies to place before and after the objects being linked to +# create a shared library. +predep_objects=$lt_predep_objects +postdep_objects=$lt_postdep_objects +predeps=$lt_predeps +postdeps=$lt_postdeps + +# The library search path used internally by the compiler when linking +# a shared library. +compiler_lib_search_path=$lt_compiler_lib_search_path + +# ### END LIBTOOL CONFIG + +_LT_EOF + + cat <<'_LT_EOF' >> "$cfgfile" + +# ### BEGIN FUNCTIONS SHARED WITH CONFIGURE + +# func_munge_path_list VARIABLE PATH +# ----------------------------------- +# VARIABLE is name of variable containing _space_ separated list of +# directories to be munged by the contents of PATH, which is string +# having a format: +# "DIR[:DIR]:" +# string "DIR[ DIR]" will be prepended to VARIABLE +# ":DIR[:DIR]" +# string "DIR[ DIR]" will be appended to VARIABLE +# "DIRP[:DIRP]::[DIRA:]DIRA" +# string "DIRP[ DIRP]" will be prepended to VARIABLE and string +# "DIRA[ DIRA]" will be appended to VARIABLE +# "DIR[:DIR]" +# VARIABLE will be replaced by "DIR[ DIR]" +func_munge_path_list () +{ + case x$2 in + x) + ;; + *:) + eval $1=\"`$ECHO $2 | $SED 's/:/ /g'` \$$1\" + ;; + x:*) + eval $1=\"\$$1 `$ECHO $2 | $SED 's/:/ /g'`\" + ;; + *::*) + eval $1=\"\$$1\ `$ECHO $2 | $SED -e 's/.*:://' -e 's/:/ /g'`\" + eval $1=\"`$ECHO $2 | $SED -e 's/::.*//' -e 's/:/ /g'`\ \$$1\" + ;; + *) + eval $1=\"`$ECHO $2 | $SED 's/:/ /g'`\" + ;; + esac +} + + +# Calculate cc_basename. Skip known compiler wrappers and cross-prefix. +func_cc_basename () +{ + for cc_temp in $*""; do + case $cc_temp in + compile | *[\\/]compile | ccache | *[\\/]ccache ) ;; + distcc | *[\\/]distcc | purify | *[\\/]purify ) ;; + \-*) ;; + *) break;; + esac + done + func_cc_basename_result=`$ECHO "$cc_temp" | $SED "s%.*/%%; s%^$host_alias-%%"` +} + + +# ### END FUNCTIONS SHARED WITH CONFIGURE + +_LT_EOF + + case $host_os in + aix3*) + cat <<\_LT_EOF >> "$cfgfile" +# AIX sometimes has problems with the GCC collect2 program. For some +# reason, if we set the COLLECT_NAMES environment variable, the problems +# vanish in a puff of smoke. +if test set != "${COLLECT_NAMES+set}"; then + COLLECT_NAMES= + export COLLECT_NAMES +fi +_LT_EOF + ;; + esac + + +ltmain=$ac_aux_dir/ltmain.sh + + + # We use sed instead of cat because bash on DJGPP gets confused if + # if finds mixed CR/LF and LF-only lines. Since sed operates in + # text mode, it properly converts lines to CR/LF. This bash problem + # is reportedly fixed, but why not run on old versions too? + sed '$q' "$ltmain" >> "$cfgfile" \ + || (rm -f "$cfgfile"; exit 1) + + mv -f "$cfgfile" "$ofile" || + (rm -f "$ofile" && cp "$cfgfile" "$ofile" && rm -f "$cfgfile") + chmod +x "$ofile" + + + cat <<_LT_EOF >> "$ofile" + +# ### BEGIN LIBTOOL TAG CONFIG: CXX + +# The linker used to build libraries. +LD=$lt_LD_CXX + +# How to create reloadable object files. +reload_flag=$lt_reload_flag_CXX +reload_cmds=$lt_reload_cmds_CXX + +# Commands used to build an old-style archive. +old_archive_cmds=$lt_old_archive_cmds_CXX + +# A language specific compiler. +CC=$lt_compiler_CXX + +# Is the compiler the GNU compiler? +with_gcc=$GCC_CXX + +# Compiler flag to turn off builtin functions. +no_builtin_flag=$lt_lt_prog_compiler_no_builtin_flag_CXX + +# Additional compiler flags for building library objects. +pic_flag=$lt_lt_prog_compiler_pic_CXX + +# How to pass a linker flag through the compiler. +wl=$lt_lt_prog_compiler_wl_CXX + +# Compiler flag to prevent dynamic linking. +link_static_flag=$lt_lt_prog_compiler_static_CXX + +# Does compiler simultaneously support -c and -o options? +compiler_c_o=$lt_lt_cv_prog_compiler_c_o_CXX + +# Whether or not to add -lc for building shared libraries. +build_libtool_need_lc=$archive_cmds_need_lc_CXX + +# Whether or not to disallow shared libs when runtime libs are static. +allow_libtool_libs_with_static_runtimes=$enable_shared_with_static_runtimes_CXX + +# Compiler flag to allow reflexive dlopens. +export_dynamic_flag_spec=$lt_export_dynamic_flag_spec_CXX + +# Compiler flag to generate shared objects directly from archives. +whole_archive_flag_spec=$lt_whole_archive_flag_spec_CXX + +# Whether the compiler copes with passing no objects directly. +compiler_needs_object=$lt_compiler_needs_object_CXX + +# Create an old-style archive from a shared archive. +old_archive_from_new_cmds=$lt_old_archive_from_new_cmds_CXX + +# Create a temporary old-style archive to link instead of a shared archive. +old_archive_from_expsyms_cmds=$lt_old_archive_from_expsyms_cmds_CXX + +# Commands used to build a shared archive. +archive_cmds=$lt_archive_cmds_CXX +archive_expsym_cmds=$lt_archive_expsym_cmds_CXX + +# Commands used to build a loadable module if different from building +# a shared archive. +module_cmds=$lt_module_cmds_CXX +module_expsym_cmds=$lt_module_expsym_cmds_CXX + +# Whether we are building with GNU ld or not. +with_gnu_ld=$lt_with_gnu_ld_CXX + +# Flag that allows shared libraries with undefined symbols to be built. +allow_undefined_flag=$lt_allow_undefined_flag_CXX + +# Flag that enforces no undefined symbols. +no_undefined_flag=$lt_no_undefined_flag_CXX + +# Flag to hardcode \$libdir into a binary during linking. +# This must work even if \$libdir does not exist +hardcode_libdir_flag_spec=$lt_hardcode_libdir_flag_spec_CXX + +# Whether we need a single "-rpath" flag with a separated argument. +hardcode_libdir_separator=$lt_hardcode_libdir_separator_CXX + +# Set to "yes" if using DIR/libNAME\$shared_ext during linking hardcodes +# DIR into the resulting binary. +hardcode_direct=$hardcode_direct_CXX + +# Set to "yes" if using DIR/libNAME\$shared_ext during linking hardcodes +# DIR into the resulting binary and the resulting library dependency is +# "absolute",i.e impossible to change by setting \$shlibpath_var if the +# library is relocated. +hardcode_direct_absolute=$hardcode_direct_absolute_CXX + +# Set to "yes" if using the -LDIR flag during linking hardcodes DIR +# into the resulting binary. +hardcode_minus_L=$hardcode_minus_L_CXX + +# Set to "yes" if using SHLIBPATH_VAR=DIR during linking hardcodes DIR +# into the resulting binary. +hardcode_shlibpath_var=$hardcode_shlibpath_var_CXX + +# Set to "yes" if building a shared library automatically hardcodes DIR +# into the library and all subsequent libraries and executables linked +# against it. +hardcode_automatic=$hardcode_automatic_CXX + +# Set to yes if linker adds runtime paths of dependent libraries +# to runtime path list. +inherit_rpath=$inherit_rpath_CXX + +# Whether libtool must link a program against all its dependency libraries. +link_all_deplibs=$link_all_deplibs_CXX + +# Set to "yes" if exported symbols are required. +always_export_symbols=$always_export_symbols_CXX + +# The commands to list exported symbols. +export_symbols_cmds=$lt_export_symbols_cmds_CXX + +# Symbols that should not be listed in the preloaded symbols. +exclude_expsyms=$lt_exclude_expsyms_CXX + +# Symbols that must always be exported. +include_expsyms=$lt_include_expsyms_CXX + +# Commands necessary for linking programs (against libraries) with templates. +prelink_cmds=$lt_prelink_cmds_CXX + +# Commands necessary for finishing linking programs. +postlink_cmds=$lt_postlink_cmds_CXX + +# Specify filename containing input files. +file_list_spec=$lt_file_list_spec_CXX + +# How to hardcode a shared library path into an executable. +hardcode_action=$hardcode_action_CXX + +# The directories searched by this compiler when creating a shared library. +compiler_lib_search_dirs=$lt_compiler_lib_search_dirs_CXX + +# Dependencies to place before and after the objects being linked to +# create a shared library. +predep_objects=$lt_predep_objects_CXX +postdep_objects=$lt_postdep_objects_CXX +predeps=$lt_predeps_CXX +postdeps=$lt_postdeps_CXX + +# The library search path used internally by the compiler when linking +# a shared library. +compiler_lib_search_path=$lt_compiler_lib_search_path_CXX + +# ### END LIBTOOL TAG CONFIG: CXX +_LT_EOF + + ;; + + esac +done # for ac_tag + + +as_fn_exit 0 +_ACEOF +ac_clean_files=$ac_clean_files_save + +test $ac_write_fail = 0 || + as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5 + + +# configure is writing to config.log, and then calls config.status. +# config.status does its own redirection, appending to config.log. +# Unfortunately, on DOS this fails, as config.log is still kept open +# by configure, so config.status won't be able to write to it; its +# output is simply discarded. So we exec the FD to /dev/null, +# effectively closing config.log, so it can be properly (re)opened and +# appended to by config.status. When coming back to configure, we +# need to make the FD available again. +if test "$no_create" != yes; then + ac_cs_success=: + ac_config_status_args= + test "$silent" = yes && + ac_config_status_args="$ac_config_status_args --quiet" + exec 5>/dev/null + $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false + exec 5>>config.log + # Use ||, not &&, to avoid exiting from the if with $? = 1, which + # would make configure fail if this is the last instruction. + $ac_cs_success || as_fn_exit 1 +fi +if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 +$as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} +fi + + + +############################################################################### +# Generating config.report +############################################################################### + +$as_echo +$as_echo "Configure script has finished system check." +$as_echo + +cat > config.report << END + Config results: + -=-=-=-=-=-=-=-=- + +Package: + name: ${PACKAGE_NAME} + version: ${PACKAGE_VERSION} + svn revision: ${SVN_REVISION} + +Build environment: + build system: ${build} + host system: ${host} + target system: ${target} + +Compiler and linker flags: + CPPFlags: ${CPPFLAGS} + CFlags: ${CFLAGS} + CXXFlags: ${CXXFLAGS} + LDFlags: ${LDFLAGS} + Libs: ${LIBS} + Defs: ${DEFS} + +Build options: + deprecated functions: ${ARG_ENABLE_DEPRECATED:-yes} + debug build: ${ARG_ENABLE_DEBUG:-no} + invariant checks: ${ARG_ENABLE_INVARIANT:-no} + logging support: ${ARG_ENABLE_LOGGING:-no} + disk statistics: ${ARG_ENABLE_DISK_STATS:-no} + +Features: + encryption support: ${ARG_ENABLE_ENCRYPTION:-yes} + dht support: ${ARG_ENABLE_DHT:-yes} + pool allocators: ${ARG_ENABLE_POOL_ALLOC:-yes} + +Extra builds: + examples: ${ARG_ENABLE_EXAMPLES:-no} + tests: ${ARG_ENABLE_TESTS:-no} + python bindings: ${ARG_ENABLE_PYTHON_BINDING:-no} + +Pthread library: + CFlags: ${PTHREAD_CFLAGS} + Libs: ${PTHREAD_LIBS} + +Boost libraries: + version: ${BOOST_VERSION} + CPPFlags: ${BOOST_CPPFLAGS} + LDFlags: ${BOOST_LDFLAGS} + boost.system: ${BOOST_SYSTEM_LIB} + boost.chrono: ${BOOST_CHRONO_LIB} + boost.random: ${BOOST_RANDOM_LIB} +END + +if test "x$ARG_ENABLE_PYTHON_BINDING" = "xyes"; then : + +cat >> config.report << END + boost.python: ${BOOST_PYTHON_LIB} + +Python environment: + -automake- + binary: ${PYTHON} + version: ${PYTHON_VERSION} + platform: ${PYTHON_PLATFORM} + prefix: ${PYTHON_PREFIX} + exec_prefix: ${PYTHON_EXEC_PREFIX} + pythondir: ${pythondir} + pkgpythondir: ${pkgpythondir} + pyexecdir: ${pyexecdir} + pkgpyexecdir: ${pkgpyexecdir} + + -m4- + cppflags: ${PYTHON_CPPFLAGS} + ldflags: ${PYTHON_LDFLAGS} + extra libs: ${PYTHON_EXTRA_LIBS} + +END + +fi + +cat >> config.report << END + +External libraries: + system libiconv: ${ARG_WITH_LIBICONV:-no} +END + +if test "x$ARG_WITH_LIBICONV" = "xyes"; then : + +cat >> config.report << END + +Iconv library: + Iconv Libs: ${ICONV_LIBS} +END + +fi + +if test "x$ARG_ENABLE_ENCRYPTION" = "xyes"; then : + +cat >> config.report << END + +OpenSSL library: + OpenSSL Libs: ${OPENSSL_LIBS} + OpenSSL LDFlags: ${OPENSSL_LDFLAGS} + OpenSSL Includes: ${OPENSSL_INCLUDES} +END + +fi + +cat config.report + +$as_echo +$as_echo "Type 'make' to compile $PACKAGE_STRING" +$as_echo "or type 'make V=1' for verbose compiling" +$as_echo "and then 'make install' to install it into $prefix" +$as_echo diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..6ccc15a --- /dev/null +++ b/configure.ac @@ -0,0 +1,705 @@ +# -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. + +# $Id$ + +AC_PREREQ([2.63]) + +AC_INIT([libtorrent-rasterbar],[1.1.1],[arvid@libtorrent.org], + [libtorrent-rasterbar],[http://www.libtorrent.org]) +AC_CONFIG_SRCDIR([src/torrent.cpp]) +AC_CONFIG_AUX_DIR([build-aux]) +AC_CONFIG_MACRO_DIR([m4]) + +# Silencing build output (automake-1.11) +m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])]) + + +############################################################################### +dnl --------------------------------------------------------------------------- +dnl interface version info +dnl --------------------------------------------------------------------------- +dnl Advanced information about versioning: +dnl * "Writing shared libraries" by Mike Hearn +dnl http://navi.cx/~mike/writing-shared-libraries.html +dnl * libtool.info chapter "Versioning" +dnl * libtool.info chapter "Updating library version information" +dnl --------------------------------------------------------------------------- +dnl Versioning: +dnl - CURRENT (Major): Increment if the interface has changes. AGE is always +dnl *changed* at the same time. +dnl - AGE (Micro): Increment if any interfaces have been added; set to 0 +dnl if any interfaces have been removed. Removal has +dnl precedence over adding, so set to 0 if both happened. +dnl It denotes upward compatibility. +dnl - REVISION (Minor): Increment any time the source changes; set to +dnl 0 if you incremented CURRENT. +dnl +dnl To summarize. Any interface *change* increment CURRENT. If that interface +dnl change does not break upward compatibility (ie it is an addition), +dnl increment AGE, Otherwise AGE is reset to 0. If CURRENT has changed, +dnl REVISION is set to 0, otherwise REVISION is incremented. +dnl --------------------------------------------------------------------------- + +m4_define([VERSION_INFO_CURRENT],[9]) +m4_define([VERSION_INFO_REVISION],[0]) +m4_define([VERSION_INFO_AGE],[0]) +INTERFACE_VERSION_INFO=VERSION_INFO_CURRENT:VERSION_INFO_REVISION:VERSION_INFO_AGE +AC_SUBST(INTERFACE_VERSION_INFO) +############################################################################### + + +############################################################################### +# Start +############################################################################### + +AS_ECHO +AS_ECHO "Building $PACKAGE_STRING" + + +############################################################################### +# Performing some basic checks and initializing the build system +############################################################################### + +AS_ECHO +AS_ECHO "Checking for a C/C++ compiler to use:" +AC_PROG_CC +AC_PROG_CPP +AC_PROG_CC_C_O +AC_PROG_CXX +AC_PROG_CXXCPP +AC_PROG_CXX_C_O + +AS_ECHO +AS_ECHO "Checking system type:" +AC_CANONICAL_BUILD +AC_CANONICAL_HOST +AC_CANONICAL_TARGET + +AS_ECHO +AS_ECHO "Initializing Automake:" +AM_INIT_AUTOMAKE([1.11 foreign]) + + +AS_ECHO +AS_ECHO "Initializing Libtool:" +LT_PREREQ([2.2.6]) +LT_INIT + + +############################################################################### +# Checking for needed base libraries +############################################################################### + +AS_ECHO +AS_ECHO "Checking for posix thread support:" + +AX_PTHREAD() + +LIBS="$PTHREAD_LIBS $LIBS" +CFLAGS="$PTHREAD_CFLAGS $CFLAGS" +CC="$PTHREAD_CC" +CXXFLAGS="$CXXFLAGS -ftemplate-depth=120" + +AS_ECHO "Checking for visibility support:" +AC_CACHE_CHECK([for __attribute__((visibility("hidden")))], + ac_cv_hidden_visibility_attribute, [ + echo 'int __attribute__ ((visibility ("hidden"))) foo (void) { return 1; }' > visibility_conftest.c + ac_cv_hidden_visibility_attribute=no + if AC_TRY_COMMAND(${CC-cc} -fvisibility=hidden -S visibility_conftest.c -o visibility_conftest.s 1>&AS_MESSAGE_LOG_FD); + then + AS_ECHO "found" + ac_cv_hidden_visibility_attribute=yes + fi + rm -f visibility_conftest.* +]) + +AS_ECHO +AS_ECHO "Checking for boost libraries:" + +AX_BOOST_BASE([1.53]) + +AX_BOOST_SYSTEM() +AS_IF([test -z "$BOOST_SYSTEM_LIB"], + [AC_MSG_ERROR(Boost.System library not found. Try using --with-boost-system=lib)]) + +AX_BOOST_CHRONO() +AS_IF([test -z "$BOOST_CHRONO_LIB"], + [AC_MSG_ERROR(Boost.Chrono library not found. Try using --with-boost-chrono=lib)]) + +AX_BOOST_RANDOM() +AS_IF([test -z "$BOOST_RANDOM_LIB"], + [AC_MSG_ERROR(Boost.Random library not found. Try using --with-boost-random=lib)]) + +CPPFLAGS="$BOOST_CPPFLAGS $CPPFLAGS" +LDFLAGS="$BOOST_LDFLAGS $LDFLAGS" +LIBS="$BOOST_CHRONO_LIB $BOOST_RANDOM_LIB $LIBS" + +############################################################################### +# Checking for functions and other stuffs +############################################################################### + + +AS_ECHO +AS_ECHO "Checking for pkg-config:" + +PKG_PROG_PKG_CONFIG([0.20]) + +AS_ECHO +AS_ECHO "Checking for functions and headers:" + +AC_SYS_LARGEFILE +AC_PROG_INSTALL +AC_PROG_LN_S +AC_PROG_MAKE_SET + +AC_CHECK_FUNCS([clock_gettime], [], + [AC_CHECK_LIB([rt], [clock_gettime], [], + [AC_CHECK_LIB([posix4], [clock_gettime], [], + [AC_MSG_WARN([clock_gettime function not found.])])])] +) + + +dnl Pass some build options to .pc file +COMPILETIME_OPTIONS="" + + +############################################################################### +# Setting configure options +############################################################################### + +AC_ARG_ENABLE( + [logging], + [AS_HELP_STRING( + [--enable-logging], + [enable logging to disk (use value "verbose" to enable verbose peer wire logging or "errors" limit logging to errors ) [default=no]])], + [[ARG_ENABLE_LOGGING=$enableval]], + [[ARG_ENABLE_LOGGING=no]] +) + +AC_ARG_ENABLE( + [debug], + [AS_HELP_STRING( + [--enable-debug], + [enable debug build [default=no]])], + [ + ARG_ENABLE_DEBUG=$enableval + ac_arg_enable_debug=$enableval + ], + [ + ARG_ENABLE_DEBUG=no + ac_arg_enable_debug=no + ] +) + +AC_ARG_ENABLE( + [dht], + [AS_HELP_STRING( + [--enable-dht], + [enable dht support (use value "logging" to add extra logging) [default=yes]])], + [[ARG_ENABLE_DHT=$enableval]], + [[ARG_ENABLE_DHT=yes]] +) + +AC_ARG_ENABLE( + [encryption], + [AS_HELP_STRING( + [--enable-encryption], + [enable encryption support (requires OpenSSL to be installed on your system, you can use --with-openssl to set the path) [default=yes]])], + [[ARG_ENABLE_ENCRYPTION=$enableval]], + [[ARG_ENABLE_ENCRYPTION=yes]] +) + +AC_ARG_ENABLE( + [export-all], + [AS_HELP_STRING( + [--enable-export-all], + [export all symbols from libtorrent, including non-public ones [default=no]])], + [[ARG_ENABLE_FULL_EXPORT=$enableval]], + [[ARG_ENABLE_FULL_EXPORT=no]] +) + +AC_ARG_ENABLE( + [pool-allocators], + [AS_HELP_STRING( + [--enable-pool-allocators], + [enable pool allocators for send buffers [default=yes]])], + [[ARG_ENABLE_POOL_ALLOC=$enableval]], + [[ARG_ENABLE_POOL_ALLOC=yes]] +) + +AC_ARG_ENABLE( + [invariant-checks], + [AS_HELP_STRING( + [--enable-invariant-checks], + [enable invariant checks (use value "full" to turn on extra expensive invariant checks) @<:@default=yes if debug is enabled, no otherwhise@:>@])], + [[ARG_ENABLE_INVARIANT=$enableval]], + [ + AS_IF([test "x$ac_arg_enable_debug" = "xyes"], + [ARG_ENABLE_INVARIANT=yes], + [ARG_ENABLE_INVARIANT=no]) + ] +) + +AC_ARG_ENABLE( + [deprecated-functions], + [AS_HELP_STRING( + [--enable-deprecated-functions], + [enable deprecated functions in the API [default=yes]])], + [[ARG_ENABLE_DEPRECATED=$enableval]], + [[ARG_ENABLE_DEPRECATED=yes]] +) + +AC_ARG_ENABLE( + [disk-stats], + [AS_HELP_STRING( + [--enable-disk-stats], + [enable disk activity logging feature [default=no]])], + [[ARG_ENABLE_DISK_STATS=$enableval]], + [[ARG_ENABLE_DISK_STATS=no]] +) + +AC_ARG_ENABLE( + [examples], + [AS_HELP_STRING( + [--enable-examples], + [build example files [default=no]])], + [[ARG_ENABLE_EXAMPLES=$enableval]], + [[ARG_ENABLE_EXAMPLES=no]] +) + +AC_ARG_ENABLE( + [tests], + [AS_HELP_STRING( + [--enable-tests], + [build test files [default=no]])], + [[ARG_ENABLE_TESTS=$enableval]], + [[ARG_ENABLE_TESTS=no]] +) + +AC_ARG_ENABLE( + [python-binding], + [AS_HELP_STRING( + [--enable-python-binding], + [build python bindings [default=no]])], + [[ARG_ENABLE_PYTHON_BINDING=$enableval]], + [[ARG_ENABLE_PYTHON_BINDING=no]] +) + +AC_ARG_WITH( + [libiconv], + [AS_HELP_STRING( + [--with-libiconv], + [enable linking against system libiconv [default=no]])], + [[ARG_WITH_LIBICONV=$withval]], + [[ARG_WITH_LIBICONV=no]] +) + +############################################################################### +# Checking configure options +############################################################################### + +AS_ECHO +AS_ECHO "Checking build options:" + +AC_MSG_CHECKING([whether deprecated functions should be enabled]) +AS_CASE(["$ARG_ENABLE_DEPRECATED"], + ["yes"|"on"], [ + AC_MSG_RESULT([yes]) + ], + ["no"|"off"], [ + AC_MSG_RESULT([no]) + AC_DEFINE([TORRENT_NO_DEPRECATE],[1],[Define to exclude all deprecated functions from the API.]) + COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DTORRENT_NO_DEPRECATE " + ], + [AC_MSG_RESULT([$ARG_ENABLE_DEPRECATED]) + AC_MSG_ERROR([Unknown option "$ARG_ENABLE_DEPRECATED". Use either "yes" or "no".])] +) + +AC_MSG_CHECKING([whether debug build should be enabled]) +AS_CASE(["$ARG_ENABLE_DEBUG"], + ["yes"], [ + AC_MSG_RESULT([yes]) + AC_DEFINE([TORRENT_DEBUG],[1],[Define to enable debug code.]) + COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DTORRENT_DEBUG " + DEBUGFLAGS="-g" + ], + ["no"], [ + AC_MSG_RESULT([no]) + AC_DEFINE([NDEBUG],[1],[Define to disable debug code.]) + #COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DNDEBUG " + DEBUGFLAGS="-Os" + ], + [AC_MSG_RESULT([$ARG_ENABLE_DEBUG]) + AC_MSG_ERROR([Unknown option "$ARG_ENABLE_DEBUG". Use either "yes" or "no".])] +) + +AC_MSG_CHECKING([whether invariant check should be enabled]) #depends: $ac_arg_enable_debug +AS_CASE(["$ARG_ENABLE_INVARIANT"], + ["yes"|"on"], [ + AC_MSG_RESULT([yes]) + AS_IF([test "x$ac_arg_enable_debug" = "xno"], + [AC_MSG_ERROR([invariant-checks: this setting only affects debug build. Try using --enable-debug.])]) + ], + ["no"|"off"], [ + AC_MSG_RESULT([no]) + AS_IF([test "x$ac_arg_enable_debug" = "xyes"], + [AC_DEFINE([TORRENT_DISABLE_INVARIANT_CHECKS],[1],[Define to disable internal invariant checks. Asserts are still enabled while debug is on.])]) + ], + ["full"], [ + AC_MSG_RESULT([full]) + AS_IF([test "x$ac_arg_enable_debug" = "xyes"], + [AC_DEFINE([TORRENT_EXPENSIVE_INVARIANT_CHECKS],[1],[Define to enable extra expensive invariant checks.])], + [AC_MSG_ERROR([invariant-checks: this setting only affects debug build. Try using --enable-debug.])]) + ], + [AC_MSG_RESULT([$ARG_ENABLE_INVARIANT]) + AC_MSG_ERROR([Unknown option "$ARG_ENABLE_INVARIANT". Use either "yes", "no" or "full".])] +) + +AC_MSG_CHECKING([whether logging to disk should be enabled]) +AS_CASE(["$ARG_ENABLE_LOGGING"], + ["yes"|"default"], [ + AC_MSG_RESULT([yes]) + ], + ["no"|"none"], [ + AC_MSG_RESULT([no]) + AC_DEFINE([TORRENT_DISABLE_LOGGING],[1],[Define to enable support for logging alerts]) + COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DTORRENT_DISABLE_LOGGING " + ], + [AC_MSG_RESULT([$ARG_ENABLE_LOGGING]) + AC_MSG_ERROR([Unknown option "$ARG_ENABLE_LOGGING". Use either "yes" or "no"])] +) + +AC_MSG_CHECKING([whether disk activity logging should be enabled]) +AS_CASE(["$ARG_ENABLE_DISK_STATS"], + ["yes"|"on"], [ + AC_MSG_RESULT([yes]) + AC_DEFINE([TORRENT_DISK_STATS],[1],[Define to create a log of all disk activities.]) + ], + ["no"|"off"], [ + AC_MSG_RESULT([no]) + ], + [AC_MSG_RESULT([$ARG_ENABLE_DISK_STATS]) + AC_MSG_ERROR([Unknown option "$ARG_ENABLE_DISK_STATS". Use either "yes" or "no".])] +) + +AS_ECHO +AS_ECHO "Checking features to be enabled:" + +AC_MSG_CHECKING([whether encryption support should be enabled]) +AS_CASE(["$ARG_ENABLE_ENCRYPTION"], + ["yes"|"on"], [ + AC_MSG_RESULT([yes]) + AC_MSG_NOTICE([encryption support: now checking for the OpenSSL library...]) + + AX_CHECK_OPENSSL([ + AC_DEFINE([TORRENT_USE_OPENSSL],[1],[Define to use OpenSSL support.]) + COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DTORRENT_USE_OPENSSL " + ], [ + AC_MSG_ERROR([OpenSSL library not found. Try using --with-openssl=DIR or disabling encryption at all.]) + ]) + ], + ["no"|"off"], [ + AC_MSG_RESULT([no]) + AC_DEFINE([TORRENT_DISABLE_ENCRYPTION],[1],[Define to disable any encryption support and avoid linking against OpenSSL.]) + COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DTORRENT_DISABLE_ENCRYPTION " + ], + [AC_MSG_RESULT([$ARG_ENABLE_ENCRYPTION]) + AC_MSG_ERROR([Unknown option "$ARG_ENABLE_ENCRYPTION". Use either "yes" or "no".])] +) + +AC_MSG_CHECKING([whether dht support should be enabled]) +AS_CASE(["$ARG_ENABLE_DHT"], + ["yes"|"on"], [ + AC_MSG_RESULT([yes]) + ], + ["no"|"off"], [ + AC_MSG_RESULT([no]) + AC_DEFINE([TORRENT_DISABLE_DHT],[1],[Define to disable the DHT support.]) + COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DTORRENT_DISABLE_DHT " + ], + ["logging"], [ + AC_MSG_RESULT([logging]) + AC_DEFINE([TORRENT_DHT_VERBOSE_LOGGING],[1],[Define to enable DHT support with verbose logging.]) + COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DTORRENT_DHT_VERBOSE_LOGGING " + ], + [AC_MSG_RESULT([$ARG_ENABLE_DHT]) + AC_MSG_ERROR([Unknown option "$ARG_ENABLE_DHT". Use either "yes", "no" or "logging".])] +) + +AC_MSG_CHECKING([whether pool allocators should be enabled]) +AS_CASE(["$ARG_ENABLE_POOL_ALLOC"], + ["yes"|"on"], [ + AC_MSG_RESULT([yes]) + ], + ["no"|"off"], [ + AC_MSG_RESULT([no]) + AC_DEFINE([TORRENT_DISABLE_POOL_ALLOCATOR],[1],[Define to disable use of boost::pool for pool allocators.]) + ], + [AC_MSG_RESULT([$ARG_ENABLE_POOL_ALLOC]) + AC_MSG_ERROR([Unknown option "$ARG_ENABLE_POOL_ALLOC". Use either "yes" or "no".])] +) + +AS_IF([test "x$ac_cv_hidden_visibility_attribute" = "xyes"], [ + AS_IF([test "x$ARG_ENABLE_FULL_EXPORT" = "xno"], [ + CXXFLAGS="$CXXFLAGS -fvisibility=hidden -fvisibility-inlines-hidden" + CFLAGS="$CFLAGS -fvisibility=hidden" + LDFLAGS="$LDFLAGS -fvisibility=hidden -fvisibility-inlines-hidden" + ]) +]) + +AS_ECHO +AS_ECHO "Checking for extra build files:" + +AC_MSG_CHECKING([whether example files should be built]) +AS_CASE(["$ARG_ENABLE_EXAMPLES"], + ["yes"], [AC_MSG_RESULT([yes])], + ["no"], [AC_MSG_RESULT([no])], + [AC_MSG_RESULT([$ARG_ENABLE_EXAMPLES]) + AC_MSG_ERROR([Unknown option "$ARG_ENABLE_EXAMPLES". Use either "yes" or "no".])] +) + +AC_MSG_CHECKING([whether test files should be built]) +AS_CASE(["$ARG_ENABLE_TESTS"], + ["yes"], [ + AC_MSG_RESULT([yes]) + AC_DEFINE([TORRENT_EXPORT_EXTRA],[1],[Define to export additional symbols for unit tests.]) + COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DTORRENT_EXPORT_EXTRA " + ], + ["no"], [AC_MSG_RESULT([no])], + [AC_MSG_RESULT([$ARG_ENABLE_TESTS]) + AC_MSG_ERROR([Unknown option "$ARG_ENABLE_TESTS". Use either "yes" or "no".])] +) + +AC_MSG_CHECKING([whether python bindings should be built]) +AS_CASE(["$ARG_ENABLE_PYTHON_BINDING"], + ["yes"], [ + AC_MSG_RESULT([yes]) + + AM_PATH_PYTHON([2.4], [], AC_MSG_ERROR([Python interpreter not found.])) + AX_PYTHON_DEVEL([>= '2.4']) + AX_BOOST_PYTHON() + + AS_IF([test -z "$BOOST_PYTHON_LIB"], + [AC_MSG_ERROR([Boost.Python library not found. Try using --with-boost-python=lib.])]) + + BOOST_PYTHON_LIB="-l$BOOST_PYTHON_LIB" + ], + ["no"], [ + AC_MSG_RESULT([no]) + ], + [AC_MSG_RESULT([$ARG_ENABLE_PYTHON_BINDING]) + AC_MSG_ERROR([Unknown option "$ARG_ENABLE_PYTHON_BINDING". Use either "yes" or "no".])] +) + +AS_ECHO +AS_ECHO "Checking for external libraries:" + +AC_MSG_CHECKING([whether to link against system libiconv]) +AS_CASE(["$ARG_WITH_LIBICONV"], + ["yes"], [ + AC_MSG_RESULT([yes]) + AM_ICONV() + AS_IF([test "x$am_cv_func_iconv" = "xyes"], [ + ICONV_LIBS=$LTLIBICONV + AC_SUBST([ICONV_LIBS]) + LIBS="$ICONV_LIBS $LIBS" + ], [ + AC_MSG_ERROR([Iconv library not found.]) + ]) + ], + ["no"], [ + ARG_WITH_LIBICONV="no" + ], + [AC_MSG_RESULT([$ARG_WITH_LIBICONV]) + AC_MSG_ERROR([Unknown option "$ARG_WITH_LIBICONV". Use either "yes" or "no".])] +) + +############################################################################### +# Setting conditional variables for Makefiles +############################################################################### + +AM_CONDITIONAL([ENABLE_DHT], [test "x$ARG_ENABLE_DHT" = "xyes" -o "x$ARG_ENABLE_DHT" = "xlogging" ]) +AM_CONDITIONAL([ENABLE_EXAMPLES], [test "x$ARG_ENABLE_EXAMPLES" = "xyes"]) +AM_CONDITIONAL([ENABLE_TESTS], [test "x$ARG_ENABLE_TESTS" = "xyes"]) +AM_CONDITIONAL([ENABLE_PYTHON_BINDING], [test "x$ARG_ENABLE_PYTHON_BINDING" = "xyes"]) + +AM_CONDITIONAL([WITH_OPENSSL], [test "x$ARG_ENABLE_ENCRYPTION" = "xyes" -o "x$ARG_ENABLE_ENCRYPTION" = "xon" ]) + + +############################################################################### +# Other useful stuff +############################################################################### + +# this works around a bug in asio in boost-1.39 +# see: https://svn.boost.org/trac/boost/ticket/3095 +AC_DEFINE([BOOST_ASIO_HASH_MAP_BUCKETS],[1021],[Define to fix a wrong behavior in boost 1.39.]) +COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DBOOST_ASIO_HASH_MAP_BUCKETS=1021 " + +AC_DEFINE([BOOST_EXCEPTION_DISABLE],[1],[Define to disable the boost.exception features.]) +COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DBOOST_EXCEPTION_DISABLE " + +AC_DEFINE([BOOST_ASIO_ENABLE_CANCELIO],[1],[Define to enable cancel support in asio on windows XP and older.]) +COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DBOOST_ASIO_ENABLE_CANCELIO " + +dnl Use possibly specific python install params +AC_ARG_VAR([PYTHON_INSTALL_PARAMS], [Set specific install parameters for python bindings.]) +AS_IF([test "x$PYTHON_INSTALL_PARAMS" = "x"], + [PYTHON_INSTALL_PARAMS='--prefix=$(DESTDIR)$(prefix)']) + +dnl Set some defines if we are building a shared library +AS_IF([test "x$enable_shared" = "xyes"], + [AC_DEFINE([TORRENT_BUILDING_SHARED],[1],[Define to exports functions and classes with default visibility in GCC.]) + COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DTORRENT_LINKING_SHARED "]) + +AC_SUBST(DEBUGFLAGS) +AC_SUBST(PYTHON_INSTALL_PARAMS) +AC_SUBST(COMPILETIME_OPTIONS) + + +# Try to guess real svn revision if any, fallback to hardcoded otherwise +SVN_REVISION=`svn info 2>/dev/null | sed -n -e '/^URL\:.*libtorrent.svn.sourceforge.net/,$!d;s,^Revision\: \([[0-9]]*\)$,\1,p'` +AS_IF([test -z "$SVN_REVISION"], + [SVN_REVISION=`sed -n -e 's/^#define LIBTORRENT_REVISION \"\$Rev\: \([0-9]*\) \$\" $/\1/p' include/libtorrent/version.hpp`]) + + +############################################################################### +# Generating Makefiles +############################################################################### + +AS_ECHO +AS_ECHO "Generating Makefiles:" + +AC_CONFIG_FILES( + [Makefile] + [src/Makefile] + [include/libtorrent/Makefile] + [examples/Makefile] + [test/Makefile] + [tools/Makefile] + [bindings/Makefile] + [bindings/python/Makefile] + [bindings/python/link_flags] + [bindings/python/compile_flags] + [libtorrent-rasterbar.pc] + [libtorrent-rasterbar-cmake.pc] +) + +AC_OUTPUT + + +############################################################################### +# Generating config.report +############################################################################### + +AS_ECHO +AS_ECHO "Configure script has finished system check." +AS_ECHO + +cat > config.report << END + Config results: + -=-=-=-=-=-=-=-=- + +Package: + name: ${PACKAGE_NAME} + version: ${PACKAGE_VERSION} + svn revision: ${SVN_REVISION} + +Build environment: + build system: ${build} + host system: ${host} + target system: ${target} + +Compiler and linker flags: + CPPFlags: ${CPPFLAGS} + CFlags: ${CFLAGS} + CXXFlags: ${CXXFLAGS} + LDFlags: ${LDFLAGS} + Libs: ${LIBS} + Defs: ${DEFS} + +Build options: + deprecated functions: ${ARG_ENABLE_DEPRECATED:-yes} + debug build: ${ARG_ENABLE_DEBUG:-no} + invariant checks: ${ARG_ENABLE_INVARIANT:-no} + logging support: ${ARG_ENABLE_LOGGING:-no} + disk statistics: ${ARG_ENABLE_DISK_STATS:-no} + +Features: + encryption support: ${ARG_ENABLE_ENCRYPTION:-yes} + dht support: ${ARG_ENABLE_DHT:-yes} + pool allocators: ${ARG_ENABLE_POOL_ALLOC:-yes} + +Extra builds: + examples: ${ARG_ENABLE_EXAMPLES:-no} + tests: ${ARG_ENABLE_TESTS:-no} + python bindings: ${ARG_ENABLE_PYTHON_BINDING:-no} + +Pthread library: + CFlags: ${PTHREAD_CFLAGS} + Libs: ${PTHREAD_LIBS} + +Boost libraries: + version: ${BOOST_VERSION} + CPPFlags: ${BOOST_CPPFLAGS} + LDFlags: ${BOOST_LDFLAGS} + boost.system: ${BOOST_SYSTEM_LIB} + boost.chrono: ${BOOST_CHRONO_LIB} + boost.random: ${BOOST_RANDOM_LIB} +END + +AS_IF([test "x$ARG_ENABLE_PYTHON_BINDING" = "xyes"], [ +cat >> config.report << END + boost.python: ${BOOST_PYTHON_LIB} + +Python environment: + -automake- + binary: ${PYTHON} + version: ${PYTHON_VERSION} + platform: ${PYTHON_PLATFORM} + prefix: ${PYTHON_PREFIX} + exec_prefix: ${PYTHON_EXEC_PREFIX} + pythondir: ${pythondir} + pkgpythondir: ${pkgpythondir} + pyexecdir: ${pyexecdir} + pkgpyexecdir: ${pkgpyexecdir} + + -m4- + cppflags: ${PYTHON_CPPFLAGS} + ldflags: ${PYTHON_LDFLAGS} + extra libs: ${PYTHON_EXTRA_LIBS} + +END +]) + +cat >> config.report << END + +External libraries: + system libiconv: ${ARG_WITH_LIBICONV:-no} +END + +AS_IF([test "x$ARG_WITH_LIBICONV" = "xyes"], [ +cat >> config.report << END + +Iconv library: + Iconv Libs: ${ICONV_LIBS} +END +]) + +AS_IF([test "x$ARG_ENABLE_ENCRYPTION" = "xyes"], [ +cat >> config.report << END + +OpenSSL library: + OpenSSL Libs: ${OPENSSL_LIBS} + OpenSSL LDFlags: ${OPENSSL_LDFLAGS} + OpenSSL Includes: ${OPENSSL_INCLUDES} +END +]) + +cat config.report + +AS_ECHO +AS_ECHO "Type 'make' to compile $PACKAGE_STRING" +AS_ECHO "or type 'make V=1' for verbose compiling" +AS_ECHO "and then 'make install' to install it into $prefix" +AS_ECHO diff --git a/docs/building.html b/docs/building.html new file mode 100644 index 0000000..1974801 --- /dev/null +++ b/docs/building.html @@ -0,0 +1,762 @@ + + + + + + +libtorrent manual + + + + + + + + +
+
+ + + + +
+

libtorrent manual

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

downloading and building

+

To acquire the latest version of libtorrent, you'll have to grab it from SVN. +You'll find instructions on how to do this here (see subversion access).

+

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

+
+

Warning

+

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

+

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

+

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

+
+
+

building from svn

+

To build libtorrent from svn you need to check out the libtorrent sources from +sourceforge. If you downloaded a release tarball, you can skip this section.

+

To check out libtorrent follow these instructions.

+
+
+

building with BBv2

+

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

+

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

+

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

+
+

Step 1: Download boost

+

You'll find boost here.

+

Extract the archive to some directory where you want it. For the sake of this +guide, let's assume you extract the package to c:\boost_1_55_0 (I'm using +a windows path in this example since if you're on linux/unix you're more likely +to use the autotools). You'll need at least version 1.49 of the boost library +in order to build libtorrent.

+
+
+

Step 2: Setup BBv2

+

First you need to build bjam. You do this by opening a terminal (In +windows, run cmd). Change directory to +c:\boost_1_55_0\tools\jam\src. Then run the script called +build.bat or build.sh on a unix system. This will build bjam and +place it in a directory starting with bin. and then have the name of your +platform. Copy the bjam.exe (or bjam 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 bjam installed. bjam can be considered an interpreter +that the boost-build system is implemented on. So boost-build uses bjam. +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 +bjam 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_55_0\tools\build\v2.

+

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

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

In a terminal window.

+

The last thing to do to complete the setup of BBv2 is to modify your +user-config.jam file. It is located in c:\boost_1_55_0\tools\build\v2. +Depending on your platform and which compiler you're using, you should add a +line for each compiler and compiler version you have installed on your system +that you want to be able to use with BBv2. For example, if you're using +Microsoft Visual Studio 12 (2013), just add a line:

+
+using msvc : 12.0 ;
+
+

If you use GCC, add the line:

+
+using gcc ;
+
+

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

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

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

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

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

+

Also see the official installation instructions.

+
+
+

Step 3: Building libtorrent

+

When building libtorrent, the Jamfile expects the environment variable +BOOST_ROOT to be set to the boost installation directory. It uses this to +find the boost libraries it depends on, so they can be built and their headers +files found. So, set this to c:\boost_1_55_0. You only need this if you're +building against a source distribution of boost.

+

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

+
+bjam msvc-7.1
+bjam gcc-3.3
+bjam 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.

+
+

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 commandline, either to shared +or static. Most operating systems will only allow linking shared against +the runtime, but on windows you can do both. Example:

+
+bjam msvc-7.1 link=static runtime-link=static
+
+
+

Note

+

When building on windows, the path boost-build puts targets in may be too +long. If you get an error message like: "The input line is long", try to +pass --abbreviate-paths on the bjam 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

+

With boost-build V2 (Milestone 11), the darwin toolset uses the -s linker +option to strip debug symbols. This option is buggy in Apple's GCC, and +will make the executable crash on startup. On Mac OS X, instead build +your release executables with the debug-symbols=on option, and +later strip your executable with strip.

+
+
+

Note

+

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

+
+
+

Note

+

When building on Solaris, you might have to specify stdlib=sun-stlport +on the bjam 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 bjam from there. To build and run the tests, go to the test +directory and run bjam.

+

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_55_0). In the windows environment, they should have the typical +windows format (c:/boost_1_55_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.

+

Build features:

+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
boost build featurevalues
boost-link
    +
  • static - links statically against the boost +libraries.
  • +
  • shared - links dynamically against the boost +libraries.
  • +
+
logging
    +
  • off - logging alerts disabled. The +reason to disable logging is to keep the binary +size low where that matters.
  • +
  • on - default. logging alerts available, +still need to be enabled by the alert mask.
  • +
+
dht
    +
  • on - build with DHT support
  • +
  • off - build without DHT support.
  • +
+
asserts
    +
  • auto - asserts are on if in debug mode
  • +
  • on - asserts are on, even in release mode
  • +
  • off - asserts are disabled
  • +
  • production - assertion failures are logged +to asserts.log in the current working +directory, but won't abort the process. +The file they are logged to can be customized +by setting the global pointer extern char +const* libtorrent_assert_log to a different +filename.
  • +
  • system use the libc assert macro
  • +
+
encryption
    +
  • on - encrypted bittorrent connections +enabled. (Message Stream encryption).
  • +
  • off - turns off support for encrypted +connections. The shipped public domain SHA-1 +implementation is used.
  • +
+
mutable-torrents
    +
  • on - mutable torrents are supported +(BEP 38) (default).
  • +
  • off - mutable torrents are not supported.
  • +
+
crypto
    +
  • built-in - (default) uses built-in SHA-1 +implementation.
  • +
  • openssl - links against openssl and +libcrypto to use for SHA-1 hashing.
  • +
  • gcrypt - links against libgcrypt to use for +SHA-1 hashing.
  • +
+
allocator
    +
  • pool - default, uses pool allocators for +send buffers.
  • +
  • system - uses malloc() and free() +instead. Might be useful to debug buffer issues +with tools like electric fence or libgmalloc.
  • +
  • debug - instruments buffer usage to catch +bugs in libtorrent.
  • +
+
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.
  • +
+
character-set

This setting will only have an affect on windows. +Other platforms are expected to support UTF-8.

+
    +
  • unicode - The unicode version of the win32 +API is used. This is default.
  • +
  • ansi - The ansi version of the win32 API is +used.
  • +
+
invariant-checks

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

+
    +
  • on - internal invariant checks are enabled.
  • +
  • off - internal invariant checks are +disabled. The resulting executable will run +faster than a regular debug build.
  • +
  • full - turns on extra expensive invariant +checks.
  • +
+
debug-symbols
    +
  • on - default for debug builds. This setting +is useful for building release builds with +symbols.
  • +
  • off - default for release builds.
  • +
+
deprecated-functions
    +
  • on - default. Includes deprecated functions +of the API (might produce warnings during build +when deprecated functions are used).
  • +
  • off - excludes deprecated functions from the +API. Generates build errors when deprecated +functions are used.
  • +
+
iconv
    +
  • auto - use iconv for string conversions for +linux and mingw and other posix platforms.
  • +
  • on - force use of iconv
  • +
  • off - force not using iconv (disables locale +awareness except on windows).
  • +
+
i2p
    +
  • on - 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.
  • +
+
+

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

+

The logs created when building vlog or log mode are put in a directory called +libtorrent_logs in the current working directory.

+

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 builtin features.

+
+
+
+

building with autotools

+

First of all, you need to install automake and autoconf. Many +unix/linux systems comes with these preinstalled.

+

The prerequisites for building libtorrent are boost.thread, boost.date_time +and boost.filesystem. Those are the compiled boost libraries needed. The +headers-only libraries needed include (but is not necessarily limited to) +boost.bind, boost.ref, boost.multi_index, boost.optional, boost.lexical_cast, +boost.integer, boost.iterator, boost.tuple, boost.array, boost.function, +boost.smart_ptr, boost.preprocessor, boost.static_assert.

+

If you want to build the client_test example, you'll also need boost.regex +and boost.program_options.

+
+

Step 1: Generating the build system

+

No build system is present if libtorrent is checked out from CVS - it +needs to be generated first. If you're building from a released tarball, +you may skip directly to Step 2: Running configure.

+

Execute the following command to generate the build system:

+
+./autotool.sh
+
+
+
+

Step 2: Running configure

+

In your shell, change directory to the libtorrent directory and run +./configure. This will look for libraries and C++ features that libtorrent +is dependent on. If something is missing or can't be found it will print an +error telling you what failed.

+

The most likely problem you may encounter is that the configure script won't +find the boost libraries. Make sure you have boost installed on your system. +The easiest way to install boost is usually to use the preferred package +system on your platform. Usually libraries and headers are installed in +standard directories where the compiler will find them, but sometimes that +may not be the case. For example when installing boost on darwin using +darwinports (the package system based on BSD ports) all libraries are +installed to /opt/local/lib and headers are installed to +/opt/local/include. By default the compiler will not look in these +directories. You have to set the enviornment variables LDFLAGS and +CXXFLAGS in order to make the compiler find those libs. In this example +you'd set them like this:

+
+export LDFLAGS=-L/opt/local/lib
+export CXXFLAGS=-I/opt/local/include
+
+

It was observed on FreeBSD (release 6.0) that one needs to add '-lpthread' to +LDFLAGS, as Boost::Thread detection will fail without it, even if +Boost::Thread is installed.

+

If you need to set these variables, it may be a good idea to add those lines +to your ~/.profile or ~/.tcshrc depending on your shell.

+

If the boost libraries are named with a suffix on your platform, you may use +the --with-boost-thread= option to specify the suffix used for the thread +library in this case. For more information about these options, run:

+
+./configure --help
+
+

On gentoo the boost libraries that are built with multi-threading support have +the suffix mt.

+

You know that the boost libraries were found if you see the following output +from the configure script:

+
+checking whether the Boost::DateTime library is available... yes
+checking for main in -lboost_date_time... yes
+checking whether the Boost::Filesystem library is available... yes
+checking for main in -lboost_filesystem... yes
+checking whether the Boost::Thread library is available... yes
+checking for main in -lboost_thread... yes
+
+

Another possible source of problems may be if the path to your libtorrent +directory contains spaces. Make sure you either rename the directories with +spaces in their names to remove the spaces or move the libtorrent directory.

+
+
+

Creating a debug build

+

To tell configure to build a debug version (with debug info, asserts +and invariant checks enabled), you have to run the configure script +with the following option:

+
+./configure --enable-debug=yes
+
+
+
+

Creating a release build

+

To tell the configure to build a release version (without debug info, +asserts and invariant checks), you have to run the configure script +with the following option:

+
+./configure --enable-debug=no
+
+

The above option make use of -DNDEBUG, which is used throughout libtorrent.

+
+
+

Step 3: Building libtorrent

+

Once the configure script is run successfully, you just type make and +libtorrent, the examples and the tests will be built.

+

When libtorrent is built it may be a good idea to run the tests, you do this +by running make check.

+

If you want to build a release version (without debug info, asserts and +invariant checks), you have to rerun the configure script and rebuild, like this:

+
+./configure --disable-debug
+make clean
+make
+
+
+
+
+

building with other build systems

+

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

+
+
+

build configurations

+

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

+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
macrodescription
NDEBUGIf you define this macro, all asserts, +invariant checks and general debug code will be +removed. Since there is quite a lot of code in +in header files in libtorrent, it may be +important to define the symbol consistently +across compilation units, including the clients +files. Potential problems is different +compilation units having different views of +structs and class layouts and sizes.
TORRENT_DISABLE_LOGGINGThis macro will disable support for logging +alerts, like log_alert, torrent_log_alert and +peer_log_alert. With this build flag, you +cannot enable those alerts.
TORRENT_STORAGE_DEBUGThis will enable extra expensive invariant +checks in the storage, including logging of +piece sorting.
TORRENT_DISK_STATSThis will create a log of all disk activity +which later can parsed and graphed using +parse_disk_log.py.
UNICODEIf building on windows this will make sure the +UTF-8 strings in pathnames are converted into +UTF-16 before they are passed to the file +operations.
TORRENT_DISABLE_POOL_ALLOCATORDisables use of boost::pool<>.
TORRENT_DISABLE_MUTABLE_TORRENTSDisables mutable torrent support (BEP 38)
TORRENT_LINKING_SHAREDIf this is defined when including the +libtorrent headers, the classes and functions +will be tagged with __declspec(dllimport) +on msvc and default visibility on GCC 4 and +later. Set this in your project if you're +linking against libtorrent as a shared library. +(This is set by the Jamfile when +link=shared is set).
TORRENT_BUILDING_SHAREDIf this is defined, the functions and classes +in libtorrent are marked with +__declspec(dllexport) on msvc, or with +default visibility on GCC 4 and later. This +should be defined when building libtorrent as +a shared library. (This is set by the Jamfile +when link=shared is set).
TORRENT_DISABLE_DHTIf this is defined, the support for trackerless +torrents will be disabled.
TORRENT_DISABLE_ENCRYPTIONThis will disable any encryption support and +the dependencies of a crypto library. +Encryption support is the peer connection +encrypted supported by clients such as +uTorrent, Azureus and KTorrent. +If this is not defined, either +TORRENT_USE_OPENSSL or +TORRENT_USE_GCRYPT must be defined.
TORRENT_DISABLE_EXTENSIONSWhen defined, libtorrent plugin support is +disabled along with support for the extension +handskake (BEP 10).
_UNICODEOn windows, this will cause the file IO +use wide character API, to properly support +non-ansi characters.
TORRENT_DISABLE_RESOLVE_COUNTRIESDefining this will disable the ability to +resolve countries of origin for peer IPs.
TORRENT_DISABLE_INVARIANT_CHECKSThis will disable internal invariant checks in +libtorrent. The invariant checks can sometime +be quite expensive, they typically don't scale +very well. This option can be used to still +build in debug mode, with asserts enabled, but +make the resulting executable faster.
TORRENT_EXPENSIVE_INVARIANT_CHECKSThis will enable extra expensive invariant +checks. Useful for finding particular bugs +or for running before releases.
TORRENT_NO_DEPRECATEThis will exclude all deprecated functions from +the header files and cpp files.
TORRENT_PRODUCTION_ASSERTSDefine to either 0 or 1. Enables assert logging +in release builds.
TORRENT_USE_ASSERTSDefine as 0 to disable asserts unconditionally.
TORRENT_USE_SYSTEM_ASSERTSUses the libc assert macro rather then the +custom one.
+

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

+
+
+

building openssl for windows

+

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

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

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

+
+
+ +
+
+
+ +
+ +
+ + diff --git a/docs/building.rst b/docs/building.rst new file mode 100644 index 0000000..9476448 --- /dev/null +++ b/docs/building.rst @@ -0,0 +1,628 @@ +================= +libtorrent manual +================= + +:Author: Arvid Norberg, arvid@libtorrent.org +:Version: 1.1.1 + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +downloading and building +======================== + +To acquire the latest version of libtorrent, you'll have to grab it from SVN. +You'll find instructions on how to do this here__ (see subversion access). + +__ http://sourceforge.net/svn/?group_id=79942 + +The build systems supported "out of the box" in libtorrent are boost-build v2 +(BBv2) and autotools (for unix-like systems). If you still can't build after +following these instructions, you can usually get help in the ``#libtorrent`` +IRC channel on ``irc.freenode.net``. + +.. warning:: + + A common mistake when building and linking against libtorrent is + to build with one set of configuration options (#defines) and + link against it using a different set of configuration options. Since + libtorrent has some code in header files, that code will not be + compatible with the built library if they see different configurations. + + Always make sure that the same TORRENT_* macros are defined when you + link against libtorrent as when you build it. + + Boost-build supports propagating configuration options to dependencies. + When building using the makefiles, this is handled by setting the + configuration options in the pkg-config file. Always use pkg-config + when linking against libtorrent. + +building from svn +----------------- + +To build libtorrent from svn you need to check out the libtorrent sources from +sourceforge. If you downloaded a release tarball, you can skip this section. + +To check out libtorrent follow these instructions__. + +__ http://sourceforge.net/svn/?group_id=79942 + +building with BBv2 +------------------ + +The primary reason to use boost-build is that it will automatically build the +dependent boost libraries with the correct compiler settings, in order to +ensure that the build targets are link compatible (see `boost guidelines`__ +for some details on this issue). + +__ http://boost.org/more/separate_compilation.html + +Since BBv2 will build the boost libraries for you, you need the full boost +source package. Having boost installed via some package system is usually not +enough (and even if it is enough, the necessary environment variables are +usually not set by the package installer). + +If you want to build against an installed copy of boost, you can skip directly +to step 3 (assuming you also have boost build installed). + + +Step 1: Download boost +~~~~~~~~~~~~~~~~~~~~~~ + +You'll find boost here__. + +__ http://sourceforge.net/project/showfiles.php?group_id=7586&package_id=8041&release_id=619445 + +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_55_0`` (I'm using +a windows path in this example since if you're on linux/unix you're more likely +to use the autotools). You'll need at least version 1.49 of the boost library +in order to build libtorrent. + + +Step 2: Setup BBv2 +~~~~~~~~~~~~~~~~~~ + +First you need to build ``bjam``. You do this by opening a terminal (In +windows, run ``cmd``). Change directory to +``c:\boost_1_55_0\tools\jam\src``. Then run the script called +``build.bat`` or ``build.sh`` on a unix system. This will build ``bjam`` and +place it in a directory starting with ``bin.`` and then have the name of your +platform. Copy the ``bjam.exe`` (or ``bjam`` 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 ``bjam`` installed. ``bjam`` can be considered an interpreter +that the boost-build system is implemented on. So boost-build uses ``bjam``. +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 +``bjam`` 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_55_0\tools\build\v2``. + +To set an environment variable in windows, type for example:: + + set BOOST_BUILD_PATH=c:\boost_1_55_0\tools\build\v2 + +In a terminal window. + +The last thing to do to complete the setup of BBv2 is to modify your +``user-config.jam`` file. It is located in ``c:\boost_1_55_0\tools\build\v2``. +Depending on your platform and which compiler you're using, you should add a +line for each compiler and compiler version you have installed on your system +that you want to be able to use with BBv2. For example, if you're using +Microsoft Visual Studio 12 (2013), just add a line:: + + using msvc : 12.0 ; + +If you use GCC, add the line:: + + using gcc ; + +If you have more than one version of GCC installed, you can add the +commandline used to invoke g++ after the version number, like this:: + + using gcc : 3.3 : g++-3.3 ; + using gcc : 4.0 : g++-4.0 ; + +Another toolset worth mentioning is the ``darwin`` toolset (For MacOS X). +From Tiger (10.4) MacOS X comes with both GCC 3.3 and GCC 4.0. Then you can +use the following toolsets:: + + using darwin : 3.3 : g++-3.3 ; + using darwin : 4.0 : g++-4.0 ; + +Note that the spaces around the semi-colons and colons are important! + +Also see the `official installation instructions`_. + +.. _`official installation instructions`: http://www.boost.org/doc/html/bbv2/installation.html + + +Step 3: Building libtorrent +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When building libtorrent, the ``Jamfile`` expects the environment variable +``BOOST_ROOT`` to be set to the boost installation directory. It uses this to +find the boost libraries it depends on, so they can be built and their headers +files found. So, set this to ``c:\boost_1_55_0``. You only need this if you're +building against a source distribution of boost. + +Then the only thing left is simply to invoke ``bjam``. If you want to specify +a specific toolset to use (compiler) you can just add that to the commandline. +For example:: + + bjam msvc-7.1 + bjam gcc-3.3 + bjam 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. + +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 commandline, either to ``shared`` +or ``static``. Most operating systems will only allow linking shared against +the runtime, but on windows you can do both. Example:: + + bjam msvc-7.1 link=static runtime-link=static + +.. note:: + + When building on windows, the path boost-build puts targets in may be too + long. If you get an error message like: "The input line is long", try to + pass --abbreviate-paths on the bjam 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:: + + With boost-build V2 (Milestone 11), the darwin toolset uses the ``-s`` linker + option to strip debug symbols. This option is buggy in Apple's GCC, and + will make the executable crash on startup. On Mac OS X, instead build + your release executables with the ``debug-symbols=on`` option, and + later strip your executable with ``strip``. + +.. note:: + + Some linux systems requires linking against ``librt`` in order to access + the POSIX clock functions. If you get an error complaining about a missing + symbol ``clock_gettime``, you have to give ``need-librt=yes`` on the + bjam command line. This will make libtorrent link against ``librt``. + +.. note:: + + When building on Solaris, you might have to specify ``stdlib=sun-stlport`` + on the bjam 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 ``bjam`` from there. To build and run the tests, go to the test +directory and run ``bjam``. + +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_55_0``). In the windows environment, they should have the typical +windows format (``c:/boost_1_55_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`_. + +Build features: + ++--------------------------+----------------------------------------------------+ +| boost build feature | values | ++==========================+====================================================+ +| ``boost-link`` | * ``static`` - links statically against the boost | +| | libraries. | +| | * ``shared`` - links dynamically against the boost | +| | libraries. | ++--------------------------+----------------------------------------------------+ +| ``logging`` | * ``off`` - logging alerts disabled. The | +| | reason to disable logging is to keep the binary | +| | size low where that matters. | +| | * ``on`` - default. logging alerts available, | +| | still need to be enabled by the alert mask. | ++--------------------------+----------------------------------------------------+ +| ``dht`` | * ``on`` - build with DHT support | +| | * ``off`` - build without DHT support. | ++--------------------------+----------------------------------------------------+ +| ``asserts`` | * ``auto`` - asserts are on if in debug mode | +| | * ``on`` - asserts are on, even in release mode | +| | * ``off`` - asserts are disabled | +| | * ``production`` - assertion failures are logged | +| | to ``asserts.log`` in the current working | +| | directory, but won't abort the process. | +| | The file they are logged to can be customized | +| | by setting the global pointer ``extern char | +| | const* libtorrent_assert_log`` to a different | +| | filename. | +| | * ``system`` use the libc assert macro | ++--------------------------+----------------------------------------------------+ +| ``encryption`` | * ``on`` - encrypted bittorrent connections | +| | enabled. (Message Stream encryption). | +| | * ``off`` - turns off support for encrypted | +| | connections. The shipped public domain SHA-1 | +| | implementation is used. | ++--------------------------+----------------------------------------------------+ +| ``mutable-torrents`` | * ``on`` - mutable torrents are supported | +| | (`BEP 38`_) (default). | +| | * ``off`` - mutable torrents are not supported. | ++--------------------------+----------------------------------------------------+ +| ``crypto`` | * ``built-in`` - (default) uses built-in SHA-1 | +| | implementation. | +| | * ``openssl`` - links against openssl and | +| | libcrypto to use for SHA-1 hashing. | +| | * ``gcrypt`` - links against libgcrypt to use for | +| | SHA-1 hashing. | ++--------------------------+----------------------------------------------------+ +| ``allocator`` | * ``pool`` - default, uses pool allocators for | +| | send buffers. | +| | * ``system`` - uses ``malloc()`` and ``free()`` | +| | instead. Might be useful to debug buffer issues | +| | with tools like electric fence or libgmalloc. | +| | * ``debug`` - instruments buffer usage to catch | +| | bugs in libtorrent. | ++--------------------------+----------------------------------------------------+ +| ``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. | ++--------------------------+----------------------------------------------------+ +| ``character-set`` | This setting will only have an affect on windows. | +| | Other platforms are expected to support UTF-8. | +| | | +| | * ``unicode`` - The unicode version of the win32 | +| | API is used. This is default. | +| | * ``ansi`` - The ansi version of the win32 API is | +| | used. | ++--------------------------+----------------------------------------------------+ +| ``invariant-checks`` | This setting only affects debug builds (where | +| | ``NDEBUG`` is not defined). It defaults to ``on``. | +| | | +| | * ``on`` - internal invariant checks are enabled. | +| | * ``off`` - internal invariant checks are | +| | disabled. The resulting executable will run | +| | faster than a regular debug build. | +| | * ``full`` - turns on extra expensive invariant | +| | checks. | ++--------------------------+----------------------------------------------------+ +| ``debug-symbols`` | * ``on`` - default for debug builds. This setting | +| | is useful for building release builds with | +| | symbols. | +| | * ``off`` - default for release builds. | ++--------------------------+----------------------------------------------------+ +| ``deprecated-functions`` | * ``on`` - default. Includes deprecated functions | +| | of the API (might produce warnings during build | +| | when deprecated functions are used). | +| | * ``off`` - excludes deprecated functions from the | +| | API. Generates build errors when deprecated | +| | functions are used. | ++--------------------------+----------------------------------------------------+ +| ``iconv`` | * ``auto`` - use iconv for string conversions for | +| | linux and mingw and other posix platforms. | +| | * ``on`` - force use of iconv | +| | * ``off`` - force not using iconv (disables locale | +| | awareness except on windows). | ++--------------------------+----------------------------------------------------+ +| ``i2p`` | * ``on`` - 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. | ++--------------------------+----------------------------------------------------+ + +The ``variant`` feature is *implicit*, which means you don't need to specify +the name of the feature, just the value. + +The logs created when building vlog or log mode are put in a directory called +``libtorrent_logs`` in the current working directory. + +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 builtin features`__. + +__ http://www.boost.org/tools/build/v2/index.html +__ http://www.boost.org/doc/html/bbv2/reference.html#bbv2.advanced.builtins.features + + +building with autotools +----------------------- + +First of all, you need to install ``automake`` and ``autoconf``. Many +unix/linux systems comes with these preinstalled. + +The prerequisites for building libtorrent are boost.thread, boost.date_time +and boost.filesystem. Those are the *compiled* boost libraries needed. The +headers-only libraries needed include (but is not necessarily limited to) +boost.bind, boost.ref, boost.multi_index, boost.optional, boost.lexical_cast, +boost.integer, boost.iterator, boost.tuple, boost.array, boost.function, +boost.smart_ptr, boost.preprocessor, boost.static_assert. + +If you want to build the ``client_test`` example, you'll also need boost.regex +and boost.program_options. + +Step 1: Generating the build system +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +No build system is present if libtorrent is checked out from CVS - it +needs to be generated first. If you're building from a released tarball, +you may skip directly to `Step 2: Running configure`_. + +Execute the following command to generate the build system:: + + ./autotool.sh + +Step 2: Running configure +~~~~~~~~~~~~~~~~~~~~~~~~~ + +In your shell, change directory to the libtorrent directory and run +``./configure``. This will look for libraries and C++ features that libtorrent +is dependent on. If something is missing or can't be found it will print an +error telling you what failed. + +The most likely problem you may encounter is that the configure script won't +find the boost libraries. Make sure you have boost installed on your system. +The easiest way to install boost is usually to use the preferred package +system on your platform. Usually libraries and headers are installed in +standard directories where the compiler will find them, but sometimes that +may not be the case. For example when installing boost on darwin using +darwinports (the package system based on BSD ports) all libraries are +installed to ``/opt/local/lib`` and headers are installed to +``/opt/local/include``. By default the compiler will not look in these +directories. You have to set the enviornment variables ``LDFLAGS`` and +``CXXFLAGS`` in order to make the compiler find those libs. In this example +you'd set them like this:: + + export LDFLAGS=-L/opt/local/lib + export CXXFLAGS=-I/opt/local/include + +It was observed on FreeBSD (release 6.0) that one needs to add '-lpthread' to +LDFLAGS, as Boost::Thread detection will fail without it, even if +Boost::Thread is installed. + +If you need to set these variables, it may be a good idea to add those lines +to your ``~/.profile`` or ``~/.tcshrc`` depending on your shell. + +If the boost libraries are named with a suffix on your platform, you may use +the ``--with-boost-thread=`` option to specify the suffix used for the thread +library in this case. For more information about these options, run:: + + ./configure --help + +On gentoo the boost libraries that are built with multi-threading support have +the suffix ``mt``. + +You know that the boost libraries were found if you see the following output +from the configure script:: + + checking whether the Boost::DateTime library is available... yes + checking for main in -lboost_date_time... yes + checking whether the Boost::Filesystem library is available... yes + checking for main in -lboost_filesystem... yes + checking whether the Boost::Thread library is available... yes + checking for main in -lboost_thread... yes + +Another possible source of problems may be if the path to your libtorrent +directory contains spaces. Make sure you either rename the directories with +spaces in their names to remove the spaces or move the libtorrent directory. + +Creating a debug build +~~~~~~~~~~~~~~~~~~~~~~ + +To tell configure to build a debug version (with debug info, asserts +and invariant checks enabled), you have to run the configure script +with the following option:: + + ./configure --enable-debug=yes + +Creating a release build +~~~~~~~~~~~~~~~~~~~~~~~~ + +To tell the configure to build a release version (without debug info, +asserts and invariant checks), you have to run the configure script +with the following option:: + + ./configure --enable-debug=no + +The above option make use of -DNDEBUG, which is used throughout libtorrent. + +Step 3: Building libtorrent +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Once the configure script is run successfully, you just type ``make`` and +libtorrent, the examples and the tests will be built. + +When libtorrent is built it may be a good idea to run the tests, you do this +by running ``make check``. + +If you want to build a release version (without debug info, asserts and +invariant checks), you have to rerun the configure script and rebuild, like this:: + + ./configure --disable-debug + make clean + make + +building with other build systems +--------------------------------- + +If you're building in MS Visual Studio, you may have to set the compiler +options "force conformance in for loop scope", "treat wchar_t as built-in +type" and "Enable Run-Time Type Info" to Yes. + +build configurations +-------------------- + +By default libtorrent is built In debug mode, and will have pretty expensive +invariant checks and asserts built into it. If you want to disable such checks +(you want to do that in a release build) you can see the table below for which +defines you can use to control the build. + ++----------------------------------------+-------------------------------------------------+ +| macro | description | ++========================================+=================================================+ +| ``NDEBUG`` | If you define this macro, all asserts, | +| | invariant checks and general debug code will be | +| | removed. Since there is quite a lot of code in | +| | in header files in libtorrent, it may be | +| | important to define the symbol consistently | +| | across compilation units, including the clients | +| | files. Potential problems is different | +| | compilation units having different views of | +| | structs and class layouts and sizes. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_DISABLE_LOGGING`` | This macro will disable support for logging | +| | alerts, like log_alert, torrent_log_alert and | +| | peer_log_alert. With this build flag, you | +| | cannot enable those alerts. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_STORAGE_DEBUG`` | This will enable extra expensive invariant | +| | checks in the storage, including logging of | +| | piece sorting. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_DISK_STATS`` | This will create a log of all disk activity | +| | which later can parsed and graphed using | +| | ``parse_disk_log.py``. | ++----------------------------------------+-------------------------------------------------+ +| ``UNICODE`` | If building on windows this will make sure the | +| | UTF-8 strings in pathnames are converted into | +| | UTF-16 before they are passed to the file | +| | operations. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_DISABLE_POOL_ALLOCATOR`` | Disables use of ``boost::pool<>``. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_DISABLE_MUTABLE_TORRENTS`` | Disables mutable torrent support (`BEP 38`_) | ++----------------------------------------+-------------------------------------------------+ +| ``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_OPENSSL`` or | +| | ``TORRENT_USE_GCRYPT`` must be defined. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_DISABLE_EXTENSIONS`` | When defined, libtorrent plugin support is | +| | disabled along with support for the extension | +| | handskake (BEP 10). | ++----------------------------------------+-------------------------------------------------+ +| ``_UNICODE`` | On windows, this will cause the file IO | +| | use wide character API, to properly support | +| | non-ansi characters. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_DISABLE_RESOLVE_COUNTRIES`` | Defining this will disable the ability to | +| | resolve countries of origin for peer IPs. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_DISABLE_INVARIANT_CHECKS`` | This will disable internal invariant checks in | +| | libtorrent. The invariant checks can sometime | +| | be quite expensive, they typically don't scale | +| | very well. This option can be used to still | +| | build in debug mode, with asserts enabled, but | +| | make the resulting executable faster. | ++----------------------------------------+-------------------------------------------------+ +| ``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 cpp 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. | ++----------------------------------------+-------------------------------------------------+ + +.. _`BEP 38`: http://www.bittorrent.org/beps/bep_0038.html + +If you experience that libtorrent uses unreasonable amounts of cpu, it will +definitely help to define ``NDEBUG``, since it will remove the invariant checks +within the library. + +building openssl for windows +---------------------------- + +To build openssl for windows with Visual Studio 7.1 (2003) execute the following commands +in a command shell:: + + perl Configure VC-WIN32 --prefix="c:/openssl + call ms\do_nasm + call "C:\Program Files\Microsoft Visual Studio .NET 2003\vc7\bin\vcvars32.bat" + nmake -f ms\nt.mak + copy inc32\openssl "C:\Program Files\Microsoft Visual Studio .NET 2003\vc7\include\" + copy out32\libeay32.lib "C:\Program Files\Microsoft Visual Studio .NET 2003\vc7\lib" + copy out32\ssleay32.lib "C:\Program Files\Microsoft Visual Studio .NET 2003\vc7\lib" + +This will also install the headers and library files in the visual studio directories to +be picked up by libtorrent. + diff --git a/docs/client_test.html b/docs/client_test.html new file mode 100644 index 0000000..4543543 --- /dev/null +++ b/docs/client_test.html @@ -0,0 +1,112 @@ + + + + + + +client_test example program + + + + + + + +
+
+ + + + +
+

client_test example program

+ +

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 hardcoded. The commandline arguments are:

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

You can start any number of torrent downloads/seeds via the commandline. +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:

+client_test.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 unpause 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 dowloading parts of pieces. It may make it easier to determine +which peer that sent the corrupt data if a piece fails the hash test.

+ +
+
+
+ +
+ +
+ + diff --git a/docs/client_test.png b/docs/client_test.png new file mode 100644 index 0000000000000000000000000000000000000000..a1733ee21bb021de85a2ab7129b6545a44c71ed7 GIT binary patch literal 664722 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%0A3< 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 asu9~T;^=&clqc)~pZ2{dFpR%d3tfftC)1qW- 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�USK7iwGbmG37!;!?k~IMOJmXhi@cB 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%0AeK`-SDVC&0lErhs3qBUpUnqGKvC&y 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 Y-2V9;tXnxBem((dae1*~5xsZ+4Y0Ag-v9sr literal 0 HcmV?d00001 diff --git a/docs/client_test.rst b/docs/client_test.rst new file mode 100644 index 0000000..2f3b9eb --- /dev/null +++ b/docs/client_test.rst @@ -0,0 +1,45 @@ +=========================== +client_test example program +=========================== + +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 hardcoded. The commandline arguments are:: + + client_test ... + +You can start any number of torrent downloads/seeds via the commandline. +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:: client_test.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`` unpause 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 dowloading parts of pieces. It may make it easier to determine +which peer that sent the corrupt data if a piece fails the hash test. + diff --git a/docs/complete_bit_prefixes.png b/docs/complete_bit_prefixes.png new file mode 100644 index 0000000000000000000000000000000000000000..3bc73874d9f321c8e4848f6084c41fbc1a3d0371 GIT binary patch literal 7795 zcmZ8`2{@GB`}bfbW5f&w5#}+JwR}-oiZKjE%9^NTYbq@YktGI08SBiDt+Fc>g_5OY z%QCVgk`h@Oq!=3eGR^<-eSg3A{a^2OJSK$=+N2pXJ4ikoEFA-JoX5MSJhO;X7dIi;A{{Q_#1&u!0u0Q z{`WgUM#dKu1p?`g?fDJa7Lf<(`N%`3@*?aOA|rX`kc~+Hne@O{S8DDEFE0=0 zMFPBs5H^K<7t%kHH$t~Fak`=viH1a?(UB(L6B+5z80o=_+?L;jmj^x`ygXlCV27h59YFy?v*?iH#eJLaWU)1RP62f*1wua&IUI8e!#Z5d1c}<0>%kOnZgxg{`U`) z?_Ae3?(J$cDlT)LCjwJ!iC*8Lp(Z9yc*&UV*!ux_hW@R+jb)BYVx%Y)+7$nemI_n8 ztd$C@Jz~qf|DNco4AU;=ovh7iW!7S)n{H)kGK)1|Cg|3-w-MzT39Pr8G{&u~iJ!#c zP(`O#U5fx)3mbGTfGy$5qtxJ8R9K2Bo|$(7#d1{OwuQ-&Qv#klpsDCA=O{L zwug-ziU7{<3la!{`{sd+ZfB$48B~j689jx_L%-qtx$#ATgce&m4ai$#!w1vQ4GgWF|F ziFY2o2AD(!g<5)FMkqdDoo_BWG7m_c6`Q$=Q-v$KUq-v2V-ii}!4H|}qvQwdAlIIb z(x&+0G1o7Go}?oR_{eb7p!{Nbw4{-NUCi4ji81CIvUGp^=R;nDV?lZ(pM$a@CzmEs z{ghYvJI zt#cXqTo3ab!+b0FT{6~Z{FnvA$BN>DSIdSaaup5mD~PohlU%pNo%Mv z>GSd69Jf|?w;vXsO$8#z-&wQK^rZN=iT_a8MY*kc0O{d<3_t8ih@`dMNEh=i^C~`m z)gcl1rn2(DKhGOydm*Xox=EW@ONJS-9ihS2l)F%lwYRdw74$Uw?a&ZU3y>t8ZDZXi z?%6dMWK6TB9t$`9M=+k_5cZkLkB5Y#UXHZtf#2+N2p0o$H+)S6Jc7^KR`PxctfmZLlmYs@^c^4xL`22UbpAG+rY!M zQieHY!NODg-iOpvN}aMOgz1jr>$hJeACKjT>>qtK0WHYwA?rg9W}{;Ox(Xc5ps4O| zRY}rZE^KzAFmvrQbA1zr7D+=y#0y+LXb{3~evML_aQfbod#Lm9r9yWVu7j7KQ|gSK zb?aJC^*eQ!fg*ol4youVle43xA!B}GR!0@Cl%Vumh{K+XJ%*>M|5`RVx&+~#XPy~| zNpMchzDz+M#AHLCFiZj*t>}Lz2QXNe{pomH*|>#w6&cti5KfxOrAky3j)X8p+ZQ*g zuSSQXuoPJe@EU`TA?C$nBz10xsZrlMg!K^0T^JW;sFEGfg2q1_oAcP+Y zZUO{|H{Q2f5T?jw{1!xjx@v;D5`;KO=+E3W6a21IC?QEgp2F96BkS6|EWYREjpU_Y z(XK6dz#=~y>gKk55BcKR-R%1&clc%m_am&Q3Ph@aHS^s$;!@a}kt2WdZTVu;7FION zd#fYgy}pW&@+oiiQVIV|bP?c$I)AZ7VPT5uCPJL24jxetI?qa%C`Tpo5$T5HCAUgN zT+~kfHk$JuoZW(jqLQwXBBf3tSHneV8NVPTeIgqK0G12lPai_5JBhp8`z$rt7C_+F z{(N;n>0l=*pOyt~6Y&SqCP1JdIEHIY#zgV>9&ptYxNF=D3SChR&0<-Kd=XUSsxJN* zXhb4R4IPx3S2i=26gJEr1EEL%oKmue`mJwwyu95}cq`c88`#VO>rT&Do_`HHIXccp z?jQO`TXyRw7{X%Td^q+TD7!<>W1gvOlB*Z6v{o~|M5@BXmWfglKM9^1Y#pixO;q0f za!ye*i^Z)P9Pg}-3|hG0Sne%=QwfLu3a_t?cFyBIZkCWR!%sbY2$b~?f=hbwD$b6z zf5>AQ-*{^&vf~5Oj#Q87xV8Qogk@ATDx}bE{YkOXi)`Sckt&>F`-WemnI;?XJ9
    #R#X0fJ7b{AkTJn== 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 + + + + + +libtorrent manual + + + + + + + + +
    +
    + + + + +
    +

    libtorrent manual

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

    contributing to libtorrent

    +

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

    +
      +
    1. +
      Testing
      +

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

      +

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

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

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

      +
      +
      +
    2. +
    3. +
      Code
      +

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

      +

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

      +
      +
      +
    4. +
    +

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

    +

    For outstanding things to do, see the todo list.

    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/contributing.rst b/docs/contributing.rst new file mode 100644 index 0000000..00a7a98 --- /dev/null +++ b/docs/contributing.rst @@ -0,0 +1,58 @@ +================= +libtorrent manual +================= + +:Author: Arvid Norberg, arvid@libtorrent.org +:Version: 1.1.1 + +.. 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`: http://lists.sourceforge.net/lists/listinfo/libtorrent-discuss + +1. Testing + This is not just limited to finding bugs and ways to reproduce crashes, but also + sub-optimal behavior is certain scenarios and finding ways to reproduce those. Please + report any issue to the bug tracker at `github`_. + + New features that need testing are streaming (``set_piece_deadline()``), the different + choking algorithms (especially the new BitTyrant choker), the disk cache options (such + as ``explicit_cache``). + +.. _`github`: https://github.com/arvidn/libtorrent/issues + +2. Documentation + Finding typos or outdated sections in the documentation. Contributing documentation + based on your own experience and experimentation with the library or with BitTorrent + in general. Non-reference documentation is very much welcome as well, higher level + descriptions on how to configure libtorrent for various situations for instance. + The reference documentation for libtorrent is generated from the header files. + For updates, please submit a `pull request`_. + +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 udpates, 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 + diff --git a/docs/cwnd.png b/docs/cwnd.png new file mode 100644 index 0000000000000000000000000000000000000000..9f850caa7a84b54f896447625f57a8e54ff3d04e GIT binary patch literal 18998 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 literal 0 HcmV?d00001 diff --git a/docs/delays.png b/docs/delays.png new file mode 100644 index 0000000000000000000000000000000000000000..66db05e98bac6c83bcfc27448a7376d774492013 GIT binary patch literal 10875 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 gesbs>QD*ylh literal 0 HcmV?d00001 diff --git a/docs/delays_thumb.png b/docs/delays_thumb.png new file mode 100644 index 0000000000000000000000000000000000000000..84739446cfa90df6736fc1fe7f7aa367062351dd GIT binary patch literal 10938 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 + + + + + + + + + + + + + + +
    +
    + + + + +
    + + +++ + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    +
    +

    Mainline DHT extensions

    +

    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 alot when debugging +and finding errors in client implementations. The string is encoded as four +characters, two characters describing the client and two characters interpreted +as a binary number describing the client version.

    +

    Currently known clients:

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

    IPv6 support

    +

    This extension is superseeded by BEP 32.

    +

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

    +

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

    +

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

    +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/dht_extensions.rst b/docs/dht_extensions.rst new file mode 100644 index 0000000..a97c716 --- /dev/null +++ b/docs/dht_extensions.rst @@ -0,0 +1,68 @@ +:Author: Arvid Norberg, arvid@libtorrent.org + +Mainline DHT extensions +======================= + +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 alot 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 superseeded by** `BEP 32`_. + +.. _`BEP 32`: http://bittorrent.org/beps/bep_0032.html + +The DHT messages that don't support IPv6 are the ``nodes`` replies. +They encode all the contacts as 6 bytes packed together in sequence in a +string. The problem is that IPv6 endpoints cannot be encoded as 6 bytes, but +needs 18 bytes. The extension libtorrent applies is to add another key, called +``nodes2``. + +``nodes2`` may be present in replies that contains a ``nodes`` key. It is encoded +as a list of strings. Each string represents one contact and is encoded as 20 +bytes node-id and then a variable length encoded IP address (6 bytes in IPv4 case +and 18 bytes in IPv6 case). + +As an optimization, libtorrent does not include the extra key in case there are +only IPv4 nodes present. + diff --git a/docs/dht_rss.html b/docs/dht_rss.html new file mode 100644 index 0000000..46f8abd --- /dev/null +++ b/docs/dht_rss.html @@ -0,0 +1,435 @@ + + + + + + +BitTorrent extension for DHT RSS feeds + + + + + + + + +
    +
    + + + + +
    +

    BitTorrent extension for DHT RSS feeds

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

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

    +

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

    +

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

    +

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

    +

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

    +
    +

    terminology

    +

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

    +
    +
    +

    linked lists

    +

    Items are chained together in a geneal 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 zeroes.

    +

    The ID an items is announced to is determined by the SHA1 hash of the bencoded representation +of the item iteself. 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 stora any arbitrary keys that +are announced to an item, within reasonable restriction such as nesting, size and numeric +range of integers.

    +
    +
    +

    skip lists

    +

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

    +

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

    +

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

    +

    Figure of the skip list in the first list item:

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

    n refers to the byte offset into the next field.

    +
    +
    +

    list-head

    +

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

    +

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

    +

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

    +
    +
    +

    messages

    +

    These are the messages to deal with linked lists.

    +

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

    +

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

    +

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

    +
    +

    requesting items

    +

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

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

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

    +

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

    +
    +
    +

    request item response

    +

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

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

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

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

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

    +

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

    +
    +
    +

    announcing items

    +

    The message format for announcing a list head:

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

    The message format for announcing a list item:

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

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

    +
    +
    +
    +

    re-announcing

    +

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

    +

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

    +

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

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

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

    +
    +
    +

    timeouts

    +

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

    +

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

    +
    +
    +

    RSS feeds

    +

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

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

    example

    +

    This is an example of an announce_item message:

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

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

    +

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

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

    (note that binary data is printed as hex)

    +
    +
    +
    +

    RSS feed URI scheme

    +

    The proposed URI scheme for DHT feeds is:

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

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

    +

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

    +
    +
    +

    rationale

    +

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

    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/dht_rss.rst b/docs/dht_rss.rst new file mode 100644 index 0000000..c3dcc32 --- /dev/null +++ b/docs/dht_rss.rst @@ -0,0 +1,397 @@ +====================================== +BitTorrent extension for DHT RSS feeds +====================================== + +:Author: Arvid Norberg, arvid@libtorrent.org +:Version: 1.1.1 + +.. 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 geneal 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 zeroes. + +The ID an items is announced to is determined by the SHA1 hash of the bencoded representation +of the item iteself. 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 stora 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 +http://cr.yp.to/, curve25519 is free from patent claims and there are open implementations +in both C and Java. + +.. _curve25519: http://cr.yp.to/ecdh.html + diff --git a/docs/dht_sec.html b/docs/dht_sec.html new file mode 100644 index 0000000..9708540 --- /dev/null +++ b/docs/dht_sec.html @@ -0,0 +1,305 @@ + + + + + + +BitTorrent DHT security extension + + + + + + + + +
    +
    + + + + +
    +

    BitTorrent DHT security extension

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

    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 estmiated +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 remoain uniform. This is why CRC32C (Castagnoli) was +chosen as the hash function.

    +

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

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

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

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

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

    +

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

    +

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

    +ip_id_v4.png +ip_id_v6.png +

    Example code code for calculating a valid node ID:

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

    test vectors:

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

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

    +
    +
    +

    bootstrapping

    +

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

    +

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

    +

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

    +

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

    +
    +
    +

    rationale

    +

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

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

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

    +

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

    +complete_bit_prefixes.png +

    This test takes into account IP addresses that are not globally routable, i.e. +reserved for local networks, multicast and other things. It also takes into +account that some /8 blocks are not in use by end-users and exremely 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 interger) is divided up into 1000 buckets. Every valid +IP and r input is run through the algorithm and the result is put in the +bucket it falls in. The expectation is that each bucket has roughly an equal +number of results falling into it. The following graph shows the resulting +histogram, comparing SHA-1 and CRC32C.

    +hash_distribution.png +

    The source code for these tests can be found here.

    +

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

    +
    +
    +

    enforcement

    +

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

    +

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

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

    backwards compatibility and transition

    +

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

    +

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

    +
    +
    +

    forward compatibility

    +

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

    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/dht_sec.rst b/docs/dht_sec.rst new file mode 100644 index 0000000..67fea18 --- /dev/null +++ b/docs/dht_sec.rst @@ -0,0 +1,256 @@ +================================= +BitTorrent DHT security extension +================================= + +:Author: Arvid Norberg, arvid@libtorrent.org +:Version: 1.1.1 + +.. 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 estmiated +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 remoain uniform. This is why CRC32C (Castagnoli) was +chosen as the hash function. + +The expression to calculate a valid ID prefix (from an IPv4 address) is:: + + crc32c((ip & 0x030f3fff) | (r << 29)) + +And for an IPv6 address (``ip`` is the high 64 bits of the address):: + + crc32c((ip & 0x0103070f1f3f7fff) | (r << 61)) + +``r`` is a random number in the range [0, 7]. The resulting integer, +representing the masked IP address is supposed to be big-endian before +hashed. The "|" operator means bit-wise OR. + +The details of implementing this is to evaluate the expression, store the +result in a big endian 64 bit integer and hash those 8 bytes with CRC32C. + +The first (most significant) 21 bits of the node ID used in the DHT MUST +match the first 21 bits of the resulting hash. The last byte of the hash MUST +match the random number (``r``) used to generate the hash. + +.. image:: ip_id_v4.png +.. image:: ip_id_v6.png + +Example code code for calculating a valid node ID:: + + uint8_t* ip; // our external IPv4 or IPv6 address (network byte order) + int num_octets; // the number of octets to consider in ip (4 or 8) + uint8_t node_id[20]; // resulting node ID + + uint8_t v4_mask[] = { 0x03, 0x0f, 0x3f, 0xff }; + uint8_t v6_mask[] = { 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff }; + uint8_t* mask = num_octets == 4 ? v4_mask : v6_mask; + + for (int i = 0; i < num_octets; ++i) + ip[i] &= mask[i]; + + uint32_t rand = std::rand() & 0xff; + uint8_t r = rand & 0x7; + ip[0] |= r << 5; + + uint32_t crc = 0; + crc = crc32c(crc, ip, num_octets); + + // only take the top 21 bits from crc + node_id[0] = (crc >> 24) & 0xff; + node_id[1] = (crc >> 16) & 0xff; + node_id[2] = ((crc >> 8) & 0xf8) | (std::rand() & 0x7); + for (int i = 3; i < 19; ++i) node_id[i] = std::rand(); + node_id[19] = rand; + +test vectors: + +.. parsed-literal:: + + IP rand example node ID + ============ ===== ========================================== + 124.31.75.21 1 **5fbfbf** f10c5d6a4ec8a88e4c6ab4c28b95eee4 **01** + 21.75.31.124 86 **5a3ce9** c14e7a08645677bbd1cfe7d8f956d532 **56** + 65.23.51.170 22 **a5d432** 20bc8f112a3d426c84764f8c2a1150e6 **16** + 84.124.73.14 65 **1b0321** dd1bb1fe518101ceef99462b947a01ff **41** + 43.213.53.83 90 **e56f6c** bf5b7c4be0237986d5243b87aa6d5130 **5a** + +The bold parts of the node ID are the important parts. The rest are +random numbers. The last bold number of each row has only its most significant +bit pulled from the CRC32C function. The lower 3 bits are random. + +bootstrapping +------------- + +In order to set ones initial node ID, the external IP needs to be known. This +is not a trivial problem. With this extension, *all* DHT responses SHOULD include +a *top-level* field called ``ip``, containing a compact binary representation of +the requestor's IP and port. That is big endian IP followed by 2 bytes of big endian +port. + +The IP portion is the same byte sequence used to verify the node ID. + +It is important that the ``ip`` field is in the top level dictionary. Nodes that +enforce the node-ID will respond with an error message ("y": "e", "e": { ... }), +whereas a node that supports this extension but without enforcing it will respond +with a normal reply ("y": "r", "r": { ... }). + +A DHT node which receives an ``ip`` result in a request SHOULD consider restarting +its DHT node with a new node ID, taking this IP into account. Since a single node +can not be trusted, there should be some mechanism to determine whether or +not the node has a correct understanding of its external IP or not. This could +be done by voting, or only restart the DHT once at least a certain number of +nodes, from separate searches, tells you your node ID is incorrect. + +rationale +--------- + +The choice of using CRC32C instead of a more traditional cryptographic hash +function is justified primarily of these reasons: + +1. it is a fast function +2. produces well distributed results +3. there is no need for the hash function to be one-way (the input set is + so small that any hash function could be reversed). +4. CRC32C (Castagnoli) is supported in hardware by SSE 4.2, which can + significantly speed up computation + +There are primarily two tests run on SHA-1 and CRC32C to establish the +distribution of results. The first one is the number of bits in the output +set that contain every possible combination of bits. The CRC32C function +has a longer such prefix in its output than SHA-1. This means nodes will still +have well uniformly distributed IDs, even when IP addresses in use are not +uniformly distributed. + +The following graph illustrate a few different hash functions with regard +to this property. + +.. image:: complete_bit_prefixes.png + +This test takes into account IP addresses that are not globally routable, i.e. +reserved for local networks, multicast and other things. It also takes into +account that some /8 blocks are not in use by end-users and exremely 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 interger) is divided up into 1000 buckets. Every valid +IP and ``r`` input is run through the algorithm and the result is put in the +bucket it falls in. The expectation is that each bucket has roughly an equal +number of results falling into it. The following graph shows the resulting +histogram, comparing SHA-1 and CRC32C. + +.. image:: hash_distribution.png + +The source code for these tests can be found here_. + +.. _here: https://github.com/arvidn/hash_complete_prefix + +The reason to use CRC32C instead of the CRC32 implemented by zlib is that +Intel CPUs have hardware support for the CRC32C calculations. The input +being exactly 4 bytes is also deliberate, to make it fit in a single +instruction. + +enforcement +----------- + +Once enforced, write tokens from peers whose node ID does not match its external +IP should be considered dropped. In other words, a peer that uses a non-matching +ID MUST never be used to store information on, regardless of which request. In the +original DHT specification only ``announce_peer`` stores data in the network, +but any future extension which stores data in the network SHOULD use the same +restriction. + +Any peer on a local network address is exempt from this node ID verification. +This includes the following IP blocks: + +10.0.0.0/8 + reserved for local networks +172.16.0.0/12 + reserved for local networks +192.168.0.0/16 + reserved for local networks +169.254.0.0/16 + reserved for self-assigned IPs +127.0.0.0/8 + reserved for loopback + + +backwards compatibility and transition +-------------------------------------- + +During some transition period, this restriction should not be enforced, and +peers whose node ID does not match this formula relative to their external IP +should not be blocked. + +Requests from peers whose node ID does not match their external IP should +always be serviced, even after the transition period. The attack this protects +from is storing data on an attacker's node, not servicing an attackers request. + +forward compatibility +--------------------- + +If the total size of the DHT grows to the point where the inherent size limit +in this proposal is too small, the modulus constants can be updated in a new +proposal, and another transition period where both sets of modulus constants +are accepted. + diff --git a/docs/dht_store.html b/docs/dht_store.html new file mode 100644 index 0000000..79c4867 --- /dev/null +++ b/docs/dht_store.html @@ -0,0 +1,538 @@ + + + + + + +BitTorrent extension for arbitrary DHT store + + + + + + + + +
    +
    + + + + +
    +

    BitTorrent extension for arbitrary DHT store

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

    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 eachother. An implementation can either store mutable and immutable items +in the same hash table internally, or in separate ones and potentially do two +lookups for get requests.

    +

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

    +

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

    +
    +
    +

    immutable items

    +

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

    +

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

    +
    +

    put message

    +

    Request:

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

    Response:

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

    get message

    +

    Request:

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

    Response:

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

    mutable items

    +

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

    +

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

    +

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

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

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

    +

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

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

    put message

    +

    Request:

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

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

    +

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

    +

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

    +

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

    +

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

    +

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

    +
    +
    +

    CAS

    +

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

    +

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

    +

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

    +

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

    +
    +
    +

    response

    +

    Response:

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

    errors

    +

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

    +

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

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

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

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

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

    +
    +
    +

    get message

    +

    Request:

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

    Response:

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

    signature verification

    +

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

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

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

    +

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

    +
    +
    +

    expiration

    +

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

    +

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

    +
    +
    +

    test vectors

    +
    +

    test 1 (mutable)

    +

    value:

    +
    +12:Hello World!
    +
    +

    buffer being signed:

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

    public key:

    +
    +77ff84905a91936367c01360803104f92432fcd904a43511876df5cdf3e7e548
    +
    +

    private key:

    +
    +e06d3183d14159228433ed599221b80bd0a5ce8352e4bdf0262f76786ef1c74d
    +b7e7a9fea2c0eb269d61e3b38e450a22e754941ac78479d6c54e1faf6037881d
    +
    +

    target ID:

    +
    +4a533d47ec9c7d95b1ad75f576cffc641853b750
    +
    +

    signature:

    +
    +305ac8aeb6c9c151fa120f120ea2cfb923564e11552d06a5d856091e5e853cff
    +1260d3f39e4999684aa92eb73ffd136e6f4f3ecbfda0ce53a1608ecd7ae21f01
    +
    +
    +
    +

    test 2 (mutable with salt)

    +

    value:

    +
    +12:Hello World!
    +
    +

    salt:

    +
    +foobar
    +
    +

    buffer being signed:

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

    public key:

    +
    +77ff84905a91936367c01360803104f92432fcd904a43511876df5cdf3e7e548
    +
    +

    private key:

    +
    +e06d3183d14159228433ed599221b80bd0a5ce8352e4bdf0262f76786ef1c74d
    +b7e7a9fea2c0eb269d61e3b38e450a22e754941ac78479d6c54e1faf6037881d
    +
    +

    target ID:

    +
    +411eba73b6f087ca51a3795d9c8c938d365e32c1
    +
    +

    signature:

    +
    +6834284b6b24c3204eb2fea824d82f88883a3d95e8b4a21b8c0ded553d17d17d
    +df9a8a7104b1258f30bed3787e6cb896fca78c58f8e03b5f18f14951a87d9a08
    +
    +
    +
    +

    test 3 (immutable)

    +

    value:

    +
    +12:Hello World!
    +
    +

    target ID:

    +
    +e5f96f6f38320f0f33959cb4d3d656452117aadb
    +
    +
    +
    +
    +

    resources

    +

    Libraries that implement ed25519 DSA:

    + +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/dht_store.rst b/docs/dht_store.rst new file mode 100644 index 0000000..86a5c90 --- /dev/null +++ b/docs/dht_store.rst @@ -0,0 +1,481 @@ +============================================ +BitTorrent extension for arbitrary DHT store +============================================ + +:Author: Arvid Norberg, arvid@libtorrent.org +:Version: 1.1.1 + +.. 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 eachother. 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: http://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: http://nacl.cr.yp.to/ +.. _libsodium: https://github.com/jedisct1/libsodium +.. _`nightcracker's ed25519`: https://github.com/nightcracker/ed25519 + diff --git a/docs/disk_cache.diagram b/docs/disk_cache.diagram new file mode 100644 index 0000000..da666fb --- /dev/null +++ b/docs/disk_cache.diagram @@ -0,0 +1,12 @@ + <---- "recently" "frequently" ---> + "used" "used" + + L1 ghost L1 L2 L2 ghost + AAAAAAAAFFFFFFFF FFFFFFFFAAAAAAAA + + cache size + <---------------> + + 2x cache size + <-------------------------------> + diff --git a/docs/disk_cache.png b/docs/disk_cache.png new file mode 100644 index 0000000000000000000000000000000000000000..5ae781de371a9fd901e2284965d8927e9daea3e3 GIT binary patch literal 1658 zcmaJ?dpOez82<67V=G<&UCfa7DHsYG;K+-n9$^IOUfmNquHt>mt>t>CLGD- zWFjktS`QPgStBu*Hlb)}%o=H@{y0zPJkRO;^L_98eE+=f_k8d3T{?_+)B_uU0RYf* zcETP304?%Y9IvbWwIV$nTmWFPoio<%XiT2Gzh`Z+*@_fD@%SUn*3z9X`OPPLlT3y* z-X;bh7E$(DozS!n@L$mP)&iD%5^^eqqmka-!TAvCokX9+C~&j2&qCl>hOVlUQ*I-G zY(oim>QacPU0o>OcHQW;Er)mX5vFAYr(H`%hAHnR80LLWBg~i-tfT^6_?d4)pC+y$ zM0aK{d-H5%n!(+KQ?gfV_?JWb#IH<^yV$%}$Cc89*iCa6j-6Zs4*hb(D*eioZHir7 zy(;rKYxP{$6y=Dhc4K(vMYBt#Sh{1GXLxv8(4Fj^6jAy4NLL$7SLiqy`YwOunsJU=;H_3`qC?P_BBmQ+ zQ4(yKHD2o`_=a|p!!6)uCL9Rz2LlHGO?w7$o=V?j7zAs_x4iy*phij zfhN|z9DD<{LyQ2$8HvhIT*4e;BFp40@}3ca?L?ryn<_YTAgi;85UyhMi5*~8So_zG zpbEKQvZZ2Nw^liR_)`lnxz&uDtnVR{lU$^Ps`LF<-Nha}FOznRI*J$He~Wg7mec z-U`p=K4SBR*7g;#>+u6mq4{0C3w8PQm7dNxqZS$NA&3lZuWD&c*^(u9nH zM}A?Wpr<`EvreH+cp~1}-#xFY9F!g+EGt8>+4HSo*A$Y9k?IB5=U{vb!{jx~fwq6N zcGQzF50Q>O7cS*T<#zPz#1@2NxXT}k>zW+`&DLZo5|5*8Jc@7%&H^M&-ouu-$L6|Z zRy}$7L=eASQ_@AJjScCaD3E^q_@R^CuDqW!KaWShF9AKRR(V8!YH&6C+-oJpH8Ucw ziyqk6%Zx9a_G+-^nvc6-nZKvX_Z4DZg&!$nhe*3eKt5)(Ur4Jivz7X*3W|Fp#{%_P zw9Bvu+mUq|x~N(J@8al4UfxdM<~KA;YLA&Dx4)@}b`6fgd`7pysQv2loT-voXxi9AoaixPt9`@HX+kfox0}UEsEv3YR$l-d>apHCK#OU zt8v8y^I}f=VZ>YToVE!~PgS>U(zK{5vO6kd6KHtF4E)w=8?QqDR>CtXPg9?V$JU}F zmPg>)ucrz!;3LC#8az(sUwa5r@9B}#S~2+z`?ff4NNGrq?H?~yZJx_Ly7|oH<8cPP zX?cY!5fR1n9z`nBwCNz8z+k}8E`k5E($TLPo?=2qeoF}dpGmpRvM9W0!qI`A#@1*@ z+kc>!98Ym^rMutT$_m?zNU=pLH&ias_rJV_6jW=(3SNUauxGBKf($}!asxW{;=s1X zUwbPA*o{~OTyfF*5gkyOI}~u09|Oq5LV~$1_d6W3_V$?TN{|ZMm?S%&Bs8>mE$y;j z-@R@xCNek~4JGR$n%Da|{E|2bqoN!VKhTzDBr=0$_VCotVr$F?T98$Xof^=4<99}c z>>mhAeeq38+ILL?Mc+@?=d`mpO&JH@(#I9l$Vb#0Ov_`5>@GOm%` zOCs1c0;c{kwLC@JR>h)Spy}+)(A`==B*G6}{lDjbPivcYxj<*DlIQxr{uIFZ03Lg1 Hzi;wiwlo&% literal 0 HcmV?d00001 diff --git a/docs/disk_io.png b/docs/disk_io.png new file mode 100644 index 0000000000000000000000000000000000000000..0ec82d167b789fa1cc5d6523896ee8c7342a26ea GIT binary patch literal 4393 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 U + + + + + +libtorrent Examples + + + + + + + + +
    +
    + + + + +
    +

    libtorrent Examples

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

    Table of contents

    + +
    +
    +

    examples

    +

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

    +
    +

    simple client

    +

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

    +
    +#include <stdlib.h>
    +#include <boost/make_shared.hpp>
    +#include "libtorrent/entry.hpp"
    +#include "libtorrent/bencode.hpp"
    +#include "libtorrent/session.hpp"
    +#include "libtorrent/torrent_info.hpp"
    +
    +int main(int argc, char* argv[])
    +{
    +  using namespace libtorrent;
    +  namespace lt = libtorrent;
    +
    +  if (argc != 2)
    +  {
    +    fputs("usage: ./simple_client torrent-file\n"
    +      "to stop the client, press return.\n", stderr);
    +    return 1;
    +  }
    +
    +  settings_pack sett;
    +  sett.set_str(settings_pack::listen_interfaces, "0.0.0.0:6881");
    +  lt::session s(sett);
    +  error_code ec;
    +  if (ec)
    +  {
    +    fprintf(stderr, "failed to open listen socket: %s\n", ec.message().c_str());
    +    return 1;
    +  }
    +  add_torrent_params p;
    +  p.save_path = "./";
    +  p.ti = boost::make_shared<torrent_info>(std::string(argv[1]), boost::ref(ec), 0);
    +  if (ec)
    +  {
    +    fprintf(stderr, "%s\n", ec.message().c_str());
    +    return 1;
    +  }
    +  s.add_torrent(p, ec);
    +  if (ec)
    +  {
    +    fprintf(stderr, "%s\n", ec.message().c_str());
    +    return 1;
    +  }
    +
    +  // wait for the user to end
    +  char a;
    +  scanf("%c\n", &a);
    +  return 0;
    +}
    +
    +
    +
    +

    make_torrent

    +

    Shows how to create a torrent from a directory tree:

    +
    +#include "libtorrent/entry.hpp"
    +#include "libtorrent/bencode.hpp"
    +#include "libtorrent/torrent_info.hpp"
    +#include "libtorrent/file.hpp"
    +#include "libtorrent/storage.hpp"
    +#include "libtorrent/hasher.hpp"
    +#include "libtorrent/create_torrent.hpp"
    +#include "libtorrent/file.hpp"
    +#include "libtorrent/file_pool.hpp"
    +#include "libtorrent/hex.hpp" // for from_hex
    +
    +#include <boost/bind.hpp>
    +
    +#ifdef TORRENT_WINDOWS
    +#include <direct.h> // for _getcwd
    +#endif
    +
    +using namespace libtorrent;
    +
    +int load_file(std::string const& filename, std::vector<char>& v, libtorrent::error_code& ec, int limit = 8000000)
    +{
    +  ec.clear();
    +  FILE* f = fopen(filename.c_str(), "rb");
    +  if (f == NULL)
    +  {
    +    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(s);
    +  if (s == 0)
    +  {
    +    fclose(f);
    +    return 0;
    +  }
    +
    +  r = 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;
    +}
    +
    +std::string branch_path(std::string const& f)
    +{
    +  if (f.empty()) return f;
    +
    +#ifdef TORRENT_WINDOWS
    +  if (f == "\\\\") return "";
    +#endif
    +  if (f == "/") return "";
    +
    +  int len = 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 == NULL || 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 == NULL) sep = first;
    +  else ++sep;
    +
    +  // return false if the first character of the filename is a .
    +  if (sep[0] == '.') return false;
    +
    +  fprintf(stderr, "%s\n", f.c_str());
    +  return true;
    +}
    +
    +void print_progress(int i, int num)
    +{
    +  fprintf(stderr, "\r%d/%d", i+1, num);
    +}
    +
    +void print_usage()
    +{
    +  fputs("usage: make_torrent FILE [OPTIONS]\n"
    +    "\n"
    +    "Generates a torrent file from the specified file\n"
    +    "or directory and writes it to standard out\n\n"
    +    "OPTIONS:\n"
    +    "-m file       generate a merkle hash tree torrent.\n"
    +    "              merkle torrents require client support\n"
    +    "              the resulting full merkle tree is written to\n"
    +    "              the specified file\n"
    +    "-w url        adds a web seed to the torrent with\n"
    +    "              the specified url\n"
    +    "-t url        adds the specified tracker to the\n"
    +    "              torrent. For multiple trackers, specify more\n"
    +    "              -t options\n"
    +    "-c comment    sets the comment to the specified string\n"
    +    "-C creator    sets the created-by field to the specified string\n"
    +    "-p bytes      enables padding files. Files larger\n"
    +    "              than bytes will be piece-aligned\n"
    +    "-s bytes      specifies a piece size for the torrent\n"
    +    "              This has to be a multiple of 16 kiB\n"
    +    "-l            Don't follow symlinks, instead encode them as\n"
    +    "              links in the torrent file\n"
    +    "-o file       specifies the output filename of the torrent file\n"
    +    "              If this is not specified, the torrent file is\n"
    +    "              printed to the standard out, except on windows\n"
    +    "              where the filename defaults to a.torrent\n"
    +    "-r file       add root certificate to the torrent, to verify\n"
    +    "              the HTTPS tracker\n"
    +    "-S info-hash  add a similar torrent by info-hash. The similar\n"
    +    "              torrent is expected to share some files with this one\n"
    +    "-L collection add a collection name to this torrent. Other torrents\n"
    +    "              in the same collection is expected to share files\n"
    +    "              with this one.\n"
    +    "-M            make the torrent compatible with mutable torrents\n"
    +    "              this means aligning large files and pad them in order\n"
    +    "              for piece hashes to uniquely indentify a file without\n"
    +    "              overlap\n"
    +    , stderr);
    +}
    +
    +int main(int argc, char* argv[])
    +{
    +  using namespace libtorrent;
    +
    +  std::string creator_str = "libtorrent";
    +  std::string comment_str;
    +
    +  if (argc < 2)
    +  {
    +    print_usage();
    +    return 1;
    +  }
    +
    +#ifndef BOOST_NO_EXCEPTIONS
    +  try
    +  {
    +#endif
    +    std::vector<std::string> web_seeds;
    +    std::vector<std::string> trackers;
    +    std::vector<std::string> collections;
    +    std::vector<sha1_hash> similar;
    +    int pad_file_limit = -1;
    +    int piece_size = 0;
    +    int flags = 0;
    +    std::string root_cert;
    +
    +    std::string outfile;
    +    std::string merklefile;
    +#ifdef TORRENT_WINDOWS
    +    // don't ever write binary data to the console on windows
    +    // it will just be interpreted as text and corrupted
    +    outfile = "a.torrent";
    +#endif
    +
    +    for (int i = 2; i < argc; ++i)
    +    {
    +      if (argv[i][0] != '-')
    +      {
    +        print_usage();
    +        return 1;
    +      }
    +
    +      switch (argv[i][1])
    +      {
    +        case 'w':
    +          ++i;
    +          web_seeds.push_back(argv[i]);
    +          break;
    +        case 't':
    +          ++i;
    +          trackers.push_back(argv[i]);
    +          break;
    +        case 'M':
    +          flags |= create_torrent::mutable_torrent_support;
    +          pad_file_limit = 0x4000;
    +          break;
    +        case 'p':
    +          ++i;
    +          pad_file_limit = atoi(argv[i]);
    +          flags |= create_torrent::optimize_alignment;
    +          break;
    +        case 's':
    +          ++i;
    +          piece_size = atoi(argv[i]);
    +          break;
    +        case 'm':
    +          ++i;
    +          merklefile = argv[i];
    +          flags |= create_torrent::merkle;
    +          break;
    +        case 'o':
    +          ++i;
    +          outfile = argv[i];
    +          break;
    +        case 'l':
    +          flags |= create_torrent::symlinks;
    +          break;
    +        case 'C':
    +          ++i;
    +          creator_str = argv[i];
    +          break;
    +        case 'c':
    +          ++i;
    +          comment_str = argv[i];
    +          break;
    +        case 'r':
    +          ++i;
    +          root_cert = argv[i];
    +          break;
    +        case 'S':
    +          {
    +          ++i;
    +          if (strlen(argv[i]) != 40)
    +          {
    +            fprintf(stderr, "invalid info-hash for -S. "
    +              "Expected 40 hex characters\n");
    +            print_usage();
    +            return 1;
    +          }
    +          sha1_hash ih;
    +          if (!from_hex(argv[i], 40, (char*)&ih[0]))
    +          {
    +            fprintf(stderr, "invalid info-hash for -S\n");
    +            print_usage();
    +            return 1;
    +          }
    +          similar.push_back(ih);
    +          }
    +          break;
    +        case 'L':
    +          ++i;
    +          collections.push_back(argv[i]);
    +          break;
    +        default:
    +          print_usage();
    +          return 1;
    +      }
    +    }
    +
    +    file_storage fs;
    +    std::string full_path = argv[1];
    +#ifdef TORRENT_WINDOWS
    +    if (full_path[1] != ':')
    +#else
    +    if (full_path[0] != '/')
    +#endif
    +    {
    +      char cwd[TORRENT_MAX_PATH];
    +#ifdef TORRENT_WINDOWS
    +      _getcwd(cwd, sizeof(cwd));
    +      full_path = cwd + ("\\" + full_path);
    +#else
    +      getcwd(cwd, sizeof(cwd));
    +      full_path = cwd + ("/" + full_path);
    +#endif
    +    }
    +
    +    add_files(fs, full_path, file_filter, flags);
    +    if (fs.num_files() == 0)
    +    {
    +      fputs("no files specified.\n", stderr);
    +      return 1;
    +    }
    +
    +    create_torrent t(fs, piece_size, pad_file_limit, flags);
    +    int tier = 0;
    +    for (std::vector<std::string>::iterator i = trackers.begin()
    +      , end(trackers.end()); i != end; ++i, ++tier)
    +      t.add_tracker(*i, tier);
    +
    +    for (std::vector<std::string>::iterator i = web_seeds.begin()
    +      , end(web_seeds.end()); i != end; ++i)
    +      t.add_url_seed(*i);
    +
    +    for (std::vector<std::string>::iterator i = collections.begin()
    +      , end(collections.end()); i != end; ++i)
    +      t.add_collection(*i);
    +
    +    for (std::vector<sha1_hash>::iterator i = similar.begin()
    +      , end(similar.end()); i != end; ++i)
    +      t.add_similar_torrent(*i);
    +
    +    error_code ec;
    +    set_piece_hashes(t, branch_path(full_path)
    +      , boost::bind(&print_progress, _1, t.num_pieces()), ec);
    +    if (ec)
    +    {
    +      fprintf(stderr, "%s\n", ec.message().c_str());
    +      return 1;
    +    }
    +
    +    fprintf(stderr, "\n");
    +    t.set_creator(creator_str.c_str());
    +    if (!comment_str.empty())
    +      t.set_comment(comment_str.c_str());
    +
    +    if (!root_cert.empty())
    +    {
    +      std::vector<char> pem;
    +      load_file(root_cert, pem, ec, 10000);
    +      if (ec)
    +      {
    +        fprintf(stderr, "failed to load root certificate for tracker: %s\n", ec.message().c_str());
    +      }
    +      else
    +      {
    +        t.set_root_cert(std::string(&pem[0], pem.size()));
    +      }
    +    }
    +
    +    // create the torrent and print it to stdout
    +    std::vector<char> torrent;
    +    bencode(back_inserter(torrent), t.generate());
    +    FILE* output = stdout;
    +    if (!outfile.empty())
    +      output = fopen(outfile.c_str(), "wb+");
    +    if (output == NULL)
    +    {
    +      fprintf(stderr, "failed to open file \"%s\": (%d) %s\n"
    +        , outfile.c_str(), errno, strerror(errno));
    +      return 1;
    +    }
    +    fwrite(&torrent[0], 1, torrent.size(), output);
    +
    +    if (output != stdout)
    +      fclose(output);
    +
    +    if (!merklefile.empty())
    +    {
    +      output = fopen(merklefile.c_str(), "wb+");
    +      if (output == NULL)
    +      {
    +        fprintf(stderr, "failed to open file \"%s\": (%d) %s\n"
    +          , merklefile.c_str(), errno, strerror(errno));
    +        return 1;
    +      }
    +      int ret = fwrite(&t.merkle_tree()[0], 20, t.merkle_tree().size(), output);
    +      if (ret != int(t.merkle_tree().size()))
    +      {
    +        fprintf(stderr, "failed to write %s: (%d) %s\n"
    +          , merklefile.c_str(), errno, strerror(errno));
    +      }
    +      fclose(output);
    +    }
    +
    +#ifndef BOOST_NO_EXCEPTIONS
    +  }
    +  catch (std::exception& e)
    +  {
    +    fprintf(stderr, "%s\n", e.what());
    +  }
    +#endif
    +
    +  return 0;
    +}
    +
    +
    +
    +

    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 "libtorrent/entry.hpp"
    +#include "libtorrent/bencode.hpp"
    +#include "libtorrent/torrent_info.hpp"
    +#include "libtorrent/announce_entry.hpp"
    +#include "libtorrent/bdecode.hpp"
    +#include "libtorrent/magnet_uri.hpp"
    +
    +int load_file(std::string const& filename, std::vector<char>& v
    +  , libtorrent::error_code& ec, int limit = 8000000)
    +{
    +  ec.clear();
    +  FILE* f = fopen(filename.c_str(), "rb");
    +  if (f == NULL)
    +  {
    +    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(s);
    +  if (s == 0)
    +  {
    +    fclose(f);
    +    return 0;
    +  }
    +
    +  r = 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;
    +}
    +
    +int main(int argc, char* argv[])
    +{
    +  using namespace libtorrent;
    +
    +  if (argc < 2 || argc > 4)
    +  {
    +    fputs("usage: dump_torrent torrent-file [total-items-limit] [recursion-limit]\n", stderr);
    +    return 1;
    +  }
    +
    +  int item_limit = 1000000;
    +  int depth_limit = 1000;
    +
    +  if (argc > 2) item_limit = atoi(argv[2]);
    +  if (argc > 3) depth_limit = atoi(argv[3]);
    +
    +  std::vector<char> buf;
    +  error_code ec;
    +  int ret = load_file(argv[1], buf, ec, 40 * 1000000);
    +  if (ret == -1)
    +  {
    +    fprintf(stderr, "file too big, aborting\n");
    +    return 1;
    +  }
    +
    +  if (ret != 0)
    +  {
    +    fprintf(stderr, "failed to load file: %s\n", ec.message().c_str());
    +    return 1;
    +  }
    +  bdecode_node e;
    +  int pos = -1;
    +  printf("decoding. recursion limit: %d total item count limit: %d\n"
    +    , depth_limit, item_limit);
    +  ret = bdecode(&buf[0], &buf[0] + buf.size(), e, ec, &pos
    +    , depth_limit, item_limit);
    +
    +  printf("\n\n----- raw info -----\n\n%s\n", print_entry(e).c_str());
    +
    +  if (ret != 0)
    +  {
    +    fprintf(stderr, "failed to decode: '%s' at character: %d\n", ec.message().c_str(), pos);
    +    return 1;
    +  }
    +
    +  torrent_info t(e, ec);
    +  if (ec)
    +  {
    +    fprintf(stderr, "%s\n", ec.message().c_str());
    +    return 1;
    +  }
    +  e.clear();
    +  std::vector<char>().swap(buf);
    +
    +  // print info about torrent
    +  printf("\n\n----- torrent file info -----\n\n"
    +    "nodes:\n");
    +
    +  typedef std::vector<std::pair<std::string, int> > node_vec;
    +  node_vec const& nodes = t.nodes();
    +  for (node_vec::const_iterator i = nodes.begin(), end(nodes.end());
    +    i != end; ++i)
    +  {
    +    printf("%s: %d\n", i->first.c_str(), i->second);
    +  }
    +  puts("trackers:\n");
    +  for (std::vector<announce_entry>::const_iterator i = t.trackers().begin();
    +    i != t.trackers().end(); ++i)
    +  {
    +    printf("%2d: %s\n", i->tier, i->url.c_str());
    +  }
    +
    +  char ih[41];
    +  to_hex((char const*)&t.info_hash()[0], 20, ih);
    +  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
    +    , t.comment().c_str()
    +    , t.creator().c_str()
    +    , make_magnet_uri(t).c_str()
    +    , t.name().c_str()
    +    , t.num_files());
    +  file_storage const& st = t.files();
    +  for (int i = 0; i < st.num_files(); ++i)
    +  {
    +    int first = st.map_file(i, 0, 0).piece;
    +    int last = st.map_file(i, (std::max)(boost::int64_t(st.file_size(i))-1, boost::int64_t(0)), 0).piece;
    +    int flags = st.file_flags(i);
    +    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 & file_storage::flag_pad_file)?'p':'-')
    +      , ((flags & file_storage::flag_executable)?'x':'-')
    +      , ((flags & file_storage::flag_hidden)?'h':'-')
    +      , ((flags & file_storage::flag_symlink)?'l':'-')
    +      , first, last
    +      , boost::uint32_t(st.mtime(i))
    +      , st.hash(i) != sha1_hash(0) ? to_hex(st.hash(i).to_string()).c_str() : ""
    +      , st.file_path(i).c_str()
    +      , (flags & file_storage::flag_symlink) ? "-> " : ""
    +      , (flags & file_storage::flag_symlink) ? st.symlink(i).c_str() : "");
    +  }
    +
    +  return 0;
    +}
    +
    +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/examples.rst b/docs/examples.rst new file mode 100644 index 0000000..21a1714 --- /dev/null +++ b/docs/examples.rst @@ -0,0 +1,52 @@ +=================== +libtorrent Examples +=================== + +:Author: Arvid Norberg, arvid@libtorrent.org +:Version: 1.1.1 + +.. 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. Note that building +``client_test`` also requires boost.regex and boost.program_options library. + +__ client_test.html + +simple client +------------- + +This is a simple client. It doesn't have much output to keep it simple: + +.. include:: ../examples/simple_client.cpp + :code: c++ + :tab-width: 2 + :start-after: */ + +make_torrent +------------ + +Shows how to create a torrent from a directory tree: + +.. include:: ../examples/make_torrent.cpp + :code: c++ + :tab-width: 2 + :start-after: */ + +dump_torrent +------------ + +This is an example of a program that will take a torrent-file as a parameter and +print information about it to std out: + +.. include:: ../examples/dump_torrent.cpp + :code: c++ + :tab-width: 2 + :start-after: */ + diff --git a/docs/extension_protocol.html b/docs/extension_protocol.html new file mode 100644 index 0000000..ce55448 --- /dev/null +++ b/docs/extension_protocol.html @@ -0,0 +1,498 @@ + + + + + + + + + + + + + + + +
    +
    + + + + +
    + + +++ + + + +
    Author:Arvid Norberg, arvid@libtorrent.org +Ludvig Strigeus, ludde@utorrent.com
    +
    +

    extension protocol for bittorrent

    +

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

    +

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

    +

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

    +

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

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

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

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

    handshake message

    +

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

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

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

    +

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

    +
    +

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

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

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

    +

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

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

    and in the encoded form:

    +

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

    +

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

    +

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

    +

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

    +

    The extension IDs must be stored for every peer, becuase 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 the bittorrent spec. with its single byte message identifiers. +It is also considered to be enough. It won't limit the total number of +extensions, only the number of extensions used simultaneously.

    +

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

    +
    +
    +
    +

    extensions

    +

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

    +

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

    +

    These are the extensions that are currently implemented.

    +
    +

    metadata from peers

    +

    Extension name: "LT_metadata"

    +
    +

    Note

    +

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

    +
    +

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

    +

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

    +

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

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

    request metadata:

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

    metadata:

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

    Don't have metadata:

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

    dont_have

    +

    Extension name: "lt_donthave"

    +

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

    +

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

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

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

    +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/extension_protocol.rst b/docs/extension_protocol.rst new file mode 100644 index 0000000..95efc20 --- /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, becuase 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 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 256:ths | +| | | 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 | +| | | 256:ths 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`: http://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 mssage itself are 20 +(the bittorrent extension message) and the message ID assigned to this +extension in the ``m`` dictionary in the handshake. + ++-----------+---------------+----------------------------------------+ +| size | name | description | ++===========+===============+========================================+ +| uint32_t | piece | index of the piece the peer no longer | +| | | has. | ++-----------+---------------+----------------------------------------+ + +The length of this message (including the extension message prefix) is 6 bytes, +i.e. one byte longer than the normal ``HAVE`` message, because of the extension +message wrapping. + diff --git a/docs/features.html b/docs/features.html new file mode 100644 index 0000000..3d365d8 --- /dev/null +++ b/docs/features.html @@ -0,0 +1,374 @@ + + + + + + +libtorrent manual + + + + + + + + +
    +
    + + + + +
    +

    libtorrent manual

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

    introduction

    +

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

    +
    +
    +

    features

    +

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

    +
    +

    extensions

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

    disk management

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

    network

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

    highlighted features

    +
    +

    disk caching

    +

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

    +

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

    +

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

    +

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

    +

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

    +

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

    +
    +
    +

    network buffers

    +

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

    +

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

    +write_disk_buffers.png +

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

    +read_disk_buffers.png +
    +
    +

    piece picker

    +

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

    +

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

    +

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

    +

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

    +
    +
    +

    share mode

    +

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

    +

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

    +

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

    +
    +
    +

    merkle hash tree torrents

    +merkle_tree.png +

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

    +

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

    +

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

    +

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

    +

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

    +

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

    +
    +
    +

    customizable file storage

    +storage.png +

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

    +

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

    +

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

    +
    +
    +

    easy to use API

    +

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

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

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

    +

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

    +
    +
    +
    +

    portability

    +

    libtorrent runs on most major operating systems, including Windows, +MacOS X, Linux, BSD and Solaris. +It uses Boost.Thread, Boost.Filesystem, Boost.Date_time and various other +boost libraries. At least version 1.46.1 of boost is required.

    +

    libtorrent uses asio, hence 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 X and BSD.

    +

    libtorrent does not build with the following compilers:

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

    libtorrent hacking

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

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

    +
    +

    terminology

    +

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

    +

    A piece is a part of the data of a torrent that has a SHA-1 hash in +the .torrent file. Pieces are almost always a power of two in size, but not +necessarily. Each piece is plit 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 distiction is between a connected peer (peer_connection) and a peer +we just know about, and may have been connected to, and may connect to in the +future (torrent_peer). The list of (not connected) peers may grow very large +if not limited (through tracker responses, DHT and peer exchange). This list +is typically limited to a few thousand peers.

    +

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

    +
    +
    +

    structure

    +

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

    +hacking.png +
    +

    session_impl

    +

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

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

    session

    +

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

    +
    +
    +

    torrent_handle

    +

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

    +
    +
    +

    torrent

    +
    +
    +

    peer_connection

    +
    +
    +

    peer_list

    +
    +
    +

    piece_picker

    +
    +
    +

    torrent_info

    +
    +
    +
    +

    threads

    +

    libtorrent starts 3 to 5 threads.

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

    disk cache

    +

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

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

    These LRUs are stored in block_cache in an array m_lru.

    +

    The cache algorithm works like this:

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

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

    +

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

    +

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

    +

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

    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/hacking.png b/docs/hacking.png new file mode 100644 index 0000000000000000000000000000000000000000..b3eec83903582b26c4a18bdf71476a2eac896477 GIT binary patch literal 6260 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 literal 0 HcmV?d00001 diff --git a/docs/im_thumb.jpg b/docs/im_thumb.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1dcfd75d3de9d878ca56be943a72284415e3498a GIT binary patch literal 12093 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 BK-vHR literal 0 HcmV?d00001 diff --git a/docs/img/blue_top.png b/docs/img/blue_top.png new file mode 100644 index 0000000000000000000000000000000000000000..bcad3a2448089d4d9e2cafa4b3dc9f8fe5da7eb8 GIT binary patch literal 2953 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 literal 0 HcmV?d00001 diff --git a/docs/img/dotline.gif b/docs/img/dotline.gif new file mode 100644 index 0000000000000000000000000000000000000000..6bd546f861a448a670ebbe75a63283f9e4a1450e GIT binary patch literal 181 zcmV;m080NyNk%w1VG01q0FeLy00030|Nq(9*#H0lEC2ui015!e0007xj3cL(d+nlv zjxH-}#xHx$Y@R5A$XJq*`KcQEt}Yy=Hr?GZe(D#Wllf?-BCsY*@{B_mQ;B=-qO-}d z2elrhzilG`Nvh(hRS2#L7qQ2+otB$8KN literal 0 HcmV?d00001 diff --git a/docs/img/minus.gif b/docs/img/minus.gif new file mode 100644 index 0000000000000000000000000000000000000000..d5fb96e8c15fcad8e2e09d2c756bf50ad886452f GIT binary patch literal 262 zcmZ?wbhEHbH(18O7_V3@nckkXEJ9cc_wr%Uyty{Ki*|>4z zx^?T;u3fuo)v6UMRxDYvWYMBUbLY;Di;DwWMSVgCWF*Kh3~bs58ZtaYI9XemnVk;u lu(P!~C*9=X=3I2l*u_L?st>zKjE$Q3IrS7BA!bJgYXA<*TwwqJ literal 0 HcmV?d00001 diff --git a/docs/img/orange.png b/docs/img/orange.png new file mode 100644 index 0000000000000000000000000000000000000000..40a5ea3864d1f4c71d8d584c0260735f18c7de1c GIT binary patch literal 24833 zcmV)UK(N1wP)KLZ*U+5Lu!Sk^o_Z5E4Meg@_7P6crJiNL9pw)e1;Xm069{HJUZAPk55R%$-RIA z6-eL&AQ0xu!e<4=008gy@A0LT~suv4>S3ILP<0Bm`DLLvaF4FK%)Nj?Pt*r}7;7Xa9z9H|HZjR63e zC`Tj$K)V27Re@400>HumpsYY5E(E}?0f1SyGDiY{y#)Yvj#!WnKwtoXnL;eg03bL5 z07D)V%>y7z1E4U{zu>7~aD})?0RX_umCct+(lZpemCzb@^6=o|A>zVpu|i=NDG+7} zl4`aK{0#b-!z=TL9Wt0BGO&T{GJWpjryhdijfaIQ&2!o}p04JRKYg3k&Tf zVxhe-O!X z{f;To;xw^bEES6JSc$k$B2CA6xl)ltA<32E66t?3@gJ7`36pmX0IY^jz)rRYwaaY4 ze(nJRiw;=Qb^t(r^DT@T3y}a2XEZW-_W%Hszxj_qD**t_m!#tW0KDiJT&R>6OvVTR z07RgHDzHHZ48atvzz&?j9lXF70$~P3Knx_nJP<+#`N z#-MZ2bTkiLfR>_b(HgWKJ%F~Nr_oF3b#wrIijHG|(J>BYjM-sajE6;FiC7vY#};Gd zST$CUHDeuEH+B^pz@B062qXfFfD`NpUW5?BY=V%GM_5c)L#QR}BeW8_2v-S%gfYS= zB9o|3v?Y2H`NVi)In3rTB8+ej^> zQ=~r95NVuDChL%G$=>7$vVg20myx%S50Foi`^m%Pw-h?Xh~i8Mq9jtJloCocWk2Nv zrJpiFnV_ms&8eQ$2&#xWpIS+6pmtC%Q-`S&GF4Q#^mhymh7E(qNMa}%YZ-ePrx>>xFPTiH1=E+A$W$=bG8>s^ zm=Bn5Rah$aDtr}@$`X}2l~$F0mFKEdRdZE8)p@E5RI61Ft6o-prbbn>P~)iy)E2AN zsU20jsWz_8Qg>31P|s0cqrPALg8E|(vWA65poU1JRAaZs8I2(p#xiB`SVGovRs-uS zYnV-9TeA7=Om+qP8+I>yOjAR1s%ETak!GFdam@h^# z)@rS0t$wXH+Irf)+G6c;?H29p+V6F6oj{!|o%K3xI`?%6x;DB|x`n#ibhIR?(H}Q3Gzd138Ei2)WAMz7W9Vy`X}HnwgyEn!VS)>mv$8&{hQn>w4zwy3R}t;BYlZQm5)6pty=DfLrs+A-|>>;~;Q z_F?uV_HFjh9n2gO9o9Q^JA86v({H5aB!kjoO6 zc9$1ZZKsN-Zl8L~mE{`ly3)1N^`o1+o7}D0ZPeY&J;i;i`%NyJ8_8Y6J?}yE@b_5a zam?eLr<8@mESk|3$_SkmS{wQ>%qC18))9_|&j{ZT zes8AvOzF(F2#DZEY>2oYX&IRp`F#{ADl)1r>QS^)ba8a|EY_^#S^HO&t^Rgqwv=MZThqqEWH8 zxJo>d=ABlR_Bh=;eM9Tw|Ih34~oTE|= zX_mAr*D$vzw@+p(E0Yc6dFE}(8oqt`+R{gE3x4zjX+Sb3_cYE^= zgB=w+-tUy`ytONMS8KgRef4hA?t0j zufM;t32jm~jUGrkaOInTZ`zyfns>EuS}G30LFK_G-==(f<51|K&cocp&EJ`SxAh3? zNO>#LI=^+SEu(FqJ)ynt=!~PC9bO$rzPJB=?=j6w@a-(u02P7 zaQ)#(uUl{HW%tYNS3ItC^iAtK(eKlL`f9+{bJzISE?u8_z3;~C8@FyI-5j_jy7l;W z_U#vU3hqqYU3!mrul&B+{ptt$59)uk{;_4iZQ%G|z+lhASr6|H35TBkl>gI*;nGLU zN7W-nBaM%pA0HbH8olyl&XeJ%vZoWz%6?Y=dFykl=imL}`%BMQ{Mhgd`HRoLu6e2R za__6DuR6yg#~-}Tc|Gx_{H@O0eebyMy5GmWADJlpK>kqk(fVV@r_fLLKIeS?{4e)} z^ZO;zpECde00d`2O+f$vv5tKEQIh}w03c&XQcVB=dL;k=fP(-4`Tqa_faw4Lbua(` z>RI+y?e7jKeZ#YO-CRvt-2K~#9!%)QH#B}ta%^*gF+b|3B@9uXP&u2)x8_e>8n zATtQV0t68j$gJQm02UyEus{%DHU9@I{s7hpAcBk_Yyc64?gc~BGu1uaSy`1?S(%ZK zc=_W#%uH1ei=$?y_xkn78t9G~FNw-<5BKYK%~X$`^PTT}pQx&G{E>S$|M(v=JpD&N zkA%cu#YcJ>dHKJ;;^JRBXYrFI3 z+T4I|+5Vzq^Vuz+T>jn1v_ELke1#+rc>Bc)Al!MQPUwEKXZvS+9{#%zkjC5ZLI4LO z5C!%28Y-X;ywH7l&GyeDm;cr!ZWSTB@-yW_6-4I0mkUwffq2k{-4}bdf4R5o%&vT2 zF4wI<K$O$g;eDr>JIoKWg_;d*SMjw=6ys*1x}i6hS;n21%d} z(Eyr3GU*ns|H+QCzuD4$kRb)TcVhSS(SP!iaQr-mFa8%cfBc^#J|cm?+7Hd-K0(g* zU{&qjIZyN6IpN8kbltQ2RJi@qYvj+AEU=PiP0wgO=axK)l^cSp-%; zYiQqH+T#BFmiYibz6L$wQAOXPYo5 z+bE9mEF+pTq8@x8_mSME5;p{rv)7z{hQq~o{g3{AboZx-=dbBQjy@fqNU|FjAUs)e z@u+3}`-$#1JN94n?0)l-_PxORrx*B#0a-=x9vueaO;;j}(!b~smuTKygIBvjGTlx4 zBRa1H@d!sz4OcBRDPKQv?Q{4nub1!cwWGIgujnDd?w4D%2N{2#M#2TGf4U&P+@R5( z(Ef{__T7f&-K9lL6z3GlijI*+%X?D$ts_qe#l}qzR%2X@HEs^sN8R1HD2G*i;dquE?QMo zK*rh9j|ijzwx9Q`0QPSB{hG(k!o2vV z_mZbQ35LTj==J1yb{DYRzJCf&o{*D4%9Kc9Kb1gfbN?;&Et&5 zgywA5pA?d!2)TD?b_`d_?(-cCf~J6~q8ibxmYvuRBy|q*g59qNx-T=t!7eu|3cT~5 zK_G64Pyah``sUVNo>%|Df|S8-x(g-I4HikF+L8RB$9*O=i!-Ll+cv~<@?D#1~j)83%GXZy=*a?Isjc)F*%5{QJ`zf6d1r!@TV=`>b!&@jB*kzS=~ zL#fQ0R1icg%&9aeYsh=h9U=i~19(N8g}|`+KmXyIU3dqL+V7^>p8K;NkW!t_VxZ-}awn+7F`rnplpRSZ-++%hSsS=${EWwd_94 zxMh!~Z1*b;7OBQ%=A_gWN*0K>3G#~DKiS}XLQ|DaWG#hjE^E%7s48e`fK;l<4$T|8 zK6NMoa0tB}b4sbc=R#W4(O*USSNU#KFV`B*LpewUX)|)B#OsXs%;No)coi95?(xfj zX0^?WB3@YiaE-rkpcx4|oe;T)o4>fFeNQ<1=?ckWi_#1Da(ZNsNDn>{ui*Ose!=Qz zEsKvm#DJ)`{88-9gyGx&^==UQ>ppJle5uZ##yyu86V#z{iv)*g%kX7lcx463)&KR3 z+^OAw1UW_eJqRl{zl>~uxw9iw%3`>uWKbsvXKa6UYya*bok$l$>%o=H5*#^!sv~!) zAUX!YSmi16f-Oh1h}b4~8S%~@Y|!#`JF3J}3Z*sTGU64XwIXGai8qOxe|n3k11EOu zmx^>;;C?M!{HqtZH6`)y(aoAy|M(^0(y{(o;O0*wknKmZ;+KK2YKYf+hHpE<**Wd| zYw!_r4{kt*g#1$R3r{%rmQ!oA>yTubZccV;xE6AR1kQUz%SnTjjW*_ddi&R0WSn^5 zdb9vb{v>%hm*ZvO2p&y>h$B2%aQ?F;H^1sweB@bvaL)GAJ^gnb=YRW*yiII>-Qib( zw23&UEPt?O`NI|XY&WDjA_#_wZU1t|<-c);KVOq~J=;$=q@6<=OGleaNJehfxxIB!af3x*QDc$qFGOmcrYy ztx}2`DSx*rH*J-??eXUY!g1LG)M8EEcElHv^vV`N?(AMKeyps2=7`@l>^_g&{ibXT zxmdKE0BDtR{K^d1WQ){DF43gir#x7?Fy1Lq+owqoLaDGL5Rnkd2~sN1kO_~J{&^xi zykzll&+fAuoCMrLS$^OV?FjEQTs&$SzTUI@V&MAEZfTx679X9Fc8aC}Tq)9qWSc~M zfwbzy<&o@5nyiouh(1=zrAiMoKJ-SG-F+xnIfB!P>T&EA*n^Nopt)Q!e79%#;syIJ z_Gof+zwLp<`A;6;&j8X-;YNr688fo8a@as?(xh?wUqb1?VlKpRcc3*UKzql!B zP$1qU^5AggcN?CQQL=5wMp2?|G7S74D{*xr!0hjW%x@ZDng1dua4|jksdz_t#5917-I`XW;7!7C&A?-k|OVX#DXBhr->Fz)Vxe@O*iE7P$fNR9ax{8i>JXcg+U&V$D{X=#U+|5~IF zi#p?pfK-i@!5(r0i;q3B91vB5KO`d|;MXnC(?5%LodQCleZR5agD<(GZ3bBcRzF>_ zct3Ibmsbog5@hD5bra7HA&8D>*q7PwFA<6Lc0_G6r1Fk?)Ncx_Y#z z^!^Tl9{!AwSbX5weF}N&@T(T+K$NpTIKy9pMgf+#o85r4nZ-{%?GGN&f8DeBi(7WT z9dNQItL2q>x4}Ko(ozGo+#0tnz1=Dj7a&hYD5U(Gkmpoq--8Hl*;-_p09Id$1DW9) zK>cmp~NKO6a@)U%$8Z(OmkeaPv=JfVc4O&xFN?0rJisY&;vAso;B%ge)D4pR}|e ze8m3KJ)2Ko7CbJ}zuFT%IJ4KwSU%$%giWc`swn3&>J4(im82d4NmDVEwiZ2Q>B=cH zF0!TszcHVi- z(Ib?z*2!BqcJDoY?=KQhmC9}fu64xyTt$Vo*o|_&bO5#`PjqOsMx#B{9$aSeV?ow| zn@@UlD{MbYXpAgAY><%AK0Aw>+F4%sWaneHyGq|fIQe<$8u0%Tf zH!a;8&1#EkT$FYDN#_%=?jb9UMRZCEAl(!-)2)(j_tSU!rANHwv*JAA(vyZry3JGN zy?B3#Ye1TEJbQ2LT^V^92DVG^^|SpeH(Er$NJ|hn#+WYWw7%M z&pg-v&xX~H8k%?27&wxx_gmU>;f+NYdtl;R(`;rELY^L^0#Z~MdeT$KAg8S!!Lyw6rugMR^3+;QJ&2Q`^ za0|gLol%0MEs#Y54fqR3ld<-W7seGL0@8Z=>rD4$&+5kunkP%T&wILOJ0mHawXA-= zCY+Z-LZTh9zG3%y$Nr0fhkw7pwIkysmE%bmzP%;K#Nwk%BuK&avSDI#!`Gr9YDLNc z`O%_|k!}M0v%N)LDd}Gt3mm<}s!98Z<_@<^XzV~OTKcmY))A+`#@d3~Lh=>Nb(922 z#ivL{JCht)e6S{*FWG&*W&6v4;p>j|@3r{H4QZE0CPhhO!^f8g+Ej&wumXC~ajqrC@j4RbWH_{oYz%}{X#ZO;z8$9=VB_%gHlaf^Q}IOoYx z$OELVWB04b+0R;dy# znJQdW5TD4K$o{v5gi1!!2+)JbVGP3OuxwBM5rs%9yhvO|& zyN9bu4*7k0iUV~_i7L(WSUo*WMp(p`&Rl7R=W&__ZD0} zzCdFlcb?rZx0dT%bgX}Gfq$~HmRH^*IU=boM9IR6DV3_|nE%D{EXRVvA3{qfsahGF zD{U#y@T{{1LTf;F9-F;=6_vEAx`pm*B|L!D56{pTp|yUs=87hk8psQTjIu8U%n?^{ z#29@td{A5g+%Y)Eb>Ie?k6OZ`Gj_k)v-@gb>=(verq!lAm$Cp0OZkoFdzaeBV1TB--ji+zy05NrOHc0Ov|yPY%J&sDhj&)1d{U=&-yE64<$?!)Z_V&^&-RyFcE2ew zii7yF$A7Rg_(iM8-HgaeIlgi#{L~XP@qC=<+R;X6$h{J8;?cEF$n_CiP<2?!Tt~X; zL6@Xmr2D2v@j#2lp6;tiSbB0dxBbRyT~mqF+2~<9fOZ%3zul9cCrDaYJ8D>>R`rBi zA$f%kk^ZYl{3@26kSqA+O)oxqT{Wu*M9;ApF+8hEe6z6sJfaCM{@yt^|Lh7K#s^{Z z6bCu;LBXPTFGvoDEu_l5reY_7NMiAWCE>E=_RntUp7++a+=C7)a@v=GW%RA#M$qJN zukwUzkwFJH<*g)xTr}r`<&~T(S&ug-N8Bb-SKmCnA2z5OMksV&?$|%wm4ttM`}T<@ zVf*WyvFS#F(L{tT6?j?9;>HNOx7nVyuyn@GO$fM9em>ZH=)UcVyTOoyobau(`f-Cl zYwF(a+rsybfUFa_o{j~=6!jc=m)L(X(0sOW1e{unF*fR{rdLkeV%~QMds61q|8qkY?AiM;Tf&p zkI*ZGm9YHy4Ay&e_ki7}8)VtB{Joa#Cj+F+`QKU7EK9}h3aQo=Mzk>Hc6f*G6EFYx zhW5k2+0Pbb4TtiKO0kukfzJv0s~+}qan-)-S^TiIR$eK(!l_wB28aVg?}kP=6mTYN7=KpVV*v-{cK13 zp2uH~4^MCsfR5p+L$l#YqDQTgKi!ab%Ia@lBFzT61wsqi*u`n5I^nv*z`zr!scTwo)k z(jF9ZZ??msN10tcJg#C@lVZCA1=#@&+GV!CN;Hoe!n-~6i}E7_*%?(RZ6RH)@b3<# z`f_un%4;5&)OnN~bsBW$_K6KsTRs-b=Iv=N9DcE~V^(qjmyh10RMoW3^kg~6;dG8& zc@4Rvd%7tRPD&4Uob{-T7Vnc>-%Z>TL1_hHoQj zlQ{buYr~OxUuG=woRnvXuQJV(JSE*)T0q=aB%Va{7!2h7!|QpX&!?M!A31b zPt@{9lxB6OTZ3ZQkch9ndY66 z_XBQmsz7@^EPBsPEk9O>8-QQ?s#rO!^506et9sAwnI^l+5#W^gG!t)4K*x6PQ4WOh z;F`xR@n%4WQD>zhDUDc~eh>J3U)O*NPAEO*yw5oAaOaIxu_CW2o=*J6dOYA@R6q*8 z)>{=R8NYP*z*2I6J1da7jK+rHrII^Xd{jW-Jks&!=%*^=ZJGy|4r>2oLjuwXs8hbXs>}ei&!n@-vM5>x0mgk7G2$VY| zU8gDE7^}Kt`HEAd8dYB3Js8^;2-?H&YA8ROCY6GjqsZO=aw@o-fO?9ghW#p!rp)lN zJCUbWIb!u*Qi91ZBPV37h@O=YsNYvLxI2m;@qF`=hg1>?}aV|Y0#E#!*J zm#d!iTZ(*goZnMbl^Bf(pNP*Bag$m7-m>%z<*-1|H0`Qz>5%~ORbg!$Xr&zIU9Y(^ z)w`{~ac7k4%bUz_6HTFj%t_cy<_^X@{PaB?pQ(5(1Wy|Bb+UzC7Ry`{Rk)j0$EJh* zZ*Lf04lJIuNFy_bh}T zMPwB2oJCsXPPI7wj8>6CIV3Hd{3tn7g8o@&LJJ({c{3y=lbEIb-r?1NbFlm{9CaO- zL?6YQ^hMfI<}2nXJ;G1FQ#qhVIkE`xHj$&2s=wYGheP9Ur^9h)L25O{pAZ*_&tZ5G zS^d;Old9H?KRj~Alf*-KYny|?<3gJ3&68|ETJ^8pLH77M!n6n_m< zdt6jSR53hgtnGT2cTq)LR{-z%Qk%Hn6+P<~gu8bAW=JFO8dlgv`ha?CP09T|?R)Voo(>a`A9diGUPH#PZX?pU}H%ohC8 z5uUihe$GeLu`z5UgIi~$fwZab&*AZ0lLR=LB0NxF7_q}!Pu@Ag1An539O>h`BGYK=!yGgbV1ed=Wd%CDC=v zLeqLb)+Q$)1+@mfYCzS~zZeMXK)CD*#@&=fUiWa1p@(A-o$tW}-PF$07{~*rhA?(p z5JVNp4si|IBs9e76j{9IV7Wz|E1ENEq=>?z7By7^w@kRDFx+5tcqx-<%#vo4qE>eE zz@5wsRq{xPFkB@*4~SFzWtlZ`GSyyWi^3|y+cC{qSzl$CgQT?o#DbG%UlQ-z9QD@N$@*d7@s(IIw|8T z)!fXSvqUP`6A2f&;6T2qN3%3^cJ_fcckoit3R;p|fNB2#KKL@w!A)5}}lyg>X zx6>^s#~rflot(Jl@7Y35-7J4r6Bf??;GDCcx22CQ(;*hlT}fl|#Qr{v!1W@`)_Vow zO)ArWM**VVh{u1Qf`+9wX$yI<0AOnL%!ScdeB`ni-+azDVw_U3JhIV~vatV6XEI+u zDtxhA<%8Kx2)Wb=ef>m4ajnCzg#KC4X_2}+p?4p?+`ULSfryV&WbKpI9GU9qT^_#1 zJXtVk-Lr@Z?Gtx6RKn@T=``>oChT*7^BOhFp9Li33W6#x>9yE(j9kd01%KdElkrQ% zd5G8L`VNSz$La;gL8E}kVy(M)n{i(77k-||(<-8ml&h(?`zE_p^gDr{Xq0M`w+hmewnqSjm(Yq;;9*%P=(0$Y6 z&lGp=&=h7ELNeMb@x?&e6j34Ptnm?Nm?!s;ZsR<_KIVs6@4_-=kVr-W$OE$)ElN^G z9H^rqK-%S^Pv{B&UK1>iT8G&b>iwA~?-SbRNh$tLj;QzK#xlOYOuAK?hoMmD?@6(g zsY~^Chy_#BF2dB?#O|}6QJv@~Gw2#DBk@j6dnhd#A;7(CZr0Yo96g4VWDn=*P`>hnK(k_wr>27tcM?moy z^0bFdkxWqc1?wUao8QG2HrJ7x=DxK zkmF$`ogtSzMDqSfUIQr>X@Xn&YX6Ok&;H1$yOQ;=f_D97+oU%_XF3v-C8H+fXfHD`-RpG<24F$3-CrX9I9cwWu!Af7>Ff zj5~Lw%H7&}jzWAE&=gHa&yA!48_KquoaPRVK-xIdm~o?vjY640!Nh^Ep`LyqJh*-P?zSYDsjpswRpJ6S&y_zx-Oebyj@oo7#dq9ZYafD znaUfMkQRvR(IJzbdzy=cEVou{#g$S2M}whkaFCUA{_S-*R3hrkvb6im@}$)dmL;g^ zQQJ8mFd-(u_TF(zi$LL#`LdpUk_7Dof+CDELns&hD^F0D#-Da4Oj)YoQTYRY03g=b4sTL(l%9I7)P&>>m0JQ z+ggubJA+%Io2?6S?S)rERzq3DrY@iQNa_c|B@lA4ze+Qi!PS4Fkz3z*Z?{mZaRY=s zX_wi3F_;|xWw=|OpGIo$CS}v7b6Fi8*s#j*vY$a{9esFX`8>?RJtaxj5pEzMp&`Ap zT&;N=W`16VTp*AG*k{7bWp1bxyDd#4>$-?HcDeX)oNBL>3Npq!1&N|XS-zqBCeHdR zs*`1_?q@}>gWh&NL`mwuhYQI4fOa~QJ|)e6urY_px7eE&wMrmf+t>W1+(9I!|CJ%6 z37382k#AB!GnMu3tDc-P%O5sYA?f!qL-aMX26_l9g-kdX;w!WBR7%G~-MnkFM{`AP zsB8v|tl;Utb@-LTpNr);&XacoaTM5|@mj%PV8NaqdBi$7C z@uWg9x+3Y6>)P08QMQUL!wc}s#?)cQDYab4uelQA93$#n?Y?miGdg(URm1L!MEg!l zcq@hKi`6?>KyGt^F9+_C7K5gw=!>Dmj&JIfvqG@!j?`?-cF7tmK<$3#Xo;tB28@!!5%k?(G&~hR&vi0l8PwCe5VO zLXyzJme#EJbF12CXrmmwD&%A)f8k*>xe>{D?vL#(U**ZQxgF zeVJFD88$*WF_NJQ;xc&?(Lre*ifLBo@^7PDhA1+k;W=ADM}G4-U-x`tsZj@MFN76@ zcj^J0d>Gy2-jV)0u-M%-tAxu25g4AQDhHBk(N%N5bw;iLp5axG1TpMNGukEMbv!5p z^oT;Gss-NRU?ikAvK^&a(ruc}@^roqEn+;Sb-##&1b+`ungEVt3 zgH6oYZ5%%@qynF(+VPDJjmMpVzkVs`F0%YE;F?gVKmQ`E+52mdId1}4So9cPWtP#j zODyLoV%w6KByscHA+a^+>A&3)9=C?4B_&?jXr_2ys9bu7pDKQiP@{dq!5mJJ{}6$+ zH>k;-AI*OxwI`zqY1{I0VyXd!rE~TNmo$$e{nu#{YMiE(LjnKNqUi3`RdF~R)QZmR z9(=@K!0_CB5kotM>(K>1pl%?mn3Fq+JVb_@ggXn+1hn(h>7I&$L+XUI4KU1Iqw#9q zs8&n=(jIK{&VuEKiEsfDSEl_I`U+CflcR^+nWoZ-A#pfBT~>liG>;woFCy9{$Ui369(eGMM(Vxu6#)$XQzs zJn2@U&*VWNwB)TB&2(Shkj{Lm((Re$tywY*FB6+xhoa;>kaj|Hftx?yTU}CE*J*I5 zuGoFCWtX-G0#yop@TMc5BqJ$yU)~ay3tVWBh1gDY{*Y->vw~|}O3a{{tcVLp6X(1p zg_6dW<3$3^rLg<_hIkWb*S;*vwrHP>8i%DxuikJjY3ZMBAwW29$y=p=ZobFU#8_L* zs5?(CEFH}muY%h)=H8uQxb_U!rBmT$q8VvXl#hKT#$<~xmG>Oe!XmWuj)dSFa4T2Lugi*?@Un%-g?=xX%W{<(B4& z!>^m-l$d9ypH!s4q~KwBTzsQ1tyn%M!tRSLF3cl;t_`5&p*vysS;z1)7|xL>xsH;1 z1xy7rUX^?-_N1-J+}{5AHQI@hEkog=<-zFTIEI%xO?ru#-KTsR%kc>EbYHQY$Ge>l zvW)(DKAB6*f;&kW%uh2-FKY*p-0+)p+JBv>^>9V3-6ch1I9_Mc zt}{JlWwK~DB%vV}_OmG^2swgV2h!f?=p&vct>9M;X|Lp6H2W>5Q_!%2D5fM#8zW{>cL)WklF~ zdPVAIuA>*BT?d*+3*2f`lpac(k(OQDkT;3u5rp$dPDS`K*#^tI47sneX}F&6KF0BWx#i+4E`6roc1UmC5S&g_s&rd`38#|}@UVx?Z#R?eX^Qz^(qQN^@yjjl z+pR@RHO7OM+*Y$7E^zxv(Nifs<{5ly*!?=83`Y*|Eon2b+Y}X2pn14})Ikt)9~U_Q z5X=shd7Pml(7suTwA>V`P-J+~+pf<{Lo3JiMcyg>eqX7hk7J5Dp(Z+O>l|Qs{Zpl< zZVNfO-;{_~4=+1GSTcMw(0@~$6KU)y{%j~I=-h)CUL{~ZZIX1I501%nlT0=#ypcwrF3$f!MpCf!7bc`f~Zq8 zD)FkvEdt^33UajWOz-HP^@InW_OYk`wl@RMRUtj(8O+rRKDN}lW(m0}^1Z>(=fnt( z;ldFf6?|&)<)X%_GLcevV2V(!;47TX8gi|m&titT1Pl@3|Dq+HV+$ySA)4= z31wu_z~QtlJV2}hZXUHImy1X!gasmb2yB0C$DgRO{AfY@WaKx5S@)nNWLWy7faB0(NnvlFKH?N!QL}vy}X^=-uZ|?b?{$--S zF0&Fc^UA`du>R?)P=N-E7S2sm8Ml9WV`AceaE1i63N0)7#xZ;&Sv_y9U-B-JE)C$dGhkH=S4w4G}3p54%^GJSSN4z`B_zO4nPxX%bkekqLw{R?; zG&GNF!*oygq;5dSq-|pV16DPSBc?yV@~OPHbJ4R=-tKevO^3L|;&FppH^sLsPwleiVaxDhPr9*%WC;Ce1TE+w&RXPT5WfCe*4E&yIG1}Ev()T+c813T|caB;XU?|JA*8zpzeUdjovOQd9yw{c_D zsYWGs9say4jrqce$%-E83w35p>+BUgh92|Tu#K8Wfm3nB4&qmN zq}^^pddurnBR#6hHkpy)A^Qiy{caO+NC=RiK~#-oNN8ZPPKD zXZq*yI4OZ`gcl$>*nO&16vozxCh|tnP7f4(k0qr?V;NCnJ}Vgs4!3fqNVHq zZ%`N4FO;-blS>#G6_W~SDh%Uho-~_0a;q^#u>~t?O7Idq8a>ho(hk<_)`9f$NV&^< za1CjvG>`0%Nqn%C@py(8WZtUeAbKJ?7llO8O* z!@p$fd=QG&sAuB5 z1NpT~4i@zW!}v49vqbyY(OeGJGAyUiTZw$_!(e!vE7b9LKGC`jD=96nW8R+1(FB(R zKXCT12;n!L?WY}QKVH%Pz<~yfgx8Ml8zTb5O(8^TMuM>Z1BY9IZWnC+VjyDlB+0hx z7w1A)3yP6Q-8+`}%lNxp%QggVHH zLbL&`zQyUl;&H_P7`iWD`?-)d24$Jgu0T3NT#rb{;)&o~SG-3m=0hrWuh}7z(0vPM z9<-Z?smfb0{%amC*?oOW+N$|eNY7oxrteX2l)059!Qn~++%3EfbH{?mJyh0@JbB-e zZk7Gl9sRS64lOtTq{B5W+DB6CO2J_5t*{c@S+o|>N|E!T3OX5~j6LOXBiMknnK^@V z!MJ~FBnG!?;bw1eQmzOL*%yU&k86~qcC4qJ;`TjmU6S)SVoDBa7ARZNwMRb);P(e4 z(L=cKmKlh8g{(Le9$ue$=SJ3|`$Ejfd0!C#hErS~N-&DE5k zBNs8u2}Mhm-b~}9Z@StwOD7nj_T+=kEsgpTYLi-nevqDx;f_OUqBZ;w~ICw$R)quq-Vz8I=LDAWh$x%JI{beA1bZ zt`>rUx>$IsYAHbOp;41ME5qxPQHr-k@6J84H3(A07J_4>(iP{;Ad}l=yd6lL$1R$Q zhBiY}s&gb7XN)D6`_x0~4RDUTU;zIvlX_*i4y27?gC_Z_xOKnWLQ`)*0K1YrT=zM+$rs)L0j?0Y}d!{)j>!kqlC-Shz5yC*VsVq zKrSp~-8W=!Q(-3i;c*K{n^GY*f#Fp~`+}%vhwQD>gSP}ecZqmy051x0<8f`7pv%eV zOEQqQ0bxPvBim0pyY5MEc?9d_VD47q{?6#M<^>|>?0&gruYJ)#P`01$4y#guyzR-k z?i(w3Dd2gkim17m5~rnyTS%^r&I->(*QF2(-eh}CCRT-2LL91Bo}5OzXCY@bl_sra z`zzr{1W`L0bf4LM(V?+7`p`I>BcW=OSWz&%u$GcE&_CN-^*6SMav6ltuIT@HhF59Q z`W*Ys-PbcGwfiQ`AbKSWH@wJ`+Q1zubJ>JN5Gbqy&Eq8s?7zHa@$r)Oksu~4Z7`0!6_8mgZeL{8IdiDavktJlhA=u) zBFhn1fK;KPgiqL4ENjp~6!Ntr4}x=6+2(CP78!q@NgIRa}i7t zA0PyVn?$@G@C(6-7NWF~3Z{Ys>tw{MfxH6=#XP390d%(*#}N(wh*w5QM<6`%G-uZP zrpaW<>^>hDo*9f2&V=)ydSu~^44QJ$(Q`8_rFjxWk>dWN9{9}q=!=h`Sw-R|BT-m> zxI#i#@K?`pl}Wb|zexCHgwMe*9PML|K1?h=6f`=9SCQdWHViRBe6=UXJo6caQn?rU zT7C-!hbF7&{Mr+@1H*To#fMVOQ`}Ey*5kHYrPHU!K$i+CvD79BIcqul!v~0Wq%Lvu zr`Lps4QGGYlJ`AV|73>?nf9F)kwiH2Ac6jQWVjm86v?VA9yP{l_6@P$fX~*ID5_8j z_+`RB7$ck`JQ&R8eJzuarCUJr7?b?9Dg`~WX|+7o5I*5nRF0108#UM`Z##6zxRsRt zYo$Y2i*F`3jeE|c38r_0dUWt6q?us%n+=P1!Jn6jwll|YW#U64HQ%pYIY!e&fDM-m z+V`M;5sA+u@n-I4$5%>w*-SIOhF{5>@9mBidHcbF{M$(X4Ycn`AyVqhO zcJH|(1K5;@J%IGc%0?#rLJ60UZg*vnXAC}8q__~x&U6LgQ6TpV`saz^*`Cc`l>8~! z0FNN#ZRu}{5X%k$78Ro}5YhH2r>+JaIbT=&`E>qL2kN?*@i1 zd-8_zCP#+K_bt4W0;5M!5z~0l1YzWFr(&`0Y0lRSFE*qrPybbCV+wf4p0LVDn;{QK zP+U-!XTsvc1?f7`eV6H<&02QaCuBXI^ci>NX&yK9-&u~+JZc!e>y6nQ%6fAp;^jiF zECc8+2q{7CT4bNeUXYcLT_#5ZZK1I$Hx3Lh!JqkwBrz%wXr9Lqv`pQNgGyO4@iqWU z_Fs1BPG+WwHVjvZ^huAu9LT*vH2ZHO&Ran%eG9$sbQ zRh*LGnmqD`DOIMG=bC}#JC5PE&_7?We9+l0O&nl(-SSd0C`dBsx298Zr$cYhO11tOoUb8 z309-PZ+`JjIawlHHiU(zd)l%1QAWhsu9+02UKar?RZ^`;i}fVg$c!H4_bWeHzgFDJ z%u3QEq@M9BvD_vBjfY)k_p2?#&P@+c$B9HYj!7yp+O?>v(e2wY$f`9~l0_qHy_s*X z1f6mp*NzTC4(X#oE`>nv&lb@35EALyF}xaR9~!8>e`+L&_VE((fNOfo=kn5CyWK!q z8TDR9{$E4)EYdw6CcPP*NCP)}goh3Lr+d1u#$z+nK6b2r7z_c6&eSI+hX8Mc zVVCK?6^5%s`!2MXt*O?#d_bLhqrKh911&ow2+d<*_cRi3Jk5n3O0xd0!E`8u(OlE9 zIN<_W$wmZ6xU_;ohjG%)nPkM6A4Lt#cL~lk$rn3Q~-EVgEFCyVG;6wQxPVg&_ zKW`Xb?hr3D4;Kutdh#w6#%9JXpncD(UCkiQTOOnkZ!+<^z$B_H-gU@@hoJ0#n~AR? z&84S#4E=M1aoQ)@x;x|aT<%MhnI+67+b|axHk92_lo4>KTGg(aXf7dKMqJ2*Ri>Ab z32j8txgS4GhRutr*gu&vLoS;#l}W9FN4^y%GgS{#GVKSB-BakFI-2uRfz@+}$HU`# zq->hCsI}XFr`$%4)$8lPTP)Tun&ON&M<1I=E5k{mmh+tnG(#b+Qj}3cnT-YC3eM~F zbIxUaBjjMmybB5EqIEf#xTrXnm?Uo1StM~nSR|4coBJV?_gnlb&tsPq6Bh?zEhYoE zRpcViDyCZAgZe_fpHBsij8Yk9&~>w%~# z_bhqfghM#3e2=Pnh_~^f1`qfPwFvZGLify)tg7GVGCVKJ0}}0#?#e0PT1}>mtmIfR z(N|@Ht4h?3sZuE_M61flCK)jSJ!6Zj42@4Cz0SHWV$#X>K9;)E2k{gu{Y6Yl_QN|c- zVGoLCqtK5tuB2j1RZouBn#{qM9OP)JR$4L?IJPdbl z7G%zTru&rqq1~XDR3iBiFvPu6}+UPnF zSY0`W@>Klf&Knye*^#3;n(BK=aZK8xNQd>g$Xbj8PsTvDjUp2TYP9j1-b10Fi&JFb zA*9kEh~z==>-k6%7g49UwZT+rn@QX9J(cDQk|u=)Q!;4DeLnb>$q>6LhZTnouCyXk zbpnmP9_97!kO&f6v{R(13E-bkl+k@h`f z<@V3}!)jHIs?|FQog$65mki_3Og>UmhuXv>?-TLH{5kyD(vbTqnvSthEB~|bz|%bO zv`++oAqI_%ERPZCFLzZOxP?JB;mi{*8eEWR)=5+f&$C2W6(e4jKO~294p%0=B#Gg1 z(V5v{Im#qk1sX1NYGaX#294V5TZ`8q4skYH5^wWd_2A~VQ+Cxo%Qke65pbQimgqbh zImxaTR?QKbaOA67+nm+UGU%J+^OgKYiq+|ur-gGzSlDpN;$v?N+nlQwTE)&i(ioO$ zF5qap%8}m%g)Fdjq}zlv4!6ws2=Qhlg$9E>#zH_SPL0{xZ%cKyU+m_H*Ua*N z5`=KxqA`^XQI0m%nF*aB_kwd_24}~4eGyObmDhw!6_o*`RT*D=cV(=nCc$r`l&4;Z z8;_<$IM4XATl_g}ZXjKI`fml&VDz7n0HuS$9%JariJZ;GeM&P&8C|U4$U|T9^URdp zGaI&OhT%m*yeqk(ao+p`OaZ}zEFIj~36Ue@n93}y&O%7pW>);gg6`R#xPf+*+>ivl z6!Fzk6a+sl8lfl{jIz9vN~dJZf0^~FQz~>V;~$E;Ig)9iM{6!3;H8@(M-Ba&1oAd= zzL{B(tBTbU`{r?re7R-#ZbS2=*cLF3QjUJMa@W6%J3hn(bgp(|F`-MFY{TPDa0>&3 ztvwe>o1VC^JZ!{J)g8_)O@7IF?x={!VIiax4d~a95~H=u81norohdx|l_M8n$l2K1 z+I5-=)Z)eTn#=VxmyYfkM$ml>nq!$So>ONz#yqR*J(@hK{%y{KrIjj&lLA^-%Hnd= zdq0&W9sL^j=1nJ$e5T3_uRnK$3s3(%quZvs@gt(1=K&ej`{zBTfAUpZ%`B?&uIK}` z;1;IzG%cu@=N4U!Wr<7@w;UNm&dKw9JjrW1i&9B`NImIRiIa{pX%ehWXV{rF>+e{- zj)UPB5#Kn{735Az^=-wP>WPhszUK4eFB^T|uPh4YT7Ki`rc1RJ?|np70rXZ}CyVRf zymLgsXWI9I>5zPxhM7pIhp*0`k+IJ;)<s*39Vl`ZWwxWOj`hJP{~>&y?s9IbEvA_g09^`2hX4#T5yU zJU)n3y#6jF9C&n~!4!4roEICz#uH{B^<;&5-#xj~2u$p-hua)}^jWy2e)QvSIo0 z1N^cvTH=HDeqHNo;hCVl&E;w;j2+wz)5zR9yBiCn+Qb~F_rGDNWk%NL62C+a&A62k z9y#J|CT+Y8u@;Gl*#Rftvsv{-f|;#*+6#GS&X0E7W?WP1?=DYPP$RKaN7c>a=kmIv zMC9(1J9>eW$bwW!cpkd}Hm4z>xE__NT4UwIHny_LV)pBz2D&Md7?r!Fc#B%`rl#4Q!-n2y|z+RG^Z)XrEMyf5;8Hk4-po*ZzUhxh&Re=nLJkyj!-xC6@^Uu#M6J5 z7`|2fc>}VaEagS>{ZZwB=2yAsWXkZfPAw_7G~Hqyi~=<~{*0EThwPeh>TU+=PYgU& zq8RtScR3vm$4-G}C2b-SWhRSI==Y__8Bc<-&>7Y6-8vB-2>naQw+{axPY{)sB4~J- z%D_``ihR`_`GA~O|98!y9Z1?1H>u!`gwfq=sh4#g{T7a0t&S}@I?l{|B|KF8TIjz? zXy+=1uA3Ois`rGFgW{%Cxlu@4A#H4@yA`-q>9h7F+9B4n&0;Mjv1oC}7WF=g;*saQ}+oT)-${4>dp=8=7S|6Mc+l1L>$=uR_TCJ3UcX`&-_ zf!JC8=NE$Ph4@V9pDDvuiz|0RLXU(@?j1S$sX7#OhvGk;sXDz6t(a+uGOMXFd%8^p zN1n|JwOAGCP@+|q!(dUHgdgx46>>nb;JmT1(^g2g4t0s_)ySJM)0{)NG{q_vYj3L5 zawVk;J5sXLR2SSrjHMDSFUJXSIq)-a zO@U|10`jf(uG6m2#ty7ePB=&DU2Ge93pV#t1ty#}^~(v3HY3`;XW@N#Hr_;?Oiu0} ztvr0Fc^EqbieGtKe1Um5e8dpP4rfz#aGYmIRK_vJYNOsOg&TRxUUFYH{Vsv~kAlDW|&-u7U8-@J6Y8>;1~j zRqtraRq;(Aa@OoJ102kqD34Li|+2{L&Gc@fyohP0k*`Z8k^6Wf5>3 zVVEpckGA-oiQ#`atJzA7q-rO3owx3}v3jC&HARE0Gl6sbGj=NSY~|%zH8lfCC3iT+ zHY*%)gh~oqSX?4JwCjl1b3c6=sc=UGpo8M{5SVNp3(aGXrdYKR)l>@v(`imJwE$}v z>R`VA*h%W`^>**U9_zNHD+5MPY;O!N)n*jD+?(7_+q&y{EbrSiIM;c&{;8jC!pnllhPZq>UQYOG-ujeMj6; zM0`6q-)qBi;nT&8zbtwo&mr&K>(5y&t+LU~bXTxFk99O!#V_sQr`v37hy!?s0t-bW#;5b2hO_U$xo>BV5?art-S5Fj7jakR`S{qaVyA!*@x-qK7QY0mD3##_eKNXw1wp4 zj+h3qb5Y`KdzyC*_#Ivr%67ibZCnmsFKtRYZ3S+bY`1!g0%=#e7G5i%aW^}H`cch7 zPcIOW!xOM7LZr(5W~k*zi(QT*jm+OW5)7gv_x6BI8@fvQA|?Q&zO=qdbQ=|^0{9Da zXo|N+uhI-EHiMCO)x{nZMKTq@>DI+uJ~DpQB81{E!XX{)=vcY~O}&lw z+5rzNpTf#U`K)549>K4a@IZ*SnRx4qGgYpr^QlU>3jonsn@WOVcP6xL;HU&QD?^Nv zSfmm`tgGSL@pvnC?{_Ke3q<^?}#&GeuP`+itqb4~BHQrv1 zhRln|)Jiq+>s)k%nP^+}WVM-UXPnqjxkf)>zM==u=LA@M51aSkh&xUg_JZc!AmDnjU|a$kFCv2s&-(gCZ(d9LNz5&-ykZpO(m!t081PXzv^z&(;)g z9tC^@@p(_)xN_vo@WRv>azc)-xc?Yc9#1hwUP>iH0?mxxF=-?93 z*W#$E`Wmig1tw3}q0AkQNvxsDD@SeP8t%!*)Wyoqz8v@A!Z5&ilhJI>;^r?b$HR<- z3t{<#70VwlSpCG~)@97oWz;2W$sOTG6T5jgvu=28U;FT8xKlF`e{G9zD4mWW-^~sx zrTO_xv&bY(qDJ*I^T!acNK+{ym-z~*nI%V{urYpZ zBs2E~Ty)$|ysYpt)zg6LYg)lGQ)TLaD0#?69xdz|@nfjasp!y(NTkm;gz3(qsn10N6+(wtxEaWr(Lc=Oil&Ak$D)N;R? zjx#<3^R)qqHr`r|*y_>y(`q)vH4eWJ@>bCp3f8HILshTozdLCz-`u(DPa z&3s4~S}M(@CtfG=-k~{~8fMN@(_9m?g=k|(sB7mVPIED&Jc)AG$fw<4*Epk%_eaVJ zA}5EvPU{-U>my;^D$9o$pljs7YTexs)t>!VDXcG@aC$GN!_US#nls6T!Cj=xP0C` z(^QrISwLt+{v>ohpAIl>H;;V?wI>on(b2Djq*@{)B`Bx5N#!gOAqr z=uxAoOZR0@|8=4L_#$WKooTV?NU_pc9ak$RaNRe}RMm4SRx)$H)$!UI?Ud`}VOTsK z!(`3@$A?_yZueyereHNWa(>Jj4bZ$?by0`)X)Z>GeeEaEeYaR!nWw=uE z-Vtvy;K@RzrJeM&kI3((GiIix1&tGs7rHaVT3=j zFQP&T|6m}#0)6)af8LXBcGHRDSBhvrBo=0}6o0d9dX5K823tsGI+;z^+Rws5uD}Ob zEb=+^B_b4AjS>|yzTnjQ{l#w9(+hUeEVukNWyK|%E74w|^W>o~u!>o-NzA0)TRx^r z-j@Qa$oGneJhVmt@cB?Y%B7N0j|%u{UdTe#O0!Bkm6>7fm9%rXurTwR2w~L|uavy; zur_!scLsv%u7zbbZ@Ti3lw(b1*Yfy3{v*0i{ztC7MQn$s%ewU%E}C;7H=&#-45-AaBQ^pn0ys*uI&ZmAI3+ zK*?HlT`Z~&*H$QrcV_Pd#I1u}arKdWFLCl;h)7gY*>sy`E}R%M+2&>~`lU^V=kb7W zAfd7Y1Jv(l2x-b2=9?Q|b;MVPr%!z$sx`u6o^5fv$FGIu2Z9TVti{|?cjcsV$um1u7U$BBBibpp1w}M1NK&aHrXFN`Mol;NhT2@E$~gt<807*qoM6N<$g5=5ip8x;= literal 0 HcmV?d00001 diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..0f5b8c1 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + +
    +
    + + + + +
    + + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.1.1
    + +
    +

    libtorrent

    +

    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.

    +

    The main goals of libtorrent are:

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

    Getting started

    +

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

    +
    +
    +

    Contribute

    +

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

    + + + + + + +
    + + + + + + + + +
    +
    +
    +

    Support

    +

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

    +

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

    +
    +
    +

    license

    +

    libtorrent is released under the BSD-license.

    +

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

    +

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

    +
    +
    +

    Acknowledgements

    +

    Written by Arvid Norberg. Copyright © 2003-2016

    +

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

    +

    Thanks to Reimond Retz for bugfixes, suggestions and testing

    +

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

    +

    Project is hosted by github.

    +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..cb033c6 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,196 @@ +:Author: Arvid Norberg, arvid@libtorrent.org +:Version: 1.1.1 + +.. raw:: html + +
    + +* download_ +* features_ +* tutorial_ +* examples_ +* overview_ +* documentation_ +* contributing_ +* building_ +* troubleshooting_ +* `tuning`_ +* screenshot_ +* `mailing list`_ (archive_) +* `who's using libtorrent?`_ +* `report bugs`_ +* `github page`_ +* `blog`_ + +-------- + +Extensions + +* `uTP`_ +* `extensions protocol`_ +* `plugin interface`_ +* `streaming`_ +* `DHT extensions`_ +* `DHT security extension`_ +* `DHT store extension`_ +* `UDP tracker protocol`_ +* `HTTP seed`_ +* multitracker_ + +-------- + +Bindings + +* python_ +* java_ +* go_ +* node_ + +-------- + +* `Introduction, slides`_ + +.. raw:: html + +
    +
    + +========== +libtorrent +========== + +.. _download: https://github.com/arvidn/libtorrent/releases +.. _features: features.html +.. _tutorial: tutorial.html +.. _contributing: contributing.html +.. _building: building.html +.. _examples: examples.html +.. _overview: manual-ref.html +.. _documentation: reference.html +.. _troubleshooting: troubleshooting.html +.. _`tuning`: tuning.html +.. _screenshot: client_test.png +.. _`uTP`: utp.html +.. _`extensions protocol`: extension_protocol.html +.. _`plugin interface`: reference-Plugins.html +.. _`streaming`: streaming.html +.. _`DHT extensions`: dht_extensions.html +.. _`DHT security extension`: dht_sec.html +.. _`DHT store extension`: dht_store.html +.. _`UDP tracker protocol`: udp_tracker_protocol.html +.. _`HTTP seed`: http://www.getright.com/seedtorrent.html +.. _multitracker: http://bittorrent.org/beps/bep_0012.html +.. _mailing list: http://lists.sourceforge.net/lists/listinfo/libtorrent-discuss +.. _archive: http://dir.gmane.org/gmane.network.bit-torrent.libtorrent +.. _`who's using libtorrent?`: projects.html +.. _`report bugs`: https://github.com/arvidn/libtorrent/issues +.. _`github page`: https://github.com/arvidn/libtorrent +.. _blog: http://blog.libtorrent.org + +.. _java: https://github.com/frostwire/frostwire-jlibtorrent/ +.. _python: python_binding.html +.. _go: https://github.com/steeve/libtorrent-go +.. _node: https://github.com/fanatid/node-libtorrent + +.. _`Introduction, slides`: bittorrent.pdf + +libtorrent is a feature complete C++ bittorrent implementation focusing +on efficiency and scalability. It runs on embedded devices as well as +desktops. It boasts a well documented library interface that is easy to +use. It comes with a `simple bittorrent client`__ demonstrating the use of +the library. + +__ client_test.html + +The main goals of libtorrent are: + +* to be cpu efficient +* to be memory efficient +* to be very easy to use + +Getting started +=============== + +The tutorial_ is an introduction to using libtorrent (C++). Also see the +`reference documentation`_. + +.. _`reference documentation`: reference.html + +Contribute +========== + +If your organization use libtorrent, please consider supporting its development. +See the contribute_ page for other ways to help out. + +.. raw:: html + + + + + + + +
    + + + + + + + + +
    +
    + + +Support +======= + +Please direct questions to the `mailing list`__, general libtorrent discussion. + +__ http://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: http://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-2016 + +Contributions by Steven Siloti, Magnus Jonsson, Daniel Wallin and Cory Nelson + +Thanks to Reimond Retz for bugfixes, suggestions and testing + +Thanks to `Umeå University`__ for providing development and test hardware. + +__ http://www.cs.umu.se + +Project is hosted by github__. + +__ https://www.github.com/arvidn/libtorrent + +.. |copy| unicode:: 0xA9 .. copyright sign + +.. raw:: html + +
    + diff --git a/docs/ip_id_v4.png b/docs/ip_id_v4.png new file mode 100644 index 0000000000000000000000000000000000000000..55d02390e9b2965af7d83816bb50bea2e778236f GIT binary patch literal 5825 zcmaJ_cQjnzw;xeM^j@Myn~|>|i0Gn=5)2~@CZa@%XweyEzIr!Wq7%#vX7ohOM2Q*_ zM6a13(IW(5-uS&g-g@h;_0Br?o_juP*R%IN=iZxOW}-(=%R>tQ0O$?$wao#5YY+f{ zOp1n_gh;HbR*)vLW)CfOh(sa@DJdzr8hQW#II#yn1nlniXaGKb1ZX~6CH4pc;2J$} z;vGkL5&)n92N2=JNaAEA5J+OxSQ#hr@$m4-$;lZT8@sr;L`FvD=jS&zHum)NjEsyB z2n3RfeOcM`-Spa-@B85W6(VtWF`C%&zJ7O?_!U6X26#%kM+zqi|C40>pLr5D_Y0CK z0KjP6*iXQfiA2EI1d%O^7-_Z-hZA)GXK>%GoRIzR$(^kpZU55!{=HBF7bI2!cB6N* z0bdqN7Sqi%z#h_YHUONB4X#1zaJXX++>r>svR{K(Lh6phk{86D9wNyfV$W!6(LQm% zq-FFy@%?BIHjJoILKN)k>RMi277!3nS6A=u?k+Aac5raW7;X?J`F#C_zKtIMz|ebj zks(10yZ`{Zyn(i+Wzg%LLb{f6Ad^n%av>t?SMI6l>Y%lmC9RI;C;P>O1VM^11Ph(x z4U&-mr**KY{Wuu~$XIhLd5e7UljaWCanuOr?&At&ej(oBJ$3E^uSTc4p8#Ql<;+7s z7PY^V|AEFo-_o0B=Ew$%y_PQzv1S+b@Z1hkd?;Nu)EgJUR7G`Adr#HBGgbBI5HumO zH9T56Ra29dol+LO@mz4%e!AaUYvO=#{?U4M2BY9JZO5ODx0GzqQh^yS-(AzO%&*?i zoOGK{h>?^c%0S<*g$6%=9`n%6&#?OG_RQa1Di(olXK+{{Dnj@YiJuD{znt%e+Pu0coqT`v!KxiB ze>lcGz4KAGxD@Nd<;76^6Z?|iie*E}@@%Ecr?TlJ>C5zuCPk;3M^Oe;Y|)rPT!I&F zn|tsbvu&&Q7qmjeV-Kgd6Wh)k9_{q`%TIz=?ba7bgi)yMt|>gvH2%;NQyPePL8izbja(lrG&)zv;WTjw;D4ScN5!v4Y(6p6It&+*^05^wp!2bvPCP66 zDgDCQP3x6~aP&ChYPDw>Uk%GDf_WVlZycK4LsoxAzEzQ+Ja?5Ih<<05L`Zc?8S&uK zm1}aA{^}p1Vo+RA0i=F2((aDSS%8iRoYhm6t&L zjUJBL>5TR(K=J{d9Egm$hClEku`kTO>kkWD!m@EvRu!=T^#0zXULa( zQCRFh3w|2aiAYF_LlbUkTU-alz#hN8!!Id z5Xht^(V2bv4Ry^;XRdaQ^K%_ourdX9%EF4(bF)t!XIST}BCAXpbbkHCi8D_4&l_yr ztl!F~Az&3&K~=wYee+HY>Y_R63!%C6R3^>*?fI;lU!$n3MI+1}TfoFuES|{h|8^^gdr%CUI}0|DM}jyq2aN-eoFj z<#XG{NSvJToa7rjcq;_Y{s+npJQbY zx@Xv}hI25_nQ}r08a8POFFp+Q=9Ur@`d28W>&3>Ye>hwwQ`g8ZiIOLu0I%Cx$(_tV zil}P>_>d1Eqs|}b<>w(tp9(fc*G3caNxOru0lPqc0h8aCd$2{G=6l3NAXHy#^U-uZ2W&O=1#Jg|2<2ri9xtXa$hNA*DQ%@)?`aMYR{>XL?@HRLqeBs(3>^C>=O9oVcxdA83$r z%X##6&Uv3_N4vW%)wMuBut0SJS{Edd+2FzpBH7U03xzS;p@AH>DUGisvOaA?SO2B1 zX~IS1&O!w9hn?APOSw~sZ+1~{Aw1F}W`oo}^o5s*RbfB>iN>0X*cY#4pfWAOGO)lh zCYVkWn2AF$vx>S&15$je5d~?&?#_nkRU&zC{1#iVLv$i?MlDPeVmb%83wCb7?N)T~ zK|lH7%M`3cX@AbhE6ubWiy2|5wxA2uz#JTph+UQ_=+k@Y83^Nx zdW zK2@E&2NfQ&bPq41v31`h|5IkIZM9AxlyOVn3jTgea>wu+{5|dS`YOhed}6eA`(00G ze73RQT0(vQ@mHUg0*l6h3=df+Pu2Q!Rw&E`R!ff_jSq>dY?Q1^sOv9fwP=k2>22GG z4#?*tJWot%K;jgB7y4k=_&QzHQJ*7I~bIzjcLQjoXSpC*#32e&yU?yBYB;?y_v z!vTdkoQJGG?MRxUE~nNPQPDqN9r}#}3gg~!m>R!PyK~7r+^HnWpm+xlcos{&CBZq; z>n`Ub)q9Yq{_s`>nYm#s4dZ8|wd`#@hca2PjF$Y5fxZc}SnB|P#iFa1l<-tdA~Ur< zw-@~AV@$RY@6RM&8Z+T{DO25+;6)lx~kz|PmwG$1A! z_ZOw{ulpa_4_Mfz*16h1aVhMv0nvxP+4+G1QUBhUs#e8JD}UeS`!@P$a8k9a%eaI3 z8OEdGn6EjaOorM(>9P4EbyzSOBe4$2XpQOjn>2X$-y_n+zquG*A3HV#Zkc1qI9} zmXY+*FrzjpA?5CbELa11NuAx_dzLN!n_NG&!ZICfS}qHF-$47Ipz_1&uP*o! zB%Jc|1Giu5px-l)gYcDB9*{v;5+YzpH5RodOl3*Q>mdd!42%XnGfd_G=4nd_Q%e6D zEZ&y+hOjBbvU^QkzstR%EpsWW9MtnC6F@l%?jYBA`GR@zyzBuNHsUW=Hi>j#Km9!;fM7q>`-0TMid%R*yC#73L93sGo zlZN*v8UN^nF;p=2H-Paw&GA`)2862325MN5qVw3xr4I*^RMI0ekv}m=0sAHjtfU^V z&LEF6`3jk=o;U@x6RzVNAJExasPa8i2C6lqoD~FceEEcfXl}Sd}pb(R3$bi z3+x~))ry--Yx@FuAG9oH4_O~4#y^*f01`-N3b>Dg12vQ$(}Yy*BVq`?HYSoE%tbcV zj*aK$pC-yK$uNabpX3ec)m5AjXx4u!`jlgRL(_Hh$YEfFu44TdkNM$w~9oO0`1Gc{cm)c#^M& z$0<|mKfAue;*L>3DJ_pFa0M?5U1peQqQ$VzP>09rw+r-o zjj^aDVbgkMOySN_V6`434!UADs`%gH;$oZqMFVKQZ#tXi31J>eK@FhSl%io^fixNq zg5i&bk%?g*bp3uj)~c>z$>8L{gz|h((DEuXtl5aj%_*+e^bb~;oI=4$AuO$Xiu--i zZ4eEJR}UZU3|2~`_4pEV)J-faWdDUxOUkv7J_${Ke4KpJtA;I$SR+>fVzAWD+yx*R zSVt%44}qf4LZvtE!?gIJemv4Yz@-=k{!RZQn8qs#wP2Mot%5zVH}M7Yw;riVro*{f@_IIezpQz z1~FMhT*Dh~UjqSEXh7;7uqf;O*gTb+s)c0xCW2Dwlr(ft$?(_y=kt`ErGWS5O~q#q zJ@d>fy!UFLsJNm6=uKRuPScjz@W7~GO_-;)`{_fazqOeWuUC9qaXw$4kICR7c8-b* z%1k^qZE?4GRLf?5p?1ne=KQM(>n`b*YJQ2mhur5a7Z(u2*>AVn7QAkSzY@l*m>cSG z<+^j{H*}^a+)G@3TcqEBFphhy_MFOq;3Q%`tcN)ej^ujl*^p^Dnz)>I6r}&pOIC+I z+U3fEbWAHon!V_;n(Scq;p#KOI)yYL0s11PTcWqvXQ-a5cgcbt2l8BOlBj$x{8F2D zxFX9^O*Tf7AR;W5Z8|?M`l&h+H@h+hx_)Z?`e;MOcZ^jkN~!i=4qM%CpZm)OZLa8r zpO~Lkr{+FZ>wa#3m<&-k)#u3B7BP>Nr(M6hl-x4Y^teo?I#!g0Paj(FP`oniX>ayn z2D8P`z~!LXu$Vv%bk+3oaNbi&@%E3)pJA!SL-kiw+PxKrk0cX0-B0uOT~FS0r&_M7 zE>7HUPPr@d(YeY?${htPjQx)sy~;c*wS~7@%oMrpDPqssG&^p!LXMd4xY{^qS-X7} z%1z7FG(63VpFGTnls(K9%{Eqx9$wVV@^rdj@xlnfW;Ar!y0aWR=C9)5nto3MSej!h z;?*Ej>nZE|J;P)T3VWQ0(lgqGgWcwSr7GQg^8Dfki)_JbVUHt`lI#Yu|H+I0^~B;6 zPM|4zA64?2QgyejIF;CWm~VSFUs($#ON{pBV=Ys1@S+3#?L5A}qf+>WgbtNaz;0V< z>D!{OWm5_$%Y@ok;ts!Yu*UBAllhqQ(U~4=#&P(WgU!_ZJN;7g^SNBY1-FR1{s^&_ zizmDBQ!2DI^}WqN4V(A^j-S*vq(LBnPS_;5GbA_Z#`XLZzi|QH*M4U|TX?5pdAVP; z<7IK&wk?p9%^QTH`pGNC7o8E~7!KFnocySzS33W;eZ6}ytz6Z8nJ+yV1(ITYC1VPC zpg2bv{rM{jpd-{L14e^~>ji3?Mzqyg-K;)H(e3fMjx7WPtk4eYHVAw(F5OJ5BKj&+ zZwCuu&Sd09UB8?wa%n zF|43P1`FP3W0gep707*V77mkqbuGFP<0!vxSEUH+^Q-go+6@4+6qqCPDz>SdEMljton*85CsLTY2J>9YTko6lo%n&`W3mA#|lT>Aguu zrAhBa6!XIU|MkE3)_UumHD}JuJ~Q7gv-ix|>%{14tKT7KAqM~ecQl|X`T)Rf7yxif zh?D?N;ainM<2z!ynue-491c&(&CR`ev;zQPxOM;zu)p7~2$^wF3cRitS;z zhcCk7006}>04@v{ikm7F5Wr7W{5po8$HKxQE-tR6rDbPl7aAIhLZNDEYTDb|hlYmM z*VpkZ4#mWh_LDx$ZX6mNe#PPTmm+ZW9hLk0xGw-aH-HVk4h6-59>hKSrys}6{5M_{ z0ATp_$PVB}L>ypb97msm3)MXg3&W`buEIRGknqEeH!Tg$A74?Noh{~u0da+Z{fPZ{ zfXSuYr6gTNBWIB?dO#RGeV8J?goV9m4|{eQ@o=4cUjd z!`%Ae4qV4@J2nWXn2Q6pwzjUUtZ;B}C@3hjwYBBs`fYo8c7 z``PS%>Jq$G)09O+kAK_Uj-z|mW%#E)-*(WET-B1tFh(L9Q$tdRrH|#KR)-B{BSQ~C ztLu-CY4_l*RcwPBY77roL?&irpd24kK0YaGHEG+6yu8a}7SP&=kKyZEl%`~C`}KyO+DmPae`sob~@o2Gj`<>j_w3+!K9 z5VqT5x+M87z42I<9p%0fGl578_j0Pc3wx#)CGi1iDySXzdbwd6=OK@TmhGt@}!w%z2Sw@TjV%gAwVC z*frm9NU623`GEG7pOJbrlRPvqe^!;bYFOdy*X8urNA|NX@=&svgLv$vHFM>IOKX$f z!9Zb!@1?qUTV0YpXS&LmBZ={a*)PmK18)w#R#Ic<6%p_$#GC5HpIuc;U7cx|ufoif z6hjxfsb169wel*(Z~|mBE?U@T8(+8AD`uX#Q|L3Jt$_5(ltUNPkcbea67$_g5XAzl zZv7u#6-0SrH^E4AyI+z=mXJ1#&!$$YL&|%&k7#-e-~q^Iwyl*4j|OnO-!AvDSSJD! zOJKfgIB%1iIcoBkv)0?+rp`5xsV=U-BDgKS(VO{~(%uJo+vu2Ek(GffS2_9E?t|{v zw0gVgfX9aKAc(XGUkF`1;0EfM7{!zHs&^zxzrEj>ImO3QDy&2J~~X zOPz=mAaCE9KUxW-U3DN(;MZngJ8@wGbT&$eQZZigKWn9)Aqi;3{+ZU#t(*0rgQLPU zhQ1VK>VcOVeaU0Uh<*+gnv1xlnLKE>Fl;wDZ?|#baT7Oa+`_qOOJ6qVYK1fY&<&&> z5xWI{FzHixJNU1hKb3oC(jGyRY3sK$Rf#ff?+)EZ+TFTs3j4`;?`yu3fWWBxFBlvJ zSW;R|Xh;v7e2~5!@U!8=yGTsozsFAy+j%}u#2$C|@*ANnIeC{!^;6b_i0N$AeUAHt zh3rvbc9P4OyR0OC5_M7q3sO!!Z;j;N9{Mw@NwLphjEo{NHUj^QT8{^UHI^1p7MGsB zk12}eQP(xhUg8&adVTuO>hjs&rJ9O74_F-7Aj|J>7VAQm?p#4BoR(!DNbSTnwjeM8*^eOSL_j zBKd84yT%3yDbIzuj0Mu{|By%zjlgv?REgNEP-hfu9L}{-;CbmRYmu^DqIuUGhnc58 z&{gJte$1nburfOQ4T*K~489QJ>sgc458ru}1RJSM<@A~@9<(!~d=R$TD#4Y^M-xU8%2W?AV6 zTH>%TKdzML`yuKfd+g$amEZAR?9MK<_XYIl*eS;wmJJAb4O8{An>R~$;y&2iF~3Go zsk)KKcPdEo%)oe|LuuYY@jg$CwIJcg+NGc8yEqS5NhjPC{gmCj`KsBDUc|?MXUJ&j zN;OHH?>AqFyXK~P(6_LdDM)>jig-uD&P@e*w#h%eW!+zbu38@#m6@4kQC(#+OBVUK ze9fF5E97`|UO@sE^wZOyk?diR9rZ`{Su0czVu&_!z^RT;5M$~TM1vkm_?=i&Afr=i$`RK<+|@U8Gl)kY#b_*H1U~9aM=OHh zRcn>s6M;+nh8lUK6F7)fIxZ?g3MWX%%;Gwh(#Ki0MmnMK#3@#H#?*RFwq8Tl>hs(! z^5n`phTRM$G=_+3V~)54pzCA?L^o{_~g6T++>=u*f5);;(>`FeRUF|_1(g#;R?21`Z?kIqJR*l z8Xk*HP)=dRWSoh2HYPgHD{8rM2!bA=9+P$o3>9LWs+k;A96Y-Uh$az6S;ePPo7YYz zwoJw<4%L6<{i(oKwEgHXqZvJ{YFr(&xi~9;_)ZPhb-vASTow6oHD)9F`Ildm59b&7 z4r|3-t$Q-t)zrpr`*`Qqg#8(_k$%5o0Xv}7YTZ+=4Qnv@yLYWV+k!&sfSInr^~I9B zvE)Vh98nb9lM=x*`Tknd0M?o$!dlnQRbI7O>1H6Vt}9IpN!c0cDO5M==2O%)&k7v?j+>MH;Gn|tw=Hy+-VO8wW>h8l&F_l$o&UFEdRNA!#G69C0 z%_|e1vd4S3i>~vV0xt2Ka@i2 zxz7pP+do;eE}U`*B=7qqNrjf|e6r zpXBRhXm-Y~10xKvU+f=7&~wCLoE~zWuO^V{Q|TQ97xl<=DK84V?k@UU(yxTKSC`OH zk`5Kr)BB|8w)^z~VN~XYBHiktBNl-jy)iAkS%feEbj51X`W>*lBXm+x@&@s-Y8EmBdozt z4L1GGUf6f>Nn?}6JduLV`E9ubU{>crbx11R-Ecor9^@tI#B;ACU--{i|C`l5mObEY zK1;Vvok(OjllqHBY2K6uoa~ENW@x0TwEX+Yl7##8b$&lPnnaylJt>wZ%WWYXhbnqF zSTfvN4X?oN`V%QEc@UO**HWKU_?xqqL|;_BY{Djhpbbi?g(?b;f3GUbEGaHq)P$Xq^#=Gm#3yvoE}w&0yY%)3OVS zsuN~1l%farm$~J%Bzb2zT3^KRhl*?+=&AJX0n!LU$iESwkOHlA_VP$OBnT>xmdtCR zK^ML6h%J$}>P^WNC_AYPlNS`-D?RjQ;c_P|C1cw|K0cq6Y}SS~hqfRuPh|U^=l&tp zr<1x0uj(8?)_sZ^BHB&_g5k6U(jwUcwE46)jK-Vzuaa>w>!wFhduEnJQl>LSVqSyX zYOkt$MSx%9faVH6f60pkUkm0}W3+EI$R1SrgP`$Pp8aMLM$2F!2LjgzaX_$EUT-h_ ziVZ!Qe!uomU7yf#x7stCRDZR(O>ASSqP$an<{5MwsFk_76axf@7rfjo%O;m}1!nqb z*J8gL7dey{L%t-7g%@=8rmw78bHk0N`MG?W26gE>7v9g}tZJM5Ix(oE+q78AzVGPt?Gy$LB;e+|xoQGAJwzc@R2Yq#5nvf>Nh{x+o~`r{gz}cYbv| zRFD;}a;)xej{fzXu1_B>%^yUbKBIiJLTH9{3zm{=Iz8l1tgnRN%gBdmJ=v0 z7o?33%iqRu2(DFzY7NuN=gB4j_a=kO1^JDQg5O+nW)(8NG~b0;0VCd#agFu$WY!pQFAe?kUqJ>|G z$|f}n(n+q8bR3V#^n*_oje!-EH$sF`tbq~U<&Mze_8U#LH$uuM7boe7)m)$GW!>Iq zH1<}Qk*=U;lslqmgw(uk<0!+jD?moM5PBKAYX`joFVrjM;amc#&^YpuU+#E{jH7yA zl2BB0CC2|{xE`z2!SHJkbSvi6%>m$S(7rbkXcarJG&)M!hPi)4By+5x1VWk^i3a^* z%89ImFwq->Q>TmjQl34?5cq#S&Mg}xdRed8KOmDp>=b1E%J%2k0`ZZVhQaz z8m-joE2@6^nIkmNW%xajB%VZa_2q!)Tj}2~^mL+#5UIwT3k4E9$+Qfa7ZLP_t|Q>6 zysIAGsvIrwh4^zn$QkHz`YNYgGbJ`Mb-s*!d6Ymc;S zbGun(jLl|C+~7ov%DGEsbr*q}s{mJB6DP<;%?{-S-NZu9jg6=4sB<{|NE~TosRpX5 z^)j$nObuS0vKf<G*Rb}dB5q7t`zxWtYzFnBE*tRxivqUTt zvp{veZ5my9_-1U)T+c|(iC{C!D+=2zTDZA;c;{gb_`nn&310X_gkR3Omrl)95&!nL zXyKM`yICmZdenL%e|)|2%qM$zImVe&UOB+ZXIU}bK{gxw4^qED9`C70N5VyII6geP zI^pvrcN{9{XXZ%t?MF;bjt(qibe{&T{p4$?C>*>y(o|CJ>*JQ~s-LYw&@Rjt*UhTw#BFDlw$QV{5%q|zRvLmzVwz~h(`jI+-Q)o{P_HG@y6{k zWf6tePg;DNb0e3Y`jGo4W4$%`9eXoW&$#!gjn$tU?;l1BIIPv&q^`&&T}pk}G6vJg zN?>)3gi%3Y;f&aScM@8#?SmhcjnBLUgWVR^~sn*AyijiNSqU+R3?BeiI4hZIxO9ol(hYvMZM(-`uUTk zWPY2lJv{gR-d))gP}TK6gzMkZeawhb831_+kGhJz(rwV#FAx$txz@#F4FGEr#rouL zu)d|fw#jZ*R(v^;9<1cjmBXCHSMjtHqE?lpC-mZ7U1EvZn{D3`=W#}6Wxlv6`GNeo z53l~pf->8j>k*sq5)SNv;5%M-aVdw)Qpc$UruqE4yI%EC<#d6G>Rj4~EZk*`iF%L6 z?F}Vs&#eqWj>)Zzj>)2kYUn|M5X(mP2j^b*9oK)uec(ml;nL1NDgRdWzmv(xG`V_S zxmo**O9e#t2f+C>B;pHS_7CurdMdP@SDQ9@eGQabpAqG(ccW&$4~LcB%6!O|9$?D9 zq(gQ8{mEQe7nbjK#w_Jg>3y)<_|pe*!4vgahwb7+mQ;uRJLoAhH*Jd=iZ);9t*R=_ zrb5-khL%5Ws5MMABvJ)rn>43R{*;$^a1R90%5&LE2^RXr<`m^!f3IrPEc2^)EH6C% ztvx(`Lbe~g5CF0ohYNRtQlKU_SzC3{M3moS(7vaZT$T7J6x zXx%#5evt05T8v7LUQKG=Xx)WL?bY>u=}sN#jX#Yyj=Jk{h?Nw(C(o%bfwKJFe>6p8 z?$31C=Uf&>fEkwtx#`R>{8PM1YSN!v<0oR!df#8gIn&_nFaZh{K5%{66t9JM%jOL> zF^8f%V!!wz7wOz8s`|Fv*dyd;*bb|D!bgx>J8p6YUbk)EWi0gYr9fGx$tCU4!eTM+ zR5whQ7rgm|2J$uh()Iu2E^^=Otk|ix2(!tr?FVK?3^)hnx?l9bZztDfus)UYqDf>i z;uM~U$)@4EcG>)CdpAloTYBpr(EugB@M#`1Z+pR4?qS{? z(dw_<3Z*QOyqb}}8O5*uMrrNjd5N1k#)mA~y>)Mhu0aV41^Z6AIF}0Ja|L* z)#M@AWE8`=m5w^Q9jFeuNG;jE3by?MA;(YG}7+H!SwUTxmlHtennBLiv^=P)o zLp<`8M=lDs)mCy{mxR+JQ|y9-meclvg#>r9uy)c~rG9vu3Hgt<*Ba90S1`-+(|;iT z^Y^AaL#)AR9>y!&U%;@Y>$tZIW)**2+9BIU;Lm8RU_9wL>JYGKDVt$Bod`a2ww#8S zyo<`RyQ*62URnqdY8iJ4{aRu=AlY{FN8>+N7|Edm$w_Fz)|HOt4N*f?TcrZ>BI3UQ D8#_6^ literal 0 HcmV?d00001 diff --git a/docs/manual-ref.html b/docs/manual-ref.html new file mode 100644 index 0000000..1469a1d --- /dev/null +++ b/docs/manual-ref.html @@ -0,0 +1,2868 @@ + + + + + + +libtorrent API Documentation + + + + + + + + +
    +
    + + + + +
    +

    libtorrent API Documentation

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

    overview

    +

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

    +

    The basic usage is as follows:

    + +

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

    +

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

    +
    +
    +

    things to keep in mind

    +

    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 them, unpause them, 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.

    +
    +
    +

    network primitives

    +

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

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

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

    +

    The using statements will give easy access to:

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

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

    +

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

    +
    +
    +

    exceptions

    +

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

    +

    There is one exception class that is used for errors in libtorrent, it is based +on boost.system's error_code class to carry the error code.

    +

    For more information, see libtorrent_exception and error_code_enum.

    +
    +

    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 libtorrent::get_libtorrent_category(), +and if matches, you know the error code refers to the list above. You can provide +your own mapping from error code to string, which is localized. In this case, you +cannot rely on error_code::message() to generate your strings.

    +

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

    +

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

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

    queuing

    +

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

    +

    To opt-out of the queuing logic, make sure your torrents are added with the +add_torrent_params::flag_auto_managed bit cleared. Or call +torrent_handle::auto_managed(false) 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 seaparate 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 diffferent 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.

    +
    +
    +

    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 abosulte time to be seeding a +torrent). Until all those targets are hit, the torrent will be prioritized for +seeding.

    +

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

    +

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

    +

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

    +

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

    +
    +
    +

    queuing options

    +

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

    +
    +

    half-started torrents

    +

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

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

    Each of those actions are associated with a cost and hence may need a seprarate +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. 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 immadiately. There must be an extended +period of no transfers before it is considered inactive and exempt from the +queuing limits.

    +
    +
    +
    +
    +

    fast resume

    +

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

    +

    To use the fast-resume data you simply give it to async_add_torrent() and +add_torrent(), and it will 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, then it will not trust the fast-resume data and just +do the checking.

    +
    +

    file format

    +

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

    + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    file-formatstring: "libtorrent resume file"
    file-versioninteger: 1
    info-hashstring, the info hash of the torrent this data is saved for.
    blocks per pieceinteger, the number of blocks per piece. Must be: piece_size +/ (16 * 1024). Clamped to be within the range [1, 256]. It +is the number of blocks per (normal sized) piece. Usually +each block is 16 * 1024 bytes in size. But if piece size is +greater than 4 megabytes, the block size will increase.
    piecesA string with piece flags, one character per piece. +Bit 1 means we have that piece. +Bit 2 means we have verified that this piece is correct. +This only applies when the torrent is in seed_mode.
    slotslist of integers. The list maps slots to piece indices. It +tells which piece is on which slot. If piece index is -2 it +means it is free, that there's no piece there. If it is -1, +means the slot isn't allocated on disk yet. The pieces have +to meet the following requirement:
    total_uploadedinteger. The number of bytes that have been uploaded in +total for this torrent.
    total_downloadedinteger. The number of bytes that have been downloaded in +total for this torrent.
    active_timeinteger. The number of seconds this torrent has been active. +i.e. not paused.
    seeding_timeinteger. The number of seconds this torrent has been active +and seeding.
    num_seedsinteger. An estimate of the number of seeds on this torrent +when the resume data was saved. This is scrape data or based +on the peer list if scrape data is unavailable.
    num_downloadersinteger. An estimate of the number of downloaders on this +torrent when the resume data was last saved. This is used as +an initial estimate until we acquire up-to-date scrape info.
    upload_rate_limitinteger. In case this torrent has a per-torrent upload rate +limit, this is that limit. In bytes per second.
    download_rate_limitinteger. The download rate limit for this torrent in case +one is set, in bytes per second.
    max_connectionsinteger. The max number of peer connections this torrent +may have, if a limit is set.
    max_uploadsinteger. The max number of unchoked peers this torrent may +have, if a limit is set.
    seed_modeinteger. 1 if the torrent is in seed mode, 0 otherwise.
    file_prioritylist of integers. One entry per file in the torrent. Each +entry is the priority of the file with the same index.
    piece_prioritystring of bytes. Each byte is interpreted as an integer and +is the priority of that piece.
    auto_managedinteger. 1 if the torrent is auto managed, otherwise 0.
    sequential_downloadinteger. 1 if the torrent is in sequential download mode, +0 otherwise.
    pausedinteger. 1 if the torrent is paused, 0 otherwise.
    trackerslist of lists of strings. The top level list lists all +tracker tiers. Each second level list is one tier of +trackers.
    mapped_fileslist of strings. If any file in the torrent has been +renamed, this entry contains a list of all the filenames. +In the same order as in the torrent file.
    url-listlist of strings. List of url-seed URLs used by this torrent. +The urls are expected to be properly encoded and not contain +any illegal url characters.
    httpseedslist of strings. List of httpseed URLs used by this torrent. +The urls are expected to be properly encoded and not contain +any illegal url characters.
    merkle treestring. In case this torrent is a merkle torrent, this is a +string containing the entire merkle tree, all nodes, +including the root and all leaves. The tree is not +necessarily complete, but complete enough to be able to send +any piece that we have, indicated by the have bitmask.
    save_pathstring. The save path where this torrent was saved. This is +especially useful when moving torrents with move_storage() +since this will be updated.
    peersstring. This string contains IPv4 and port pairs of peers we +were connected to last session. The endpoints are in compact +representation. 4 bytes IPv4 address followed by 2 bytes +port. Hence, the length of this string should be divisible +by 6.
    banned_peersstring. This string has the same format as peers but +instead represent IPv4 peers that we have banned.
    peers6string. This string contains IPv6 and port pairs of peers we +were connected to last session. The endpoints are in compact +representation. 16 bytes IPv6 address followed by 2 bytes +port. The length of this string should be divisible by 18.
    banned_peers6string. This string has the same format as peers6 but +instead represent IPv6 peers that we have banned.
    infoIf this field is present, it should be the info-dictionary +of the torrent this resume data is for. Its SHA-1 hash must +match the one in the info-hash field. When present, +the torrent is loaded from here, meaning the torrent can be +added purely from resume data (no need to load the .torrent +file separately). This may have performance advantages.
    unfinished

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

    + ++++ + + + + + + + + + + + +
    pieceinteger, the index of the piece this entry +refers to.
    bitmaskstring, a binary bitmask representing the +blocks that have been downloaded in this +piece.
    adler32The adler32 checksum of the data in the +blocks specified by bitmask.
    +
    file sizeslist where each entry corresponds to a file in the file list +in the metadata. Each entry has a list of two values, the +first value is the size of the file in bytes, the second +is the time stamp when the last time someone wrote to it. +This information is used to compare with the files on disk. +All the files must match exactly this information in order +to consider the resume data as current. Otherwise a full +re-check is issued.
    allocationThe allocation mode for the storage. Can be either full +or sparse. If this is full, the file sizes and +timestamps are disregarded. Pieces are assumed not to have +moved around even if the files have been modified after the +last resume data checkpoint.
    +
    +
    +
    +

    storage allocation

    +

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

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

    sparse allocation

    +

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

    +

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

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

    full allocation

    +

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

    +
    +
      +
    • It may take longer to start the torrent, since it will need to fill the files +with zeroes. 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 +filesystems' 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.

    +
    +
    +

    dynamic loading of torrent files

    +

    libtorrent has a feature that can unload idle torrents from memory. The purpose +of this is to support being active on many more torrents than the RAM permits. +This is useful for both embedded devices that have limited RAM and servers +seeding tens of thousands of torrents.

    +

    The most significant parts of loaded torrents that use RAM are the piece +hashes (20 bytes per piece) and the file list. The entire info-dictionary +of the .torrent file is kept in RAM.

    +

    In order to activate the dynamic loading of torrent files, set the load +function on the session. See set_load_function().

    +

    When a load function is set on the session, the dynamic load/unload +feature is enabled. Torrents are kept in an LRU. Every time an operation +is performed, on a torrent or from a peer, that requires the metadata of +the torrent to be loaded, the torrent is bumped up in the LRU. When a torrent +is paused or queued, it is demoted to the least recently used torrent in +the LRU, since it's a good candidate for eviction.

    +

    To configure how many torrents are allowed to be loaded at the same time, +set settings_pack::active_loaded_limit on the session.

    +

    Torrents can be exempt from being unloaded by being pinned. Pinned torrents +still count against the limit, but are never considered for eviction. +You can either pin a torrent when adding it, in add_torrent_params +(see async_add_torrent() and add_torrent()), or after ading it with the +set_pinned() function on torrent_handle.

    +

    Torrents that start out without metadata (e.g. magnet links or http downloads) +are automatically pinned. This is important in order to give the client a +chance to save the metadata to disk once it's received (see metadata_received_alert).

    +

    Once the metadata is saved to disk, it might make sense to unpin the torrent.

    +
    +
    +

    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 contigous 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, intead 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

    +
    +
    +
    +

    predictive piece announce

    +

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

    +

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

    +

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

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

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

    +
    +
    +

    peer classes

    +

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

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

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

    +

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

    +

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

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

    A peer class can be considered a more general form of lables 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 to torrents, with the ??? call, in which +case all its peers will belong to the class. They can also be assigned based on +the peer's IP address. See set_peer_class_filter() for more information.

    +
    +
    +

    SSL torrents

    +

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

    +utp_stack.png +

    During the SSL handshake, both peers need to authenticate by providing a +certificate that is signed by the CA certificate found in the .torrent file. +These peer certificates are expected to be privided 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 pick which port the SSL socket should bind to, set +settings_pack::ssl_listen to a different port. It defaults to port 4433. +This setting is only taken into account when the normal listen socket is opened +(i.e. just changing this setting won't necessarily close and re-open the SSL +socket). To not listen on an SSL socket at all, set ssl_listen to 0.

    +

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

    +

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

    +

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

    +

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

    +
    +

    testing

    +

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

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

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

    +

    One way to create the certificates is to use the CA.sh script that comes +with openssl, like thisi (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. This is primarily useful for troubleshooting of production +systems and performance tuning.

    +

    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 userful 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 currenly being downloaded.

    +

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

    +

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

    +

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

    +

    The available stats metrics are:

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

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

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

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

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

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

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

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

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

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

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

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

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

    these counters break down the reasons to +disconnect peers.

    + + + + + + + + + + + + + + + + + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    peer.num_tcp_peerscounter
    peer.num_socks5_peerscounter
    peer.num_http_proxy_peerscounter
    peer.num_utp_peerscounter
    peer.num_i2p_peerscounter
    peer.num_ssl_peerscounter
    peer.num_ssl_socks5_peerscounter
    peer.num_ssl_http_proxy_peerscounter
    peer.num_ssl_utp_peerscounter
    peer.num_peers_half_opencounter
    peer.num_peers_connectedcounter
    peer.num_peers_up_interestedcounter
    peer.num_peers_down_interestedcounter
    peer.num_peers_up_unchoked_allcounter
    peer.num_peers_up_unchoked_optimisticcounter
    peer.num_peers_up_unchokedcounter
    peer.num_peers_down_unchokedcounter
    peer.num_peers_up_requestscounter
    peer.num_peers_down_requestscounter
    peer.num_peers_end_gamecounter
    peer.num_peers_up_diskcounter
    peer.num_peers_down_diskcounter
    +

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

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

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

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

    total number of bytes sent and received by the session

    + + ++++ + + + + + + + + + + + + + +
    nametype
    net.limiter_up_queuecounter
    net.limiter_down_queuecounter
    +

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

    + + ++++ + + + + + + + + + + + + + +
    nametype
    net.limiter_up_bytescounter
    net.limiter_down_bytescounter
    +

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

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

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

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

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

    + ++++ + + + + + + + + + + +
    nametype
    net.has_incoming_connectionscounter
    +

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

    + + + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    ses.num_checking_torrentscounter
    ses.num_stopped_torrentscounter
    ses.num_upload_only_torrentscounter
    ses.num_downloading_torrentscounter
    ses.num_seeding_torrentscounter
    ses.num_queued_seeding_torrentscounter
    ses.num_queued_download_torrentscounter
    ses.num_error_torrentscounter
    +

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

    + ++++ + + + + + + + + + + +
    nametype
    ses.non_filter_torrentscounter
    +

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

    + + ++++ + + + + + + + + + + + + + +
    nametype
    ses.num_loaded_torrentscounter
    ses.num_pinned_torrentscounter
    +

    the number of torrents that are currently loaded

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

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

    + ++++ + + + + + + + + + + +
    nametype
    ses.torrent_evicted_countercounter
    +

    this counts the number of times a torrent has been +evicted (only applies when dynamic loading of torrent files +is enabled).

    + ++++ + + + + + + + + + + +
    nametype
    ses.num_unchoke_slotscounter
    +

    the number of allowed unchoked peers

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

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

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

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

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

    the number of pieces considered while picking pieces

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

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

    + + + + + + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    disk.request_latencycounter
    disk.pinned_blockscounter
    disk.disk_blocks_in_usecounter
    disk.queued_disk_jobscounter
    disk.num_running_disk_jobscounter
    disk.num_read_jobscounter
    disk.num_write_jobscounter
    disk.num_jobscounter
    disk.num_writing_threadscounter
    disk.num_running_threadscounter
    disk.blocked_disk_jobscounter
    +

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

    + + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    disk.queued_write_bytescounter
    disk.arc_mru_sizecounter
    disk.arc_mru_ghost_sizecounter
    disk.arc_mfu_sizecounter
    disk.arc_mfu_ghost_sizecounter
    disk.arc_write_sizecounter
    disk.arc_volatile_sizecounter
    +

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

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

    the number of blocks written and read from disk in total. A block is +16 kiB.

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

    the total number of blocks run through SHA-1 hashing

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

    the number of blocks read from the disk cache

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

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

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

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

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

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

    + + + + + + + + + + + + + + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    disk.num_fenced_readcounter
    disk.num_fenced_writecounter
    disk.num_fenced_hashcounter
    disk.num_fenced_move_storagecounter
    disk.num_fenced_release_filescounter
    disk.num_fenced_delete_filescounter
    disk.num_fenced_check_fastresumecounter
    disk.num_fenced_save_resume_datacounter
    disk.num_fenced_rename_filecounter
    disk.num_fenced_stop_torrentcounter
    disk.num_fenced_cache_piececounter
    disk.num_fenced_flush_piececounter
    disk.num_fenced_flush_hashedcounter
    disk.num_fenced_flush_storagecounter
    disk.num_fenced_trim_cachecounter
    disk.num_fenced_file_prioritycounter
    disk.num_fenced_load_torrentcounter
    disk.num_fenced_clear_piececounter
    disk.num_fenced_tick_storagecounter
    +

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

    + ++++ + + + + + + + + + + +
    nametype
    dht.dht_nodescounter
    +

    The number of nodes in the DHT routing table

    + ++++ + + + + + + + + + + +
    nametype
    dht.dht_node_cachecounter
    +

    The number of replacement nodes in the DHT routing table

    + ++++ + + + + + + + + + + +
    nametype
    dht.dht_torrentscounter
    +

    the number of torrents currently tracked by our DHT node

    + ++++ + + + + + + + + + + +
    nametype
    dht.dht_peerscounter
    +

    the number of peers currently tracked by our DHT node

    + ++++ + + + + + + + + + + +
    nametype
    dht.dht_immutable_datacounter
    +

    the number of immutable data items tracked by our DHT node

    + ++++ + + + + + + + + + + +
    nametype
    dht.dht_mutable_datacounter
    +

    the number of mutable data items tracked by our DHT node

    + ++++ + + + + + + + + + + +
    nametype
    dht.dht_allocated_observerscounter
    +

    the number of RPC observers currently allocated

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

    the total number of DHT messages sent and received

    + ++++ + + + + + + + + + + +
    nametype
    dht.dht_messages_out_droppedcounter
    +

    the number of outgoing messages that failed to be +sent

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

    the total number of bytes sent and received by the DHT

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

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

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

    the number of failed incoming DHT requests by kind of request

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

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

    + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    utp.num_utp_idlecounter
    utp.num_utp_syn_sentcounter
    utp.num_utp_connectedcounter
    utp.num_utp_fin_sentcounter
    utp.num_utp_close_waitcounter
    utp.num_utp_deletedcounter
    +

    the number of uTP sockets in each respective state

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

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

    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/manual-ref.rst b/docs/manual-ref.rst new file mode 100644 index 0000000..b9c1b95 --- /dev/null +++ b/docs/manual-ref.rst @@ -0,0 +1,1034 @@ +============================ +libtorrent API Documentation +============================ + +:Author: Arvid Norberg, arvid@libtorrent.org +:Version: 1.1.1 + +.. contents:: Table of contents + :depth: 1 + :backlinks: none + +overview +======== + +The interface of libtorrent consists of a few classes. The main class is +the ``session``, it contains the main loop that serves all torrents. + +The basic usage is as follows: + +* construct a `session`__ +* load `session`__ state from settings file (see `load_state()`__) +* start extensions (see `add_extension()`__). +* start DHT, LSD, UPnP, NAT-PMP etc (see start_dht(), start_lsd(), start_upnp() + and start_natpmp()). +* parse .torrent-files and add them to the `session`__ (see `torrent_info`__, + `async_add_torrent()`__ and `add_torrent()`__) +* main loop (see `session`__) + + * poll for alerts (see `wait_for_alert()`__, `pop_alerts()`__) + * handle updates to torrents, (see `state_update_alert`__). + * handle other alerts, (see `alert`__). + * query the `session`__ for information (see session::status()). + * add and remove torrents from the `session`__ (`remove_torrent()`__) + +* save resume data for all torrent_handles (optional, see + `save_resume_data()`__) +* save `session`__ state (see `save_state()`__) +* destruct `session`__ object + +Each class and function is described in this manual, you may want to have a +look at the tutorial_ as well. + +.. _tutorial: tutorial.html + +For a description on how to create torrent files, see `create_torrent`__. + +.. _make_torrent: make_torrent.html + +things to keep in mind +====================== + +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 them, unpause them, 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. + +network primitives +================== + +There are a few typedefs in the ``libtorrent`` namespace which pulls +in network types from the ``boost::asio`` namespace. These are:: + + typedef boost::asio::ip::address address; + typedef boost::asio::ip::address_v4 address_v4; + typedef boost::asio::ip::address_v6 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`: http://asio.sourceforge.net/asio-0.3.8/doc/asio/reference.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. + +There is one exception class that is used for errors in libtorrent, it is based +on boost.system's ``error_code`` class to carry the error code. + +For more information, see `libtorrent_exception`__ and `error_code_enum`__. + +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 ``libtorrent::get_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() != libtorrent::get_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* ]* + +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 +`add_torrent_params::flag_auto_managed`__ bit *cleared*. Or call +``torrent_handle::auto_managed(false)`` 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 seaparate 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 diffferent 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. + +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 abosulte 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 seprarate +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. ``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 immadiately. There must be an extended +period of no transfers before it is considered inactive and exempt from the +queuing limits. + +fast resume +=========== + +The fast resume mechanism is a way to remember which pieces are downloaded +and where they are put between sessions. You can generate fast resume data by +calling `save_resume_data()`__ on `torrent_handle`__. You can +then save this data to disk and use it when resuming the torrent. libtorrent +will not check the piece hashes then, and rely on the information given in the +fast-resume data. The fast-resume data also contains information about which +blocks, in the unfinished pieces, were downloaded, so it will not have to +start from scratch on the partially downloaded pieces. + +To use the fast-resume data you simply give it to `async_add_torrent()`__ and +`add_torrent()`__, and it will 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, then it will not trust the fast-resume data and just +do the checking. + +file format +----------- + +The file format is a bencoded dictionary containing the following fields: + ++--------------------------+--------------------------------------------------------------+ +| ``file-format`` | string: "libtorrent resume file" | +| | | ++--------------------------+--------------------------------------------------------------+ +| ``file-version`` | integer: 1 | +| | | ++--------------------------+--------------------------------------------------------------+ +| ``info-hash`` | string, the info hash of the torrent this data is saved for. | +| | | ++--------------------------+--------------------------------------------------------------+ +| ``blocks per piece`` | integer, the number of blocks per piece. Must be: piece_size | +| | / (16 * 1024). Clamped to be within the range [1, 256]. It | +| | is the number of blocks per (normal sized) piece. Usually | +| | each block is 16 * 1024 bytes in size. But if piece size is | +| | greater than 4 megabytes, the block size will increase. | +| | | ++--------------------------+--------------------------------------------------------------+ +| ``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. | ++--------------------------+--------------------------------------------------------------+ +| ``slots`` | list of integers. The list maps slots to piece indices. It | +| | tells which piece is on which slot. If piece index is -2 it | +| | means it is free, that there's no piece there. If it is -1, | +| | means the slot isn't allocated on disk yet. The pieces have | +| | to meet the following requirement: | ++--------------------------+--------------------------------------------------------------+ +| ``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. | ++--------------------------+--------------------------------------------------------------+ +| ``num_seeds`` | integer. An estimate of the number of seeds on this torrent | +| | when the resume data was saved. This is scrape data or based | +| | on the peer list if scrape data is unavailable. | ++--------------------------+--------------------------------------------------------------+ +| ``num_downloaders`` | integer. An estimate of the number of downloaders on this | +| | torrent when the resume data was last saved. This is used as | +| | an initial estimate until we acquire up-to-date scrape info. | ++--------------------------+--------------------------------------------------------------+ +| ``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. | ++--------------------------+--------------------------------------------------------------+ +| ``seed_mode`` | integer. 1 if the torrent is in seed mode, 0 otherwise. | ++--------------------------+--------------------------------------------------------------+ +| ``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. | ++--------------------------+--------------------------------------------------------------+ +| ``auto_managed`` | integer. 1 if the torrent is auto managed, otherwise 0. | ++--------------------------+--------------------------------------------------------------+ +| ``sequential_download`` | integer. 1 if the torrent is in sequential download mode, | +| | 0 otherwise. | ++--------------------------+--------------------------------------------------------------+ +| ``paused`` | integer. 1 if the torrent is paused, 0 otherwise. | ++--------------------------+--------------------------------------------------------------+ +| ``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 httpseed URLs used by this torrent. | +| | The urls are expected to be properly encoded and not contain | +| | any illegal url characters. | ++--------------------------+--------------------------------------------------------------+ +| ``merkle tree`` | string. In case this torrent is a merkle torrent, this is a | +| | string containing the entire merkle tree, all nodes, | +| | including the root and all leaves. The tree is not | +| | necessarily complete, but complete enough to be able to send | +| | any piece that we have, indicated by the have bitmask. | ++--------------------------+--------------------------------------------------------------+ +| ``save_path`` | string. The save path where this torrent was saved. This is | +| | especially useful when moving torrents with move_storage() | +| | since this will be updated. | ++--------------------------+--------------------------------------------------------------+ +| ``peers`` | string. This string contains IPv4 and port pairs of peers we | +| | were connected to last session. The endpoints are in compact | +| | representation. 4 bytes IPv4 address followed by 2 bytes | +| | port. Hence, the length of this string should be divisible | +| | by 6. | ++--------------------------+--------------------------------------------------------------+ +| ``banned_peers`` | string. This string has the same format as ``peers`` but | +| | instead represent IPv4 peers that we have banned. | ++--------------------------+--------------------------------------------------------------+ +| ``peers6`` | string. This string contains IPv6 and port pairs of peers we | +| | were connected to last session. The endpoints are in compact | +| | representation. 16 bytes IPv6 address followed by 2 bytes | +| | port. The length of this string should be divisible by 18. | ++--------------------------+--------------------------------------------------------------+ +| ``banned_peers6`` | string. This string has the same format as ``peers6`` but | +| | instead represent IPv6 peers that we have banned. | ++--------------------------+--------------------------------------------------------------+ +| ``info`` | If this field is present, it should be the info-dictionary | +| | of the torrent this resume data is for. Its SHA-1 hash must | +| | match the one in the ``info-hash`` field. When present, | +| | the torrent is loaded from here, meaning the torrent can be | +| | added purely from resume data (no need to load the .torrent | +| | file separately). This may have performance advantages. | ++--------------------------+--------------------------------------------------------------+ +| ``unfinished`` | list of dictionaries. Each dictionary represents an | +| | piece, and has the following layout: | +| | | +| | +-------------+--------------------------------------------+ | +| | | ``piece`` | integer, the index of the piece this entry | | +| | | | refers to. | | +| | +-------------+--------------------------------------------+ | +| | | ``bitmask`` | string, a binary bitmask representing the | | +| | | | blocks that have been downloaded in this | | +| | | | piece. | | +| | +-------------+--------------------------------------------+ | +| | | ``adler32`` | The adler32 checksum of the data in the | | +| | | | blocks specified by ``bitmask``. | | +| | | | | | +| | +-------------+--------------------------------------------+ | +| | | ++--------------------------+--------------------------------------------------------------+ +| ``file sizes`` | list where each entry corresponds to a file in the file list | +| | in the metadata. Each entry has a list of two values, the | +| | first value is the size of the file in bytes, the second | +| | is the time stamp when the last time someone wrote to it. | +| | This information is used to compare with the files on disk. | +| | All the files must match exactly this information in order | +| | to consider the resume data as current. Otherwise a full | +| | re-check is issued. | ++--------------------------+--------------------------------------------------------------+ +| ``allocation`` | The allocation mode for the storage. Can be either ``full`` | +| | or ``sparse``. If this is full, the file sizes and | +| | timestamps are disregarded. Pieces are assumed not to have | +| | moved around even if the files have been modified after the | +| | last resume data checkpoint. | ++--------------------------+--------------------------------------------------------------+ + +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 zeroes. 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 + filesystems' 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. + +.. _`BEP 17`: http://bittorrent.org/beps/bep_0017.html +.. _`BEP 19`: http://bittorrent.org/beps/bep_0019.html + +dynamic loading of torrent files +================================ + +libtorrent has a feature that can unload idle torrents from memory. The purpose +of this is to support being active on many more torrents than the RAM permits. +This is useful for both embedded devices that have limited RAM and servers +seeding tens of thousands of torrents. + +The most significant parts of loaded torrents that use RAM are the piece +hashes (20 bytes per piece) and the file list. The entire info-dictionary +of the .torrent file is kept in RAM. + +In order to activate the dynamic loading of torrent files, set the load +function on the `session`__. See `set_load_function()`__. + +When a load function is set on the `session`__, the dynamic load/unload +feature is enabled. Torrents are kept in an LRU. Every time an operation +is performed, on a torrent or from a peer, that requires the metadata of +the torrent to be loaded, the torrent is bumped up in the LRU. When a torrent +is paused or queued, it is demoted to the least recently used torrent in +the LRU, since it's a good candidate for eviction. + +To configure how many torrents are allowed to be loaded at the same time, +set `settings_pack::active_loaded_limit`__ on the `session`__. + +Torrents can be exempt from being unloaded by being *pinned*. Pinned torrents +still count against the limit, but are never considered for eviction. +You can either pin a torrent when adding it, in ``add_torrent_params`` +(see `async_add_torrent()`__ and `add_torrent()`__), or after ading it with the +`set_pinned()`__ function on `torrent_handle`__. + +Torrents that start out without metadata (e.g. magnet links or http downloads) +are automatically pinned. This is important in order to give the client a +chance to save the metadata to disk once it's received (see `metadata_received_alert`__). + +Once the metadata is saved to disk, it might make sense to unpin the torrent. + +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 contigous 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, intead 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* + +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:: + + enum { + global_peer_class_id, + tcp_peer_class_id, + local_peer_class_id + }; + +A peer class can be considered a more general form of *lables* 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 to torrents, with the ??? call, in which +case all its peers will belong to the class. They can also be assigned based on +the peer's IP address. See `set_peer_class_filter()`__ for more information. + +SSL torrents +============ + +Torrents may have an SSL root (CA) certificate embedded in them. Such torrents +are called *SSL torrents*. An SSL torrent talks to all bittorrent peers over +SSL. The protocols are layered like this: + +.. image:: utp_stack.png + +During the SSL handshake, both peers need to authenticate by providing a +certificate that is signed by the CA certificate found in the .torrent file. +These peer certificates are expected to be privided 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 pick which port the SSL socket should bind to, set +`settings_pack::ssl_listen`__ to a different port. It defaults to port 4433. +This setting is only taken into account when the normal listen socket is opened +(i.e. just changing this setting won't necessarily close and re-open the SSL +socket). To not listen on an SSL socket at all, set ``ssl_listen`` to 0. + +This feature is only available if libtorrent is build with openssl support +(``TORRENT_USE_OPENSSL``) and requires at least openSSL version 1.0, since it +needs SNI support. + +Peer certificates must have at least one *SubjectAltName* field of type +dNSName. At least one of the fields must *exactly* match the name of the +torrent. This is a byte-by-byte comparison, the UTF-8 encoding must be +identical (i.e. there's no unicode normalization going on). This is the +recommended way of verifying certificates for HTTPS servers according to `RFC +2818`_. Note the difference that for torrents only *dNSName* fields are taken +into account (not IP address fields). The most specific (i.e. last) *Common +Name* field is also taken into account if no *SubjectAltName* did not match. + +If any of these fields contain a single asterisk ("*"), the certificate is +considered covering any torrent, allowing it to be reused for any torrent. + +The purpose of matching the torrent name with the fields in the peer +certificate is to allow a publisher to have a single root certificate for all +torrents it distributes, and issue separate peer certificates for each torrent. +A peer receiving a certificate will not necessarily be able to access all +torrents published by this root certificate (only if it has a "star cert"). + +.. _`RFC 2818`: http://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 thisi (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. This is primarily useful for troubleshooting of production +systems and performance tuning. + +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 userful 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 currenly 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 + + +__ reference-Core.html#session +__ reference-Core.html#session +__ reference-Core.html#load_state() +__ reference-Core.html#add_extension() +__ reference-Core.html#session +__ reference-Core.html#torrent_info +__ reference-Core.html#async_add_torrent() +__ reference-Core.html#add_torrent() +__ reference-Core.html#session +__ reference-Core.html#wait_for_alert() +__ reference-Core.html#pop_alerts() +__ reference-Alerts.html#state_update_alert +__ reference-Alerts.html#alert +__ reference-Core.html#session +__ reference-Core.html#session +__ reference-Core.html#remove_torrent() +__ reference-Core.html#save_resume_data() +__ reference-Core.html#session +__ reference-Core.html#save_state() +__ reference-Core.html#session +__ reference-Create_Torrents.html#create_torrent +__ reference-Core.html#set_upload_mode() +__ reference-Error_Codes.html#libtorrent_exception +__ reference-Error_Codes.html#error_code_enum +__ reference-Core.html#flag_auto_managed +__ reference-Core.html#queue_position +__ reference-Settings.html#active_limit +__ reference-Settings.html#auto_manage_interval +__ reference-Core.html#save_resume_data() +__ reference-Core.html#queue_position() +__ reference-Core.html#queue_position_up() +__ reference-Core.html#queue_position_down() +__ reference-Core.html#queue_position_top() +__ reference-Core.html#queue_position_bottom() +__ reference-Core.html#allocating +__ reference-Settings.html#active_downloads +__ reference-Core.html#queue_position +__ reference-Core.html#queue_position +__ reference-Settings.html#active_seeds +__ reference-Settings.html#share_ratio_limit +__ reference-Settings.html#seed_time_ratio_limit +__ reference-Settings.html#seed_time_limit +__ reference-Settings.html#active_tracker_limit +__ reference-Settings.html#active_dht_limit +__ reference-Settings.html#active_lsd_limit +__ reference-Settings.html#auto_manage_prefer_seeds +__ reference-Settings.html#dont_count_slow_torrents +__ reference-Core.html#save_resume_data() +__ reference-Core.html#torrent_handle +__ reference-Core.html#async_add_torrent() +__ reference-Core.html#add_torrent() +__ reference-Core.html#session +__ reference-Core.html#set_load_function() +__ reference-Core.html#session +__ reference-Settings.html#active_loaded_limit +__ reference-Core.html#session +__ reference-Core.html#async_add_torrent() +__ reference-Core.html#add_torrent() +__ reference-Core.html#set_pinned() +__ reference-Core.html#torrent_handle +__ reference-Alerts.html#metadata_received_alert +__ reference-Settings.html#initial_picker_threshold +__ reference-Settings.html#use_parole_mode +__ reference-Settings.html#whole_pieces_threshold +__ reference-Core.html#create_peer_class() +__ reference-Core.html#session +__ reference-Core.html#delete_peer_class() +__ reference-Core.html#set_peer_class() +__ reference-Core.html#get_peer_class() +__ reference-Core.html#set_peer_class_filter() +__ reference-Core.html#set_ssl_certificate() +__ reference-Core.html#torrent_handle +__ reference-Alerts.html#torrent_need_cert_alert +__ reference-Settings.html#ssl_listen +__ reference-Core.html#session_stats_metrics() +__ reference-Core.html#post_session_stats() +__ reference-Core.html#session +__ reference-Alerts.html#session_stats_alert +__ reference-Alerts.html#alert +__ reference-Alerts.html#session_stats_alert + diff --git a/docs/merkle_tree.png b/docs/merkle_tree.png new file mode 100644 index 0000000000000000000000000000000000000000..303b68f90004933b3ef604d4092d4f9af7252cfb GIT binary patch literal 18792 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 literal 0 HcmV?d00001 diff --git a/docs/our_delay_base.png b/docs/our_delay_base.png new file mode 100644 index 0000000000000000000000000000000000000000..d14ad9ed86ad21a5fed783be6a3beecd1f9c1f96 GIT binary patch literal 34999 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 + + + + + +projects using libtorrent + + + + + + + +

    +
    + + + + +
    +

    projects using libtorrent

    + +

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

    +
    +

    Wyzo

    +

    wyzo is a media browser with built-in bittorrent support.

    +
    +
    +

    deluge

    +

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

    +
    +
    +

    qBittorrent

    +

    qBittorrent is a QT bittorrent client available for linux (likely portable to +most other desktops as well). Written by Christophe Dumez.

    +
    +
    +

    tonidoplug

    +

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

    +
    +
    +

    Folx

    +

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

    +
    +
    +

    Miro

    +

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

    +
    +
    +

    MooPolice

    +

    MooPolice is a windows bittorrent client with a unique look.

    +
    +
    +

    LeechCraft

    +

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

    +
    +
    +

    Free download manager

    +

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

    +
    +
    +

    btg

    +

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

    +
    +
    +

    electric sheep

    +

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

    +
    +
    +

    Tvitty

    +

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

    +
    +
    +

    hrktorrent

    +

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

    +
    +
    +

    FatRat

    +

    FatRat is an open source download manager for Linux/Unix systems written in +C++ with the help of the Trolltech Qt 4 library. It's simple to use and +install.

    +
    +
    +

    halite BitTorrent

    +

    Halite is a windows bittorrent client controllabel via an xml-rpc +interface.

    +
    +
    +

    Arctic Torrent

    +

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

    +
    +
    +

    bubba

    +

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

    +
    +
    +

    tvblob

    +

    The BLOBbox represents the ability to harness all of the content available +on the web, without any filtering or pre-selection by a third party just +like surfing the web.

    +

    This means that anyone will have the ability to reach viewers via the Internet +directly on TV, without them having to connect a PC.

    +
    +
    +

    Flush

    +

    Flush is a GTK-based BitTorrent client.

    +
    +
    +

    Lince

    +

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

    +
    +
    +

    Linkage

    +

    Linkage is a gtkmm client that aims to be middle weight.

    +
    +
    +

    Bitfox

    +

    Bitfox is a firefox plugin integrating bittorrent downloads in firefox.

    +
    +
    +

    BitSlug

    +

    BitSlug is a MacOSX cocoa client.

    +
    +
    +

    DelCo

    +

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

    +
    +
    +

    Torrent2Exe

    +

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

    +
    +
    +

    ZyXEL NSA-220

    +

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

    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/projects.rst b/docs/projects.rst new file mode 100644 index 0000000..f208099 --- /dev/null +++ b/docs/projects.rst @@ -0,0 +1,234 @@ +projects using libtorrent +========================= + +These are some of the public projects that uses libtorrent. If you want your +project listed here, let me_ know. + +.. _me: mailto:arvid@libtorrent.org + + +Wyzo +---- + +wyzo_ is a media browser with built-in bittorrent support. + +.. _wyzo: http://www.wyzo.com/ + +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 QT bittorrent client available for linux (likely portable to +most other desktops as well). Written by Christophe Dumez. + +.. _qBittorrent: http://www.qbittorrent.org/ + +tonidoplug +---------- + +Tonidoplug_ is a tiny, low-power, low-cost home server and +NAS device powered by Tonido software that allows you to access +your apps, files, music and media from anywhere. + +.. _Tonidoplug: http://www.tonidoplug.com/ + +Folx +---- + +Folx_ is a torrent client and download manager for Mac OS X. +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 (accessable through a web browser). +Written by Michael Wojciechowski and Johan Strom. + +.. _btg: http://btg.berlios.de// + +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: http://tvitty.com + +hrktorrent +---------- + +hrktorrent_ hrktorrent is a light console torrent client written in C++. + +.. _hrktorrent: http://50hz.ws/hrktorrent/ + +FatRat +------ + +FatRat_ is an open source download manager for Linux/Unix systems written in +C++ with the help of the Trolltech Qt 4 library. It's simple to use and +install. + +.. _FatRat: http://fatrat.dolezel.info + +halite BitTorrent +----------------- + +Halite_ is a windows bittorrent client controllabel 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`: http://www.int64.org/arctic.html + +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: http://excito.com/bubba/about-bubba.html + +tvblob +------ + +The BLOBbox_ represents the ability to harness all of the content available +on the web, without any filtering or pre-selection by a third party just +like surfing the web. + +This means that anyone will have the ability to reach viewers via the Internet +directly on TV, without them having to connect a PC. + +.. _BLOBbox: http://www.tvblob.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/ + +Linkage +------- + +Linkage_ is a gtkmm client that aims to be middle weight. + +.. _Linkage: http://code.google.com/p/linkage/ + +Bitfox +------ + +Bitfox_ is a firefox plugin integrating bittorrent downloads in firefox. + +.. _Bitfox: http://code.google.com/p/bitfox/ + +BitSlug +------- + +BitSlug_ is a MacOSX 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_ 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: http://us.zyxel.com/Products/details.aspx?PC1IndexFlag=20050125090459&CategoryGroupNo=758BFE64-3A95-463C-9E1E-3D30E3B58D9C + diff --git a/docs/python_binding.html b/docs/python_binding.html new file mode 100644 index 0000000..ef17b6a --- /dev/null +++ b/docs/python_binding.html @@ -0,0 +1,208 @@ + + + + + + +libtorrent python binding + + + + + + + + +
    +
    + + + + +
    +

    libtorrent python binding

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

    building

    +

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

    +
    +

    building using setup.py

    +

    There is a setup.py shipped with libtorrent that can be used on windows. +On windows the setup.py will invoke bjam and assume that you have boost +sources at $BOOST_PATH. The resulting executable is self-contained, it does +not depend any boost or libtorrent dlls.

    +

    On other systems, the setup.py is generated by running +./configure --enable-python-binding.

    +

    To build the Python bindings do:

    +
      +
    1. Run:

      +
      +python setup.py build
      +
      +
    2. +
    3. As root, run:

      +
      +python setup.py install
      +
      +
    4. +
    +
    +
    +

    building using boost build

    +

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

    +

    Make sure your user config contains the following line:

    +
    +using python : 2.3 ;
    +
    +

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

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

    The bindings require at least python version 2.2.

    +

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

    +

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

    +

    For example:

    +
    +$ bjam dht-support=on link=static
    +
    +

    On Mac OS X, this will produce the following python module:

    +
    +bin/darwin-4.0/release/dht-support-on/link-static/logging-none/threading-multi/libtorrent.so
    +
    +
    +
    +
    +

    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.
    +
    libtorrent::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.

    +

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

    +

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

    +
    +import libtorrent as lt
    +import time
    +
    +ses = lt.session()
    +ses.listen_on(6881, 6891)
    +
    +e = lt.bdecode(open("test.torrent", 'rb').read())
    +info = lt.torrent_info(e)
    +
    +params = { 'save_path': '.', \
    +        'storage_mode': lt.storage_mode_t.storage_mode_sparse, \
    +        'ti': info }
    +h = ses.add_torrent(params)
    +
    +s = h.status()
    +while (not s.is_seeding):
    +        s = h.status()
    +
    +        state_str = ['queued', 'checking', 'downloading metadata', \
    +                'downloading', 'finished', 'seeding', 'allocating']
    +        print '%.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, state_str[s.state])
    +
    +        time.sleep(1)
    +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/python_binding.rst b/docs/python_binding.rst new file mode 100644 index 0000000..e653998 --- /dev/null +++ b/docs/python_binding.rst @@ -0,0 +1,140 @@ +========================= +libtorrent python binding +========================= + +:Author: Arvid Norberg, arvid@libtorrent.org + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +building +======== + +Building the libtorrent python bindings will produce a shared library (DLL) +which is a python module that can be imported in a python program. + +building using setup.py +----------------------- + +There is a ``setup.py`` shipped with libtorrent that can be used on windows. +On windows the setup.py will invoke ``bjam`` and assume that you have boost +sources at ``$BOOST_PATH``. The resulting executable is self-contained, it does +not depend any boost or libtorrent dlls. + +On other systems, the setup.py is generated by running +``./configure --enable-python-binding``. + +To build the Python bindings do: + +1. Run:: + + python setup.py build + +2. As root, run:: + + python setup.py install + + +building using boost build +-------------------------- + +To set up your build environment, you need to add some settings to your +``$BOOST_BUILD_PATH/user-config.jam``. + +Make sure your user config contains the following line:: + + using python : 2.3 ; + +Set the version to the version of python you have installed or want to use. If +you've installed python in a non-standard location, you have to add the prefix +path used when you installed python as a second option. Like this:: + + using python : 2.6 : /usr/bin/python2.6 : /usr/include/python2.6 : /usr/lib/python2.6 ; + +The bindings require *at least* python version 2.2. + +For more information on how to install and set up boost-build, see the +`building libtorrent`__ section. + +.. __: building.html#step-2-setup-bbv2 + +Once you have boost-build set up, you cd to the ``bindings/python`` +directory and invoke ``bjam`` with the apropriate settings. For the available +build variants, see `libtorrent build options`_. + +.. _`libtorrent build options`: building.html#step-3-building-libtorrent + +For example:: + + $ bjam dht-support=on link=static + +On Mac OS X, this will produce the following python module:: + + bin/darwin-4.0/release/dht-support-on/link-static/logging-none/threading-multi/libtorrent.so + +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. + +libtorrent::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 + +For an example python program, see ``client.py`` in the ``bindings/python`` +directory. + +A very simple example usage of the module would be something like this:: + + import libtorrent as lt + import time + + ses = lt.session() + ses.listen_on(6881, 6891) + + e = lt.bdecode(open("test.torrent", 'rb').read()) + info = lt.torrent_info(e) + + params = { 'save_path': '.', \ + 'storage_mode': lt.storage_mode_t.storage_mode_sparse, \ + 'ti': info } + h = ses.add_torrent(params) + + s = h.status() + while (not s.is_seeding): + s = h.status() + + state_str = ['queued', 'checking', 'downloading metadata', \ + 'downloading', 'finished', 'seeding', 'allocating'] + print '%.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, state_str[s.state]) + + time.sleep(1) + diff --git a/docs/read_disk_buffers.diagram b/docs/read_disk_buffers.diagram new file mode 100644 index 0000000..1d04a25 --- /dev/null +++ b/docs/read_disk_buffers.diagram @@ -0,0 +1,16 @@ + + "copy into peer's" "encrypt in place" ++------------------+ "send buffer" +---------------+ "(no copy)" +---------------+ +| "receive buffer" +----------------->| "send buffer" +------------------>| "encrypted" | +| | | | | "send buffer" | ++------------------+ +---------------+ +---------------+ + ^ | + | "read() from file" "write() to socket" | + | "(copy)" "user space" "(copy)" | +- - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - + | "kernel space" | + | v ++-------+-------------+ +-----------------+ +| "kernel page cache" | | "socket kernel" | +| | | "buffer" | ++---------------------+ +-----------------+ diff --git a/docs/read_disk_buffers.png b/docs/read_disk_buffers.png new file mode 100644 index 0000000000000000000000000000000000000000..2c7edeee2a0d14831ae3ba5ecbaf31c3e948c3bf GIT binary patch literal 3444 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 literal 0 HcmV?d00001 diff --git a/docs/reference-Alerts.html b/docs/reference-Alerts.html new file mode 100644 index 0000000..5d9aea3 --- /dev/null +++ b/docs/reference-Alerts.html @@ -0,0 +1,3489 @@ + + + + + + + + + + + + + + + +
    +
    + + + + +
    + + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.1.1
    +
    +

    Alerts

    +
    +

    Table of contents

    + +
    +

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

    +

    By default, only errors are reported. settings_pack::alert_mask can be +used to specify which kinds of events should be reported. The alert mask is +comprised by bits from the category_t enum.

    +

    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.

    +
    +

    alert

    +

    Declared in "libtorrent/alert.hpp"

    +

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

    +
    +class alert
    +{
    +   time_point timestamp () const;
    +   virtual int type () const = 0;
    +   virtual char const* what () const = 0;
    +   virtual std::string message () const = 0;
    +   virtual int category () const = 0;
    +
    +   enum category_t
    +   {
    +      error_notification,
    +      peer_notification,
    +      port_mapping_notification,
    +      storage_notification,
    +      tracker_notification,
    +      debug_notification,
    +      status_notification,
    +      progress_notification,
    +      ip_block_notification,
    +      performance_warning,
    +      dht_notification,
    +      stats_notification,
    +      session_log_notification,
    +      torrent_log_notification,
    +      peer_log_notification,
    +      incoming_request_notification,
    +      dht_log_notification,
    +      dht_operation_notification,
    +      port_mapping_log_notification,
    +      picker_log_notification,
    +      all_categories,
    +   };
    +};
    +
    +
    +

    timestamp()

    +
    +time_point timestamp () const;
    +
    +

    a timestamp is automatically created in the constructor

    +
    +
    +

    type()

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

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

    +

    e.g:

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

    what()

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

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

    +
    +
    +

    message()

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

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

    +
    +
    +

    category()

    +
    +virtual int category () const = 0;
    +
    +

    returns a bitmask specifying which categories this alert belong to.

    +
    +
    +

    enum category_t

    +

    Declared in "libtorrent/alert.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    error_notification1

    Enables alerts that report an error. This includes:

    +
      +
    • tracker errors
    • +
    • tracker warnings
    • +
    • file errors
    • +
    • resume data failures
    • +
    • web seed errors
    • +
    • .torrent files errors
    • +
    • listen socket errors
    • +
    • port mapping errors
    • +
    +
    peer_notification2Enables alerts when peers send invalid requests, get banned or +snubbed.
    port_mapping_notification4Enables alerts for port mapping events. For NAT-PMP and UPnP.
    storage_notification8Enables alerts for events related to the storage. File errors and +synchronization events for moving the storage, renaming files etc.
    tracker_notification16Enables all tracker events. Includes announcing to trackers, +receiving responses, warnings and errors.
    debug_notification32Low level alerts for when peers are connected and disconnected.
    status_notification64Enables alerts for when a torrent or the session changes state.
    progress_notification128Alerts for when blocks are requested and completed. Also when +pieces are completed.
    ip_block_notification256Alerts when a peer is blocked by the ip blocker or port blocker.
    performance_warning512Alerts when some limit is reached that might limit the download +or upload rate.
    dht_notification1024Alerts on events in the DHT node. For incoming searches or +bootstrapping being done etc.
    stats_notification2048If you enable these alerts, you will receive a stats_alert +approximately once every second, for every active torrent. +These alerts contain all statistics counters for the interval since +the lasts stats alert.
    session_log_notification8192Enables debug logging alerts. These are available unless libtorrent +was built with logging disabled (TORRENT_DISABLE_LOGGING). The +alerts being posted are log_alert and are session wide.
    torrent_log_notification16384Enables debug logging alerts for torrents. These are available +unless libtorrent was built with logging disabled +(TORRENT_DISABLE_LOGGING). The alerts being posted are +torrent_log_alert and are torrent wide debug events.
    peer_log_notification32768Enables debug logging alerts for peers. These are available unless +libtorrent was built with logging disabled +(TORRENT_DISABLE_LOGGING). The alerts being posted are +peer_log_alert and low-level peer events and messages.
    incoming_request_notification65536enables the incoming_request_alert.
    dht_log_notification131072enables dht_log_alert, debug logging for the DHT
    dht_operation_notification262144enable events from pure dht operations not related to torrents
    port_mapping_log_notification524288enables port mapping log events. This log is useful +for debugging the UPnP or NAT-PMP implementation
    picker_log_notification1048576enables verbose logging from the piece picker.
    all_categories2147483647

    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.

    +
    +
    +
    +
    +

    torrent_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

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

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

    message()

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

    returns the message associated with this alert

    +
    +
    handle
    +
    The torrent_handle pointing to the torrent this +alert is associated with.
    +
    +
    +
    +
    +

    peer_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

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

    +
    +struct peer_alert : torrent_alert
    +{
    +   virtual int category () const override;
    +   virtual std::string message () const override;
    +
    +   static const int alert_type = 1;
    +   static const int static_category = alert::peer_notification;
    +   tcp::endpoint ip;
    +   peer_id pid;
    +};
    +
    +
    +
    ip
    +
    The peer's IP address and port.
    +
    +
    +
    pid
    +
    the peer ID, if known.
    +
    +
    +
    +

    tracker_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

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

    +
    +struct tracker_alert : torrent_alert
    +{
    +   virtual int category () const override;
    +   virtual std::string message () const override;
    +   char const* tracker_url () const;
    +
    +   static const int alert_type = 2;
    +   static const int static_category = alert::tracker_notification;
    +};
    +
    +
    +

    tracker_url()

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

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

    +
    +
    +
    +

    torrent_added_alert

    +

    Declared in "libtorrent/alert_types.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 status_notification bit is set in the alert_mask.

    +
    +struct torrent_added_alert final : torrent_alert
    +{
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::status_notification;
    +};
    +
    +
    +
    +

    torrent_removed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

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

    +

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

    +

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

    +
    +struct torrent_removed_alert final : torrent_alert
    +{
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::status_notification;
    +   sha1_hash info_hash;
    +};
    +
    +
    +
    +

    read_piece_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

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

    +

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

    +
    +struct read_piece_alert final : torrent_alert
    +{
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::storage_notification;
    +   error_code ec;
    +   boost::shared_array<char> buffer;
    +   int piece;
    +   int size;
    +};
    +
    +
    +
    +

    file_completed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

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

    +
    +struct file_completed_alert final : torrent_alert
    +{
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::progress_notification;
    +   int index;
    +};
    +
    +
    +
    index
    +
    refers to the index of the file that completed.
    +
    +
    +
    +

    file_renamed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

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

    +
    +struct file_renamed_alert final : torrent_alert
    +{
    +   virtual std::string message () const override;
    +   char const* new_name () const;
    +
    +   static const int static_category = alert::storage_notification;
    +   int index;
    +};
    +
    +
    +
    index
    +
    refers to the index of the file that was renamed,
    +
    +
    +
    +

    file_rename_failed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

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

    +
    +struct file_rename_failed_alert final : torrent_alert
    +{
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::storage_notification;
    +   int index;
    +   error_code error;
    +};
    +
    + +
    +
    index error
    +
    refers to the index of the file that was supposed to be renamed, +error is the error code returned from the filesystem.
    +
    +
    +
    +

    performance_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

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

    +
    +struct performance_alert final : torrent_alert
    +{
    +   virtual 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,
    +      bittyrant_with_no_uplimit,
    +      too_few_outgoing_ports,
    +      too_few_file_descriptors,
    +      num_warnings,
    +   };
    +
    +   static const int static_category = alert::performance_warning;
    +   performance_warning_t warning_code;
    +};
    +
    +
    +

    enum performance_warning_t

    +

    Declared in "libtorrent/alert_types.hpp"

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

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

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

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

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

    state_changed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    Generated whenever a torrent changes its state.

    +
    +struct state_changed_alert final : torrent_alert
    +{
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::status_notification;
    +   torrent_status::state_t state;
    +   torrent_status::state_t prev_state;
    +};
    +
    +
    +
    state
    +
    the new state of the torrent.
    +
    +
    +
    prev_state
    +
    the previous state.
    +
    +
    +
    +

    tracker_error_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

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

    +

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

    +
    +struct tracker_error_alert final : tracker_alert
    +{
    +   virtual std::string message () const override;
    +   char const* error_message () const;
    +
    +   static const int static_category = alert::tracker_notification | alert::error_notification;
    +   int times_in_row;
    +   int status_code;
    +   error_code error;
    +};
    +
    +
    +

    error_message()

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

    the message associated with this error

    +
    +
    +
    +

    tracker_warning_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

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

    +
    +struct tracker_warning_alert final : tracker_alert
    +{
    +   virtual std::string message () const override;
    +   char const* warning_message () const;
    +
    +   static const int static_category = alert::tracker_notification | alert::error_notification;
    +};
    +
    +
    +

    warning_message()

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

    the message associated with this warning

    +
    +
    +
    +

    scrape_reply_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a scrape request succeeds.

    +
    +struct scrape_reply_alert final : tracker_alert
    +{
    +   virtual std::string message () const override;
    +
    +   int incomplete;
    +   int complete;
    +};
    +
    + +
    +
    incomplete complete
    +
    the data returned in the scrape response. These numbers +may be -1 if the response was malformed.
    +
    +
    +
    +

    scrape_failed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

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

    +
    +struct scrape_failed_alert final : tracker_alert
    +{
    +   virtual std::string message () const override;
    +   char const* error_message () const;
    +
    +   static const int static_category = alert::tracker_notification | alert::error_notification;
    +   error_code error;
    +};
    +
    +
    +

    error_message()

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

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

    +
    +
    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().
    +
    +
    +
    +
    +

    tracker_reply_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

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

    +
    +struct tracker_reply_alert final : tracker_alert
    +{
    +   virtual std::string message () const override;
    +
    +   int num_peers;
    +};
    +
    +
    +
    num_peers
    +
    tells how many peers the tracker returned in this response. This is +not expected to be more thant the num_want settings. These are not necessarily +all new peers, some of them may already be connected.
    +
    +
    +
    +

    dht_reply_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

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

    +
    +struct dht_reply_alert final : tracker_alert
    +{
    +   virtual std::string message () const override;
    +
    +   int num_peers;
    +};
    +
    +
    +
    +

    tracker_announce_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

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

    +
    +struct tracker_announce_alert final : tracker_alert
    +{
    +   virtual std::string message () const override;
    +
    +   int event;
    +};
    +
    +
    +
    event
    +

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

    +
      +
    1. None
    2. +
    3. Completed
    4. +
    5. Started
    6. +
    7. Stopped
    8. +
    +
    +
    +
    +
    +

    hash_failed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

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

    +
    +struct hash_failed_alert final : torrent_alert
    +{
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::status_notification;
    +   int piece_index;
    +};
    +
    +
    +
    +

    peer_ban_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

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

    +
    +struct peer_ban_alert final : peer_alert
    +{
    +   virtual std::string message () const override;
    +};
    +
    +
    +
    +

    peer_unsnubbed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

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

    +
    +struct peer_unsnubbed_alert final : peer_alert
    +{
    +   virtual std::string message () const override;
    +};
    +
    +
    +
    +

    peer_snubbed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

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

    +
    +struct peer_snubbed_alert final : peer_alert
    +{
    +   virtual std::string message () const override;
    +};
    +
    +
    +
    +

    peer_error_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

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

    +
    +struct peer_error_alert final : peer_alert
    +{
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::peer_notification;
    +   int operation;
    +   error_code error;
    +};
    +
    +
    +
    operation
    +
    a NULL-terminated string of the low-level operation that failed, or NULL if +there was no low level disk operation.
    +
    +
    +
    error
    +
    tells you what error caused this alert.
    +
    +
    +
    +

    peer_connect_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

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

    +
    +struct peer_connect_alert final : peer_alert
    +{
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::debug_notification;
    +   int socket_type;
    +};
    +
    +
    +
    +

    peer_disconnected_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

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

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

    invalid_request_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

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

    +
    +struct invalid_request_alert final : peer_alert
    +{
    +   virtual std::string message () const override;
    +
    +   peer_request request;
    +   bool we_have;
    +   bool peer_interested;
    +   bool withheld;
    +};
    +
    +
    +
    request
    +
    the request we received from the peer
    +
    +
    +
    we_have
    +
    true if we have this piece
    +
    +
    +
    peer_interested
    +
    true if the peer indicated that it was interested to download before +sending the request
    +
    +
    +
    withheld
    +
    if this is true, the peer is not allowed to download this piece because +of superseeding rules.
    +
    +
    +
    +

    torrent_finished_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

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

    +
    +struct torrent_finished_alert final : torrent_alert
    +{
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::status_notification;
    +};
    +
    +
    +
    +

    piece_finished_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    this alert is posted every time a piece completes downloading +and passes the hash check. This alert derives from torrent_alert +which contains the torrent_handle to the torrent the piece belongs to.

    +
    +struct piece_finished_alert final : torrent_alert
    +{
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::progress_notification;
    +   int piece_index;
    +};
    +
    +
    +
    piece_index
    +
    the index of the piece that finished
    +
    +
    +
    +

    request_dropped_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

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

    +
    +struct request_dropped_alert final : peer_alert
    +{
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::progress_notification
    +   | alert::peer_notification;
    +   int block_index;
    +   int piece_index;
    +};
    +
    +
    +
    +

    block_timeout_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a block request times out.

    +
    +struct block_timeout_alert final : peer_alert
    +{
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::progress_notification
    +   | alert::peer_notification;
    +   int block_index;
    +   int piece_index;
    +};
    +
    +
    +
    +

    block_finished_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

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

    +
    +struct block_finished_alert final : peer_alert
    +{
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::progress_notification;
    +   int block_index;
    +   int piece_index;
    +};
    +
    +
    +
    +

    block_downloading_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

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

    +
    +struct block_downloading_alert final : peer_alert
    +{
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::progress_notification;
    +   int block_index;
    +   int piece_index;
    +};
    +
    +
    +
    +

    unwanted_block_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

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

    +
    +struct unwanted_block_alert final : peer_alert
    +{
    +   virtual std::string message () const override;
    +
    +   int block_index;
    +   int piece_index;
    +};
    +
    +
    +
    +

    storage_moved_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

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

    +
    +struct storage_moved_alert final : torrent_alert
    +{
    +   virtual std::string message () const override;
    +   char const* storage_path () const;
    +
    +   static const int static_category = alert::storage_notification;
    +};
    +
    +
    +

    storage_path()

    +
    +char const* storage_path () const;
    +
    +

    the path the torrent was moved to

    +
    +
    +
    +

    storage_moved_failed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    The storage_moved_failed_alert is generated when an attempt to move the storage, +via torrent_handle::move_storage(), fails.

    +
    +struct storage_moved_failed_alert final : torrent_alert
    +{
    +   virtual std::string message () const override;
    +   char const* file_path () const;
    +
    +   static const int static_category = alert::storage_notification;
    +   error_code error;
    +   char const* operation;
    +};
    +
    +
    +

    file_path()

    +
    +char const* file_path () const;
    +
    +

    If the error happened for a specific file, this returns its path.

    +
    +
    operation
    +
    If the error happened in a specific disk operation this is a NULL +terminated string naming which one, otherwise it's NULL.
    +
    +
    +
    +
    +

    torrent_deleted_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a request to delete the files of a torrent complete.

    +

    The info_hash is the info-hash of the torrent that was just deleted. Most of +the time the torrent_handle in the torrent_alert will be invalid by the time +this alert arrives, since the torrent is being deleted. The info_hash member +is hence the main way of identifying which torrent just completed the delete.

    +

    This alert is posted in the storage_notification category, and that bit +needs to be set in the alert_mask.

    +
    +struct torrent_deleted_alert final : torrent_alert
    +{
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::storage_notification;
    +   sha1_hash info_hash;
    +};
    +
    +
    +
    +

    torrent_delete_failed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a request to delete the files of a torrent fails. +Just removing a torrent from the session cannot fail

    +
    +struct torrent_delete_failed_alert final : torrent_alert
    +{
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::storage_notification
    +   | alert::error_notification;
    +   error_code error;
    +   sha1_hash info_hash;
    +};
    +
    +
    +
    error
    +
    tells you why it failed.
    +
    +
    +
    info_hash
    +
    the info hash of the torrent whose files failed to be deleted
    +
    +
    +
    +

    save_resume_data_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated as a response to a torrent_handle::save_resume_data request. +It is generated once the disk IO thread is done writing the state for this torrent.

    +
    +struct save_resume_data_alert final : torrent_alert
    +{
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::storage_notification;
    +   boost::shared_ptr<entry> resume_data;
    +};
    +
    +
    +
    resume_data
    +
    points to the resume data.
    +
    +
    +
    +

    save_resume_data_failed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated instead of save_resume_data_alert if there was an error +generating the resume data. error describes what went wrong.

    +
    +struct save_resume_data_failed_alert final : torrent_alert
    +{
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::storage_notification
    +   | alert::error_notification;
    +   error_code error;
    +};
    +
    +
    +
    error
    +
    the error code from the resume_data failure
    +
    +
    +
    +

    torrent_paused_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated as a response to a torrent_handle::pause request. It is +generated once all disk IO is complete and the files in the torrent have been closed. +This is useful for synchronizing with the disk.

    +
    +struct torrent_paused_alert final : torrent_alert
    +{
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::status_notification;
    +};
    +
    +
    +
    +

    torrent_resumed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated as a response to a torrent_handle::resume() request. It is +generated when a torrent goes from a paused state to an active state.

    +
    +struct torrent_resumed_alert final : torrent_alert
    +{
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::status_notification;
    +};
    +
    +
    +
    +

    torrent_checked_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is posted when a torrent completes checking. i.e. when it transitions +out of the checking files state into a state where it is ready to start downloading

    +
    +struct torrent_checked_alert final : torrent_alert
    +{
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::status_notification;
    +};
    +
    +
    +
    +

    url_seed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a HTTP seed name lookup fails.

    +
    +struct url_seed_alert final : torrent_alert
    +{
    +   virtual std::string message () const override;
    +   char const* server_url () const;
    +   char const* error_message () const;
    +
    +   static const int static_category = alert::peer_notification | alert::error_notification;
    +   error_code error;
    +};
    +
    +
    +

    server_url()

    +
    +char const* server_url () const;
    +
    +

    the URL the error is associated with

    +
    +
    +

    error_message()

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

    in case the web server sent an error message, this function returns +it.

    +
    +
    error
    +
    the error the web seed encountered. If this is not set, the server +sent an error message, call error_message().
    +
    +
    +
    +
    +

    file_error_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    If the storage fails to read or write files that it needs access to, this alert is +generated and the torrent is paused.

    +
    +struct file_error_alert final : torrent_alert
    +{
    +   virtual std::string message () const override;
    +   char const* filename () const;
    +
    +   static const int static_category = alert::status_notification
    +   | alert::storage_notification;
    +   error_code error;
    +   char const* operation;
    +};
    +
    +
    +

    filename()

    +
    +char const* filename () const;
    +
    +

    the file that experienced the error

    + +
    +
    error operation
    +
    the error code describing the error.
    +
    +
    +
    +
    +

    metadata_failed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when the metadata has been completely received and the info-hash +failed to match it. i.e. the metadata that was received was corrupt. libtorrent will +automatically retry to fetch it in this case. This is only relevant when running a +torrent-less download, with the metadata extension provided by libtorrent.

    +
    +struct metadata_failed_alert final : torrent_alert
    +{
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::error_notification;
    +   error_code error;
    +};
    +
    +
    +
    error
    +
    indicates what failed when parsing the metadata. This error is +what's returned from lazy_bdecode().
    +
    +
    +
    +

    metadata_received_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when the metadata has been completely received and the torrent +can start downloading. It is not generated on torrents that are started with metadata, but +only those that needs to download it from peers (when utilizing the libtorrent extension).

    +

    There are no additional data members in this alert.

    +

    Typically, when receiving this alert, you would want to save the torrent file in order +to load it back up again when the session is restarted. Here's an example snippet of +code to do that:

    +
    +torrent_handle h = alert->handle();
    +if (h.is_valid()) {
    +        boost::shared_ptr<torrent_info const> ti = h.torrent_file();
    +        create_torrent ct(*ti);
    +        entry te = ct.generate();
    +        std::vector<char> buffer;
    +        bencode(std::back_inserter(buffer), te);
    +        FILE* f = fopen((to_hex(ti->info_hash().to_string()) + ".torrent").c_str(), "wb+");
    +        if (f) {
    +                fwrite(&buffer[0], 1, buffer.size(), f);
    +                fclose(f);
    +        }
    +}
    +
    +
    +struct metadata_received_alert final : torrent_alert
    +{
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::status_notification;
    +};
    +
    +
    +
    +

    udp_error_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is posted when there is an error on the UDP socket. The +UDP socket is used for all uTP, DHT and UDP tracker traffic. It's +global to the session.

    +
    +struct udp_error_alert final : alert
    +{
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::error_notification;
    +   udp::endpoint endpoint;
    +   error_code error;
    +};
    +
    +
    +
    endpoint
    +
    the source address associated with the error (if any)
    +
    +
    +
    error
    +
    the error code describing the error
    +
    +
    +
    +

    external_ip_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    Whenever libtorrent learns about the machines external IP, this alert is +generated. The external IP address can be acquired from the tracker (if it +supports that) or from peers that supports the extension protocol. +The address can be accessed through the external_address member.

    +
    +struct external_ip_alert final : alert
    +{
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::status_notification;
    +   address external_address;
    +};
    +
    +
    +
    external_address
    +
    the IP address that is believed to be our external IP
    +
    +
    +
    +

    listen_failed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when none of the ports, given in the port range, to +session can be opened for listening. The endpoint member is the +interface and port that failed, error is the error code describing +the failure.

    +

    libtorrent may sometimes try to listen on port 0, if all other ports failed. +Port 0 asks the operating system to pick a port that's free). If that fails +you may see a listen_failed_alert with port 0 even if you didn't ask to +listen on it.

    +
    +struct listen_failed_alert final : alert
    +{
    +   virtual std::string message () const override;
    +   char const* listen_interface () const;
    +
    +   enum socket_type_t
    +   {
    +      tcp,
    +      tcp_ssl,
    +      udp,
    +      i2p,
    +      socks5,
    +      utp_ssl,
    +   };
    +
    +   enum op_t
    +   {
    +      parse_addr,
    +      open,
    +      bind,
    +      listen,
    +      get_peer_name,
    +      accept,
    +   };
    +
    +   static const int static_category = alert::status_notification | alert::error_notification;
    +   error_code error;
    +   int operation;
    +   socket_type_t sock_type;
    +   tcp::endpoint endpoint;
    +};
    +
    +
    +

    listen_interface()

    +
    +char const* listen_interface () const;
    +
    +

    the interface libtorrent attempted to listen on that failed.

    +
    +
    +

    enum socket_type_t

    +

    Declared in "libtorrent/alert_types.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    tcp0 
    tcp_ssl1 
    udp2 
    i2p3 
    socks54 
    utp_ssl5 
    +
    +
    +

    enum op_t

    +

    Declared in "libtorrent/alert_types.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    parse_addr0 
    open1 
    bind2 
    listen3 
    get_peer_name4 
    accept5 
    +
    +
    error
    +
    the error the system returned
    +
    +
    +
    operation
    +
    the specific low level operation that failed. See op_t.
    +
    +
    +
    sock_type
    +
    the type of listen socket this alert refers to.
    +
    +
    +
    endpoint
    +
    the address and port libtorrent attempted to listen on
    +
    +
    +
    +
    +

    listen_succeeded_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is posted when the listen port succeeds to be opened on a +particular interface. endpoint is the endpoint that successfully +was opened for listening.

    +
    +struct listen_succeeded_alert final : alert
    +{
    +   virtual std::string message () const override;
    +
    +   enum socket_type_t
    +   {
    +      tcp,
    +      tcp_ssl,
    +      udp,
    +      i2p,
    +      socks5,
    +      utp_ssl,
    +   };
    +
    +   static const int static_category = alert::status_notification;
    +   tcp::endpoint endpoint;
    +   socket_type_t sock_type;
    +};
    +
    +
    +

    enum socket_type_t

    +

    Declared in "libtorrent/alert_types.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    tcp0 
    tcp_ssl1 
    udp2 
    i2p3 
    socks54 
    utp_ssl5 
    +
    +
    endpoint
    +
    the endpoint libtorrent ended up listening on. The address +refers to the local interface and the port is the listen port.
    +
    +
    +
    sock_type
    +
    the type of listen socket this alert refers to.
    +
    +
    +
    +
    +

    portmap_error_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a NAT router was successfully found but some +part of the port mapping request failed. It contains a text message that +may help the user figure out what is wrong. This alert is not generated in +case it appears the client is not running on a NAT:ed network or if it +appears there is no NAT router that can be remote controlled to add port +mappings.

    +
    +struct portmap_error_alert final : alert
    +{
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::port_mapping_notification
    +   | alert::error_notification;
    +   int mapping;
    +   int map_type;
    +   error_code error;
    +};
    +
    +
    +
    mapping
    +
    refers to the mapping index of the port map that failed, i.e. +the index returned from add_mapping().
    +
    +
    +
    map_type
    +
    is 0 for NAT-PMP and 1 for UPnP.
    +
    +
    +
    error
    +
    tells you what failed.
    +
    +
    +
    +

    portmap_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a NAT router was successfully found and +a port was successfully mapped on it. On a NAT:ed network with a NAT-PMP +capable router, this is typically generated once when mapping the TCP +port and, if DHT is enabled, when the UDP port is mapped.

    +
    +struct portmap_alert final : alert
    +{
    +   virtual std::string message () const override;
    +
    +   enum protocol_t
    +   {
    +      tcp,
    +      udp,
    +   };
    +
    +   static const int static_category = alert::port_mapping_notification;
    +   int mapping;
    +   int external_port;
    +   int map_type;
    +   int protocol;
    +};
    +
    +
    +

    enum protocol_t

    +

    Declared in "libtorrent/alert_types.hpp"

    + +++++ + + + + + + + + + + + + + + + + +
    namevaluedescription
    tcp0 
    udp1 
    +
    +
    mapping
    +
    refers to the mapping index of the port map that failed, i.e. +the index returned from add_mapping().
    +
    +
    +
    external_port
    +
    the external port allocated for the mapping.
    +
    +
    +
    map_type
    +
    0 for NAT-PMP and 1 for UPnP.
    +
    +
    +
    protocol
    +
    the protocol this mapping was for. one of protocol_t enums
    +
    +
    +
    +
    +

    portmap_log_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated to log informational events related to either +UPnP or NAT-PMP. They contain a log line and the type (0 = NAT-PMP +and 1 = UPnP). Displaying these messages to an end user is only useful +for debugging the UPnP or NAT-PMP implementation. This alert is only +posted if the alert::port_mapping_log_notification flag is enabled in +the alert mask.

    +
    +struct portmap_log_alert final : alert
    +{
    +   virtual std::string message () const override;
    +   char const* log_message () const;
    +
    +   static const int static_category = alert::port_mapping_log_notification;
    +   int map_type;
    +};
    +
    +
    +

    log_message()

    +
    +char const* log_message () const;
    +
    +

    the message associated with this log line

    +
    +
    +
    +

    fastresume_rejected_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a fastresume file has been passed to +add_torrent() but the files on disk did not match the fastresume file. +The error_code explains the reason why the resume file was rejected.

    +
    +struct fastresume_rejected_alert final : torrent_alert
    +{
    +   virtual std::string message () const override;
    +   char const* file_path () const;
    +
    +   static const int static_category = alert::status_notification
    +   | alert::error_notification;
    +   error_code error;
    +   char const* operation;
    +};
    +
    +
    +

    file_path()

    +
    +char const* file_path () const;
    +
    +

    If the error happened to a specific file, this returns the path to it.

    +
    +
    operation
    +
    If the error happened in a disk operation. a NULL-terminated string of +the name of that operation. operation is NULL otherwise.
    +
    +
    +
    +
    +

    peer_blocked_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is posted when an incoming peer connection, or a peer that's about to be added +to our peer list, is blocked for some reason. This could be any of:

    +
      +
    • the IP filter
    • +
    • i2p mixed mode restrictions (a normal peer is not allowed on an i2p swarm)
    • +
    • the port filter
    • +
    • the peer has a low port and no_connect_privileged_ports is enabled
    • +
    • the protocol of the peer is blocked (uTP/TCP blocking)
    • +
    +
    +struct peer_blocked_alert final : torrent_alert
    +{
    +   virtual std::string message () const override;
    +
    +   enum reason_t
    +   {
    +      ip_filter,
    +      port_filter,
    +      i2p_mixed,
    +      privileged_ports,
    +      utp_disabled,
    +      tcp_disabled,
    +      invalid_local_interface,
    +   };
    +
    +   static const int static_category = alert::ip_block_notification;
    +   address ip;
    +   int reason;
    +};
    +
    +
    +

    enum reason_t

    +

    Declared in "libtorrent/alert_types.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    ip_filter0 
    port_filter1 
    i2p_mixed2 
    privileged_ports3 
    utp_disabled4 
    tcp_disabled5 
    invalid_local_interface6 
    +
    +
    ip
    +
    the address that was blocked.
    +
    +
    +
    +
    +

    dht_announce_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a DHT node announces to an info-hash on our +DHT node. It belongs to the dht_notification category.

    +
    +struct dht_announce_alert final : alert
    +{
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::dht_notification;
    +   address ip;
    +   int port;
    +   sha1_hash info_hash;
    +};
    +
    +
    +
    +

    dht_get_peers_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a DHT node sends a get_peers message to +our DHT node. It belongs to the dht_notification category.

    +
    +struct dht_get_peers_alert final : alert
    +{
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::dht_notification;
    +   sha1_hash info_hash;
    +};
    +
    +
    +
    +

    stats_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is posted approximately once every second, and it contains +byte counters of most statistics that's tracked for torrents. Each active +torrent posts these alerts regularly.

    +
    +struct stats_alert final : torrent_alert
    +{
    +   virtual std::string message () const override;
    +
    +   enum stats_channel
    +   {
    +      upload_payload,
    +      upload_protocol,
    +      download_payload,
    +      download_protocol,
    +      upload_ip_protocol,
    +      deprecated1,
    +      deprecated2,
    +      download_ip_protocol,
    +      deprecated3,
    +      deprecated4,
    +      num_channels,
    +   };
    +
    +   static const int static_category = alert::stats_notification;
    +   int transferred[num_channels];
    +   int interval;
    +};
    +
    +
    +

    enum stats_channel

    +

    Declared in "libtorrent/alert_types.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    upload_payload0 
    upload_protocol1 
    download_payload2 
    download_protocol3 
    upload_ip_protocol4 
    deprecated15 
    deprecated26 
    download_ip_protocol7 
    deprecated38 
    deprecated49 
    num_channels10 
    +
    +
    transferred[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.
    +
    +
    +
    interval
    +
    the number of milliseconds during which these stats were collected. +This is typically just above 1000, but if CPU is limited, it may be +higher than that.
    +
    +
    +
    +
    +

    cache_flushed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is posted when the disk cache has been flushed for a specific +torrent as a result of a call to torrent_handle::flush_cache(). This +alert belongs to the storage_notification category, which must be +enabled to let this alert through. The alert is also posted when removing +a torrent from the session, once the outstanding cache flush is complete +and the torrent does no longer have any files open.

    +
    +struct cache_flushed_alert final : torrent_alert
    +{
    +   static const int static_category = alert::storage_notification;
    +};
    +
    +
    +
    +

    anonymous_mode_alert

    +

    Declared in "libtorrent/alert_types.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 anonymous_mode_alert final : torrent_alert
    +{
    +   virtual std::string message () const override;
    +
    +   enum kind_t
    +   {
    +      tracker_not_anonymous,
    +   };
    +
    +   static const int static_category = alert::error_notification;
    +   int kind;
    +   std::string str;
    +};
    +
    +
    +

    enum kind_t

    +

    Declared in "libtorrent/alert_types.hpp"

    + +++++ + + + + + + + + + + + + +
    namevaluedescription
    tracker_not_anonymous0means 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.
    + +
    +
    kind str
    +
    specifies what error this is, see kind_t.
    +
    +
    +
    +
    +

    lsd_peer_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when we receive a local service discovery message +from a peer for a torrent we're currently participating in.

    +
    +struct lsd_peer_alert final : peer_alert
    +{
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::peer_notification;
    +};
    +
    +
    +
    +

    trackerid_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is posted whenever a tracker responds with a trackerid. +The tracker ID is like a cookie. The libtorrent will store the tracker ID +for this tracker and repeat it in subsequent announces.

    +
    +struct trackerid_alert final : tracker_alert
    +{
    +   virtual std::string message () const override;
    +   char const* tracker_id () const;
    +
    +   static const int static_category = alert::status_notification;
    +};
    +
    +
    +

    tracker_id()

    +
    +char const* tracker_id () const;
    +
    +

    The tracker ID returned by the tracker

    +
    +
    +
    +

    dht_bootstrap_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is posted when the initial DHT bootstrap is done.

    +
    +struct dht_bootstrap_alert final : alert
    +{
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::dht_notification;
    +};
    +
    +
    +
    +

    torrent_error_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This is posted whenever a torrent is transitioned into the error state.

    +
    +struct torrent_error_alert final : torrent_alert
    +{
    +   virtual std::string message () const override;
    +   char const* filename () const;
    +
    +   static const int static_category = alert::error_notification | alert::status_notification;
    +   error_code error;
    +};
    +
    +
    +

    filename()

    +
    +char const* filename () const;
    +
    +

    the filename (or object) the error occurred on.

    +
    +
    error
    +
    specifies which error the torrent encountered.
    +
    +
    +
    +
    +

    torrent_need_cert_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This is always posted for SSL torrents. This is a reminder to the client that +the torrent won't work unless torrent_handle::set_ssl_certificate() is called with +a valid certificate. Valid certificates MUST be signed by the SSL certificate +in the .torrent file.

    +
    +struct torrent_need_cert_alert final : torrent_alert
    +{
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::status_notification;
    +   error_code error;
    +};
    +
    +
    +
    +

    incoming_connection_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    The incoming connection alert is posted every time we successfully accept +an incoming connection, through any mean. The most straight-forward ways +of accepting incoming connections are through the TCP listen socket and +the UDP listen socket for uTP sockets. However, connections may also be +accepted through a Socks5 or i2p listen socket, or via an SSL listen +socket.

    +
    +struct incoming_connection_alert final : alert
    +{
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::peer_notification;
    +   int socket_type;
    +   tcp::endpoint ip;
    +};
    +
    +
    +
    socket_type
    +

    tells you what kind of socket the connection was accepted +as:

    +
      +
    1. none (no socket instantiated)
    2. +
    3. TCP
    4. +
    5. Socks5
    6. +
    7. HTTP
    8. +
    9. uTP
    10. +
    11. i2p
    12. +
    13. SSL/TCP
    14. +
    15. SSL/Socks5
    16. +
    17. HTTPS (SSL/HTTP)
    18. +
    19. SSL/uTP
    20. +
    +
    +
    +
    +
    ip
    +
    is the IP address and port the connection came from.
    +
    +
    +
    +

    add_torrent_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is always posted when a torrent was attempted to be added +and contains the return status of the add operation. The torrent handle of the new +torrent can be found in the base class' handle member. If adding +the torrent failed, error contains the error code.

    +
    +struct add_torrent_alert final : torrent_alert
    +{
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::status_notification;
    +   add_torrent_params params;
    +   error_code error;
    +};
    +
    +
    +
    params
    +
    a copy of the parameters used when adding the torrent, it can be used +to identify which invocation to async_add_torrent() caused this alert.
    +
    +
    +
    error
    +
    set to the error, if one occurred while adding the torrent.
    +
    +
    +
    +

    state_update_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is only posted when requested by the user, by calling +session::post_torrent_updates() on the session. It contains the torrent +status of all torrents that changed since last time this message was +posted. Its category is status_notification, but it's not subject to +filtering, since it's only manually posted anyway.

    +
    +struct state_update_alert final : alert
    +{
    +   state_update_alert (aux::stack_allocator& alloc
    +      , std::vector<torrent_status> st);
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::status_notification;
    +   std::vector<torrent_status> status;
    +};
    +
    +
    +
    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.
    +
    +
    +
    +

    mmap_cache_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +
    +struct mmap_cache_alert final : alert
    +{
    +   virtual std::string message () const override;
    +   mmap_cache_alert (aux::stack_allocator& alloc
    +      , error_code const& ec);
    +
    +   static const int static_category = alert::error_notification;
    +   error_code error;
    +};
    +
    +
    +
    +

    session_stats_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    The session_stats_alert is posted when the user requests session statistics by +calling post_session_stats() on the session object. Its category is +status_notification, but it is not subject to filtering, since it's only +manually posted anyway.

    +
    +struct session_stats_alert final : alert
    +{
    +   session_stats_alert (aux::stack_allocator& alloc, counters const& cnt);
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::stats_notification;
    +   boost::uint64_t values[counters::num_counters];
    +};
    +
    +
    +
    values[counters
    +

    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.

    +
    +
    +
    +
    +

    dht_error_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    posted when something fails in the DHT. This is not necessarily a fatal +error, but it could prevent proper operation

    +
    +struct dht_error_alert final : alert
    +{
    +   virtual std::string message () const override;
    +
    +   enum op_t
    +   {
    +      unknown,
    +      hostname_lookup,
    +   };
    +
    +   static const int static_category = alert::error_notification | alert::dht_notification;
    +   error_code error;
    +   op_t operation;
    +};
    +
    +
    +

    enum op_t

    +

    Declared in "libtorrent/alert_types.hpp"

    + +++++ + + + + + + + + + + + + + + + + +
    namevaluedescription
    unknown0 
    hostname_lookup1 
    +
    +
    error
    +
    the error code
    +
    +
    +
    operation
    +
    the operation that failed
    +
    +
    +
    +
    +

    dht_immutable_item_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    this alert is posted as a response to a call to session::get_item(), +specifically the overload for looking up immutable items in the DHT.

    +
    +struct dht_immutable_item_alert final : alert
    +{
    +   dht_immutable_item_alert (aux::stack_allocator& alloc, sha1_hash const& t
    +      , entry const& i);
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::dht_notification;
    +   sha1_hash target;
    +   entry item;
    +};
    +
    +
    +
    target
    +
    the target hash of the immutable item. This must +match the sha-1 hash of the bencoded form of item.
    +
    +
    +
    item
    +
    the data for this item
    +
    +
    +
    +

    dht_mutable_item_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    this alert is posted as a response to a call to session::get_item(), +specifically the overload for looking up mutable items in the DHT.

    +
    +struct dht_mutable_item_alert final : alert
    +{
    +   dht_mutable_item_alert (aux::stack_allocator& alloc
    +      , boost::array<char, 32> k
    +      , boost::array<char, 64> sig
    +      , boost::uint64_t sequence
    +      , std::string const& s
    +      , entry const& i
    +      , bool a);
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::dht_notification;
    +   boost::array<char, 32> key;
    +   boost::array<char, 64> signature;
    +   boost::uint64_t seq;
    +   std::string salt;
    +   entry item;
    +   bool authoritative;
    +};
    +
    +
    +
    key
    +
    the public key that was looked up
    +
    +
    +
    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.
    +
    +
    +
    seq
    +
    the sequence number of this item
    +
    +
    +
    salt
    +
    the salt, if any, used to lookup and store this item. If no +salt was used, this is an empty string
    +
    +
    +
    item
    +
    the data for this item
    +
    +
    +
    authoritative
    +
    the last response for mutable data is authoritative.
    +
    +
    +
    +

    dht_put_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    this is posted when a DHT put operation completes. This is useful if the +client is waiting for a put to complete before shutting down for instance.

    +
    +struct dht_put_alert final : alert
    +{
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::dht_notification;
    +   sha1_hash target;
    +   boost::array<char, 32> public_key;
    +   boost::array<char, 64> signature;
    +   std::string salt;
    +   boost::uint64_t seq;
    +   int num_success;
    +};
    +
    +
    +
    target
    +
    the target hash the item was stored under if this was an immutable +item.
    +
    + + + +
    +
    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.
    +
    +
    +
    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.
    +
    +
    +
    +

    i2p_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    this alert is used to report errors in the i2p SAM connection

    +
    +struct i2p_alert final : alert
    +{
    +   i2p_alert (aux::stack_allocator& alloc, error_code const& ec);
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::error_notification;
    +   error_code error;
    +};
    +
    +
    +
    error
    +
    the error that occurred in the i2p SAM connection
    +
    +
    +
    +

    dht_outgoing_get_peers_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when we send a get_peers request +It belongs to the dht_notification category.

    +
    +struct dht_outgoing_get_peers_alert final : alert
    +{
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::dht_notification;
    +   sha1_hash info_hash;
    +   sha1_hash obfuscated_info_hash;
    +   udp::endpoint ip;
    +};
    +
    +
    +
    info_hash
    +
    the info_hash of the torrent we're looking for peers for.
    +
    +
    +
    obfuscated_info_hash
    +
    if this was an obfuscated lookup, this is the info-hash target +actually sent to the node.
    +
    +
    +
    ip
    +
    the endpoint we're sending this query to
    +
    +
    +
    +

    log_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is posted by some session wide event. Its main purpose is +trouble shooting and debugging. It's not enabled by the default alert +mask and is enabled by the alert::session_log_notification bit. +Furthermore, it's by default disabled as a build configuration.

    +
    +struct log_alert final : alert
    +{
    +   virtual std::string message () const override;
    +   char const* msg () const;
    +
    +   static const int static_category = alert::session_log_notification;
    +};
    +
    +
    +

    msg()

    +
    +char const* msg () const;
    +
    +

    returns the log message

    +
    +
    +
    +

    torrent_log_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is posted by torrent wide events. It's meant to be used for +trouble shooting and debugging. It's not enabled by the default alert +mask and is enabled by the alert::torrent_log_notification bit. By +default it is disabled as a build configuration.

    +
    +struct torrent_log_alert final : torrent_alert
    +{
    +   virtual std::string message () const override;
    +   char const* msg () const;
    +
    +   static const int static_category = alert::torrent_log_notification;
    +};
    +
    +
    +

    msg()

    +
    +char const* msg () const;
    +
    +

    returns the log message

    +
    +
    +
    +

    peer_log_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is posted by events specific to a peer. It's meant to be used +for trouble shooting and debugging. It's not enabled by the default alert +mask and is enabled by the alert::peer_log_notification bit. By +default it is disabled as a build configuration.

    +
    +struct peer_log_alert final : peer_alert
    +{
    +   virtual std::string message () const override;
    +   char const* msg () const;
    +
    +   enum direction_t
    +   {
    +      incoming_message,
    +      outgoing_message,
    +      incoming,
    +      outgoing,
    +      info,
    +   };
    +
    +   static const int static_category = alert::peer_log_notification;
    +   char const* event_type;
    +   direction_t direction;
    +};
    +
    +
    +

    msg()

    +
    +char const* msg () const;
    +
    +

    returns the log message

    +
    +
    +

    enum direction_t

    +

    Declared in "libtorrent/alert_types.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    incoming_message0 
    outgoing_message1 
    incoming2 
    outgoing3 
    info4 
    +
    +
    event_type
    +
    string literal indicating the kind of event. For messages, this is the +message name.
    +
    +
    +
    +
    +

    lsd_error_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    posted if the local service discovery socket fails to start properly. +it's categorized as error_notification.

    +
    +struct lsd_error_alert final : alert
    +{
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::error_notification;
    +   error_code error;
    +};
    +
    +
    +
    error
    +
    The error code
    +
    +
    +
    +

    dht_lookup

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    holds statistics about a current dht_lookup operation. +a DHT lookup is the traversal of nodes, looking up a +set of target nodes in the DHT for retrieving and possibly +storing information in the DHT

    +
    +struct dht_lookup
    +{
    +   char const* type;
    +   int outstanding_requests;
    +   int timeouts;
    +   int responses;
    +   int branch_factor;
    +   int nodes_left;
    +   int last_sent;
    +   int first_timeout;
    +};
    +
    +
    +
    type
    +
    string literal indicating which kind of lookup this is
    +
    +
    +
    outstanding_requests
    +
    the number of outstanding request to individual nodes +this lookup has right now
    +
    +
    +
    timeouts
    +
    the total number of requests that have timed out so far +for this lookup
    +
    +
    +
    responses
    +
    the total number of responses we have received for this +lookup so far for this lookup
    +
    +
    +
    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.
    +
    +
    +
    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.
    +
    +
    +
    last_sent
    +
    the number of seconds ago the +last message was sent that's still +outstanding
    +
    +
    +
    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
    +
    +
    +
    +

    dht_routing_bucket

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    struct to hold information about a single DHT routing table bucket

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

    dht_stats_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    contains current DHT state. Posted in response to session::post_dht_stats().

    +
    +struct dht_stats_alert final : alert
    +{
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::stats_notification;
    +   std::vector<dht_lookup> active_requests;
    +   std::vector<dht_routing_bucket> routing_table;
    +};
    +
    +
    +
    active_requests
    +
    a vector of the currently running DHT lookups.
    +
    +
    +
    routing_table
    +
    contains information about every bucket in the DHT routing +table.
    +
    +
    +
    +

    incoming_request_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    posted every time an incoming request from a peer is accepted and queued +up for being serviced. This alert is only posted if +the alert::incoming_request_notification flag is enabled in the alert +mask.

    +
    +struct incoming_request_alert final : peer_alert
    +{
    +   virtual std::string message () const override;
    +
    +   static const int static_category = alert::incoming_request_notification;
    +   peer_request req;
    +};
    +
    +
    +
    req
    +
    the request this peer sent to us
    +
    +
    +
    +

    dht_log_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +
    +struct dht_log_alert final : alert
    +{
    +   dht_log_alert (aux::stack_allocator& alloc
    +      , dht_module_t m, char const* msg);
    +   virtual std::string message () const override;
    +   char const* log_message () const;
    +
    +   enum dht_module_t
    +   {
    +      tracker,
    +      node,
    +      routing_table,
    +      rpc_manager,
    +      traversal,
    +   };
    +
    +   static const int static_category = alert::dht_log_notification;
    +   dht_module_t module;
    +};
    +
    +
    +

    log_message()

    +
    +char const* log_message () const;
    +
    +

    the log message

    +
    +
    +

    enum dht_module_t

    +

    Declared in "libtorrent/alert_types.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    tracker0 
    node1 
    routing_table2 
    rpc_manager3 
    traversal4 
    +
    +
    module
    +
    the module, or part, of the DHT that produced this log message.
    +
    +
    +
    +
    +

    dht_pkt_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is posted every time a DHT message is sent or received. It is +only posted if the alert::dht_log_notification alert category is +enabled. It contains a verbatim copy of the message.

    +
    +struct dht_pkt_alert final : alert
    +{
    +   dht_pkt_alert (aux::stack_allocator& alloc, char const* buf, int size
    +      , dht_pkt_alert::direction_t d, udp::endpoint ep);
    +   virtual std::string message () const override;
    +   int pkt_size () const;
    +   char const* pkt_buf () const;
    +
    +   enum direction_t
    +   {
    +      incoming,
    +      outgoing,
    +   };
    +
    +   static const int static_category = alert::dht_log_notification;
    +   direction_t dir;
    +   udp::endpoint node;
    +};
    +
    + +
    +

    pkt_size() pkt_buf()

    +
    +int pkt_size () const;
    +char const* pkt_buf () const;
    +
    +

    returns a pointer to the packet buffer and size of the packet, +respectively. This buffer is only valid for as long as the alert itself +is valid, which is owned by libtorrent and reclaimed whenever +pop_alerts() is called on the session.

    +
    +
    +

    enum direction_t

    +

    Declared in "libtorrent/alert_types.hpp"

    + +++++ + + + + + + + + + + + + + + + + +
    namevaluedescription
    incoming0 
    outgoing1 
    +
    +
    dir
    +
    whether this is an incoming or outgoing packet.
    +
    +
    +
    node
    +
    the DHT node we received this packet from, or sent this packet to +(depending on dir).
    +
    +
    +
    +
    +

    dht_get_peers_reply_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +
    +struct dht_get_peers_reply_alert final : alert
    +{
    +   dht_get_peers_reply_alert (aux::stack_allocator& alloc
    +      , sha1_hash const& ih
    +      , std::vector<tcp::endpoint> const& v);
    +   virtual std::string message () const override;
    +   int num_peers () const;
    +   std::vector<tcp::endpoint> peers () const;
    +
    +   static const int static_category = alert::dht_operation_notification;
    +   sha1_hash info_hash;
    +};
    +
    +
    +
    +

    dht_direct_response_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This is posted exactly once for every call to session_handle::dht_direct_request. +If the request failed, response() will return a default constructed bdecode_node.

    +
    +struct dht_direct_response_alert final : alert
    +{
    +   dht_direct_response_alert (aux::stack_allocator& alloc, void* userdata
    +      , udp::endpoint const& addr, bdecode_node const& response);
    +   virtual std::string message () const override;
    +   dht_direct_response_alert (aux::stack_allocator& alloc, void* userdata
    +      , udp::endpoint const& addr);
    +   bdecode_node response () const;
    +
    +   static const int static_category = alert::dht_notification;
    +   void* userdata;
    +   udp::endpoint addr;
    +};
    +
    + +
    +

    dht_direct_response_alert() message()

    +
    +virtual std::string message () const override;
    +dht_direct_response_alert (aux::stack_allocator& alloc, void* userdata
    +      , udp::endpoint const& addr);
    +
    +

    for when there was a timeout so we don't have a response

    +
    +
    +
    +

    picker_log_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    this is posted when one or more blocks are picked by the piece picker, +assuming the verbose piece picker logging is enabled (see +picker_log_notification).

    +
    +struct picker_log_alert : peer_alert
    +{
    +   virtual std::string message () const override;
    +   std::vector<piece_block> blocks () const;
    +
    +   enum picker_flags_t
    +   {
    +      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,
    +   };
    +
    +   static const int static_category = alert::picker_log_notification;
    +   boost::uint32_t picker_flags;
    +};
    +
    +
    +

    enum picker_flags_t

    +

    Declared in "libtorrent/alert_types.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    partial_ratio1the ratio of partial pieces is too high. This forces a preference +for picking blocks from partial pieces.
    prioritize_partials2 
    rarest_first_partials4 
    rarest_first8 
    reverse_rarest_first16 
    suggested_pieces32 
    prio_sequential_pieces64 
    sequential_pieces128 
    reverse_pieces256 
    time_critical512 
    random_pieces1024 
    prefer_contiguous2048 
    reverse_sequential4096 
    backup18192 
    backup216384 
    end_game32768 
    +
    +
    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.
    +
    +
    +
    +
    +

    alert_cast()

    +

    Declared in "libtorrent/alert.hpp"

    +
    +template <class T> T* alert_cast (alert* a);
    +template <class T> T const* alert_cast (alert const* 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

    +
    +
    +
    +

    operation_name()

    +

    Declared in "libtorrent/alert_types.hpp"

    +
    +char const* operation_name (int op);
    +
    +

    maps an operation id (from peer_error_alert and peer_disconnected_alert) +to its name. See peer_connection for the constants

    +
    +
    +

    enum operation_t

    +

    Declared in "libtorrent/operations.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    op_bittorrent0this is used when the bittorrent logic +determines to disconnect
    op_iocontrol1a call to iocontrol failed
    op_getpeername2a call to getpeername failed (querying the remote IP of a +connection)
    op_getname3a call to getname failed (querying the local IP of a +connection)
    op_alloc_recvbuf4an attempt to allocate a receive buffer failed
    op_alloc_sndbuf5an attempt to allocate a send buffer failed
    op_file_write6writing to a file failed
    op_file_read7reading from a file failed
    op_file8a non-read and non-write file operation failed
    op_sock_write9a socket write operation failed
    op_sock_read10a socket read operation failed
    op_sock_open11a call to open(), to create a socket socket failed
    op_sock_bind12a call to bind() on a socket failed
    op_available13an attempt to query the number of bytes available to read from a socket +failed
    op_encryption14a call related to bittorrent protocol encryption failed
    op_connect15an attempt to connect a socket failed
    op_ssl_handshake16establishing an SSL connection failed
    op_get_interface17a connection failed to satisfy the bind interface setting
    +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/reference-Bdecoding.html b/docs/reference-Bdecoding.html new file mode 100644 index 0000000..4f4e841 --- /dev/null +++ b/docs/reference-Bdecoding.html @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + +
    +
    + + + + +
    + + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.1.1
    +
    +

    Bdecoding

    +
    +

    Table of contents

    + +
    +
    +

    bdecode_node

    +

    Declared in "libtorrent/bdecode.hpp"

    +

    Sometimes it's important to get a non-owning reference to the root node ( +to be able to copy it as a reference for instance). For that, use the +non_owning() member function.

    +

    There are 5 different types of nodes, see type_t.

    +
    +struct bdecode_node
    +{
    +   friend int bdecode (char const* start, char const* end, bdecode_node& ret
    +      , error_code& ec, int* error_pos, int depth_limit
    +      , int token_limit);
    +   bdecode_node ();
    +   bdecode_node (bdecode_node const&);
    +   bdecode_node& operator= (bdecode_node const&);
    +   type_t type () const;
    +   operator bool () const;
    +   bdecode_node non_owning () const;
    +   std::pair<char const*, int> data_section () const;
    +   bdecode_node list_at (int i) const;
    +   std::string list_string_value_at (int i
    +      , char const* default_val = "");
    +   boost::int64_t list_int_value_at (int i
    +      , boost::int64_t default_val = 0);
    +   int list_size () const;
    +   std::string dict_find_string_value (char const* key
    +      , char const* default_value = "") const;
    +   bdecode_node dict_find_string (char const* key) const;
    +   int dict_size () const;
    +   boost::int64_t dict_find_int_value (char const* key
    +      , boost::int64_t default_val = 0) const;
    +   bdecode_node dict_find (std::string key) const;
    +   bdecode_node dict_find_list (char const* key) const;
    +   bdecode_node dict_find (char const* key) const;
    +   bdecode_node dict_find_dict (std::string key) const;
    +   bdecode_node dict_find_dict (char const* key) const;
    +   std::pair<std::string, bdecode_node> dict_at (int i) const;
    +   bdecode_node dict_find_int (char const* key) const;
    +   boost::int64_t int_value () const;
    +   int string_length () const;
    +   std::string string_value () const;
    +   char const* string_ptr () const;
    +   void clear ();
    +   void swap (bdecode_node& n);
    +   void reserve (int tokens);
    +   void switch_underlying_buffer (char const* buf);
    +
    +   enum type_t
    +   {
    +      none_t,
    +      dict_t,
    +      list_t,
    +      string_t,
    +      int_t,
    +   };
    +};
    +
    +
    +

    bdecode_node()

    +
    +bdecode_node ();
    +
    +

    creates a default constructed node, it will have the type none_t.

    + +
    +
    +

    bdecode_node() operator=()

    +
    +bdecode_node (bdecode_node const&);
    +bdecode_node& operator= (bdecode_node const&);
    +
    +

    For owning nodes, the copy will create a copy of the tree, but the +underlying buffer remains the same.

    +
    +
    +

    type()

    +
    +type_t type () const;
    +
    +

    the type of this node. See type_t.

    +
    +
    +

    bool()

    +
    +operator bool () const;
    +
    +

    returns true if type() != none_t.

    +
    +
    +

    non_owning()

    +
    +bdecode_node non_owning () const;
    +
    +

    return a non-owning reference to this node. This is useful to refer to +the root node without copying it in assignments.

    +
    +
    +

    data_section()

    +
    +std::pair<char const*, int> data_section () 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.

    + + + +
    +
    +

    list_at() list_string_value_at() list_int_value_at() list_size()

    +
    +bdecode_node list_at (int i) const;
    +std::string list_string_value_at (int i
    +      , char const* default_val = "");
    +boost::int64_t list_int_value_at (int i
    +      , boost::int64_t default_val = 0);
    +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.

    + + + + + + + + +
    +
    +

    dict_size() dict_find_dict() dict_find_string() dict_find_int() dict_at() dict_find_list() dict_find_int_value() dict_find() dict_find_string_value()

    +
    +std::string dict_find_string_value (char const* key
    +      , char const* default_value = "") const;
    +bdecode_node dict_find_string (char const* key) const;
    +int dict_size () const;
    +boost::int64_t dict_find_int_value (char const* key
    +      , boost::int64_t default_val = 0) const;
    +bdecode_node dict_find (std::string key) const;
    +bdecode_node dict_find_list (char const* key) const;
    +bdecode_node dict_find (char const* key) const;
    +bdecode_node dict_find_dict (std::string key) const;
    +bdecode_node dict_find_dict (char const* key) const;
    +std::pair<std::string, bdecode_node> dict_at (int i) const;
    +bdecode_node dict_find_int (char const* key) const;
    +
    +

    Functions with the dict_ prefix operates on dictionaries. They are +only valid if type() == dict_t. In case a key you're looking up +contains a 0 byte, you cannot use the null-terminated string overloads, +but have to use std::string instead. dict_find_list will return a +valid bdecode_node if the key is found _and_ it is a list. Otherwise +it will return a default-constructed bdecode_node.

    +

    Functions with the _value suffix return the value of the node +directly, rather than the nodes. In case the node is not found, or it has +a different type, a default value is returned (which can be specified).

    +
    +
    +

    int_value()

    +
    +boost::int64_t int_value () const;
    +
    +

    this function is only valid if type() == int_t. It returns the +value of the integer.

    + + +
    +
    +

    string_ptr() string_length() string_value()

    +
    +int string_length () const;
    +std::string string_value () const;
    +char const* string_ptr () const;
    +
    +

    these functions are only valid if type() == string_t. They return +the string values. Note that string_ptr() is not null-terminated. +string_length() returns the number of bytes in the string.

    +
    +
    +

    clear()

    +
    +void clear ();
    +
    +

    resets the bdecoded_node to a default constructed state. If this is +an owning node, the tree is freed and all child nodes are invalidated.

    +
    +
    +

    swap()

    +
    +void swap (bdecode_node& n);
    +
    +

    Swap contents.

    +
    +
    +

    reserve()

    +
    +void reserve (int tokens);
    +
    +

    pre-allocate 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().

    +
    +
    +

    switch_underlying_buffer()

    +
    +void switch_underlying_buffer (char const* buf);
    +
    +

    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().

    +
    +
    +

    enum type_t

    +

    Declared in "libtorrent/bdecode.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    none_t0uninitialized or default constructed. This is also used +to indicate that a node was not found in some cases.
    dict_t1a dictionary node. The dict_find_ functions are valid.
    list_t2a list node. The list_ functions are valid.
    string_t3a string node, the string_ functions are valid.
    int_t4an integer node. The int_ functions are valid.
    +
    +
    +
    +

    print_entry()

    +

    Declared in "libtorrent/bdecode.hpp"

    +
    +std::string print_entry (bdecode_node const& e
    +   , bool single_line = false, int indent = 0);
    +
    +

    print the bencoded structure in a human-readable format to a string +that's returned.

    +
    +
    +

    bdecode()

    +

    Declared in "libtorrent/bdecode.hpp"

    +
    +int bdecode (char const* start, char const* end, bdecode_node& ret
    +   , error_code& ec, int* error_pos = 0, int depth_limit = 100
    +   , int token_limit = 1000000);
    +
    +

    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-null, will be set to the byte offset +into the buffer where the parse failure occurred.

    +

    depth_limit specifies the max number of nested lists or dictionaries are +allowed in the data structure. (This affects the stack usage of the +function, be careful not to set it too high).

    +

    token_limit is the max number of tokens allowed to be parsed from the +buffer. This is simply a sanity check to not have unbounded memory usage.

    +

    The resulting bdecode_node is an owning node. That means it will +be holding the whole parsed tree. When iterating lists and dictionaries, +those bdecode_node objects will simply have references to the root or +owning bdecode_node. If the root node is destructed, all other nodes +that refer to anything in that tree become invalid.

    +

    However, the underlying buffer passed in to this function (start, end) +must also remain valid while the bdecoded tree is used. The parsed tree +produced by this function does not copy any data out of the buffer, but +simply produces references back into it.

    +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/reference-Bencoding.html b/docs/reference-Bencoding.html new file mode 100644 index 0000000..278f5e3 --- /dev/null +++ b/docs/reference-Bencoding.html @@ -0,0 +1,431 @@ + + + + + + + + + + + + + + + +
    +
    + + + + +
    + + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.1.1
    +
    +

    Bencoding

    +
    +

    Table of contents

    + +
    +

    Bencoding is a common representation in bittorrent used for 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 state between sessions.

    +

    Strings in bencoded structures are not necessarily representing 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.

    +

    There are two mechanims to decode bencoded buffers in libtorrent.

    +

    The most flexible one is bdecode() bencode(), which returns a structure +represented by entry. Oncea buffer has been decoded with this function, it +can be discarded. The entry does not contain any references back to it. This +means that bdecode() copies all the data out of the buffer and into its own +hierarchy. This makes this function expensive, which might matter if you're +parsing large amounts of data.

    +

    Another consideration is that bdecode() bencode() is a recursive parser. +For this reason, in order to avoid DoS attacks by triggering a stack +overflow, there is a recursion limit. This limit is a sanity check to make +sure it doesn't run the risk of busting the stack.

    +

    The second mechanism is the decode function for 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.

    +

    Not only is this function more efficient because of less memory allocation +and data copy, the parser is also not recursive, which means it probably +performs a little bit better and can have a higher recursion limit on the +structures it's parsing.

    +
    +

    entry

    +

    Declared in "libtorrent/entry.hpp"

    +

    The entry class represents one node in a bencoded hierarchy. It works as a +variant type, it can be either a list, a dictionary (std::map), an integer +or a string.

    +
    +class entry
    +{
    +   data_type type () const;
    +   entry (list_type const&);
    +   entry (integer_type const&);
    +   entry (dictionary_type const&);
    +   entry (string_type const&);
    +   entry (preformatted_type const&);
    +   entry (data_type t);
    +   void operator= (string_type const&);
    +   void operator= (integer_type const&);
    +   void operator= (entry const&);
    +   void operator= (dictionary_type const&);
    +   void operator= (list_type const&);
    +   void operator= (preformatted_type const&);
    +   void operator= (bdecode_node const&);
    +   preformatted_type& preformatted ();
    +   const integer_type& integer () const;
    +   const string_type& string () const;
    +   const preformatted_type& preformatted () const;
    +   const dictionary_type& dict () const;
    +   string_type& string ();
    +   list_type& list ();
    +   dictionary_type& dict ();
    +   integer_type& integer ();
    +   const list_type& list () const;
    +   void swap (entry& e);
    +   entry& operator[] (std::string const& key);
    +   const entry& operator[] (std::string const& key) const;
    +   entry& operator[] (char const* key);
    +   const entry& operator[] (char const* key) const;
    +   entry const* find_key (char const* key) const;
    +   entry* find_key (char const* key);
    +   entry* find_key (std::string const& key);
    +   entry const* find_key (std::string const& key) const;
    +   std::string to_string () const;
    +
    +   enum data_type
    +   {
    +      int_t,
    +      string_t,
    +      list_t,
    +      dictionary_t,
    +      undefined_t,
    +      preformatted_t,
    +   };
    +
    +   mutable boost::uint8_t m_type_queried:1;
    +};
    +
    +
    +

    type()

    +
    +data_type type () const;
    +
    +

    returns the concrete type of the entry

    +
    +
    +

    entry()

    +
    +entry (list_type const&);
    +entry (integer_type const&);
    +entry (dictionary_type const&);
    +entry (string_type const&);
    +entry (preformatted_type const&);
    +
    +

    constructors directly from a specific type. +The content of the argument is copied into the +newly constructed entry

    +
    +
    +

    entry()

    +
    +entry (data_type t);
    +
    +

    construct an empty entry of the specified type. +see data_type enum.

    +
    +
    +

    operator=()

    +
    +void operator= (string_type const&);
    +void operator= (integer_type const&);
    +void operator= (entry const&);
    +void operator= (dictionary_type const&);
    +void operator= (list_type const&);
    +void operator= (preformatted_type const&);
    +void operator= (bdecode_node const&);
    +
    +

    copies the structure of the right hand side into this +entry.

    + + + + +
    +
    +

    string() integer() dict() preformatted() list()

    +
    +preformatted_type& preformatted ();
    +const integer_type& integer () const;
    +const string_type& string () const;
    +const preformatted_type& preformatted () const;
    +const dictionary_type& dict () const;
    +string_type& string ();
    +list_type& list ();
    +dictionary_type& dict ();
    +integer_type& integer ();
    +const list_type& list () const;
    +
    +

    The integer(), string(), list() and dict() functions +are accessors that return the respective type. If the entry object +isn't of the type you request, the accessor will throw +libtorrent_exception (which derives from std::runtime_error). You +can ask an entry for its type through the type() function.

    +

    If you want to create an entry you give it the type you want it to +have in its constructor, and then use one of the non-const accessors +to get a reference which you then can assign the value you want it to +have.

    +

    The typical code to get info from a torrent file will then look like +this:

    +
    +entry torrent_file;
    +// ...
    +
    +// throws if this is not a dictionary
    +entry::dictionary_type const& dict = torrent_file.dict();
    +entry::dictionary_type::const_iterator i;
    +i = dict.find("announce");
    +if (i != dict.end())
    +{
    +        std::string tracker_url = i->second.string();
    +        std::cout << tracker_url << "\n";
    +}
    +
    +

    The following code is equivalent, but a little bit shorter:

    +
    +entry torrent_file;
    +// ...
    +
    +// throws if this is not a dictionary
    +if (entry* i = torrent_file.find_key("announce"))
    +{
    +        std::string tracker_url = i->string();
    +        std::cout << tracker_url << "\n";
    +}
    +
    +

    To make it easier to extract information from a torrent file, the +class torrent_info exists.

    +
    +
    +

    swap()

    +
    +void swap (entry& e);
    +
    +

    swaps the content of this with e.

    +
    +
    +

    operator[]()

    +
    +entry& operator[] (std::string const& key);
    +const entry& operator[] (std::string const& key) const;
    +entry& operator[] (char const* key);
    +const entry& operator[] (char const* key) const;
    +
    +

    All of these functions requires the entry to be a dictionary, if it +isn't they will throw libtorrent::type_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 libtorrent::type_error.

    +
    +
    +

    find_key()

    +
    +entry const* find_key (char const* key) const;
    +entry* find_key (char const* key);
    +entry* find_key (std::string const& key);
    +entry const* find_key (std::string const& key) const;
    +
    +

    These functions requires the entry to be a dictionary, if it isn't +they will throw libtorrent::type_error.

    +

    They will look for an element at the given key in the dictionary, if +the element cannot be found, they will return 0. If an element with +the given key is found, the return a pointer to it.

    +
    +
    +

    to_string()

    +
    +std::string to_string () const;
    +
    +

    returns a pretty-printed string representation +of the bencoded structure, with JSON-style syntax

    +
    +
    +

    enum data_type

    +

    Declared in "libtorrent/entry.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    int_t0 
    string_t1 
    list_t2 
    dictionary_t3 
    undefined_t4 
    preformatted_t5 
    +
    +
    m_type_queried
    +
    in debug mode this is set to false by bdecode to indicate that the +program has not yet queried the type of this entry, and should not +assume that it has a certain type. This is asserted in the accessor +functions. This does not apply if exceptions are used.
    +
    + +
    +
    +
    +

    bdecode() bencode()

    +

    Declared in "libtorrent/bencode.hpp"

    +
    +template<class InIt> entry bdecode (InIt start, InIt end);
    +template<class OutIt> int bencode (OutIt out, const entry& e);
    +template<class InIt> entry bdecode (InIt start, InIt end, int& len);
    +
    +

    These functions will encode data to bencoded or decode bencoded data.

    +

    If possible, bdecode() producing a bdecode_node should be preferred +over this function.

    +

    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.

    +

    The OutIt and InIt are iterators +(InputIterator and OutputIterator respectively). They +are templates and are usually instantiated as ostream_iterator, +back_insert_iterator or istream_iterator. These +functions will assume that the iterator refers to a character +(char). So, if you want to encode entry e into a buffer +in memory, you can do it like this:

    +
    +std::vector<char> buffer;
    +bencode(std::back_inserter(buf), e);
    +
    +

    If you want to decode a torrent file from a buffer in memory, you can do it like this:

    +
    +std::vector<char> buffer;
    +// ...
    +entry e = bdecode(buf.begin(), buf.end());
    +
    +

    Or, if you have a raw char buffer:

    +
    +const char* buf;
    +// ...
    +entry e = bdecode(buf, buf + data_size);
    +
    +

    Now we just need to know how to retrieve information from the entry.

    +

    If bdecode() encounters invalid encoded data in the range given to it +it will return a default constructed entry object.

    +
    +
    +

    operator<<()

    +

    Declared in "libtorrent/entry.hpp"

    +
    +inline std::ostream& operator<< (std::ostream& os, const entry& e);
    +
    +

    prints the bencoded structure to the ostream as a JSON-style structure.

    +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/reference-Core.html b/docs/reference-Core.html new file mode 100644 index 0000000..f46d745 --- /dev/null +++ b/docs/reference-Core.html @@ -0,0 +1,5408 @@ + + + + + + + + + + + + + + + +
    +
    + + + + +
    + + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.1.1
    +
    +

    Core

    + +
    +

    add_torrent_params

    +

    Declared in "libtorrent/add_torrent_params.hpp"

    +

    The add_torrent_params is a parameter pack for adding torrents to a +session. The key fields when adding a torrent are:

    +
      +
    • ti - when you have a .torrent file
    • +
    • url - when you have a magnet link
    • +
    • info_hash - when all you have is an info-hash (this is similar to a +magnet link)
    • +
    +

    one of those fields need to 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.

    +
    +struct add_torrent_params
    +{
    +   add_torrent_params (storage_constructor_type sc = default_storage_constructor);
    +
    +   enum flags_t
    +   {
    +      flag_seed_mode,
    +      flag_override_resume_data,
    +      flag_upload_mode,
    +      flag_share_mode,
    +      flag_apply_ip_filter,
    +      flag_paused,
    +      flag_auto_managed,
    +      flag_duplicate_is_error,
    +      flag_merge_resume_trackers,
    +      flag_update_subscribe,
    +      flag_super_seeding,
    +      flag_sequential_download,
    +      flag_use_resume_save_path,
    +      flag_pinned,
    +      flag_merge_resume_http_seeds,
    +      flag_stop_when_ready,
    +   };
    +
    +   int version;
    +   boost::shared_ptr<torrent_info> ti;
    +   std::vector<std::string> trackers;
    +   std::vector<std::string> url_seeds;
    +   std::vector<std::pair<std::string, int> > dht_nodes;
    +   std::string name;
    +   std::string save_path;
    +   std::vector<char> resume_data;
    +   storage_mode_t storage_mode;
    +   storage_constructor_type storage;
    +   void* userdata;
    +   std::vector<boost::uint8_t> file_priorities;
    +   std::string trackerid;
    +   std::string url;
    +   std::string uuid;
    +   std::string source_feed_url;
    +   boost::uint64_t flags;
    +   sha1_hash info_hash;
    +   int max_uploads;
    +   int max_connections;
    +   int upload_limit;
    +   int download_limit;
    +};
    +
    +
    +

    add_torrent_params()

    +
    +add_torrent_params (storage_constructor_type sc = default_storage_constructor);
    +
    +

    The constructor can be used to initialize the storage constructor, +which determines the storage mechanism for the downloaded or seeding +data for the torrent. For more information, see the storage field.

    +
    +
    +

    enum flags_t

    +

    Declared in "libtorrent/add_torrent_params.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    flag_seed_mode1

    If flag_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 flag_seed_mode on a torrent without metadata (a +.torrent file) is a no-op and will be ignored.

    +

    If resume data is passed in with this torrent, the seed mode saved +in there will override the seed mode you set here.

    +
    flag_override_resume_data2If flag_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). +If this flag is set, but file_priorities is empty, file priorities +are still loaded from the resume data, if present.
    flag_upload_mode4

    If flag_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. 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 (flag_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.

    +
    flag_share_mode8

    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.

    +
    flag_apply_ip_filter16determines 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.
    flag_paused32specifies whether or not the torrent is to be started in a paused +state. I.e. it won't connect to the tracker or any of the peers +until it's resumed. This is typically a good way of avoiding race +conditions when setting configuration options on torrents before +starting them.
    flag_auto_managed64

    If the torrent is auto-managed (flag_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 flag_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.

    +

    If you pass in resume data, the auto_managed state of the torrent +when the resume data was saved will override the auto_managed state +you pass in here. You can override this by setting +override_resume_data.

    +
    flag_duplicate_is_error128 
    flag_merge_resume_trackers256defaults to off 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.
    flag_update_subscribe512on by default and means that this torrent will be part of state +updates when calling post_torrent_updates().
    flag_super_seeding1024sets the torrent into super seeding mode. If the torrent is not a +seed, this flag has no effect. It has the same effect as calling +torrent_handle::super_seeding(true) on the torrent handle +immediately after adding it.
    flag_sequential_download2048sets the sequential download state for the torrent. It has the same +effect as calling torrent_handle::sequential_download(true) on +the torrent handle immediately after adding it.
    flag_use_resume_save_path4096if 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.
    flag_pinned8192indicates 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.
    flag_merge_resume_http_seeds32768defaults to off 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.
    flag_stop_when_ready16384the stop when ready flag. Setting this flag is equivalent to calling +torrent_handle::stop_when_ready() immediately after the torrent is +added.
    +
    +
    version
    +
    filled in by the constructor and should be left untouched. It is used +for forward binary compatibility.
    +
    +
    +
    ti
    +
    torrent_info object with the torrent to add. Unless the url or +info_hash is set, this is required to be initialized.
    +
    +
    +
    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.
    +
    +
    +
    url_seeds
    +
    url seeds to be added to the torrent (BEP 17).
    +
    + +
    +
    dht_nodes name
    +
    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.
    +
    +
    +
    save_path
    +

    the path where the torrent is or will be stored. Note that this may +also be stored in resume data. If you want the save path saved in +the resume data to be used, you need to set the +flag_use_resume_save_path flag.

    +
    +

    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 "..".

    +
    +
    +
    +
    +
    resume_data
    +
    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().
    +
    +
    +
    storage_mode
    +
    One of the values from storage_mode_t. For more information, see +storage allocation.
    +
    +
    +
    storage
    +
    can be used to customize how the data is stored. The default storage +will simply write the data to the files it belongs to, but it could be +overridden to save everything to a single file at a specific location +or encrypt the content on disk for instance. For more information +about the storage_interface that needs to be implemented for a custom +storage, see storage_interface.
    +
    +
    +
    userdata
    +
    The userdata parameter is optional and will be passed on to the +extension constructor functions, if any +(see torrent_handle::add_extension()).
    +
    +
    +
    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().
    +
    +
    +
    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.
    +
    +
    +
    url
    +
    If you specify a url, the torrent will be set in +downloading_metadata state until the .torrent file has been +downloaded. If there's any error while downloading, the torrent will +be stopped and the torrent error state (torrent_status::error) +will indicate what went wrong. The url may be set to a magnet link.
    +
    +
    +
    uuid
    +
    if uuid is specified, it is used to find duplicates. If another +torrent is already running with the same UUID as the one being added, +it will be considered a duplicate. This is mainly useful for RSS feed +items which has UUIDs specified.
    +
    +
    +
    source_feed_url
    +
    should point to the URL of the RSS feed this torrent comes from, if it +comes from an RSS feed.
    +
    +
    +
    flags
    +

    flags controlling aspects of this torrent and how it's added. See +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.

    +
    +
    +
    +
    +
    info_hash
    +
    set this to the info hash of the torrent to add in case the info-hash +is the only known property of the torrent. i.e. you don't have a +.torrent file nor a magnet link.
    +
    + + + +
    +
    max_uploads max_connections upload_limit download_limit
    +

    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

    +
    +
    +
    +
    +
    +

    announce_entry

    +

    Declared in "libtorrent/announce_entry.hpp"

    +

    this class holds information about one bittorrent tracker, as it +relates to a specific torrent.

    +
    +struct announce_entry
    +{
    +   announce_entry (std::string const& u);
    +   announce_entry (announce_entry const&) = default;
    +   ~announce_entry ();
    +   announce_entry& operator= (announce_entry const&) = default;
    +   announce_entry ();
    +   int next_announce_in () const;
    +   int min_announce_in () const;
    +   void reset ();
    +   void failed (aux::session_settings const& sett, int retry_interval = 0);
    +   bool can_announce (time_point now, bool is_seed) const;
    +   bool is_working () const;
    +   void trim ();
    +
    +   enum tracker_source
    +   {
    +      source_torrent,
    +      source_client,
    +      source_magnet_link,
    +      source_tex,
    +   };
    +
    +   std::string url;
    +   std::string trackerid;
    +   std::string message;
    +   error_code last_error;
    +   time_point next_announce;
    +   time_point min_announce;
    +   int scrape_incomplete;
    +   int scrape_complete;
    +   int scrape_downloaded;
    +   boost::uint8_t tier;
    +   boost::uint8_t fail_limit;
    +   boost::uint8_t fails:7;
    +   bool updating:1;
    +   boost::uint8_t source:4;
    +   bool verified:1;
    +   bool start_sent:1;
    +   bool complete_sent:1;
    +   bool send_stats:1;
    +};
    +
    + + +
    +

    announce_entry() ~announce_entry() operator=()

    +
    +announce_entry (std::string const& u);
    +announce_entry (announce_entry const&) = default;
    +~announce_entry ();
    +announce_entry& operator= (announce_entry const&) = default;
    +announce_entry ();
    +
    +

    constructs a tracker announce entry with u as the URL.

    + +
    +
    +

    min_announce_in() next_announce_in()

    +
    +int next_announce_in () const;
    +int min_announce_in () const;
    +
    +

    returns the number of seconds to the next announce on this tracker. +min_announce_in() returns the number of seconds until we are +allowed to force another tracker update with this tracker.

    +

    If the last time this tracker was contacted failed, last_error is +the error code describing what error occurred.

    +
    +
    +

    reset()

    +
    +void reset ();
    +
    +

    reset announce counters and clears the started sent flag. +The announce_entry will look like we've never talked to +the tracker.

    +
    +
    +

    failed()

    +
    +void failed (aux::session_settings const& sett, int retry_interval = 0);
    +
    +

    updates the failure counter and time-outs for re-trying. +This is called when the tracker announce fails.

    +
    +
    +

    can_announce()

    +
    +bool can_announce (time_point now, bool is_seed) const;
    +
    +

    returns true if we can announce to this tracker now. +The current time is passed in as now. The is_seed +argument is necessary because once we become a seed, we +need to announce right away, even if the re-announce timer +hasn't expired yet.

    +
    +
    +

    is_working()

    +
    +bool is_working () const;
    +
    +

    returns true if the last time we tried to announce to this +tracker succeeded, or if we haven't tried yet.

    +
    +
    +

    trim()

    +
    +void trim ();
    +
    +

    trims whitespace characters from the beginning of the URL.

    +
    +
    +

    enum tracker_source

    +

    Declared in "libtorrent/announce_entry.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    source_torrent1the tracker was part of the .torrent file
    source_client2the tracker was added programatically via the add_troacker()_ function
    source_magnet_link4the tracker was part of a magnet link
    source_tex8the tracker was received from the swarm via tracker exchange
    +
    +
    url
    +
    tracker URL as it appeared in the torrent file
    +
    +
    +
    trackerid
    +
    the current &trackerid= argument passed to the tracker. +this is optional and is normally empty (in which case no +trackerid is sent).
    +
    +
    +
    message
    +
    if this tracker has returned an error or warning message +that message is stored here
    +
    +
    +
    last_error
    +
    if this tracker failed the last time it was contacted +this error code specifies what error occurred
    +
    +
    +
    next_announce
    +
    the time of next tracker announce
    +
    +
    +
    min_announce
    +
    no announces before this time
    +
    + + +
    +
    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. the number of +current downloaders
    +
    +
    +
    tier
    +
    the tier this tracker belongs to
    +
    +
    +
    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
    +
    +
    +
    fails
    +
    the number of times in a row we have failed to announce to this +tracker.
    +
    +
    +
    updating
    +
    true while we're waiting for a response from the tracker.
    +
    +
    +
    source
    +
    a bitmask specifying which sources we got this tracker from.
    +
    +
    +
    verified
    +
    set to true the first time we receive a valid response +from this tracker.
    +
    +
    +
    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.
    +
    +
    +
    complete_sent
    +
    set to true when we send a event=completed.
    +
    +
    +
    send_stats
    +
    this is false the stats sent to this tracker will be 0
    +
    +
    +
    +
    +

    cache_status

    +

    Declared in "libtorrent/disk_io_thread.hpp"

    +

    this struct holds a number of statistics counters +relevant for the disk io thread and disk cache.

    +
    +struct cache_status
    +{
    +   cache_status ();
    +
    +   std::vector<cached_piece_info> pieces;
    +};
    +
    +
    +

    cache_status()

    +
    +cache_status ();
    +
    +

    initializes all counters to 0

    +
    +
    +
    +

    bt_peer_connection_handle

    +

    Declared in "libtorrent/peer_connection_handle.hpp"

    +
    +struct bt_peer_connection_handle : public 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 (boost::shared_ptr<crypto_plugin> crypto);
    +   void switch_send_crypto (boost::shared_ptr<crypto_plugin> crypto);
    +   boost::shared_ptr<bt_peer_connection> native_handle () const;
    +};
    +
    +
    +
    +

    peer_info

    +

    Declared in "libtorrent/peer_info.hpp"

    +

    holds information and statistics about one peer +that libtorrent is connected to

    +
    +struct peer_info
    +{
    +   enum peer_flags_t
    +   {
    +      interesting,
    +      choked,
    +      remote_interested,
    +      remote_choked,
    +      supports_extensions,
    +      local_connection,
    +      handshake,
    +      connecting,
    +      on_parole,
    +      seed,
    +      optimistic_unchoke,
    +      snubbed,
    +      upload_only,
    +      endgame_mode,
    +      holepunched,
    +      i2p_socket,
    +      utp_socket,
    +      ssl_socket,
    +      rc4_encrypted,
    +      plaintext_encrypted,
    +   };
    +
    +   enum peer_source_flags
    +   {
    +      tracker,
    +      dht,
    +      pex,
    +      lsd,
    +      resume_data,
    +      incoming,
    +   };
    +
    +   enum connection_type_t
    +   {
    +      standard_bittorrent,
    +      web_seed,
    +      http_seed,
    +   };
    +
    +   enum bw_state
    +   {
    +      bw_idle,
    +      bw_limit,
    +      bw_network,
    +      bw_disk,
    +   };
    +
    +   std::string client;
    +   bitfield pieces;
    +   boost::int64_t total_download;
    +   boost::int64_t total_upload;
    +   time_duration last_request;
    +   time_duration last_active;
    +   time_duration download_queue_time;
    +   boost::uint32_t flags;
    +   boost::uint32_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 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;
    +   int downloading_piece_index;
    +   int downloading_block_index;
    +   int downloading_progress;
    +   int downloading_total;
    +   int connection_type;
    +   int remote_dl_rate;
    +   int pending_disk_bytes;
    +   int pending_disk_read_bytes;
    +   int send_quota;
    +   int receive_quota;
    +   int rtt;
    +   int num_pieces;
    +   int download_rate_peak;
    +   int upload_rate_peak;
    +   float progress;
    +   int progress_ppm;
    +   int estimated_reciprocation_rate;
    +   tcp::endpoint ip;
    +   tcp::endpoint local_endpoint;
    +   char read_state;
    +   char write_state;
    +};
    +
    +
    +

    enum peer_flags_t

    +

    Declared in "libtorrent/peer_info.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    interesting1we are interested in pieces from this peer.
    choked2we have choked this peer.
    remote_interested4the peer is interested in us
    remote_choked8the peer has choked us.
    supports_extensions16means that this peer supports the +extension protocol.
    local_connection32The 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.
    handshake64The connection is opened, and waiting for the +handshake. Until the handshake is done, the peer +cannot be identified.
    connecting128The connection is in a half-open state (i.e. it is +being connected).
    on_parole512The 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.
    seed1024This peer is a seed (it has all the pieces).
    optimistic_unchoke2048This 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.
    snubbed4096This 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.
    upload_only8192This 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.
    endgame_mode16384This 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.
    holepunched32768This 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.
    i2p_socket65536indicates that this socket is runnin on top of the +I2P transport.
    utp_socket131072indicates that this socket is a uTP socket
    ssl_socket262144indicates that this socket is running on top of an SSL +(TLS) channel
    rc4_encrypted1048576this connection is obfuscated with RC4
    plaintext_encrypted2097152the handshake of this connection was obfuscated +with a diffie-hellman exchange
    +
    +
    +

    enum peer_source_flags

    +

    Declared in "libtorrent/peer_info.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    tracker1The peer was received from the tracker.
    dht2The peer was received from the kademlia DHT.
    pex4The peer was received from the peer exchange +extension.
    lsd8The peer was received from the local service +discovery (The peer is on the local network).
    resume_data16The peer was added from the fast resume data.
    incoming32we received an incoming connection from this peer
    +
    +
    +

    enum connection_type_t

    +

    Declared in "libtorrent/peer_info.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    standard_bittorrent0Regular bittorrent connection
    web_seed1HTTP connection using the BEP 19 protocol
    http_seed2HTTP connection using the BEP 17 protocol
    +
    +
    +

    enum bw_state

    +

    Declared in "libtorrent/peer_info.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    bw_idle0The peer is not waiting for any external events to +send or receive data.
    bw_limit1The peer is waiting for the rate limiter.
    bw_network2The 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.
    bw_disk4The peer is waiting for the disk I/O thread to catch +up writing buffers to disk before downloading more.
    +
    +
    client
    +
    a string describing the software at the other end of the connection. +In some cases this information is not available, then it will contain +a string that may give away something about which software is running +in the other end. In the case of a web seed, the server type and +version will be a part of this string.
    +
    +
    +
    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).
    +
    + +
    +
    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.
    +
    + +
    +
    last_request last_active
    +
    the time since we last sent a request to this peer and since any +transfer occurred with this peer
    +
    +
    +
    download_queue_time
    +
    the time until all blocks in the request queue will be downloaded
    +
    +
    +
    flags
    +
    tells you in which state the peer is in. It is set to +any combination of the peer_flags_t enum.
    +
    +
    +
    source
    +
    a combination of flags describing from which sources this peer +was received. See peer_source_flags.
    +
    + +
    +
    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
    +
    + +
    +
    payload_up_speed payload_down_speed
    +
    The transfer rates of payload data only updated about once per second
    +
    +
    +
    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()_
    +
    +
    +
    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.
    +
    + +
    +
    send_buffer_size used_send_buffer
    +
    the number of bytes allocated +and used for the peer's send buffer, respectively.
    +
    + +
    +
    receive_buffer_size used_receive_buffer
    +
    the number of bytes +allocated and used as receive buffer, respectively.
    +
    +
    +
    num_hashfails
    +
    the number of pieces this peer has participated in sending us that +turned out to fail the hash check.
    +
    +
    +
    download_queue_length
    +
    this is the number of requests we have sent to this peer that we +haven't got a response for yet
    +
    +
    +
    timed_out_requests
    +
    the number of block requests that have timed out, and are still in the +download queue
    +
    +
    +
    busy_requests
    +
    the number of busy requests in the download queue. A budy request is a +request for a block we've also requested from a different peer
    +
    +
    +
    requests_in_buffer
    +
    the number of requests messages that are currently in the send buffer +waiting to be sent.
    +
    +
    +
    target_dl_queue_length
    +
    the number of requests that is tried to be maintained (this is +typically a function of download speed)
    +
    +
    +
    upload_queue_length
    +
    the number of piece-requests we have received from this peer +that we haven't answered with a piece yet.
    +
    +
    +
    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.
    +
    + + + +
    +
    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.
    +
    +
    +
    connection_type
    +
    the kind of connection this peer uses. See connection_type_t.
    +
    +
    +
    remote_dl_rate
    +
    an estimate of the rate this peer is downloading at, in +bytes per second.
    +
    +
    +
    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.
    +
    +
    +
    pending_disk_read_bytes
    +
    number of outstanding bytes to read +from disk
    +
    + +
    +
    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.
    +
    +
    +
    rtt
    +
    an estimated round trip time to this peer, in milliseconds. It is +estimated by timing the the tcp connect(). It may be 0 for +incoming connections.
    +
    +
    +
    num_pieces
    +
    the number of pieces this peer has.
    +
    + +
    +
    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.
    +
    +
    +
    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.
    +
    +
    +
    progress_ppm
    +
    indicates the download progress of the peer in the range [0, 1000000] +(parts per million).
    +
    +
    +
    estimated_reciprocation_rate
    +
    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.
    +
    +
    +
    ip
    +
    the IP-address to this peer. The type is an asio endpoint. For +more info, see the asio documentation.
    +
    +
    +
    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.
    +
    + +
    +
    read_state write_state
    +
    bitmasks indicating what state this peer +is in with regards to sending and receiving data. The states are declared in the +bw_state enum.
    +
    +
    +
    +
    +

    peer_request

    +

    Declared in "libtorrent/peer_request.hpp"

    +

    represents a byte range within a piece. Internally this is +is used for incoming piece requests.

    +
    +struct peer_request
    +{
    +   bool operator== (peer_request const& r) const;
    +
    +   int piece;
    +   int start;
    +   int length;
    +};
    +
    +
    +

    operator==()

    +
    +bool operator== (peer_request const& r) const;
    +
    +

    returns true if the right hand side peer_request refers to the same +range as this does.

    +
    +
    piece
    +
    the index of the piece in which the range starts.
    +
    +
    +
    start
    +
    the offset within that piece where the range starts.
    +
    +
    +
    length
    +
    the size of the range, in bytes.
    +
    +
    +
    +
    +

    session_proxy

    +

    Declared in "libtorrent/session.hpp"

    +

    this is a holder for the internal session implementation object. Once the +session destruction is explicitly initiated, this holder is used to +synchronize the completion of the shutdown. The lifetime of this object +may outlive session, causing the session destructor to not block. The +session_proxy destructor will block however, until the underlying session +is done shutting down.

    +
    +class session_proxy
    +{
    +   ~session_proxy ();
    +   session_proxy (session_proxy const&) = default;
    +   session_proxy& operator= (session_proxy const&) = default;
    +   session_proxy ();
    +};
    +
    + + +
    +

    session_proxy() ~session_proxy() operator=()

    +
    +~session_proxy ();
    +session_proxy (session_proxy const&) = default;
    +session_proxy& operator= (session_proxy const&) = default;
    +session_proxy ();
    +
    +

    default constructor, does not refer to any session +implementation object.

    +
    +
    +
    +

    session

    +

    Declared in "libtorrent/session.hpp"

    +

    The session holds all state that spans multiple torrents. Among other +things it runs the network loop and manages all torrents. Once it's +created, the session object will spawn the main thread that will do all +the work. The main thread will be idle as long it doesn't have any +torrents to participate in.

    +

    You have some control over session configuration through the +session::apply_settings() member function. To change one or more +configuration options, create a settings_pack. object and fill it with +the settings to be set and pass it in to session::apply_settings().

    +

    see apply_settings().

    +
    +class session: public boost::noncopyable, public session_handle
    +{
    +   session (settings_pack const& pack = settings_pack()
    +      , int flags = start_default_features | add_default_plugins);
    +   session (settings_pack const& pack
    +      , io_service& ios
    +      , int flags = start_default_features | add_default_plugins);
    +   ~session ();
    +   session_proxy abort ();
    +};
    +
    +
    +

    session()

    +
    +session (settings_pack const& pack = settings_pack()
    +      , int flags = start_default_features | add_default_plugins);
    +
    +

    Constructs the session obects which acts as the container of torrents. +It provides configuration options across torrents (such as rate limits, +disk cache, ip filter etc.). In order to avoid a race condition between +starting the session and configuring it, you can pass in a +settings_pack object. Its settings will take effect before the session +starts up.

    +

    The flags parameter can be used to start default features (upnp & +nat-pmp) and default plugins (ut_metadata, ut_pex and smart_ban). The +default is to start those features. If you do not want them to start, +pass 0 as the flags parameter.

    +
    +
    +

    session()

    +
    +session (settings_pack const& pack
    +      , io_service& ios
    +      , int flags = start_default_features | add_default_plugins);
    +
    +

    overload of the constructor that takes an external io_service to run +the session object on. This is primarily useful for tests that may want +to run multiple sessions on a single io_service, or low resource +systems where additional threads are expensive and sharing an +io_service with other events is fine.

    +
    +

    Warning

    +

    The session object does not cleanly terminate with an external +io_service. The io_service::run() call _must_ have returned +before it's safe to destruct the session. Which means you MUST +call session::abort() and save the session_proxy first, then +destruct the session object, then sync with the io_service, then +destruct the session_proxy object.

    +
    +
    +
    +

    ~session()

    +
    +~session ();
    +
    +

    The destructor of session will notify all trackers that our torrents +have been shut down. If some trackers are down, they will time out. +All this before the destructor of session returns. So, it's advised +that any kind of interface (such as windows) are closed before +destructing the session object. Because it can take a few second for +it to finish. The timeout can be set with apply_settings().

    +
    +
    +

    abort()

    +
    +session_proxy abort ();
    +
    +

    In case you want to destruct the session asynchronously, you can +request a session destruction proxy. If you don't do this, the +destructor of the session object will block while the trackers are +contacted. If you keep one session_proxy to the session when +destructing it, the destructor will not block, but start to close down +the session, the destructor of the proxy will then synchronize the +threads. So, the destruction of the session is performed from the +session destructor call until the session_proxy destructor +call. The session_proxy does not have any operations on it (since +the session is being closed down, no operations are allowed on it). +The only valid operation is calling the destructor:

    +
    +class session_proxy
    +{
    +public:
    +        session_proxy();
    +        ~session_proxy()
    +};
    +
    +
    +
    +
    +

    session_handle

    +

    Declared in "libtorrent/session_handle.hpp"

    +
    +struct session_handle
    +{
    +   session_handle ();
    +   session_handle (aux::session_impl* impl);
    +   bool is_valid () const;
    +   void load_state (bdecode_node const& e, boost::uint32_t flags = 0xffffffff);
    +   void save_state (entry& e, boost::uint32_t flags = 0xffffffff) const;
    +   void refresh_torrent_status (std::vector<torrent_status>* ret
    +      , boost::uint32_t flags = 0) const;
    +   void get_torrent_status (std::vector<torrent_status>* ret
    +      , boost::function<bool(torrent_status const&)> const& pred
    +      , boost::uint32_t flags = 0) const;
    +   void post_torrent_updates (boost::uint32_t flags = 0xffffffff);
    +   void post_session_stats ();
    +   void post_dht_stats ();
    +   torrent_handle find_torrent (sha1_hash const& info_hash) const;
    +   std::vector<torrent_handle> get_torrents () const;
    +   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);
    +   void resume ();
    +   void pause ();
    +   bool is_paused () const;
    +   void set_load_function (user_load_function_t fun);
    +   void get_cache_info (cache_status* ret, torrent_handle h = torrent_handle(), int flags = 0) const;
    +   bool is_dht_running () const;
    +   dht_settings get_dht_settings () const;
    +   void set_dht_settings (dht_settings const& settings);
    +   void set_dht_storage (dht::dht_storage_constructor_type sc);
    +   void add_dht_router (std::pair<std::string, int> const& node);
    +   void add_dht_node (std::pair<std::string, int> const& node);
    +   void dht_get_item (sha1_hash const& target);
    +   void dht_get_item (boost::array<char, 32> key
    +      , std::string salt = std::string());
    +   sha1_hash dht_put_item (entry data);
    +   void dht_put_item (boost::array<char, 32> key
    +      , boost::function<void(entry&, boost::array<char,64>&
    +      , boost::uint64_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, int flags = 0);
    +   void dht_direct_request (udp::endpoint ep, entry const& e, void* userdata = 0);
    +   void add_extension (boost::function<boost::shared_ptr<torrent_plugin>(
    +      torrent_handle const&, void*)> ext);
    +   void add_extension (boost::shared_ptr<plugin> ext);
    +   ip_filter get_ip_filter () const;
    +   void set_ip_filter (ip_filter const& f);
    +   void set_port_filter (port_filter const& f);
    +   peer_id id () const;
    +   void set_key (int key);
    +   bool is_listening () const;
    +   unsigned short listen_port () const;
    +   unsigned short ssl_listen_port () const;
    +   void set_peer_class_filter (ip_filter const& f);
    +   void set_peer_class_type_filter (peer_class_type_filter const& f);
    +   int create_peer_class (char const* name);
    +   void delete_peer_class (int cid);
    +   peer_class_info get_peer_class (int cid);
    +   void set_peer_class (int cid, peer_class_info const& pci);
    +   void remove_torrent (const torrent_handle& h, int options = 0);
    +   settings_pack get_settings () const;
    +   void apply_settings (settings_pack const& s);
    +   void pop_alerts (std::vector<alert*>* alerts);
    +   void set_alert_notify (boost::function<void()> const& fun);
    +   alert* wait_for_alert (time_duration max_wait);
    +   void delete_port_mapping (int handle);
    +   int add_port_mapping (protocol_type t, int external_port, int local_port);
    +   aux::session_impl* native_handle () const;
    +
    +   enum save_state_flags_t
    +   {
    +      save_settings,
    +      save_dht_settings,
    +      save_dht_state,
    +      save_encryption_settings,
    +   };
    +
    +   enum options_t
    +   {
    +      delete_files,
    +      delete_partfile,
    +   };
    +
    +   enum session_flags_t
    +   {
    +      add_default_plugins,
    +      start_default_features,
    +   };
    +
    +   enum protocol_type
    +   {
    +      udp,
    +      tcp,
    +   };
    +};
    +
    + +
    +

    load_state() save_state()

    +
    +void load_state (bdecode_node const& e, boost::uint32_t flags = 0xffffffff);
    +void save_state (entry& e, boost::uint32_t flags = 0xffffffff) const;
    +
    +

    loads and saves all session settings, including dht_settings, +encryption settings and proxy settings. save_state writes all keys +to the entry that's passed in, which needs to either not be +initialized, or initialized as a dictionary.

    +

    load_state expects a bdecode_node which can be built from a bencoded +buffer with bdecode().

    +

    The flags argument is used to filter which parts of the session +state to save or load. By default, all state is saved/restored (except +for the individual torrents). see save_state_flags_t

    + +
    +
    +

    get_torrent_status() refresh_torrent_status()

    +
    +void refresh_torrent_status (std::vector<torrent_status>* ret
    +      , boost::uint32_t flags = 0) const;
    +void get_torrent_status (std::vector<torrent_status>* ret
    +      , boost::function<bool(torrent_status const&)> const& pred
    +      , boost::uint32_t flags = 0) 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.

    +
    +
    +

    post_torrent_updates()

    +
    +void post_torrent_updates (boost::uint32_t flags = 0xffffffff);
    +
    +

    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 torrent_handle::status_flags_t.

    +
    +
    +

    post_session_stats()

    +
    +void post_session_stats ();
    +
    +

    This function will post a session_stats_alert object, containing a +snapshot of the performance counters from the internals of libtorrent. +To interpret these counters, query the session via +session_stats_metrics().

    +

    For more information, see the session statistics section.

    +
    +
    +

    post_dht_stats()

    +
    +void post_dht_stats ();
    +
    +

    This will cause a dht_stats_alert to be posted.

    + +
    +
    +

    find_torrent() get_torrents()

    +
    +torrent_handle find_torrent (sha1_hash const& info_hash) const;
    +std::vector<torrent_handle> get_torrents () 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.

    + +
    +
    +

    add_torrent() async_add_torrent()

    +
    +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);
    +
    +

    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 libtorrent_exception 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.

    +

    all torrent_handles must be destructed before the session is destructed!

    + + +
    +
    +

    pause() resume() is_paused()

    +
    +void resume ();
    +void pause ();
    +bool is_paused () const;
    +
    +

    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.

    +
    +
    +

    set_load_function()

    +
    +void set_load_function (user_load_function_t fun);
    +
    +

    This function enables dynamic loading of torrent files. When a +torrent is unloaded but needs to be availabe 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 the info-hash of the torrent is passed in to the function +and it is expected to fill in the passed in vector<char> 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<char>& buf, error_code& ec);
    +
    +
    +
    +

    get_cache_info()

    +
    +void get_cache_info (cache_status* ret, torrent_handle h = torrent_handle(), int flags = 0) const;
    +
    +

    Fills in the cache_status struct with information about the given torrent. +If flags is session::disk_cache_no_pieces the cache_status::pieces field +will not be set. This may significantly reduce the cost of this call.

    + + +
    +
    +

    get_dht_settings() is_dht_running() set_dht_settings()

    +
    +bool is_dht_running () const;
    +dht_settings get_dht_settings () const;
    +void set_dht_settings (dht_settings const& settings);
    +
    +

    set_dht_settings sets some parameters available to the dht node. +See dht_settings for more information.

    +

    is_dht_running() returns true if the DHT support has been started +and false +otherwise.

    +

    get_dht_settings() returns the current settings

    +
    +
    +

    set_dht_storage()

    +
    +void set_dht_storage (dht::dht_storage_constructor_type sc);
    +
    +

    set_dht_storage set a dht custom storage constructor function +to be used internally when the dht is created.

    +

    Since the dht storage is a critical component for the dht behavior, +this function will only be effective the next time the dht is started. +If you never touch this feature, a default map-memory based storage +is used.

    +

    If you want to make sure the dht is initially created with your +custom storage, create a session with the setting +settings_pack::enable_dht to false, set your constructor function +and call apply_settings with settings_pack::enable_dht to true.

    + +
    +
    +

    add_dht_router() add_dht_node()

    +
    +void add_dht_router (std::pair<std::string, int> const& node);
    +void add_dht_node (std::pair<std::string, int> const& node);
    +
    +

    add_dht_node takes a host name and port pair. That endpoint will be +pinged, and if a valid DHT reply is received, the node will be added to +the routing table.

    +

    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.

    +
    +
    +

    dht_get_item()

    +
    +void dht_get_item (sha1_hash const& target);
    +
    +

    query the DHT for an immutable item at the target hash. +the result is posted as a dht_immutable_item_alert.

    +
    +
    +

    dht_get_item()

    +
    +void dht_get_item (boost::array<char, 32> key
    +      , std::string salt = std::string());
    +
    +

    query the DHT for a mutable item under the public key key. +this is an ed25519 key. salt is optional and may be left +as an empty string if no salt is to be used. +if the item is found in the DHT, a dht_mutable_item_alert is +posted.

    +
    +
    +

    dht_put_item()

    +
    +sha1_hash dht_put_item (entry data);
    +
    +

    store the given bencoded data as an immutable item in the DHT. +the returned hash is the key that is to be used to look the item +up again. It's just the sha-1 hash of the bencoded form of the +structure.

    +
    +
    +

    dht_put_item()

    +
    +void dht_put_item (boost::array<char, 32> key
    +      , boost::function<void(entry&, boost::array<char,64>&
    +      , boost::uint64_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.
    +
    boost::array<char,64>& signature
    +
    the signature authenticating the current value. This may be zeroes +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.
    +
    boost::uint64_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 blobs' values are 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_direct_request()

    +
    +void dht_direct_request (udp::endpoint ep, entry const& e, void* userdata = 0);
    +
    +

    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.

    +
    +
    +

    add_extension()

    +
    +void add_extension (boost::function<boost::shared_ptr<torrent_plugin>(
    +      torrent_handle const&, void*)> ext);
    +void add_extension (boost::shared_ptr<plugin> ext);
    +
    +

    This function adds an extension to this session. The argument is a +function object that is called with a torrent_handle and which should +return a boost::shared_ptr<torrent_plugin>. To write custom +plugins, see libtorrent plugins. For the typical bittorrent client +all of these extensions should be added. The main plugins implemented +in libtorrent are:

    +
    +
    metadata extension
    +
    Allows peers to download the metadata (.torren files) from the swarm +directly. Makes it possible to join a swarm with just a tracker and +info-hash.
    +
    +
    +#include <libtorrent/extensions/metadata_transfer.hpp>
    +ses.add_extension(&libtorrent::create_metadata_plugin);
    +
    +
    +
    uTorrent metadata
    +
    Same as metadata extension but compatible with uTorrent.
    +
    +
    +#include <libtorrent/extensions/ut_metadata.hpp>
    +ses.add_extension(&libtorrent::create_ut_metadata_plugin);
    +
    +
    +
    uTorrent peer exchange
    +
    Exchanges peers between clients.
    +
    +
    +#include <libtorrent/extensions/ut_pex.hpp>
    +ses.add_extension(&libtorrent::create_ut_pex_plugin);
    +
    +
    +
    smart ban plugin
    +
    A plugin that, with a small overhead, can ban peers +that sends bad data with very high accuracy. Should +eliminate most problems on poisoned torrents.
    +
    +
    +#include <libtorrent/extensions/smart_ban.hpp>
    +ses.add_extension(&libtorrent::create_smart_ban_plugin);
    +
    + +
    +
    +

    get_ip_filter() set_ip_filter()

    +
    +ip_filter get_ip_filter () const;
    +void set_ip_filter (ip_filter const& 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.

    +
    +
    +

    set_port_filter()

    +
    +void set_port_filter (port_filter const& f);
    +
    +

    apply port_filter f to incoming and outgoing peers. a port filter +will reject making outgoing peer connections to certain remote ports. +The main intention is to be able to avoid triggering certain +anti-virus software by connecting to SMTP, FTP ports.

    +
    +
    +

    id()

    +
    +peer_id id () const;
    +
    +

    returns the raw peer ID used by libtorrent. When anonymous mode is set +the peer ID is randomized per peer.

    +
    +
    +

    set_key()

    +
    +void set_key (int key);
    +
    +

    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.

    + + +
    +
    +

    listen_port() ssl_listen_port() is_listening()

    +
    +bool is_listening () const;
    +unsigned short listen_port () 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. If the +port specified in settings_pack::listen_interfaces failed, libtorrent +will try to bind to the next port, and so on. If it fails +settings_pack::max_retry_port_bind times, it will bind to port 0 +(meaning the OS picks the port). The only way to know which port it +ended up binding to is to ask for it by calling listen_port().

    +

    If all ports in the specified range fails to be opened for listening, +libtorrent will try to use port 0 (which tells the operating system to +pick a port that's free). If that still fails you may see a +listen_failed_alert with port 0 even if you didn't ask to listen on +it.

    +

    It is possible to prevent libtorrent from binding to port 0 by passing +in the flag session::no_system_port in the flags argument.

    +

    The interface parameter can also be a hostname that will resolve to +the device you want to listen on. If you don't specify an interface, +libtorrent may attempt to listen on multiple interfaces (typically +0.0.0.0 and ::). This means that if your IPv6 interface doesn't work, +you may still see a listen_failed_alert, even though the IPv4 port +succeeded.

    +

    The flags parameter can either be 0 or +session::listen_reuse_address, which will set the reuse address +socket option on the listen socket(s). By default, the listen socket +does not use reuse address. If you're running a service that needs to +run on a specific port no matter if it's in use, set this flag.

    +
    +
    +

    set_peer_class_filter()

    +
    +void set_peer_class_filter (ip_filter const& f);
    +
    +

    Sets the peer class filter for this session. All new peer connections +will take this into account and be added to the peer classes specified +by this filter, based on the peer's IP address.

    +

    The ip-filter essentially maps an IP -> uint32. Each bit in that 32 +bit integer represents a peer class. The least significant bit +represents class 0, the next bit class 1 and so on.

    +

    For more info, see ip_filter.

    +

    For example, to make all peers in the range 200.1.1.0 - 200.1.255.255 +belong to their own peer class, apply the following filter:

    +
    +ip_filter f;
    +int my_class = ses.create_peer_class("200.1.x.x IP range");
    +f.add_rule(address_v4::from_string("200.1.1.0")
    +        , address_v4::from_string("200.1.255.255")
    +        , 1 << 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.

    +

    For more information, see peer classes.

    +
    +
    +

    set_peer_class_type_filter()

    +
    +void set_peer_class_type_filter (peer_class_type_filter const& f);
    +
    +

    Sets and gets the peer class type filter. This is controls automatic +peer class assignments to peers based on what kind of socket it is.

    +

    It does not only support assigning peer classes, it also supports +removing peer classes based on socket type.

    +

    The order of these rules being applied are:

    +
      +
    1. peer-class IP filter
    2. +
    3. peer-class type filter, removing classes
    4. +
    5. peer-class type filter, adding classes
    6. +
    +

    For more information, see peer classes. +TODO: add get_peer_class_type_filter() as well

    +
    +
    +

    create_peer_class()

    +
    +int 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.

    +
    +
    +

    delete_peer_class()

    +
    +void delete_peer_class (int 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.

    + +
    +
    +

    get_peer_class() set_peer_class()

    +
    +peer_class_info get_peer_class (int cid);
    +void set_peer_class (int 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.

    +
    +
    +

    remove_torrent()

    +
    +void remove_torrent (const torrent_handle& h, int options = 0);
    +
    +

    remove_torrent() will close all peer connections associated with +the torrent and tell the tracker that we've stopped participating in +the swarm. This operation cannot fail. When it completes, you will +receive a torrent_removed_alert.

    +

    The optional second argument options can be used to delete all the +files downloaded by this torrent. To do so, pass in the value +session::delete_files. The removal of the torrent is asynchronous, +there is no guarantee that adding the same torrent immediately after +it was removed will not throw a libtorrent_exception exception. Once +the torrent is deleted, a torrent_deleted_alert is posted.

    + +
    +
    +

    get_settings() apply_settings()

    +
    +settings_pack get_settings () const;
    +void apply_settings (settings_pack const& s);
    +
    +

    Applies the settings specified by the settings_pack s. This is an +asynchronous operation that will return immediately and actually apply +the settings to the main thread of libtorrent some time later.

    + + +
    +
    +

    pop_alerts() wait_for_alert() set_alert_notify()

    +
    +void pop_alerts (std::vector<alert*>* alerts);
    +void set_alert_notify (boost::function<void()> const& fun);
    +alert* wait_for_alert (time_duration max_wait);
    +
    +

    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 NULL.

    +

    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).

    +

    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.

    + +
    +
    +

    add_port_mapping() delete_port_mapping()

    +
    +void delete_port_mapping (int handle);
    +int add_port_mapping (protocol_type t, int external_port, int local_port);
    +
    +

    add_port_mapping adds a port forwarding on UPnP and/or NAT-PMP, +whichever is enabled. The return value is a handle referring to the +port mapping that was just created. Pass it to delete_port_mapping() +to remove it.

    +
    +
    +

    native_handle()

    +
    +aux::session_impl* native_handle () const;
    +
    +

    This function is intended only for use by plugins. This type does +not have a stable API and should be relied on as little as possible.

    +
    +
    +

    enum save_state_flags_t

    +

    Declared in "libtorrent/session_handle.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    save_settings1saves settings (i.e. the settings_pack)
    save_dht_settings2saves dht_settings
    save_dht_state4saves dht state such as nodes and node-id, possibly accelerating +joining the DHT if provided at next session startup.
    save_encryption_settings32save pe_settings
    +
    +
    +

    enum options_t

    +

    Declared in "libtorrent/session_handle.hpp"

    + +++++ + + + + + + + + + + + + + + + + +
    namevaluedescription
    delete_files1delete the files belonging to the torrent from disk. +including the part-file, if there is one
    delete_partfile2delete just the part-file associated with this torrent
    +
    +
    +

    enum session_flags_t

    +

    Declared in "libtorrent/session_handle.hpp"

    + +++++ + + + + + + + + + + + + + + + + +
    namevaluedescription
    add_default_plugins1this will add common extensions like ut_pex, ut_metadata, lt_tex +smart_ban and possibly others.
    start_default_features2this will start features like DHT, local service discovery, UPnP +and NAT-PMP.
    +
    +
    +

    enum protocol_type

    +

    Declared in "libtorrent/session_handle.hpp"

    + +++++ + + + + + + + + + + + + + + + + +
    namevaluedescription
    udp1 
    tcp2 
    +
    +
    +
    +

    stats_metric

    +

    Declared in "libtorrent/session_stats.hpp"

    +

    describes one statistics metric from the session. For more information, +see the session statistics section.

    +
    +struct stats_metric
    +{
    +   char const* name;
    +   int value_index;
    +   int type;
    +};
    +
    +
    +
    +

    block_info

    +

    Declared in "libtorrent/torrent_handle.hpp"

    +

    holds the state of a block in a piece. Who we requested +it from and how far along we are at downloading it.

    +
    +struct block_info
    +{
    +   void set_peer (tcp::endpoint const& ep);
    +   tcp::endpoint peer () const;
    +
    +   enum block_state_t
    +   {
    +      none,
    +      requested,
    +      writing,
    +      finished,
    +   };
    +
    +   unsigned bytes_progress:15;
    +   unsigned block_size:15;
    +   unsigned state:2;
    +   unsigned num_peers:14;
    +};
    +
    + +
    +

    set_peer() peer()

    +
    +void set_peer (tcp::endpoint const& ep);
    +tcp::endpoint peer () const;
    +
    +

    The peer is the ip address of the peer this block was downloaded from.

    +
    +
    +

    enum block_state_t

    +

    Declared in "libtorrent/torrent_handle.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    none0This block has not been downloaded or requested form any peer.
    requested1The block has been requested, but not completely downloaded yet.
    writing2The block has been downloaded and is currently queued for being +written to disk.
    finished3The block has been written to disk.
    +
    +
    bytes_progress
    +
    the number of bytes that have been received for this block
    +
    +
    +
    block_size
    +
    the total number of bytes in this block.
    +
    +
    +
    state
    +
    the state this block is in (see block_state_t)
    +
    +
    +
    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.
    +
    +
    +
    +
    +

    partial_piece_info

    +

    Declared in "libtorrent/torrent_handle.hpp"

    +

    This class holds information about pieces that have outstanding requests +or outstanding writes

    +
    +struct partial_piece_info
    +{
    +   enum state_t
    +   {
    +      none,
    +      slow,
    +      medium,
    +      fast,
    +   };
    +
    +   int piece_index;
    +   int blocks_in_piece;
    +   int finished;
    +   int writing;
    +   int requested;
    +   block_info* blocks;
    +   state_t piece_state;
    +};
    +
    +
    +

    enum state_t

    +

    Declared in "libtorrent/torrent_handle.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    none0 
    slow1 
    medium2 
    fast3 
    +
    +
    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.
    +
    +
    +
    blocks_in_piece
    +
    the number of blocks in this piece
    +
    +
    +
    finished
    +
    the number of blocks that are in the finished state
    +
    +
    +
    writing
    +
    the number of blocks that are in the writing state
    +
    +
    +
    requested
    +
    the number of blocks that are in the requested state
    +
    +
    +
    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.

    +
    +
    +
    +
    +
    piece_state
    +

    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_handle

    +

    Declared in "libtorrent/torrent_handle.hpp"

    +

    You will usually have to store your torrent handles somewhere, since it's +the object through which you retrieve information about the torrent and +aborts the torrent.

    +
    +

    Warning

    +

    Any member function that returns a value or fills in a value has to be +made synchronously. This means it has to wait for the main thread to +complete the query before it can return. This might potentially be +expensive if done from within a GUI thread that needs to stay +responsive. Try to avoid querying for information you don't need, and +try to do it in as few calls as possible. You can get most of the +interesting information about a torrent from the +torrent_handle::status() call.

    +
    +

    The default constructor will initialize the handle to an invalid state. +Which means you cannot perform any operation on it, unless you first +assign it a valid handle. If you try to perform any operation on an +uninitialized handle, it will throw invalid_handle.

    +
    +

    Warning

    +

    All operations on a torrent_handle may throw libtorrent_exception +exception, in case the handle is no longer referring to a torrent. +There is one exception is_valid() will never throw. Since the torrents +are processed by a background thread, there is no guarantee that a +handle will remain valid between two calls.

    +
    +
    +struct torrent_handle
    +{
    +   torrent_handle ();
    +   torrent_handle (torrent_handle const& t);
    +   torrent_handle& operator= (torrent_handle const&) = default;
    +   void add_piece (int piece, char const* data, int flags = 0) const;
    +   void read_piece (int piece) const;
    +   bool have_piece (int piece) const;
    +   void get_peer_info (std::vector<peer_info>& v) const;
    +   torrent_status status (boost::uint32_t flags = 0xffffffff) const;
    +   void get_download_queue (std::vector<partial_piece_info>& queue) const;
    +   void reset_piece_deadline (int index) const;
    +   void clear_piece_deadlines () const;
    +   void set_piece_deadline (int index, int deadline, int flags = 0) const;
    +   void set_priority (int prio) const;
    +   void file_progress (std::vector<boost::int64_t>& progress, int flags = 0) const;
    +   void file_status (std::vector<pool_file_status>& status) const;
    +   void clear_error () const;
    +   std::vector<announce_entry> trackers () const;
    +   void replace_trackers (std::vector<announce_entry> const&) const;
    +   void add_tracker (announce_entry const&) const;
    +   void add_url_seed (std::string const& url) const;
    +   void remove_url_seed (std::string const& url) const;
    +   std::set<std::string> url_seeds () const;
    +   void add_http_seed (std::string const& url) const;
    +   void remove_http_seed (std::string const& url) const;
    +   std::set<std::string> http_seeds () const;
    +   void add_extension (
    +      boost::function<boost::shared_ptr<torrent_plugin>(torrent_handle const&, void*)> const& ext
    +      , void* userdata = 0);
    +   bool set_metadata (char const* metadata, int size) const;
    +   bool is_valid () const;
    +   void pause (int flags = 0) const;
    +   void resume () const;
    +   void stop_when_ready (bool b) const;
    +   void set_upload_mode (bool b) const;
    +   void set_share_mode (bool b) const;
    +   void flush_cache () const;
    +   void apply_ip_filter (bool b) const;
    +   void force_recheck () const;
    +   void save_resume_data (int flags = 0) const;
    +   bool need_save_resume_data () const;
    +   void auto_managed (bool m) const;
    +   void queue_position_down () const;
    +   void queue_position_top () const;
    +   int queue_position () const;
    +   void queue_position_bottom () const;
    +   void queue_position_up () const;
    +   void set_ssl_certificate (std::string const& certificate
    +      , std::string const& private_key
    +      , std::string const& dh_params
    +      , std::string const& passphrase = "");
    +   void set_ssl_certificate_buffer (std::string const& certificate
    +      , std::string const& private_key
    +      , std::string const& dh_params);
    +   storage_interface* get_storage_impl () const;
    +   boost::shared_ptr<const torrent_info> torrent_file () const;
    +   void use_interface (const char* net_interface) const;
    +   void piece_availability (std::vector<int>& avail) const;
    +   int piece_priority (int index) const;
    +   std::vector<int> piece_priorities () const;
    +   void piece_priority (int index, int priority) const;
    +   void prioritize_pieces (std::vector<int> const& pieces) const;
    +   void prioritize_pieces (std::vector<std::pair<int, int> > const& pieces) const;
    +   int file_priority (int index) const;
    +   void prioritize_files (std::vector<int> const& files) const;
    +   void file_priority (int index, int priority) const;
    +   std::vector<int> file_priorities () const;
    +   void force_reannounce (int seconds = 0, int tracker_index = -1) const;
    +   void force_dht_announce () const;
    +   void scrape_tracker (int idx = -1) const;
    +   int upload_limit () const;
    +   int download_limit () const;
    +   void set_upload_limit (int limit) const;
    +   void set_download_limit (int limit) const;
    +   void set_pinned (bool p) const;
    +   void set_sequential_download (bool sd) const;
    +   void connect_peer (tcp::endpoint const& adr, int source = 0
    +      , int flags = 0x1 + 0x4 + 0x8) const;
    +   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, int flags = 0) const;
    +   void rename_file (int index, std::string const& new_name) const;
    +   void super_seeding (bool on) const;
    +   sha1_hash info_hash () const;
    +   bool operator!= (const torrent_handle& h) const;
    +   bool operator< (const torrent_handle& h) const;
    +   bool operator== (const torrent_handle& h) const;
    +   boost::uint32_t id () const;
    +   boost::shared_ptr<torrent> native_handle () const;
    +
    +   enum flags_t
    +   {
    +      overwrite_existing,
    +   };
    +
    +   enum status_flags_t
    +   {
    +      query_distributed_copies,
    +      query_accurate_download_counters,
    +      query_last_seen_complete,
    +      query_pieces,
    +      query_verified_pieces,
    +      query_torrent_file,
    +      query_name,
    +      query_save_path,
    +   };
    +
    +   enum deadline_flags
    +   {
    +      alert_when_available,
    +   };
    +
    +   enum file_progress_flags_t
    +   {
    +      piece_granularity,
    +   };
    +
    +   enum pause_flags_t
    +   {
    +      graceful_pause,
    +   };
    +
    +   enum save_resume_flags_t
    +   {
    +      flush_disk_cache,
    +      save_info_dict,
    +      only_if_modified,
    +   };
    +};
    +
    +
    +

    torrent_handle()

    +
    +torrent_handle ();
    +
    +

    constructs a torrent handle that does not refer to a torrent. +i.e. is_valid() will return false.

    +
    +
    +

    add_piece()

    +
    +void add_piece (int piece, char const* data, int flags = 0) 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.

    +
    +
    +

    read_piece()

    +
    +void read_piece (int 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.

    +
    +
    +

    have_piece()

    +
    +bool have_piece (int piece) const;
    +
    +

    Returns true if this piece has been completely downloaded, and false +otherwise.

    +
    +
    +

    get_peer_info()

    +
    +void get_peer_info (std::vector<peer_info>& v) const;
    +
    +

    takes a reference to a vector that will be cleared and filled with one +entry for each peer connected to this torrent, given the handle is +valid. If the torrent_handle is invalid, it will throw +libtorrent_exception exception. Each entry in the vector contains +information about that particular peer. See peer_info.

    +
    +
    +

    status()

    +
    +torrent_status status (boost::uint32_t flags = 0xffffffff) const;
    +
    +

    status() will return a structure with information about the status +of this torrent. If the torrent_handle is invalid, it will throw +libtorrent_exception 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 the status_flags_t enum.

    +
    +
    +

    get_download_queue()

    +
    +void get_download_queue (std::vector<partial_piece_info>& queue) const;
    +
    +

    get_download_queue() takes a non-const reference to a vector which +it will fill with information about pieces that are partially +downloaded or not downloaded at all but partially requested. See +partial_piece_info for the fields in the returned vector.

    + + +
    +
    +

    clear_piece_deadlines() reset_piece_deadline() set_piece_deadline()

    +
    +void reset_piece_deadline (int index) const;
    +void clear_piece_deadlines () const;
    +void set_piece_deadline (int index, int deadline, int flags = 0) 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.

    +

    The flags parameter can be 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.

    +

    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 do the same thing 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.

    +
    +
    +

    set_priority()

    +
    +void set_priority (int prio) const;
    +
    +

    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 prioritization where +every interested peer on a high priority torrent will be unchoked +before any other, lower priority, torrents have any peers unchoked.

    +
    +
    +

    file_progress()

    +
    +void file_progress (std::vector<boost::int64_t>& progress, int flags = 0) const;
    +
    +

    This function fills in the supplied vector with the the number of +bytes downloaded of each file in this torrent. The progress values are +ordered the same as the files in the torrent_info. This operation is +not very cheap. Its complexity is O(n + mj). Where n is the number +of files, m is the number of downloading pieces and j is the +number of blocks in a piece.

    +

    The flags parameter can be used to specify the granularity of the +file progress. If left at the default value of 0, the progress will be +as accurate as possible, but also more expensive to calculate. If +torrent_handle::piece_granularity is specified, the progress will +be specified in piece granularity. i.e. only pieces that have been +fully downloaded and passed the hash check count. When specifying +piece granularity, the operation is a lot cheaper, since libtorrent +already keeps track of this internally and no calculation is required.

    +
    +
    +

    file_status()

    +
    +void file_status (std::vector<pool_file_status>& status) const;
    +
    +

    This function fills in the passed in vector with status about files +that are open for this torrent. Any file that is not open in this +torrent, 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 pool_file_status.

    +
    +
    +

    clear_error()

    +
    +void clear_error () const;
    +
    +

    If the torrent is in an error state (i.e. torrent_status::error is +non-empty), this will clear the error and start the torrent again.

    + + +
    +
    +

    add_tracker() replace_trackers() trackers()

    +
    +std::vector<announce_entry> trackers () const;
    +void replace_trackers (std::vector<announce_entry> const&) const;
    +void add_tracker (announce_entry const&) const;
    +
    +

    trackers() will return the list of trackers for this torrent. The +announce entry contains both a string url which specify the +announce url for the tracker as well as an int tier, which is +specifies the order in which this tracker is tried. If you want +libtorrent to use another list of trackers for this torrent, you can +use replace_trackers() which takes a list of the same form as the +one returned from trackers() and will replace it. If you want an +immediate effect, you have to call force_reannounce(). See +announce_entry.

    +

    add_tracker() will look if the specified tracker is already in the +set. If it is, it doesn't do anything. If it's not in the current set +of trackers, it will insert it in the tier specified in the +announce_entry.

    +

    The updated set of trackers will be saved in the resume data, and when +a torrent is started with resume data, the trackers from the resume +data will replace the original ones.

    + + +
    +
    +

    url_seeds() add_url_seed() remove_url_seed()

    +
    +void add_url_seed (std::string const& url) const;
    +void remove_url_seed (std::string const& url) const;
    +std::set<std::string> 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.

    + + +
    +
    +

    http_seeds() remove_http_seed() add_http_seed()

    +
    +void add_http_seed (std::string const& url) const;
    +void remove_http_seed (std::string const& url) const;
    +std::set<std::string> http_seeds () const;
    +
    +

    These functions are identical as the *_url_seed() variants, but +they operate on BEP 17 web seeds instead of BEP 19.

    +

    See http seeding for more information.

    +
    +
    +

    add_extension()

    +
    +void add_extension (
    +      boost::function<boost::shared_ptr<torrent_plugin>(torrent_handle const&, void*)> const& ext
    +      , void* userdata = 0);
    +
    +

    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()

    +
    +bool set_metadata (char const* metadata, int size) 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.

    +
    +
    +

    is_valid()

    +
    +bool is_valid () const;
    +
    +

    Returns true if this handle refers to a valid torrent and false if it +hasn't been initialized or if the torrent it refers to has been +aborted. Note that a handle may become invalid after it has been added +to the session. Usually this is because the storage for the torrent is +somehow invalid or if the filenames are not allowed (and hence cannot +be opened/created) on your filesystem. If such an error occurs, a +file_error_alert is generated and all handles that refers to that +torrent will become invalid.

    + +
    +
    +

    pause() resume()

    +
    +void pause (int flags = 0) const;
    +void resume () const;
    +
    +

    pause(), and resume() will disconnect all peers and reconnect +all peers respectively. When a torrent is paused, it will however +remember all share ratios to all peers and remember all potential (not +connected) peers. Torrents may be paused automatically if there is a +file error (e.g. disk full) or something similar. See +file_error_alert.

    +

    To know if a torrent is paused or not, call +torrent_handle::status() and inspect torrent_status::paused.

    +

    The flags argument to pause can be set to +torrent_handle::graceful_pause which 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.

    +
    +

    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 automanaged first. Torrents are auto-managed by default when added +to the session. For more information, see queuing.

    +
    +
    +
    +

    stop_when_ready()

    +
    +void stop_when_ready (bool b) const;
    +
    +

    set or clear the stop-when-ready flag. 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 auto_manage(false) and pause() were called on the torrent.

    +

    Note that the torrent may transition into a downloading state while +calling this function, 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 function is used to set it 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.

    +
    +
    +

    set_upload_mode()

    +
    +void set_upload_mode (bool b) const;
    +
    +

    Explicitly sets the upload mode of the torrent. In upload mode, the +torrent will not request any pieces. If the torrent is auto managed, +it will automatically be taken out of upload mode periodically (see +settings_pack::optimistic_disk_retry). Torrents are +automatically put in upload mode whenever they encounter a disk write +error.

    +

    m should be true to enter upload mode, and false to leave it.

    +

    To test if a torrent is in upload mode, call +torrent_handle::status() and inspect +torrent_status::upload_mode.

    +
    +
    +

    set_share_mode()

    +
    +void set_share_mode (bool b) const;
    +
    +

    Enable or disable share mode for this torrent. When in share mode, the +torrent will not necessarily be downloaded, especially not the whole +of it. Only parts that are likely to be distributed to more than 2 +other peers are downloaded, and only if the previous prediction was +correct.

    +
    +
    +

    flush_cache()

    +
    +void flush_cache () const;
    +
    +

    Instructs libtorrent to flush all the disk caches for this torrent and +close all file handles. This is done asynchronously and you will be +notified that it's complete through cache_flushed_alert.

    +

    Note that by the time you get the alert, libtorrent may have cached +more data for the torrent, but you are guaranteed that whatever cached +data libtorrent had by the time you called +torrent_handle::flush_cache() has been written to disk.

    +
    +
    +

    apply_ip_filter()

    +
    +void apply_ip_filter (bool b) const;
    +
    +

    Set to true to apply the session global IP filter to this torrent +(which is the default). Set to false to make this torrent ignore the +IP filter.

    +
    +
    +

    force_recheck()

    +
    +void force_recheck () const;
    +
    +

    force_recheck puts the torrent back in a state where it assumes to +have no resume data. All peers will be disconnected and the torrent +will stop announcing to the tracker. The torrent will be added to the +checking queue, and will be checked (all the files will be read and +compared to the piece hashes). Once the check is complete, the torrent +will start connecting to peers again, as normal.

    +
    +
    +

    save_resume_data()

    +
    +void save_resume_data (int flags = 0) const;
    +
    +

    save_resume_data() asks libtorrent to generate fast-resume data for +this torrent.

    +

    The flags argument is a bitmask of flags ORed together. see +save_resume_flags_t

    +

    This operation is asynchronous, save_resume_data will return +immediately. The resume data is delivered when it's done through an +save_resume_data_alert.

    +

    The fast resume data will be empty in the following cases:

    +
    +
      +
    1. The torrent handle is invalid.
    2. +
    3. The torrent hasn't received valid metadata and was started without +metadata (see libtorrent's metadata from peers extension)
    4. +
    +
    +

    Note that by the time you receive the fast resume data, it may already +be invalid if the torrent is still downloading! The recommended +practice is to first pause the session, then generate the fast resume +data, and then close it down. Make sure to not remove_torrent() before +you receive the save_resume_data_alert though. There's no need to +pause when saving intermittent resume data.

    +
    +

    Warning

    +

    If you pause every torrent individually instead of pausing the +session, every torrent will have its paused state saved in the +resume data!

    +
    +
    +

    Warning

    +

    The resume data contains the modification timestamps for all files. +If one file has been modified when the torrent is added again, the +will be rechecked. When shutting down, make sure to flush the disk +cache before saving the resume data. This will make sure that the +file timestamps are up to date and won't be modified after saving +the resume data. The recommended way to do this is to pause the +torrent, which will flush the cache and disconnect all peers.

    +
    +
    +

    Note

    +

    It is typically a good idea to save resume data whenever a torrent +is completed or paused. In those cases you don't need to pause the +torrent or the session, since the torrent will do no more writing to +its files. If you save resume data for torrents when they are +paused, you can accelerate the shutdown process by not saving resume +data again for paused torrents. Completed torrents should have their +resume data saved when they complete and on exit, since their +statistics might be updated.

    +
    +In full allocation mode the resume data is never invalidated by +subsequent writes to the files, since pieces won't move around. This +means that you don't need to pause before writing resume data in full +or sparse mode. If you don't, however, any data written to disk after +you saved resume data and before the session closed is lost.
    +
    +

    It also means that if the resume data is out dated, libtorrent will +not re-check the files, but assume that it is fairly recent. The +assumption is that it's better to loose a little bit than to re-check +the entire file.

    +

    It is still a good idea to save resume data periodically during +download as well as when closing down.

    +

    Example code to pause and save resume data for all torrents and wait +for the alerts:

    +
    +extern int outstanding_resume_data; // global counter of outstanding resume data
    +std::vector<torrent_handle> handles = ses.get_torrents();
    +ses.pause();
    +for (torrent_handle i : handles)
    +{
    +        torrent_handle& h = *i;
    +        if (!h.is_valid()) continue;
    +        torrent_status s = h.status();
    +        if (!s.has_metadata) continue;
    +        if (!s.need_save_resume_data()) continue;
    +
    +        h.save_resume_data();
    +        ++outstanding_resume_data;
    +}
    +
    +while (outstanding_resume_data > 0)
    +{
    +        alert const* a = ses.wait_for_alert(seconds(10));
    +
    +        // if we don't get an alert within 10 seconds, abort
    +        if (a == 0) break;
    +
    +        std::vector<alert*> alerts;
    +        ses.pop_alerts(&alerts);
    +
    +        for (alert* i : alerts)
    +        {
    +                if (alert_cast<save_resume_data_failed_alert>(a))
    +                {
    +                        process_alert(a);
    +                        --outstanding_resume_data;
    +                        continue;
    +                }
    +
    +                save_resume_data_alert const* rd = alert_cast<save_resume_data_alert>(a);
    +                if (rd == 0)
    +                {
    +                        process_alert(a);
    +                        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);
    +                out.unsetf(std::ios_base::skipws);
    +                bencode(std::ostream_iterator<char>(out), *rd->resume_data);
    +                --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.

    +
    +
    +
    +

    need_save_resume_data()

    +
    +bool need_save_resume_data () const;
    +
    +

    This function returns true if any whole chunk has been downloaded +since the torrent was first loaded or since the last time the resume +data was saved. When saving resume data periodically, it makes sense +to skip any torrent which hasn't downloaded anything since the last +time.

    +
    +

    Note

    +

    A torrent's resume data is considered saved as soon as the alert is +posted. It is important to make sure this alert is received and +handled in order for this function to be meaningful.

    +
    +
    +
    +

    auto_managed()

    +
    +void auto_managed (bool m) const;
    +
    +

    changes whether the torrent is auto managed or not. For more info, +see queuing.

    + + + + +
    +
    +

    queue_position() queue_position_up() queue_position_bottom() queue_position_down() queue_position_top()

    +
    +void queue_position_down () const;
    +void queue_position_top () const;
    +int queue_position () const;
    +void queue_position_bottom () const;
    +void queue_position_up () 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.

    + +
    +
    +

    set_ssl_certificate_buffer() set_ssl_certificate()

    +
    +void set_ssl_certificate (std::string const& certificate
    +      , std::string const& private_key
    +      , std::string const& dh_params
    +      , std::string const& passphrase = "");
    +void set_ssl_certificate_buffer (std::string const& certificate
    +      , std::string const& private_key
    +      , std::string const& dh_params);
    +
    +

    For SSL torrents, use this to specify a path to a .pem file to use as +this client's certificate. The certificate must be signed by the +certificate in the .torrent file to be valid.

    +

    The set_ssl_certificate_buffer() overload takes the actual certificate, +private key and DH params as strings, rather than paths to files. This +overload is only available when libtorrent is built against boost +1.54 or later.

    +

    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.

    +
    +
    +

    get_storage_impl()

    +
    +storage_interface* get_storage_impl () const;
    +
    +

    Returns the storage implementation for this torrent. This depends on the +storage constructor function that was passed to add_torrent.

    +
    +
    +

    torrent_file()

    +
    +boost::shared_ptr<const torrent_info> torrent_file () const;
    +
    +

    Returns a pointer to the torrent_info object associated with this +torrent. The torrent_info object may be a copy of the internal object. +If the torrent doesn't have metadata, the pointer will not be +initialized (i.e. a NULL pointer). The torrent may be in a state +without metadata only if it was started without a .torrent file, e.g. +by using the libtorrent extension of just supplying a tracker and +info-hash.

    +
    +
    +

    use_interface()

    +
    +void use_interface (const char* net_interface) 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.

    +
    +
    +

    piece_availability()

    +
    +void piece_availability (std::vector<int>& avail) const;
    +
    +

    Fills the specified std::vector<int> with the availability for +each piece in this torrent. libtorrent does not keep track of +availability for seeds, so if the torrent is seeding the availability +for all pieces is reported as 0.

    +

    The piece availability is the number of peers that we are connected +that has advertised having a particular piece. This is the information +that libtorrent uses in order to prefer picking rare pieces.

    + + +
    +
    +

    piece_priority() prioritize_pieces() piece_priorities()

    +
    +int piece_priority (int index) const;
    +std::vector<int> piece_priorities () const;
    +void piece_priority (int index, int priority) const;
    +void prioritize_pieces (std::vector<int> const& pieces) const;
    +void prioritize_pieces (std::vector<std::pair<int, int> > const& pieces) const;
    +
    +

    These functions are used to set and get the priority of individual +pieces. By default all pieces have priority 4. That means that the +random rarest first algorithm is effectively active for all pieces. +You may however change the priority of individual pieces. There are 8 +priority levels. 0 means not to download the piece at all. Otherwise, +lower priority values means less likely to be picked. Piece priority +takes precedence over piece availability. Every priority-7 piece will +be attempted to be picked before a priority 6 piece and so on.

    +

    Piece priorities can not be changed for torrents that have not +downloaded the metadata yet. For instance, magnet links and torrents +added by URL 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.

    +

    piece_priorities returns a vector with one element for each piece +in the torrent. Each element is the current priority of that piece.

    + + +
    +
    +

    file_priorities() prioritize_files() file_priority()

    +
    +int file_priority (int index) const;
    +void prioritize_files (std::vector<int> const& files) const;
    +void file_priority (int index, int priority) const;
    +std::vector<int> 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.

    +

    file_priorities() returns a vector with the priorities of all +files.

    +

    The priority values are the same as for piece_priority().

    +

    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.

    + +
    +
    +

    force_reannounce() force_dht_announce()

    +
    +void force_reannounce (int seconds = 0, int tracker_index = -1) const;
    +void force_dht_announce () 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.

    +

    force_dht_announce will announce the torrent to the DHT +immediately.

    +
    +
    +

    scrape_tracker()

    +
    +void scrape_tracker (int idx = -1) const;
    +
    +

    scrape_tracker() will send a scrape request to a tracker. By +default (idx = -1) it will scrape the last working tracker. If +idx is >= 0, the tracker with the specified index will scraped.

    +

    A scrape request queries the tracker for statistics such as total +number of incomplete peers, complete peers, number of downloads etc.

    +

    This request will specifically update the num_complete and +num_incomplete fields in the torrent_status struct once it +completes. When it completes, it will generate a scrape_reply_alert. +If it fails, it will generate a scrape_failed_alert.

    + + + +
    +
    +

    set_upload_limit() upload_limit() download_limit() set_download_limit()

    +
    +int upload_limit () const;
    +int download_limit () const;
    +void set_upload_limit (int limit) const;
    +void set_download_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.

    +
    +
    +

    set_pinned()

    +
    +void set_pinned (bool p) const;
    +
    +

    A pinned torrent may not be unloaded by libtorrent. When the dynamic +loading and unloading of torrents is enabled (by setting a load +function on the session), this can be used to exempt certain torrents +from the unloading logic.

    +

    Magnet links, and other torrents that start out without having +metadata are pinned automatically. This is to give the client a chance +to get the metadata and save it before it's unloaded. In this case, it +may be useful to un-pin the torrent once its metadata has been saved +to disk.

    +

    For more information about dynamically loading and unloading torrents, +see dynamic loading of torrent files.

    +
    +
    +

    set_sequential_download()

    +
    +void set_sequential_download (bool sd) const;
    +
    +

    set_sequential_download() enables or disables sequential +download. When enabled, the piece picker will pick pieces in sequence +instead of rarest first. In this mode, piece priorities are ignored, +with the exception of priority 7, which are still preferred over the +sequential piece order.

    +

    Enabling sequential download will affect the piece distribution +negatively in the swarm. It should be used sparingly.

    +
    +
    +

    connect_peer()

    +
    +void connect_peer (tcp::endpoint const& adr, int source = 0
    +      , int flags = 0x1 + 0x4 + 0x8) 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 libtorrent_exception. The second (optional) +argument will be bitwised 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.

    +

    flags are the same flags that are passed along with the ut_pex extension.

    + ++++ + + + + + + + + + + + + + + +
    0x01peer supports encryption.
    0x02peer is a seed
    0x04supports uTP. If this is not set, the peer will only be contacted +over TCP.
    0x08supports holepunching 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
    + +
    +
    +

    max_uploads() set_max_uploads()

    +
    +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.

    + +
    +
    +

    max_connections() set_max_connections()

    +
    +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.

    +
    +
    +

    move_storage()

    +
    +void move_storage (std::string const& save_path, int flags = 0) 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 = 0
    • +
    • fail_if_exist = 1
    • +
    • dont_replace = 2
    • +
    +
    +

    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 takes the existing file in the target +directory, if there is one. The source files will still be removed in +that case.

    +

    Files that have been renamed to have absolute pahts 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.

    +
    +
    +

    rename_file()

    +
    +void rename_file (int 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.

    +
    +
    +

    super_seeding()

    +
    +void super_seeding (bool on) const;
    +
    +

    Enables or disabled super seeding/initial seeding for this torrent. +The torrent needs to be a seed for this to take effect.

    +
    +
    +

    info_hash()

    +
    +sha1_hash info_hash () const;
    +
    +

    info_hash() returns the info-hash of the torrent. If this handle +is to a torrent that hasn't loaded yet (for instance by being added) +by a URL, the returned value is undefined.

    + + +
    +
    +

    operator!=() operator<() operator==()

    +
    +bool operator!= (const torrent_handle& h) const;
    +bool operator< (const torrent_handle& h) const;
    +bool operator== (const torrent_handle& h) const;
    +
    +

    comparison operators. The order of the torrents is unspecified +but stable.

    +
    +
    +

    native_handle()

    +
    +boost::shared_ptr<torrent> native_handle () const;
    +
    +

    This function is intended only for use by plugins and the alert +dispatch function. This type does not have a stable API and should +be relied on as little as possible.

    +
    +
    +

    enum flags_t

    +

    Declared in "libtorrent/torrent_handle.hpp"

    + +++++ + + + + + + + + + + + + +
    namevaluedescription
    overwrite_existing1 
    +
    +
    +

    enum status_flags_t

    +

    Declared in "libtorrent/torrent_handle.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    query_distributed_copies1calculates distributed_copies, distributed_full_copies and +distributed_fraction.
    query_accurate_download_counters2includes partial downloaded blocks in total_done and +total_wanted_done.
    query_last_seen_complete4includes last_seen_complete.
    query_pieces8includes pieces.
    query_verified_pieces16includes verified_pieces (only applies to torrents in seed +mode).
    query_torrent_file32includes torrent_file, which is all the static information from +the .torrent file.
    query_name64includes 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.
    query_save_path128includes save_path, the path to the directory the files of the +torrent are saved to.
    +
    +
    +

    enum deadline_flags

    +

    Declared in "libtorrent/torrent_handle.hpp"

    + +++++ + + + + + + + + + + + + +
    namevaluedescription
    alert_when_available1 
    +
    +
    +

    enum file_progress_flags_t

    +

    Declared in "libtorrent/torrent_handle.hpp"

    + +++++ + + + + + + + + + + + + +
    namevaluedescription
    piece_granularity1only calculate file progress at piece granularity. This makes +the file_progress() call cheaper and also only takes bytes that +have passed the hash check into account, so progress cannot +regress in this mode.
    +
    +
    +

    enum pause_flags_t

    +

    Declared in "libtorrent/torrent_handle.hpp"

    + +++++ + + + + + + + + + + + + +
    namevaluedescription
    graceful_pause1 
    +
    +
    +

    enum save_resume_flags_t

    +

    Declared in "libtorrent/torrent_handle.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    flush_disk_cache1the 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.
    save_info_dict2the 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).
    only_if_modified4if 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.
    +
    +
    +
    +

    web_seed_entry

    +

    Declared in "libtorrent/torrent_info.hpp"

    +

    the web_seed_entry holds information about a web seed (also known +as URL seed or HTTP seed). It is essentially a URL with some state +associated with it. For more information, see BEP 17 and BEP 19.

    +
    +struct web_seed_entry
    +{
    +   web_seed_entry (std::string const& url_, type_t type_
    +      , std::string const& auth_ = std::string()
    +      , headers_t const& extra_headers_ = headers_t());
    +   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;
    +   boost::uint8_t type;
    +};
    +
    +
    +

    operator==()

    +
    +bool operator== (web_seed_entry const& e) const;
    +
    +

    URL and type comparison

    +
    +
    +

    operator<()

    +
    +bool operator< (web_seed_entry const& e) const;
    +
    +

    URL and type less-than comparison

    +
    +
    +

    enum type_t

    +

    Declared in "libtorrent/torrent_info.hpp"

    + +++++ + + + + + + + + + + + + + + + + +
    namevaluedescription
    url_seed0 
    http_seed1 
    +
    +
    url
    +
    The URL of the web seed
    +
    +
    +
    auth
    +
    Optional authentication. If this is set, it's passed +in as HTTP basic auth to the web seed. The format is: +username:password.
    +
    +
    +
    extra_headers
    +
    Any extra HTTP headers that need to be passed to the web seed
    +
    +
    +
    type
    +
    The type of web seed (see type_t)
    +
    +
    +
    +
    +

    torrent_info

    +

    Declared in "libtorrent/torrent_info.hpp"

    +

    TODO: there may be some opportunities to optimize the size if torrent_info. +specifically to turn some std::string and std::vector into pointers

    +
    +class torrent_info
    +{
    +   torrent_info (std::string const& filename, int flags = 0);
    +   torrent_info (std::string const& filename, error_code& ec, int flags = 0);
    +   torrent_info (char const* buffer, int size, error_code& ec, int flags = 0);
    +   torrent_info (sha1_hash const& info_hash, int flags = 0);
    +   torrent_info (bdecode_node const& torrent_file, error_code& ec, int flags = 0);
    +   torrent_info (char const* buffer, int size, int flags = 0);
    +   torrent_info (bdecode_node const& torrent_file, int flags = 0);
    +   torrent_info (torrent_info const& t);
    +   ~torrent_info ();
    +   file_storage const& files () const;
    +   file_storage const& orig_files () const;
    +   void rename_file (int index, std::string const& new_filename);
    +   void remap_files (file_storage const& f);
    +   std::vector<announce_entry> const& trackers () const;
    +   void add_tracker (std::string const& url, int tier = 0);
    +   std::vector<sha1_hash> similar_torrents () const;
    +   std::vector<std::string> collections () const;
    +   void add_url_seed (std::string const& url
    +      , std::string const& extern_auth = std::string()
    +      , web_seed_entry::headers_t const& extra_headers = web_seed_entry::headers_t());
    +   std::vector<web_seed_entry> const& web_seeds () const;
    +   void set_web_seeds (std::vector<web_seed_entry> 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());
    +   boost::int64_t total_size () const;
    +   int num_pieces () const;
    +   int piece_length () const;
    +   const sha1_hash& info_hash () const;
    +   int num_files () const;
    +   std::vector<file_slice> map_block (int piece, boost::int64_t offset, int size) const;
    +   peer_request map_file (int file, boost::int64_t offset, int size) const;
    +   void unload ();
    +   void load (char const* buffer, int size, error_code& ec);
    +   std::string ssl_cert () const;
    +   bool is_valid () const;
    +   bool priv () const;
    +   bool is_i2p () const;
    +   sha1_hash hash_for_piece (int index) const;
    +   char const* hash_for_piece_ptr (int index) const;
    +   int piece_size (int index) const;
    +   bool is_loaded () const;
    +   std::vector<sha1_hash> const& merkle_tree () const;
    +   void set_merkle_tree (std::vector<sha1_hash>& h);
    +   boost::optional<time_t> creation_date () const;
    +   const std::string& name () const;
    +   const std::string& comment () const;
    +   const std::string& creator () const;
    +   nodes_t const& nodes () const;
    +   void add_node (std::pair<std::string, int> const& node);
    +   bool parse_info_section (bdecode_node const& e, error_code& ec, int flags);
    +   bdecode_node info (char const* key) const;
    +   void swap (torrent_info& ti);
    +   int metadata_size () const;
    +   boost::shared_array<char> metadata () const;
    +   bool is_merkle_torrent () const;
    +   bool parse_torrent_file (bdecode_node const& libtorrent, error_code& ec, int flags);
    +};
    +
    +
    +

    torrent_info()

    +
    +torrent_info (std::string const& filename, int flags = 0);
    +torrent_info (std::string const& filename, error_code& ec, int flags = 0);
    +torrent_info (char const* buffer, int size, error_code& ec, int flags = 0);
    +torrent_info (sha1_hash const& info_hash, int flags = 0);
    +torrent_info (bdecode_node const& torrent_file, error_code& ec, int flags = 0);
    +torrent_info (char const* buffer, int size, int flags = 0);
    +torrent_info (bdecode_node const& torrent_file, int flags = 0);
    +torrent_info (torrent_info const& t);
    +
    +

    The constructor that takes an info-hash will initialize the info-hash +to the given value, but leave all other fields empty. This is used +internally when downloading torrents without the metadata. The +metadata will be created by libtorrent as soon as it has been +downloaded from the swarm.

    +

    The constructor that takes a bdecode_node will create a torrent_info +object from the information found in the given torrent_file. The +bdecode_node represents a tree node in an bencoded file. To load an +ordinary .torrent file into a bdecode_node, use bdecode().

    +

    The version that takes a buffer pointer and a size will decode it as a +.torrent file and initialize the torrent_info object for you.

    +

    The version that takes a filename will simply load the torrent file +and decode it inside the constructor, for convenience. This might not +be the most suitable for applications that want to be able to report +detailed errors on what might go wrong.

    +

    There is an upper limit on the size of the torrent file that will be +loaded by the overload taking a filename. If it's important that even +very large torrent files are loaded, use one of the other overloads.

    +

    The overloads that takes an error_code const& never throws if an +error occur, they will simply set the error code to describe what went +wrong and not fully initialize the torrent_info object. The overloads +that do not take the extra error_code parameter will always throw if +an error occurs. These overloads are not available when building +without exception support.

    +

    The flags argument is currently unused.

    +
    +
    +

    ~torrent_info()

    +
    +~torrent_info ();
    +
    +

    frees all storage associated with this torrent_info object

    + +
    +
    +

    orig_files() files()

    +
    +file_storage const& files () const;
    +file_storage const& orig_files () const;
    +
    +

    The file_storage object contains the information on how to map the +pieces to files. It is separated from the torrent_info object because +when creating torrents a storage object needs to be created without +having a torrent file. When renaming files in a storage, the storage +needs to make its own copy of the file_storage in order to make its +mapping differ from the one in the torrent file.

    +

    orig_files() returns the original (unmodified) file storage for +this torrent. This is used by the web server connection, which needs +to request files with the original names. Filename may be changed using +torrent_info::rename_file().

    +

    For more information on the file_storage object, see the separate +document on how to create torrents.

    +
    +
    +

    rename_file()

    +
    +void rename_file (int index, std::string const& new_filename);
    +
    +

    Renames a 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 multifile +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.

    +
    +
    +

    remap_files()

    +
    +void remap_files (file_storage const& f);
    +
    +

    Remaps the file storage to a new file layout. This can be used to, for +instance, download all data in a torrent to a single file, or to a +number of fixed size sector aligned files, regardless of the number +and sizes of the files in the torrent.

    +

    The new specified file_storage must have the exact same size as +the current one.

    + +
    +
    +

    trackers() add_tracker()

    +
    +std::vector<announce_entry> const& trackers () const;
    +void add_tracker (std::string const& url, int tier = 0);
    +
    +

    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.

    + +
    +
    +

    collections() similar_torrents()

    +
    +std::vector<sha1_hash> similar_torrents () const;
    +std::vector<std::string> collections () const;
    +
    +

    These two functions are related to BEP38 (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.

    + + + +
    +
    +

    add_url_seed() set_web_seeds() add_http_seed() web_seeds()

    +
    +void add_url_seed (std::string const& url
    +      , std::string const& extern_auth = std::string()
    +      , web_seed_entry::headers_t const& extra_headers = web_seed_entry::headers_t());
    +std::vector<web_seed_entry> const& web_seeds () const;
    +void set_web_seeds (std::vector<web_seed_entry> 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. Currently, the only transport protocol supported for +the url is http.

    +

    set_web_seeds() replaces all web seeds with the ones specified in +the seeds vector.

    +

    The extern_auth argument can be used for other authorization +schemes than basic HTTP authorization. If set, it will override any +username and password found in the URL itself. The string will be sent +as the HTTP authorization header's value (without specifying "Basic").

    +

    The extra_headers argument defaults to an empty list, but can be +used to insert custom HTTP headers in the requests to a specific web +seed.

    +

    See http seeding for more information.

    + + +
    +
    +

    piece_length() num_pieces() total_size()

    +
    +boost::int64_t total_size () const;
    +int num_pieces () const;
    +int piece_length () const;
    +
    +

    total_size(), piece_length() and num_pieces() returns the +total number of bytes the torrent-file represents (all the files in +it), the number of byte for each piece and the total number of pieces, +respectively. The difference between piece_size() and +piece_length() is that piece_size() takes the piece index as +argument and gives you the exact size of that piece. It will always be +the same as piece_length() except in the case of the last piece, +which may be smaller.

    +
    +
    +

    info_hash()

    +
    +const sha1_hash& info_hash () const;
    +
    +

    returns the info-hash of the torrent

    +
    +
    +

    num_files()

    +
    +int num_files () const;
    +
    +

    If you need index-access to files you can use the num_files() and +file_path() et.al. to access files using indices.

    +
    +
    +

    map_block()

    +
    +std::vector<file_slice> map_block (int piece, boost::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.

    +
    +
    +

    map_file()

    +
    +peer_request map_file (int file, boost::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().

    + +
    +
    +

    unload() load()

    +
    +void unload ();
    +void load (char const* buffer, int size, error_code& ec);
    +
    +

    load and unload this torrent info

    +
    +
    +

    ssl_cert()

    +
    +std::string 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 the public certificate in x509 format.

    +
    +
    +

    is_valid()

    +
    +bool is_valid () const;
    +
    +

    returns true if this torrent_info object has a torrent loaded. +This is primarily used to determine if a magnet link has had its +metadata resolved yet or not.

    +
    +
    +

    priv()

    +
    +bool priv () const;
    +
    +

    returns true if this torrent is private. i.e., it should not be +distributed on the trackerless network (the kademlia DHT).

    +
    +
    +

    is_i2p()

    +
    +bool is_i2p () const;
    +
    +

    returns true if this is an i2p torrent. This is determined by whether +or not it has a tracker whose URL domain name ends with ".i2p". i2p +torrents disable the DHT and local peer discovery as well as talking +to peers over anything other than the i2p network.

    + + +
    +
    +

    hash_for_piece_ptr() hash_for_piece() piece_size()

    +
    +sha1_hash hash_for_piece (int index) const;
    +char const* hash_for_piece_ptr (int index) const;
    +int piece_size (int 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 null-terminated.

    + +
    +
    +

    set_merkle_tree() merkle_tree()

    +
    +std::vector<sha1_hash> const& merkle_tree () const;
    +void set_merkle_tree (std::vector<sha1_hash>& h);
    +
    +

    merkle_tree() returns a reference to the merkle tree for this +torrent, if any.

    +

    set_merkle_tree() moves the passed in merkle tree into the +torrent_info object. i.e. h will not be identical after the call. +You need to set the merkle tree for a torrent that you've just created +(as a merkle torrent). The merkle tree is retrieved from the +create_torrent::merkle_tree() function, and need to be saved +separately from the torrent file itself. Once it's added to +libtorrent, the merkle tree will be persisted in the resume data.

    + + + +
    +
    +

    creator() creation_date() name() comment()

    +
    +boost::optional<time_t> creation_date () const;
    +const std::string& name () const;
    +const std::string& comment () const;
    +const std::string& creator () const;
    +
    +

    name() returns the name of the torrent.

    +

    comment() returns the comment associated with the torrent. If +there's no comment, it will return an empty 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, the +optional object will be uninitialized.

    +

    Both the name and the comment is UTF-8 encoded strings.

    +

    creator() returns the creator string in the torrent. If there is +no creator string it will return an empty string.

    +
    +
    +

    nodes()

    +
    +nodes_t 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).

    +
    +
    +

    add_node()

    +
    +void add_node (std::pair<std::string, int> const& node);
    +
    +

    This is used when creating torrent. Use this to add a known DHT node. +It may be used, by the client, to bootstrap into the DHT network.

    +
    +
    +

    parse_info_section()

    +
    +bool parse_info_section (bdecode_node const& e, error_code& ec, int flags);
    +
    +

    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). flags are +currently unused.

    +
    +
    +

    info()

    +
    +bdecode_node info (char const* key) const;
    +
    +

    This function looks up keys from the info-dictionary of the loaded +torrent file. It can be used to access extension values put in the +.torrent file. If the specified key cannot be found, it returns NULL.

    +
    +
    +

    swap()

    +
    +void swap (torrent_info& ti);
    +
    +

    swap the content of this and ti`.

    + +
    +
    +

    metadata_size() metadata()

    +
    +int metadata_size () const;
    +boost::shared_array<char> metadata () const;
    +
    +

    metadata() returns a the raw info section of the torrent file. The size +of the metadata is returned by metadata_size().

    +
    +
    +

    is_merkle_torrent()

    +
    +bool is_merkle_torrent () const;
    +
    +

    returns whether or not this is a merkle torrent. +see BEP30.

    +
    +
    +
    +

    torrent_status

    +

    Declared in "libtorrent/torrent_status.hpp"

    +

    holds a snapshot of the status of a torrent, as queried by +torrent_handle::status().

    +
    +struct torrent_status
    +{
    +   bool operator== (torrent_status const& st) const;
    +
    +   enum state_t
    +   {
    +      checking_files,
    +      downloading_metadata,
    +      downloading,
    +      finished,
    +      seeding,
    +      allocating,
    +      checking_resume_data,
    +   };
    +
    +   torrent_handle handle;
    +   std::string _dummy_string_;
    +   error_code errc;
    +   int error_file;
    +   std::string save_path;
    +   std::string name;
    +   boost::weak_ptr<const torrent_info> torrent_file;
    +   time_duration next_announce;
    +   std::string current_tracker;
    +   boost::int64_t total_download;
    +   boost::int64_t total_upload;
    +   boost::int64_t total_payload_download;
    +   boost::int64_t total_payload_upload;
    +   boost::int64_t total_failed_bytes;
    +   boost::int64_t total_redundant_bytes;
    +   bitfield pieces;
    +   bitfield verified_pieces;
    +   boost::int64_t total_done;
    +   boost::int64_t total_wanted_done;
    +   boost::int64_t total_wanted;
    +   boost::int64_t all_time_upload;
    +   boost::int64_t all_time_download;
    +   time_t added_time;
    +   time_t completed_time;
    +   time_t last_seen_complete;
    +   storage_mode_t storage_mode;
    +   float progress;
    +   int progress_ppm;
    +   int queue_position;
    +   int download_rate;
    +   int upload_rate;
    +   int download_payload_rate;
    +   int upload_payload_rate;
    +   int num_seeds;
    +   int num_peers;
    +   int num_complete;
    +   int num_incomplete;
    +   int list_seeds;
    +   int list_peers;
    +   int connect_candidates;
    +   int num_pieces;
    +   int distributed_full_copies;
    +   int distributed_fraction;
    +   float distributed_copies;
    +   int block_size;
    +   int num_uploads;
    +   int num_connections;
    +   int uploads_limit;
    +   int connections_limit;
    +   int up_bandwidth_queue;
    +   int down_bandwidth_queue;
    +   int time_since_upload;
    +   int time_since_download;
    +   int active_time;
    +   int finished_time;
    +   int seeding_time;
    +   int seed_rank;
    +   int last_scrape;
    +   int priority;
    +   state_t state;
    +   bool need_save_resume;
    +   bool ip_filter_applies;
    +   bool upload_mode;
    +   bool share_mode;
    +   bool super_seeding;
    +   bool paused;
    +   bool auto_managed;
    +   bool sequential_download;
    +   bool is_seeding;
    +   bool is_finished;
    +   bool has_metadata;
    +   bool has_incoming;
    +   bool seed_mode;
    +   bool moving_storage;
    +   bool is_loaded;
    +   bool announcing_to_trackers;
    +   bool announcing_to_lsd;
    +   bool announcing_to_dht;
    +   bool stop_when_ready;
    +   sha1_hash info_hash;
    +};
    +
    +
    +

    operator==()

    +
    +bool operator== (torrent_status const& st) const;
    +
    +

    compares if the torrent status objects come from the same torrent. i.e. +only the torrent_handle field is compared.

    +
    +
    +

    enum state_t

    +

    Declared in "libtorrent/torrent_status.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    checking_files1The torrent has not started its download yet, and is +currently checking existing files.
    downloading_metadata2The torrent is trying to download metadata from peers. +This assumes the metadata_transfer extension is in use.
    downloading3The torrent is being downloaded. This is the state +most torrents will be in most of the time. The progress +meter will tell how much of the files that has been +downloaded.
    finished4In this state the torrent has finished downloading but +still doesn't have the entire torrent. i.e. some pieces +are filtered and won't get downloaded.
    seeding5In this state the torrent has finished downloading and +is a pure seeder.
    allocating6If the torrent was started in full allocation mode, this +indicates that the (disk) storage for the torrent is +allocated.
    checking_resume_data7The torrent is currently checking the fastresume 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.
    +
    +
    handle
    +
    a handle to the torrent whose status the object represents.
    +
    + + +
    +
    _dummy_string_ errc error_file
    +
    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. +internal
    +
    +
    +
    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.
    +
    +
    +
    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.
    +
    +
    +
    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.
    +
    +
    +
    next_announce
    +
    the time until the torrent will announce itself to the tracker.
    +
    +
    +
    current_tracker
    +
    the URL of the last working tracker. If no tracker request has +been successful yet, it's set to an empty string.
    +
    + +
    +
    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.
    +
    + +
    +
    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.
    +
    +
    +
    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.
    +
    +
    +
    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.
    +
    +
    +
    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.
    +
    +
    +
    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.
    +
    +
    +
    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).
    +
    +
    +
    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).
    +
    +
    +
    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
    +
    + +
    +
    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.
    +
    +
    +
    added_time
    +
    the posix-time when this torrent was added. i.e. what time(NULL) +returned at the time.
    +
    +
    +
    completed_time
    +
    the posix-time when this torrent was finished. If the torrent is not +yet finished, this is 0.
    +
    +
    +
    last_seen_complete
    +
    the time when we, or one of our peers, last saw a complete copy of +this torrent.
    +
    +
    +
    storage_mode
    +
    The allocation mode for the torrent. See storage_mode_t for the +options. For more information, see storage allocation.
    +
    +
    +
    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.
    +
    +
    +
    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.

    +
    +
    +
    +
    queue_position
    +
    the position this torrent has in the download +queue. If the torrent is a seed or finished, this is -1.
    +
    + +
    +
    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.
    +
    + +
    +
    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.
    +
    +
    +
    num_seeds
    +
    the number of peers that are seeding that this client is +currently connected to.
    +
    +
    +
    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().
    +
    + +
    +
    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.
    +
    + +
    +
    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.
    +
    +
    +
    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.
    +
    +
    +
    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.
    +
    +
    +
    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.
    +
    +
    +
    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 +distrbuted_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.

    +
    +
    +
    +
    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).
    +
    +
    +
    +
    +
    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 +larger if the pieces are larger.
    +
    +
    +
    num_uploads
    +
    the number of unchoked peers in this torrent.
    +
    +
    +
    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.
    +
    +
    +
    uploads_limit
    +
    the set limit of upload slots (unchoked peers) for this torrent.
    +
    +
    +
    connections_limit
    +
    the set limit of number of connections for this torrent.
    +
    + +
    +
    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.
    +
    + +
    +
    time_since_upload time_since_download
    +
    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)
    +
    + + +
    +
    active_time finished_time seeding_time
    +
    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.
    +
    +
    +
    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
    +
    +
    +
    last_scrape
    +
    the number of seconds since this torrent acquired scrape data. +If it has never done that, this value is -1.
    +
    +
    +
    priority
    +
    the priority of this torrent
    +
    +
    +
    state
    +
    the main state the torrent is in. See torrent_status::state_t.
    +
    +
    +
    need_save_resume
    +
    true if this torrent has unsaved changes +to its download state and statistics since the last resume data +was saved.
    +
    +
    +
    ip_filter_applies
    +
    true if the session global IP filter applies +to this torrent. This defaults to true.
    +
    +
    +
    upload_mode
    +
    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.
    +
    +
    +
    share_mode
    +
    true if the torrent is currently in share-mode, i.e. not downloading +the torrent, but just helping the swarm out.
    +
    +
    +
    super_seeding
    +
    true if the torrent is in super seeding mode
    +
    +
    +
    paused
    +
    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().
    +
    +
    +
    auto_managed
    +
    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
    +
    +
    +
    sequential_download
    +
    true when the torrent is in sequential download mode. In this mode +pieces are downloaded in order rather than rarest first.
    +
    +
    +
    is_seeding
    +
    true if all pieces have been downloaded.
    +
    +
    +
    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.
    +
    +
    +
    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).
    +
    +
    +
    has_incoming
    +
    true if there has ever been an incoming connection attempt to this +torrent.
    +
    +
    +
    seed_mode
    +
    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.
    +
    +
    +
    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.
    +
    +
    +
    is_loaded
    +
    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.
    +
    + + +
    +
    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.
    +
    +
    +
    stop_when_ready
    +
    this reflects whether the stop_when_ready flag is currently enabled +on this torrent. For more information, see +torrent_handle::stop_when_ready().
    +
    +
    +
    info_hash
    +
    the info-hash for this torrent
    +
    +
    +
    +
    +

    dht_storage_counters

    +

    Declared in "libtorrent/kademlia/dht_storage.hpp"

    +

    This structure hold the relevant counters for the storage

    +
    +struct dht_storage_counters
    +{
    +   boost::int32_t torrents;
    +   boost::int32_t peers;
    +   boost::int32_t immutable_data;
    +   boost::int32_t mutable_data;
    +};
    +
    +
    +
    +

    dht_storage_interface

    +

    Declared in "libtorrent/kademlia/dht_storage.hpp"

    +

    The DHT storage interface is a pure virtual class that can +be implemented to customize how the data for the DHT is stored.

    +

    The default storage implementation uses three maps in RAM to save +the peers, mutable and immutable items and it's designed to +provide a fast and fully compliant behavior of the BEPs.

    +

    libtorrent comes with one built-in storage implementation: +dht_default_storage (private non-accessible class). Its +constructor function is called dht_default_storage_constructor().

    +
    +struct dht_storage_interface
    +{
    +   virtual bool get_peers (sha1_hash const& info_hash
    +      , bool noseed, bool scrape
    +      , entry& peers) const = 0;
    +   virtual void announce_peer (sha1_hash const& info_hash
    +      , tcp::endpoint const& endp
    +      , std::string const& 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
    +      , char const* buf, int size
    +      , address const& addr) = 0;
    +   virtual bool get_mutable_item_seq (sha1_hash const& target
    +      , boost::int64_t& seq) const = 0;
    +   virtual bool get_mutable_item (sha1_hash const& target
    +      , boost::int64_t seq, bool force_fill
    +      , entry& item) const = 0;
    +   virtual void put_mutable_item (sha1_hash const& target
    +      , char const* buf, int size
    +      , char const* sig
    +      , boost::int64_t seq
    +      , char const* pk
    +      , char const* salt, int salt_size
    +      , address const& addr) = 0;
    +   virtual void tick () = 0;
    +   virtual dht_storage_counters counters () const = 0;
    +   virtual ~dht_storage_interface ();
    +};
    +
    +
    +

    get_peers()

    +
    +virtual bool get_peers (sha1_hash const& info_hash
    +      , bool noseed, bool scrape
    +      , entry& peers) const = 0;
    +
    +

    This function retrieve the peers tracked by the DHT +corresponding to the given info_hash. You can specify if +you want only seeds and/or you are scraping the data.

    +

    For future implementers: +If the torrent tracked contains a name, such a name +must be stored as a string in peers["n"]

    +

    If the scrape parameter is true, you should fill these keys:

    +
    +peers["BFpe"] - with the standard bit representation of a
    +                256 bloom filter containing the downloaders
    +peers["BFsd"] - with the standard bit representation of a
    +                256 bloom filter containing the seeders
    +
    +

    If the scrape parameter is false, you should fill the +key peers["values"] with a list containing a subset of +peers tracked by the given info_hash. Such a list should +consider the value of dht_settings::max_peers_reply. +If noseed is true only peers marked as no seed should be included.

    +

    returns true if an entry with the info_hash is found and +the data is returned inside the (entry) out parameter peers.

    +
    +
    +

    announce_peer()

    +
    +virtual void announce_peer (sha1_hash const& info_hash
    +      , tcp::endpoint const& endp
    +      , std::string const& 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.

    +
    +
    +

    get_immutable_item()

    +
    +virtual bool get_immutable_item (sha1_hash const& target
    +      , entry& item) const = 0;
    +
    +

    This function retrieves the immutable item given its target hash.

    +

    For future implementers: +The value should be returned as an entry in the key item["v"].

    +

    returns true if the item is found and the data is returned +inside the (entry) out parameter item.

    +
    +
    +

    put_immutable_item()

    +
    +virtual void put_immutable_item (sha1_hash const& target
    +      , char const* buf, int size
    +      , address const& addr) = 0;
    +
    +

    Store the item's data. This layer is only for storage. +The authentication of the item is performed by the upper layer.

    +

    For implementers: +This data can be stored only if the target is not already +present. The implementation should consider the value of +dht_settings::max_dht_items.

    +
    +
    +

    get_mutable_item_seq()

    +
    +virtual bool get_mutable_item_seq (sha1_hash const& target
    +      , boost::int64_t& 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.

    +
    +
    +

    get_mutable_item()

    +
    +virtual bool get_mutable_item (sha1_hash const& target
    +      , boost::int64_t 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.

    +
    +
    +

    put_mutable_item()

    +
    +virtual void put_mutable_item (sha1_hash const& target
    +      , char const* buf, int size
    +      , char const* sig
    +      , boost::int64_t seq
    +      , char const* pk
    +      , char const* salt, int salt_size
    +      , address const& addr) = 0;
    +
    +

    Store the item's data. This layer is only for storage. +The authentication of the item is performed by the upper layer.

    +

    For implementers: +The sequence number should be checked if the item is already +present. The implementation should consider the value of +dht_settings::max_dht_items.

    +
    +
    +

    tick()

    +
    +virtual void tick () = 0;
    +
    +

    This function is called periodically (non-constant frequency).

    +

    For implementers: +Use this functions for expire peers or items or any other +storage cleanup.

    +
    +
    +
    +

    to_hex()

    +

    Declared in "libtorrent/hex.hpp"

    +
    +void to_hex (char const *in, int len, char* out);
    +std::string to_hex (std::string const& s);
    +
    +

    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.

    +
    +
    +

    from_hex()

    +

    Declared in "libtorrent/hex.hpp"

    +
    +bool from_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.

    +
    +
    +

    make_magnet_uri()

    +

    Declared in "libtorrent/magnet_uri.hpp"

    +
    +std::string make_magnet_uri (torrent_handle const& handle);
    +std::string make_magnet_uri (torrent_info const& info);
    +
    +

    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.

    +
    +
    +

    parse_magnet_uri()

    +

    Declared in "libtorrent/magnet_uri.hpp"

    +
    +void parse_magnet_uri (std::string const& uri, add_torrent_params& p, error_code& ec);
    +
    +

    This function parses out information from the magnet link and populates the +add_torrent_params object.

    +
    +
    +

    session_stats_metrics()

    +

    Declared in "libtorrent/session_stats.hpp"

    +
    +std::vector<stats_metric> session_stats_metrics ();
    +
    +

    This free function returns the list of available metrics exposed by +libtorrent's statistics API. Each metric has a name and a value index. +The value index is the index into the array in session_stats_alert where +this metric's value can be found when the session stats is sampled (by +calling post_session_stats()).

    +
    +
    +

    find_metric_idx()

    +

    Declared in "libtorrent/session_stats.hpp"

    +
    +int find_metric_idx (char const* 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.

    +
    +
    +

    hash_value()

    +

    Declared in "libtorrent/torrent_handle.hpp"

    +
    +std::size_t hash_value (torrent_status const& ts);
    +
    +

    allows torrent_handle to be used in unordered_map and unordered_set.

    +
    +
    +

    is_utp_stream_logging()

    +

    Declared in "libtorrent/utp_stream.hpp"

    +
    +bool is_utp_stream_logging ();
    +
    +
    +
    +

    set_utp_stream_logging()

    +

    Declared in "libtorrent/utp_stream.hpp"

    +
    +void set_utp_stream_logging (bool enable);
    +
    +

    This function should be used at the very beginning and very end of your program.

    +
    +
    +

    version()

    +

    Declared in "libtorrent/version.hpp"

    +
    +char const* version ();
    +
    +

    returns the libtorrent version as string form in this format: +"<major>.<minor>.<tiny>.<tag>"

    +
    +
    +

    dht_default_storage_constructor()

    +

    Declared in "libtorrent/kademlia/dht_storage.hpp"

    +
    +dht_storage_interface* dht_default_storage_constructor (sha1_hash const& id
    +   , dht_settings const& settings);
    +
    +
    +
    +

    sign_mutable_item()

    +

    Declared in "libtorrent/kademlia/item.hpp"

    +
    +void sign_mutable_item (
    +   std::pair<char const*, int> v
    +   , std::pair<char const*, int> salt
    +   , boost::uint64_t seq
    +   , char const* pk
    +   , char const* sk
    +   , char* sig);
    +
    +

    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.

    +
    +
    +

    verify_message()

    +

    Declared in "libtorrent/kademlia/msg.hpp"

    +
    +bool verify_message (bdecode_node const& msg, key_desc_t const desc[]
    +   , bdecode_node ret[], int size, char* error, int error_size);
    +
    +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/reference-Create_Torrents.html b/docs/reference-Create_Torrents.html new file mode 100644 index 0000000..dab87a2 --- /dev/null +++ b/docs/reference-Create_Torrents.html @@ -0,0 +1,483 @@ + + + + + + + + + + + + + + + +
    +
    + + + + +
    + + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.1.1
    +
    +

    Create Torrents

    + +

    This section describes the functions and classes that are used +to create torrent files. It is a layered API with low level classes +and higher level convenience functions. A torrent is created in 4 +steps:

    +
      +
    1. first the files that will be part of the torrent are determined.
    2. +
    3. the torrent properties are set, such as tracker url, web seeds, +DHT nodes etc.
    4. +
    5. Read through all the files in the torrent, SHA-1 all the data +and set the piece hashes.
    6. +
    7. The torrent is bencoded into a file or buffer.
    8. +
    +

    If there are a lot of files and or deep directory hierarchies to +traverse, step one can be time consuming.

    +

    Typically step 3 is by far the most time consuming step, since it +requires to read all the bytes from all the files in the torrent.

    +

    All of these classes and functions are declared by including +libtorrent/create_torrent.hpp.

    +

    example:

    +
    +file_storage fs;
    +
    +// recursively adds files in directories
    +add_files(fs, "./my_torrent");
    +
    +create_torrent t(fs);
    +t.add_tracker("http://my.tracker.com/announce");
    +t.set_creator("libtorrent example");
    +
    +// reads the files and calculates the hashes
    +set_piece_hashes(t, ".");
    +
    +ofstream out("my_torrent.torrent", std::ios_base::binary);
    +bencode(std::ostream_iterator<char>(out), t.generate());
    +
    +
    +

    create_torrent

    +

    Declared in "libtorrent/create_torrent.hpp"

    +

    This class holds state for creating a torrent. After having added +all information to it, call create_torrent::generate() to generate +the torrent. The entry that's returned can then be bencoded into a +.torrent file using bencode().

    +
    +struct create_torrent
    +{
    +   create_torrent (torrent_info const& ti);
    +   create_torrent (file_storage& fs, int piece_size = 0
    +      , int pad_file_limit = -1, int flags = optimize_alignment
    +      , int alignment = -1);
    +   entry generate () const;
    +   file_storage const& files () const;
    +   void set_comment (char const* str);
    +   void set_creator (char const* str);
    +   void set_hash (int index, sha1_hash const& h);
    +   void set_file_hash (int index, sha1_hash const& h);
    +   void add_url_seed (std::string const& url);
    +   void add_http_seed (std::string const& url);
    +   void add_node (std::pair<std::string, int> const& node);
    +   void add_tracker (std::string const& url, int tier = 0);
    +   void set_root_cert (std::string const& pem);
    +   bool priv () const;
    +   void set_priv (bool p);
    +   int num_pieces () const;
    +   int piece_length () const;
    +   int piece_size (int i) const;
    +   std::vector<sha1_hash> const& merkle_tree () const;
    +   void add_similar_torrent (sha1_hash ih);
    +   void add_collection (std::string c);
    +
    +   enum flags_t
    +   {
    +      optimize_alignment,
    +      merkle,
    +      modification_time,
    +      symlinks,
    +      mutable_torrent_support,
    +   };
    +};
    +
    +
    +

    create_torrent()

    +
    +create_torrent (torrent_info const& ti);
    +create_torrent (file_storage& fs, int piece_size = 0
    +      , int pad_file_limit = -1, int flags = optimize_alignment
    +      , int alignment = -1);
    +
    +

    The piece_size is the size of each piece in bytes. It must +be a multiple of 16 kiB. If a piece size of 0 is specified, a +piece_size will be calculated such that the torrent file is roughly 40 kB.

    +

    If a pad_size_limit is specified (other than -1), any file larger than +the specified number of bytes will be preceded by a pad file to align it +with the start of a piece. The pad_file_limit is ignored unless the +optimize_alignment flag is passed. Typically it doesn't make sense +to set this any lower than 4kiB.

    +

    The overload that takes a torrent_info object will make a verbatim +copy of its info dictionary (to preserve the info-hash). The copy of +the info dictionary will be used by create_torrent::generate(). This means +that none of the member functions of create_torrent that affects +the content of the info dictionary (such as set_hash()), will +have any affect.

    +

    The flags arguments specifies options for the torrent creation. It can +be any combination of the flags defined by create_torrent::flags_t.

    +

    alignment is used when pad files are enabled. This is the size +eligible files are aligned to. The default is -1, which means the +piece size of the torrent.

    +
    +
    +

    generate()

    +
    +entry generate () const;
    +
    +

    This function will generate the .torrent file as a bencode tree. In order to +generate the flat file, use the bencode() function.

    +

    It may be useful to add custom entries to the torrent file before bencoding it +and saving it to disk.

    +

    If anything goes wrong during torrent generation, this function will return +an empty entry structure. You can test for this condition by querying the +type of the entry:

    +
    +file_storage fs;
    +// add file ...
    +create_torrent t(fs);
    +// add trackers and piece hashes ...
    +e = t.generate();
    +
    +if (e.type() == entry::undefined_t)
    +{
    +        // something went wrong
    +}
    +
    +

    For instance, you cannot generate a torrent with 0 files in it. If you don't add +any files to the file_storage, torrent generation will fail.

    +
    +
    +

    files()

    +
    +file_storage const& files () const;
    +
    +

    returns an immutable reference to the file_storage used to create +the torrent from.

    +
    +
    +

    set_comment()

    +
    +void set_comment (char const* str);
    +
    +

    Sets the comment for the torrent. The string str should be utf-8 encoded. +The comment in a torrent file is optional.

    +
    +
    +

    set_creator()

    +
    +void set_creator (char const* str);
    +
    +

    Sets the creator of the torrent. The string str should be utf-8 encoded. +This is optional.

    +
    +
    +

    set_hash()

    +
    +void set_hash (int 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().

    +
    +
    +

    set_file_hash()

    +
    +void set_file_hash (int index, sha1_hash const& h);
    +
    +

    This sets the sha1 hash for this file. This hash will end up under the key sha1 +associated with this file (for multi-file torrents) or in the root info dictionary +for single-file torrents.

    + +
    +
    +

    add_url_seed() add_http_seed()

    +
    +void add_url_seed (std::string const& url);
    +void add_http_seed (std::string const& 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.

    +
    +
    +

    add_node()

    +
    +void add_node (std::pair<std::string, int> const& 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.

    +
    +
    +

    add_tracker()

    +
    +void add_tracker (std::string const& 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.

    +
    +
    +

    set_root_cert()

    +
    +void set_root_cert (std::string const& pem);
    +
    +

    This function sets an X.509 certificate in PEM format to the torrent. This makes the +torrent an SSL torrent. An SSL torrent requires that each peer has a valid certificate +signed by this root certificate. For SSL torrents, all peers are connecting over SSL +connections. For more information, see the section on ssl torrents.

    +

    The string is not the path to the cert, it's the actual content of the certificate, +loaded into a std::string.

    + +
    +
    +

    priv() set_priv()

    +
    +bool priv () const;
    +void set_priv (bool p);
    +
    +

    Sets and queries the private flag of the torrent. +Torrents with the private flag set ask clients to not use any other +sources than the tracker for peers, and to not advertise itself publicly, +apart from the tracker.

    +
    +
    +

    num_pieces()

    +
    +int num_pieces () const;
    +
    +

    returns the number of pieces in the associated file_storage object.

    + +
    +
    +

    piece_length() piece_size()

    +
    +int piece_length () const;
    +int piece_size (int 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.

    +
    +
    +

    merkle_tree()

    +
    +std::vector<sha1_hash> const& merkle_tree () const;
    +
    +

    This function returns the merkle hash tree, if the torrent was created as a merkle +torrent. The tree is created by generate() and won't be valid until that function +has been called. When creating a merkle tree torrent, the actual tree itself has to +be saved off separately and fed into libtorrent the first time you start seeding it, +through the torrent_info::set_merkle_tree() function. From that point onwards, the +tree will be saved in the resume data.

    + +
    +
    +

    add_collection() add_similar_torrent()

    +
    +void add_similar_torrent (sha1_hash ih);
    +void add_collection (std::string c);
    +
    +

    Add similar torrents (by info-hash) or collections of similar torrents. +Similar torrents are expected to share some files with this torrent. +Torrents sharing a collection name with this torrent are also expected +to share files with this torrent. A torrent may have more than one +collection and more than one similar torrents. For more information, +see BEP 38.

    +
    +
    +

    enum flags_t

    +

    Declared in "libtorrent/create_torrent.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    optimize_alignment1This 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.
    merkle2This 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.
    modification_time4This 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.
    symlinks8If 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.
    mutable_torrent_support16to create a torrent that can be updated via a mutable torrent +(see BEP38). This also needs to be enabled for torrents that update +another torrent.
    +
    +
    +
    +

    add_files()

    +

    Declared in "libtorrent/create_torrent.hpp"

    +
    +void add_files (file_storage& fs, std::string const& file
    +   , boost::function<bool(std::string)> p, boost::uint32_t flags = 0);
    +void add_files (file_storage& fs, std::string const& file
    +   , boost::uint32_t flags = 0);
    +
    +

    Adds the file specified by path to the file_storage object. In case path +refers to a directory, files will be added recursively from the directory.

    +

    If specified, the predicate p is called once for every file and directory that +is encountered. Files for which p returns true are added, and directories for +which p returns true are traversed. p must have the following signature:

    +
    +bool Pred(std::string const& p);
    +
    +

    The path that is passed in to the predicate is the full path of the file or +directory. If no predicate is specified, all files are added, and all directories +are traversed.

    +

    The ".." directory is never traversed.

    +

    The flags argument should be the same as the flags passed to the create_torrent +constructor.

    +
    +
    +

    set_piece_hashes()

    +

    Declared in "libtorrent/create_torrent.hpp"

    +
    +void set_piece_hashes (create_torrent& t, std::string const& p
    +   , boost::function<void(int)> const& f, error_code& ec);
    +inline void set_piece_hashes (create_torrent& t, std::string const& p);
    +inline void set_piece_hashes (create_torrent& t, std::string const& p, error_code& ec);
    +
    +

    This function will assume that the files added to the torrent file exists at path +p, read those files and hash the content and set the hashes in the create_torrent +object. The optional function f is called in between every hash that is set. f +must have the following signature:

    +
    +void Fun(int);
    +
    +

    The overloads that don't take an error_code& may throw an exception in case of a +file error, the other overloads sets the error code to reflect the error, if any.

    +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/reference-Custom_Storage.html b/docs/reference-Custom_Storage.html new file mode 100644 index 0000000..ea86c36 --- /dev/null +++ b/docs/reference-Custom_Storage.html @@ -0,0 +1,649 @@ + + + + + + + + + + + + + + + +
    +
    + + + + +
    + + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.1.1
    +
    +

    Custom Storage

    + +

    libtorrent provides a customization point for storage of data. By default, +(default_storage) downloaded files are saved to disk according with the +general conventions of bittorrent clients, mimicing the original file layout +when the torrent was created. The libtorrent user may define a custom +storage to store piece data in a different way.

    +

    A custom storage implementation must derive from and implement the +storage_interface. You must also provide a function that constructs the +custom storage object and provide this function to the add_torrent() call +via add_torrent_params. Either passed in to the constructor or by setting +the add_torrent_params::storage field.

    +

    This is an example storage implementation that stores all pieces in a +std::map, i.e. in RAM. It's not necessarily very useful in practice, but +illustrates the basics of implementing a custom storage.

    +
    +struct temp_storage : storage_interface
    +{
    +        temp_storage(file_storage const& fs) : m_files(fs) {}
    +        virtual bool initialize(storage_error& se) { return false; }
    +        virtual bool has_any_file() { return false; }
    +        virtual int read(char* buf, int piece, int offset, int size)
    +        {
    +                std::map<int, std::vector<char> >::const_iterator i = m_file_data.find(piece);
    +                if (i == m_file_data.end()) return 0;
    +                int available = i->second.size() - offset;
    +                if (available <= 0) return 0;
    +                if (available > size) available = size;
    +                memcpy(buf, &i->second[offset], available);
    +                return available;
    +        }
    +        virtual int write(const char* buf, int piece, int offset, int size)
    +        {
    +                std::vector<char>& data = m_file_data[piece];
    +                if (data.size() < offset + size) data.resize(offset + size);
    +                std::memcpy(&data[offset], buf, size);
    +                return size;
    +        }
    +        virtual bool rename_file(int file, std::string const& new_name)
    +        { assert(false); return false; }
    +        virtual bool move_storage(std::string const& save_path) { return false; }
    +        virtual bool verify_resume_data(bdecode_node const& rd
    +                , std::vector<std::string> const* links
    +                , storage_error& error) { return false; }
    +        virtual bool write_resume_data(entry& rd) const { return false; }
    +        virtual boost::int64_t physical_offset(int piece, int offset)
    +        { return piece * m_files.piece_length() + offset; };
    +        virtual sha1_hash hash_for_slot(int piece, partial_hash& ph, int piece_size)
    +        {
    +                int left = piece_size - ph.offset;
    +                assert(left >= 0);
    +                if (left > 0)
    +                {
    +                        std::vector<char>& data = m_file_data[piece];
    +                        // if there are padding files, those blocks will be considered
    +                        // completed even though they haven't been written to the storage.
    +                        // in this case, just extend the piece buffer to its full size
    +                        // and fill it with zeroes.
    +                        if (data.size() < piece_size) data.resize(piece_size, 0);
    +                        ph.h.update(&data[ph.offset], left);
    +                }
    +                return ph.h.final();
    +        }
    +        virtual bool release_files() { return false; }
    +        virtual bool delete_files() { return false; }
    +
    +        std::map<int, std::vector<char> > m_file_data;
    +        file_storage m_files;
    +};
    +
    +storage_interface* temp_storage_constructor(storage_params const& params)
    +{
    +        return new temp_storage(*params.files);
    +}
    +
    +
    +

    disk_buffer_holder

    +

    Declared in "libtorrent/disk_buffer_holder.hpp"

    +

    The disk buffer holder acts like a scoped_ptr that frees a disk buffer +when it's destructed, unless it's released. release returns the disk +buffer and transfers ownership and responsibility to free it to the caller.

    +

    A disk buffer is freed by passing it to session_impl::free_disk_buffer().

    +

    get() returns the pointer without transferring responsibility. If +this buffer has been released, buffer() will return 0.

    +
    +struct disk_buffer_holder
    +{
    +   disk_buffer_holder (buffer_allocator_interface& alloc, disk_io_job const& j);
    +   ~disk_buffer_holder ();
    +   char* release ();
    +   char* get () const;
    +   void reset (char* buf = 0);
    +   void reset (disk_io_job const& j);
    +   void swap (disk_buffer_holder& h);
    +   block_cache_reference ref () const;
    +};
    +
    +
    +

    disk_buffer_holder()

    +
    +disk_buffer_holder (buffer_allocator_interface& alloc, disk_io_job const& j);
    +
    +

    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()

    +
    +~disk_buffer_holder ();
    +
    +

    frees any unreleased disk buffer held by this object

    +
    +
    +

    release()

    +
    +char* release ();
    +
    +

    return the held disk buffer and clear it from the +holder. The responsibility to free it is passed on +to the caller

    +
    +
    +

    get()

    +
    +char* get () const;
    +
    +

    return a pointer to the held buffer

    +
    +
    +

    reset()

    +
    +void reset (char* buf = 0);
    +void reset (disk_io_job const& j);
    +
    +

    set the holder object to hold the specified buffer +(or NULL by default). If it's already holding a +disk buffer, it will first be freed.

    +
    +
    +

    swap()

    +
    +void swap (disk_buffer_holder& h);
    +
    +

    swap pointers of two disk buffer holders.

    +
    +
    +
    +

    file_pool

    +

    Declared in "libtorrent/file_pool.hpp"

    +

    this is an internal cache of open file handles. It's primarily used by +storage_interface implementations. It provides semi weak guarantees of +not opening more file handles than specified. Given multiple threads, +each with the ability to lock a file handle (via smart pointer), there +may be windows where more file handles are open.

    +
    +struct file_pool : boost::noncopyable
    +{
    +   ~file_pool ();
    +   file_pool (int size = 40);
    +   file_handle open_file (void* st, std::string const& p
    +      , int file_index, file_storage const& fs, int m, error_code& ec);
    +   void release (void* st = NULL);
    +   void release (void* st, int file_index);
    +   void resize (int size);
    +   int size_limit () const;
    +};
    +
    + +
    +

    ~file_pool() file_pool()

    +
    +~file_pool ();
    +file_pool (int size = 40);
    +
    +

    size specifies the number of allowed files handles +to hold open at any given time.

    +
    +
    +

    open_file()

    +
    +file_handle open_file (void* st, std::string const& p
    +      , int file_index, file_storage const& fs, int m, error_code& ec);
    +
    +

    return an open file handle to file at file_index in the +file_storage fs opened at save path p. m is the +file open mode (see file::open_mode_t).

    +
    +
    +

    release()

    +
    +void release (void* st = NULL);
    +void release (void* st, int file_index);
    +
    +

    release all files belonging to the specified storage_interface (st) +the overload that takes file_index releases only the file with +that index in storage st.

    +
    +
    +

    resize()

    +
    +void resize (int size);
    +
    +

    update the allowed number of open file handles to size.

    +
    +
    +

    size_limit()

    +
    +int size_limit () const;
    +
    +

    returns the current limit of number of allowed open file handles held +by the file_pool.

    +
    +
    +
    +

    storage_interface

    +

    Declared in "libtorrent/storage.hpp"

    +

    The storage interface is a pure virtual class that can be implemented to +customize how and where data for a torrent is stored. The default storage +implementation uses regular files in the filesystem, mapping the files in +the torrent in the way one would assume a torrent is saved to disk. +Implementing your own storage interface makes it possible to store all +data in RAM, or in some optimized order on disk (the order the pieces are +received for instance), or saving multifile torrents in a single file in +order to be able to take advantage of optimized disk-I/O.

    +

    It is also possible to write a thin class that uses the default storage +but modifies some particular behavior, for instance encrypting the data +before it's written to disk, and decrypting it when it's read again.

    +

    The storage interface is based on pieces. Avery read and write operation +happens in the piece-space. Each piece fits 'piece_size' number +of bytes. All access is done by writing and reading whole or partial +pieces.

    +

    libtorrent comes with two built-in storage implementations; +default_storage and disabled_storage. Their constructor functions +are called default_storage_constructor() and +disabled_storage_constructor respectively. The disabled storage does +just what it sounds like. It throws away data that's written, and it +reads garbage. It's useful mostly for benchmarking and profiling purpose.

    +
    +struct storage_interface
    +{
    +   virtual void initialize (storage_error& ec) = 0;
    +   virtual int writev (file::iovec_t const* bufs, int num_bufs
    +      , int piece, int offset, int flags, storage_error& ec) = 0;
    +   virtual int readv (file::iovec_t const* bufs, int num_bufs
    +      , int piece, int offset, int flags, storage_error& ec) = 0;
    +   virtual bool has_any_file (storage_error& ec) = 0;
    +   virtual void set_file_priority (std::vector<boost::uint8_t> const& prio
    +      , storage_error& ec) = 0;
    +   virtual int move_storage (std::string const& save_path, int flags
    +      , storage_error& ec) = 0;
    +   virtual bool verify_resume_data (bdecode_node const& rd
    +      , std::vector<std::string> const* links
    +      , storage_error& ec) = 0;
    +   virtual void write_resume_data (entry& rd, storage_error& ec) const = 0;
    +   virtual void release_files (storage_error& ec) = 0;
    +   virtual void rename_file (int index, std::string const& new_filename
    +      , storage_error& ec) = 0;
    +   virtual void delete_files (int options, storage_error& ec) = 0;
    +   virtual bool tick ();
    +   aux::session_settings const& settings () const;
    +
    +   aux::session_settings* m_settings;
    +};
    +
    +
    +

    initialize()

    +
    +virtual void initialize (storage_error& ec) = 0;
    +
    +

    This function is called when the storage is to be initialized. The +default storage will create directories and empty files at this point. +If allocate_files is true, it will also ftruncate all files to +their target size.

    +

    If an error occurs, storage_error should be set to reflect it.

    + +
    +
    +

    writev() readv()

    +
    +virtual int writev (file::iovec_t const* bufs, int num_bufs
    +      , int piece, int offset, int flags, storage_error& ec) = 0;
    +virtual int readv (file::iovec_t const* bufs, int num_bufs
    +      , int piece, int offset, int flags, storage_error& ec) = 0;
    +
    +

    These functions should read and write the data in or to the given +piece at the given offset. It should read or write +num_bufs buffers sequentially, where the size of each buffer is +specified in the buffer array bufs. The file::iovec_t type has the +following members:

    +
    +struct iovec_t { void* iov_base; size_t iov_len; };
    +
    +

    These functions may be called simultaneously from multiple threads. +Make sure they are thread safe. The file in libtorrent is thread +safe when it can fall back to pread, preadv or the windows +equivalents. On targets where read operations cannot be thread safe +(i.e one has to seek first and then read), only one disk thread is +used.

    +

    Every buffer in bufs can be assumed to be page aligned and be of a +page aligned size, except for the last buffer of the torrent. The +allocated buffer can be assumed to fit a fully page aligned number of +bytes though. This is useful when reading and writing the last piece +of a file in unbuffered mode.

    +

    The offset is aligned to 16 kiB boundaries most of the time, but +there are rare exceptions when it's not. Specifically if the read +cache is disabled/or full and a peer requests unaligned data. Most +clients request aligned data.

    +

    The number of bytes read or written should be returned, or -1 on +error. If there's an error, the storage_error must be filled out +to represent the error that occurred.

    +
    +
    +

    has_any_file()

    +
    +virtual bool has_any_file (storage_error& ec) = 0;
    +
    +

    This function is called when first checking (or re-checking) the +storage for a torrent. It should return true if any of the files that +is used in this storage exists on disk. If so, the storage will be +checked for existing pieces before starting the download.

    +

    If an error occurs, storage_error should be set to reflect it.

    +
    +
    +

    set_file_priority()

    +
    +virtual void set_file_priority (std::vector<boost::uint8_t> const& prio
    +      , storage_error& ec) = 0;
    +
    +

    change the priorities of files. This is a fenced job and is +guaranteed to be the only running function on this storage +when called

    +
    +
    +

    move_storage()

    +
    +virtual int move_storage (std::string const& save_path, int flags
    +      , storage_error& ec) = 0;
    +
    +

    This function should move all the files belonging to the storage to +the new save_path. The default storage moves the single file or the +directory of the torrent.

    +

    Before moving the files, any open file handles may have to be closed, +like release_files().

    +

    If an error occurs, storage_error should be set to reflect it.

    +

    returns one of: +| no_error = 0 +| fatal_disk_error = -1 +| need_full_check = -2 +| file_exist = -4

    +
    +
    +

    verify_resume_data()

    +
    +virtual bool verify_resume_data (bdecode_node const& rd
    +      , std::vector<std::string> const* links
    +      , storage_error& ec) = 0;
    +
    +

    This function should verify the resume data rd with the files +on disk. If the resume data seems to be up-to-date, return true. If +not, set error to a description of what mismatched and return false.

    +

    The default storage may compare file sizes and time stamps of the files.

    +

    If an error occurs, storage_error should be set to reflect it.

    +

    This function should verify the resume data rd with the files +on disk. If the resume data seems to be up-to-date, return true. If +not, set error to a description of what mismatched and return false.

    +

    If the links pointer is non-null, 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.

    +
    +
    +

    write_resume_data()

    +
    +virtual void write_resume_data (entry& rd, storage_error& ec) const = 0;
    +
    +

    This function should fill in resume data, the current state of the +storage, in rd. The default storage adds file timestamps and +sizes.

    +

    Returning true indicates an error occurred.

    +

    If an error occurs, storage_error should be set to reflect it.

    +
    +
    +

    release_files()

    +
    +virtual void release_files (storage_error& ec) = 0;
    +
    +

    This function should release all the file handles that it keeps open +to files belonging to this storage. The default implementation just +calls file_pool::release_files().

    +

    If an error occurs, storage_error should be set to reflect it.

    +
    +
    +

    rename_file()

    +
    +virtual void rename_file (int index, std::string const& new_filename
    +      , storage_error& ec) = 0;
    +
    +

    Rename file with index file to the thame new_name.

    +

    If an error occurs, storage_error should be set to reflect it.

    +
    +
    +

    delete_files()

    +
    +virtual void delete_files (int options, storage_error& ec) = 0;
    +
    +

    This function should delete some or all of the storage for this torrent. +The options parameter specifies whether to delete all files or just +the partfile. options are set to the same value as the options +passed to session::remove_torrent().

    +

    If an error occurs, storage_error should be set to reflect it.

    +

    The disk_buffer_pool is used to allocate and free disk buffers. It +has the following members:

    +
    +struct disk_buffer_pool : boost::noncopyable
    +{
    +        char* allocate_buffer(char const* category);
    +        void free_buffer(char* buf);
    +
    +        char* allocate_buffers(int blocks, char const* category);
    +        void free_buffers(char* buf, int blocks);
    +
    +        int block_size() const { return m_block_size; }
    +
    +        void release_memory();
    +};
    +
    +
    +
    +

    tick()

    +
    +virtual bool tick ();
    +
    +

    called periodically (useful for deferred flushing). When returning +false, it means no more ticks are necessary. Any disk job submitted +will re-enable ticking. The default will always turn ticking back +off again.

    +
    +
    +

    settings()

    +
    +aux::session_settings const& settings () const;
    +
    +

    access global session_settings

    +
    +
    m_settings
    +
    initialized in disk_io_thread::perform_async_job
    +
    +
    +
    +
    +

    default_storage

    +

    Declared in "libtorrent/storage.hpp"

    +

    The default implementation of storage_interface. Behaves as a normal +bittorrent client. It is possible to derive from this class in order to +override some of its behavior, when implementing a custom storage.

    +
    +class default_storage : public storage_interface, boost::noncopyable
    +{
    +   default_storage (storage_params const& params);
    +   virtual int move_storage (std::string const& save_path, int flags
    +      , storage_error& ec) override;
    +   virtual bool verify_resume_data (bdecode_node const& rd
    +      , std::vector<std::string> const* links
    +      , storage_error& error) override;
    +   virtual void initialize (storage_error& ec) override;
    +   virtual bool tick () override;
    +   virtual void delete_files (int options, storage_error& ec) override;
    +   virtual void rename_file (int index, std::string const& new_filename
    +      , storage_error& ec) override;
    +   virtual void set_file_priority (std::vector<boost::uint8_t> const& prio
    +      , storage_error& ec) override;
    +   virtual void write_resume_data (entry& rd, storage_error& ec) const override;
    +   virtual void release_files (storage_error& ec) override;
    +   virtual bool has_any_file (storage_error& ec) override;
    +   int readv (file::iovec_t const* bufs, int num_bufs
    +      , int piece, int offset, int flags, storage_error& ec) override;
    +   int writev (file::iovec_t const* bufs, int num_bufs
    +      , int piece, int offset, int flags, storage_error& ec) override;
    +   file_storage const& files () const;
    +   static void disk_write_access_log (bool enable);
    +   static bool disk_write_access_log ();
    +};
    +
    +
    +

    default_storage()

    +
    +default_storage (storage_params const& params);
    +
    +

    constructs the default_storage based on the give file_storage (fs). +mapped is an optional argument (it may be NULL). If non-NULL it +represents the file mapping that have been made to the torrent before +adding it. That's where files are supposed to be saved and looked for +on disk. save_path is the root save folder for this torrent. +file_pool is the cache of file handles that the storage will use. +All files it opens will ask the file_pool to open them. file_prio +is a vector indicating the priority of files on startup. It may be +an empty vector. Any file whose index is not represented by the vector +(because the vector is too short) are assumed to have priority 1. +this is used to treat files with priority 0 slightly differently.

    +
    +
    +

    files()

    +
    +file_storage const& files () const;
    +
    +

    if the files in this storage are mapped, returns the mapped +file_storage, otherwise returns the original file_storage object.

    +
    +
    +
    +

    enum move_flags_t

    +

    Declared in "libtorrent/storage.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    always_replace_files0replace any files in the destination when copying +or moving the storage
    fail_if_exist1if any files that we want to copy exist in the destination +exist, fail the whole operation and don't perform +any copy or move. There is an inherent race condition +in this mode. The files are checked for existence before +the operation starts. In between the check and performing +the copy, the destination files may be created, in which +case they are replaced.
    dont_replace2if any file exist in the target, take those files instead +of the ones we may have in the source.
    +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/reference-Error_Codes.html b/docs/reference-Error_Codes.html new file mode 100644 index 0000000..d5125fd --- /dev/null +++ b/docs/reference-Error_Codes.html @@ -0,0 +1,1315 @@ + + + + + + + + + + + + + + + +
    +
    + + + + +
    + + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.1.1
    +
    +

    Error Codes

    + +
    +

    libtorrent_exception

    +

    Declared in "libtorrent/error_code.hpp"

    +
    +struct libtorrent_exception: std::exception
    +{
    +   libtorrent_exception (libtorrent_exception const&) = default;
    +   virtual const char* what () const TORRENT_EXCEPTION_THROW_SPECIFIER;
    +   virtual ~libtorrent_exception () TORRENT_EXCEPTION_THROW_SPECIFIER;
    +   libtorrent_exception& operator= (libtorrent_exception const&) = default;
    +   libtorrent_exception (error_code const& s);
    +   error_code error () const;
    +};
    +
    +
    +
    +

    storage_error

    +

    Declared in "libtorrent/error_code.hpp"

    +

    used by storage to return errors +also includes which underlying file the +error happened on

    +
    +struct storage_error
    +{
    +   storage_error (error_code e);
    +   storage_error ();
    +   operator bool () const;
    +   char const* operation_str () const;
    +
    +   error_code ec;
    +   boost::int32_t file:24;
    +   boost::uint32_t operation:8;
    +};
    +
    +
    +

    operation_str()

    +
    +char const* operation_str () const;
    +
    +

    Returns a string literal representing the file operation +that failed. If there were no failure, it returns +an empty string.

    +
    +
    ec
    +
    the error that occurred
    +
    +
    +
    file
    +
    the file the error occurred on
    +
    +
    +
    operation
    +
    A code from file_operation_t enum, indicating what +kind of operation failed.
    +
    +
    +
    +
    +

    get_bdecode_category()

    +

    Declared in "libtorrent/bdecode.hpp"

    +
    +boost::system::error_category& get_bdecode_category ();
    +
    +
    +
    +

    get_libtorrent_category()

    +

    Declared in "libtorrent/error_code.hpp"

    +
    +boost::system::error_category& get_libtorrent_category ();
    +
    +

    return the instance of the libtorrent_error_category which +maps libtorrent error codes to human readable error messages.

    +
    +
    +

    get_http_category()

    +

    Declared in "libtorrent/error_code.hpp"

    +
    +boost::system::error_category& get_http_category ();
    +
    +

    returns the error_category for HTTP errors

    +
    +
    +

    get_gzip_category()

    +

    Declared in "libtorrent/gzip.hpp"

    +
    +boost::system::error_category& get_gzip_category ();
    +
    +

    get the error_category for zip errors

    +
    +
    +

    get_i2p_category()

    +

    Declared in "libtorrent/i2p_stream.hpp"

    +
    +boost::system::error_category& get_i2p_category ();
    +
    +

    returns the error category for I2P errors

    +
    +
    +

    get_socks_category()

    +

    Declared in "libtorrent/socks5_stream.hpp"

    +
    +boost::system::error_category& get_socks_category ();
    +
    +

    returns the error_category for SOCKS5 errors

    +
    +
    +

    get_upnp_category()

    +

    Declared in "libtorrent/upnp.hpp"

    +
    +boost::system::error_category& get_upnp_category ();
    +
    +

    the boost.system error category for UPnP errors

    +
    +
    +

    enum error_code_enum

    +

    Declared in "libtorrent/bdecode.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    no_error0Not an error
    expected_digit1expected digit in bencoded string
    expected_colon2expected colon in bencoded string
    unexpected_eof3unexpected end of file in bencoded string
    expected_value4expected value (list, dict, int or string) in bencoded string
    depth_exceeded5bencoded recursion depth limit exceeded
    limit_exceeded6bencoded item count limit exceeded
    overflow7integer overflow
    error_code_max8the number of error codes
    +
    +
    +

    enum error_code_enum

    +

    Declared in "libtorrent/error_code.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    no_error0Not an error
    file_collision1Two torrents has files which end up overwriting each other
    failed_hash_check2A piece did not match its piece hash
    torrent_is_no_dict3The .torrent file does not contain a bencoded dictionary at +its top level
    torrent_missing_info4The .torrent file does not have an info dictionary
    torrent_info_no_dict5The .torrent file's info entry is not a dictionary
    torrent_missing_piece_length6The .torrent file does not have a piece length entry
    torrent_missing_name7The .torrent file does not have a name entry
    torrent_invalid_name8The .torrent file's name entry is invalid
    torrent_invalid_length9The length of a file, or of the whole .torrent file is invalid. +Either negative or not an integer
    torrent_file_parse_failed10Failed to parse a file entry in the .torrent
    torrent_missing_pieces11The pieces field is missing or invalid in the .torrent file
    torrent_invalid_hashes12The pieces string has incorrect length
    too_many_pieces_in_torrent13The .torrent file has more pieces than is supported by libtorrent
    invalid_swarm_metadata14The metadata (.torrent file) that was received from the swarm +matched the info-hash, but failed to be parsed
    invalid_bencoding15The file or buffer is not correctly bencoded
    no_files_in_torrent16The .torrent file does not contain any files
    invalid_escaped_string17The string was not properly url-encoded as expected
    session_is_closing18Operation is not permitted since the session is shutting down
    duplicate_torrent19There's already a torrent with that info-hash added to the +session
    invalid_torrent_handle20The supplied torrent_handle is not referring to a valid torrent
    invalid_entry_type21The type requested from the entry did not match its type
    missing_info_hash_in_uri22The specified URI does not contain a valid info-hash
    file_too_short23One of the files in the torrent was unexpectedly small. This +might be caused by files being changed by an external process
    unsupported_url_protocol24The URL used an unknown protocol. Currently http and +https (if built with openssl support) are recognized. For +trackers udp is recognized as well.
    url_parse_error25The URL did not conform to URL syntax and failed to be parsed
    peer_sent_empty_piece26The peer sent a 'piece' message of length 0
    parse_failed27A bencoded structure was corrupt and failed to be parsed
    invalid_file_tag28The fast resume file was missing or had an invalid file version +tag
    missing_info_hash29The fast resume file was missing or had an invalid info-hash
    mismatching_info_hash30The info-hash did not match the torrent
    invalid_hostname31The URL contained an invalid hostname
    invalid_port32The URL had an invalid port
    port_blocked33The port is blocked by the port-filter, and prevented the +connection
    expected_close_bracket_in_address34The IPv6 address was expected to end with ']'
    destructing_torrent35The torrent is being destructed, preventing the operation to +succeed
    timed_out36The connection timed out
    upload_upload_connection37The peer is upload only, and we are upload only. There's no point +in keeping the connection
    uninteresting_upload_peer38The peer is upload only, and we're not interested in it. There's +no point in keeping the connection
    invalid_info_hash39The peer sent an unknown info-hash
    torrent_paused40The torrent is paused, preventing the operation from succeeding
    invalid_have41The peer sent an invalid have message, either wrong size or +referring to a piece that doesn't exist in the torrent
    invalid_bitfield_size42The bitfield message had the incorrect size
    too_many_requests_when_choked43The peer kept requesting pieces after it was choked, possible +abuse attempt.
    invalid_piece44The peer sent a piece message that does not correspond to a +piece request sent by the client
    no_memory45memory allocation failed
    torrent_aborted46The torrent is aborted, preventing the operation to succeed
    self_connection47The peer is a connection to ourself, no point in keeping it
    invalid_piece_size48The peer sent a piece message with invalid size, either negative +or greater than one block
    timed_out_no_interest49The peer has not been interesting or interested in us for too +long, no point in keeping it around
    timed_out_inactivity50The peer has not said anything in a long time, possibly dead
    timed_out_no_handshake51The peer did not send a handshake within a reasonable amount of +time, it might not be a bittorrent peer
    timed_out_no_request52The peer has been unchoked for too long without requesting any +data. It might be lying about its interest in us
    invalid_choke53The peer sent an invalid choke message
    invalid_unchoke54The peer send an invalid unchoke message
    invalid_interested55The peer sent an invalid interested message
    invalid_not_interested56The peer sent an invalid not-interested message
    invalid_request57The peer sent an invalid piece request message
    invalid_hash_list58The peer sent an invalid hash-list message (this is part of the +merkle-torrent extension)
    invalid_hash_piece59The peer sent an invalid hash-piece message (this is part of the +merkle-torrent extension)
    invalid_cancel60The peer sent an invalid cancel message
    invalid_dht_port61The peer sent an invalid DHT port-message
    invalid_suggest62The peer sent an invalid suggest piece-message
    invalid_have_all63The peer sent an invalid have all-message
    invalid_have_none64The peer sent an invalid have none-message
    invalid_reject65The peer sent an invalid reject message
    invalid_allow_fast66The peer sent an invalid allow fast-message
    invalid_extended67The peer sent an invalid extension message ID
    invalid_message68The peer sent an invalid message ID
    sync_hash_not_found69The synchronization hash was not found in the encrypted handshake
    invalid_encryption_constant70The encryption constant in the handshake is invalid
    no_plaintext_mode71The peer does not support plaintext, which is the selected mode
    no_rc4_mode72The peer does not support rc4, which is the selected mode
    unsupported_encryption_mode73The peer does not support any of the encryption modes that the +client supports
    unsupported_encryption_mode_selected74The peer selected an encryption mode that the client did not +advertise and does not support
    invalid_pad_size75The pad size used in the encryption handshake is of invalid size
    invalid_encrypt_handshake76The encryption handshake is invalid
    no_incoming_encrypted77The client is set to not support incoming encrypted connections +and this is an encrypted connection
    no_incoming_regular78The client is set to not support incoming regular bittorrent +connections, and this is a regular connection
    duplicate_peer_id79The client is already connected to this peer-ID
    torrent_removed80Torrent was removed
    packet_too_large81The packet size exceeded the upper sanity check-limit
    reserved82 
    http_error83The web server responded with an error
    missing_location84The web server response is missing a location header
    invalid_redirection85The web seed redirected to a path that no longer matches the +.torrent directory structure
    redirecting86The connection was closed because it redirected to a different +URL
    invalid_range87The HTTP range header is invalid
    no_content_length88The HTTP response did not have a content length
    banned_by_ip_filter89The IP is blocked by the IP filter
    too_many_connections90At the connection limit
    peer_banned91The peer is marked as banned
    stopping_torrent92The torrent is stopping, causing the operation to fail
    too_many_corrupt_pieces93The peer has sent too many corrupt pieces and is banned
    torrent_not_ready94The torrent is not ready to receive peers
    peer_not_constructed95The peer is not completely constructed yet
    session_closing96The session is closing, causing the operation to fail
    optimistic_disconnect97The peer was disconnected in order to leave room for a +potentially better peer
    torrent_finished98The torrent is finished
    no_router99No UPnP router found
    metadata_too_large100The metadata message says the metadata exceeds the limit
    invalid_metadata_request101The peer sent an invalid metadata request message
    invalid_metadata_size102The peer advertised an invalid metadata size
    invalid_metadata_offset103The peer sent a message with an invalid metadata offset
    invalid_metadata_message104The peer sent an invalid metadata message
    pex_message_too_large105The peer sent a peer exchange message that was too large
    invalid_pex_message106The peer sent an invalid peer exchange message
    invalid_lt_tracker_message107The peer sent an invalid tracker exchange message
    too_frequent_pex108The peer sent an pex messages too often. This is a possible +attempt of and attack
    no_metadata109The operation failed because it requires the torrent to have +the metadata (.torrent file) and it doesn't have it yet. +This happens for magnet links before they have downloaded the +metadata, and also torrents added by URL.
    invalid_dont_have110The peer sent an invalid dont_have message. The don't have +message is an extension to allow peers to advertise that the +no longer has a piece they previously had.
    requires_ssl_connection111The peer tried to connect to an SSL torrent without connecting +over SSL.
    invalid_ssl_cert112The peer tried to connect to a torrent with a certificate +for a different torrent.
    not_an_ssl_torrent113the torrent is not an SSL torrent, and the operation requires +an SSL torrent
    banned_by_port_filter114peer was banned because its listen port is within a banned port +range, as specified by the port_filter.
    unsupported_protocol_version120The NAT-PMP router responded with an unsupported protocol version
    natpmp_not_authorized121You are not authorized to map ports on this NAT-PMP router
    network_failure122The NAT-PMP router failed because of a network failure
    no_resources123The NAT-PMP router failed because of lack of resources
    unsupported_opcode124The NAT-PMP router failed because an unsupported opcode was sent
    missing_file_sizes130The resume data file is missing the 'file sizes' entry
    no_files_in_resume_data131The resume data file 'file sizes' entry is empty
    missing_pieces132The resume data file is missing the 'pieces' and 'slots' entry
    mismatching_number_of_files133The number of files in the resume data does not match the number +of files in the torrent
    mismatching_file_size134One of the files on disk has a different size than in the fast +resume file
    mismatching_file_timestamp135One of the files on disk has a different timestamp than in the +fast resume file
    not_a_dictionary136The resume data file is not a dictionary
    invalid_blocks_per_piece137The 'blocks per piece' entry is invalid in the resume data file
    missing_slots138The resume file is missing the 'slots' entry, which is required +for torrents with compact allocation. DEPRECATED
    too_many_slots139The resume file contains more slots than the torrent
    invalid_slot_list140The 'slot' entry is invalid in the resume data
    invalid_piece_index141One index in the 'slot' list is invalid
    pieces_need_reorder142The pieces on disk needs to be re-ordered for the specified +allocation mode. This happens if you specify sparse allocation +and the files on disk are using compact storage. The pieces needs +to be moved to their right position. DEPRECATED
    resume_data_not_modified143this error is returned when asking to save resume data and +specifying the flag to only save when there's anything new to save +(torrent_handle::only_if_modified) and there wasn't anything changed.
    http_parse_error150The HTTP header was not correctly formatted
    http_missing_location151The HTTP response was in the 300-399 range but lacked a location +header
    http_failed_decompress152The HTTP response was encoded with gzip or deflate but +decompressing it failed
    no_i2p_router160The URL specified an i2p address, but no i2p router is configured
    no_i2p_endpoint161i2p acceptor is not available yet, can't announce without endpoint
    scrape_not_available170The tracker URL doesn't support transforming it into a scrape +URL. i.e. it doesn't contain "announce.
    invalid_tracker_response171invalid tracker response
    invalid_peer_dict172invalid peer dictionary entry. Not a dictionary
    tracker_failure173tracker sent a failure message
    invalid_files_entry174missing or invalid 'files' entry
    invalid_hash_entry175missing or invalid 'hash' entry
    invalid_peers_entry176missing or invalid 'peers' and 'peers6' entry
    invalid_tracker_response_length177udp tracker response packet has invalid size
    invalid_tracker_transaction_id178invalid transaction id in udp tracker response
    invalid_tracker_action179invalid action field in udp tracker response
    error_code_max180the number of error codes
    +
    +
    +

    enum http_errors

    +

    Declared in "libtorrent/error_code.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    cont100 
    ok200 
    created201 
    accepted202 
    no_content204 
    multiple_choices300 
    moved_permanently301 
    moved_temporarily302 
    not_modified304 
    bad_request400 
    unauthorized401 
    forbidden403 
    not_found404 
    internal_server_error500 
    not_implemented501 
    bad_gateway502 
    service_unavailable503 
    +
    +
    +

    enum error_code_enum

    +

    Declared in "libtorrent/gzip.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    no_error0Not an error
    invalid_gzip_header1the supplied gzip buffer has invalid header
    inflated_data_too_large2the gzip buffer would inflate to more bytes than the specified +maximum size, and was rejected.
    data_did_not_terminate3available inflate data did not terminate
    space_exhausted4output space exhausted before completing inflate
    invalid_block_type5invalid block type (type == 3)
    invalid_stored_block_length6stored block length did not match one's complement
    too_many_length_or_distance_codes7dynamic block code description: too many length or distance codes
    code_lengths_codes_incomplete8dynamic block code description: code lengths codes incomplete
    repeat_lengths_with_no_first_length9dynamic block code description: repeat lengths with no first length
    repeat_more_than_specified_lengths10dynamic block code description: repeat more than specified lengths
    invalid_literal_length_code_lengths11dynamic block code description: invalid literal/length code lengths
    invalid_distance_code_lengths12dynamic block code description: invalid distance code lengths
    invalid_literal_code_in_block13invalid literal/length or distance code in fixed or dynamic block
    distance_too_far_back_in_block14distance is too far back in fixed or dynamic block
    unknown_gzip_error15an unknown error occurred during gzip inflation
    error_code_max16the number of error codes
    +
    +
    +

    enum i2p_error_code

    +

    Declared in "libtorrent/i2p_stream.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    no_error0 
    parse_failed1 
    cant_reach_peer2 
    i2p_error3 
    invalid_key4 
    invalid_id5 
    timeout6 
    key_not_found7 
    duplicated_id8 
    num_errors9 
    +
    +
    +

    enum socks_error_code

    +

    Declared in "libtorrent/socks5_stream.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    no_error0 
    unsupported_version1 
    unsupported_authentication_method2 
    unsupported_authentication_version3 
    authentication_error4 
    username_required5 
    general_failure6 
    command_not_supported7 
    no_identd8 
    identd_error9 
    num_errors10 
    +
    +
    +

    enum error_code_enum

    +

    Declared in "libtorrent/upnp.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    no_error0No error
    invalid_argument402One of the arguments in the request is invalid
    action_failed501The request failed
    value_not_in_array714The specified value does not exist in the array
    source_ip_cannot_be_wildcarded715The source IP address cannot be wild-carded, but +must be fully specified
    external_port_cannot_be_wildcarded716The external port cannot be wildcarded, but must +be specified
    port_mapping_conflict718The port mapping entry specified conflicts with a +mapping assigned previously to another client
    internal_port_must_match_external724Internal and external port value must be the same
    only_permanent_leases_supported725The NAT implementation only supports permanent +lease times on port mappings
    remote_host_must_be_wildcard726RemoteHost must be a wildcard and cannot be a +specific IP addres or DNS name
    external_port_must_be_wildcard727ExternalPort must be a wildcard and cannot be a +specific port
    +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/reference-Filter.html b/docs/reference-Filter.html new file mode 100644 index 0000000..75303dd --- /dev/null +++ b/docs/reference-Filter.html @@ -0,0 +1,245 @@ + + + + + + + + + + + + + + + +
    +
    + + + + +
    + + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.1.1
    +
    +

    Filter

    +
    +

    Table of contents

    + +
    +
    +

    ip_filter

    +

    Declared in "libtorrent/ip_filter.hpp"

    +

    The ip_filter class is a set of rules that uniquely categorizes all +ip addresses as allowed or disallowed. The default constructor creates +a single rule that allows all addresses (0.0.0.0 - 255.255.255.255 for +the IPv4 range, and the equivalent range covering all addresses for the +IPv6 range).

    +

    A default constructed ip_filter does not filter any address.

    +
    +struct ip_filter
    +{
    +   void add_rule (address first, address last, boost::uint32_t flags);
    +   int access (address const& addr) const;
    +   filter_tuple_t export_filter () const;
    +
    +   enum access_flags
    +   {
    +      blocked,
    +   };
    +};
    +
    +
    +

    add_rule()

    +
    +void add_rule (address first, address last, boost::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.

    +
    +
    +

    access()

    +
    +int 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.

    +
    +
    +

    export_filter()

    +
    +filter_tuple_t export_filter () const;
    +
    +

    This function will return the current state of the filter in the minimum number of +ranges possible. They are sorted from ranges in low addresses to high addresses. Each +entry in the returned vector is a range with the access control specified in its +flags field.

    +

    The return value is a tuple containing two range-lists. One for IPv4 addresses +and one for IPv6 addresses.

    +
    +
    +

    enum access_flags

    +

    Declared in "libtorrent/ip_filter.hpp"

    + +++++ + + + + + + + + + + + + +
    namevaluedescription
    blocked1indicates that IPs in this range should not be connected +to nor accepted as incoming connections
    +
    +
    +
    +

    port_filter

    +

    Declared in "libtorrent/ip_filter.hpp"

    +

    the port filter maps non-overlapping port ranges to flags. This +is primarily used to indicate whether a range of ports should +be connected to or not. The default is to have the full port +range (0-65535) set to flag 0.

    +
    +class port_filter
    +{
    +   void add_rule (boost::uint16_t first, boost::uint16_t last, boost::uint32_t flags);
    +   int access (boost::uint16_t port) const;
    +
    +   enum access_flags
    +   {
    +      blocked,
    +   };
    +};
    +
    +
    +

    add_rule()

    +
    +void add_rule (boost::uint16_t first, boost::uint16_t last, boost::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.

    +
    +
    +

    access()

    +
    +int access (boost::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 acces_flags.

    +
    +
    +

    enum access_flags

    +

    Declared in "libtorrent/ip_filter.hpp"

    + +++++ + + + + + + + + + + + + +
    namevaluedescription
    blocked1this flag indicates that destination ports in the +range should not be connected to
    +
    +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/reference-Plugins.html b/docs/reference-Plugins.html new file mode 100644 index 0000000..7751328 --- /dev/null +++ b/docs/reference-Plugins.html @@ -0,0 +1,778 @@ + + + + + + + + + + + + + + + +
    +
    + + + + +
    + + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.1.1
    +
    +

    Plugins

    + +

    libtorrent has a plugin interface for implementing extensions to the protocol. +These can be general extensions for transferring metadata or peer exchange +extensions, or it could be used to provide a way to customize the protocol +to fit a particular (closed) network.

    +

    In short, the plugin interface makes it possible to:

    +
      +
    • register extension messages (sent in the extension handshake), see +extensions.
    • +
    • add data and parse data from the extension handshake.
    • +
    • send extension messages and standard bittorrent messages.
    • +
    • override or block the handling of standard bittorrent messages.
    • +
    • save and restore state via the session state
    • +
    • see all alerts that are posted
    • +
    +
    +

    a word of caution

    +

    Writing your own plugin is a very easy way to introduce serious bugs such as +dead locks and race conditions. Since a plugin has access to internal +structures it is also quite easy to sabotage libtorrent's operation.

    +

    All the callbacks in this interface are called with the main libtorrent thread +mutex locked. And they 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 the mutex to be locked. Furthermore, you would +also need to have a mutex on 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). See boost thread's mutex. If you need to send out a message from +another thread, it is advised to use an internal queue, and do the actual +sending in tick().

    +

    Since the plugin interface gives you easy access to internal structures, it +is not supported as a stable API. Plugins should be considered specific to a +specific version of libtorrent. Although, in practice the internals mostly +don't change that dramatically.

    +
    +
    +
    +

    plugin-interface

    +

    The plugin interface consists of three base classes that the plugin may +implement. These are called plugin, torrent_plugin and peer_plugin. +They are found in the <libtorrent/extensions.hpp> header.

    +

    These plugins are instantiated for each session, torrent and possibly each peer, +respectively.

    +

    For plugins that only need per torrent state, it is enough to only implement +torrent_plugin and pass a constructor function or function object to +session::add_extension() or torrent_handle::add_extension() (if the +torrent has already been started and you want to hook in the extension at +run-time).

    +

    The signature of the function is:

    +
    +boost::shared_ptr<torrent_plugin> (*)(torrent_handle const&, void*);
    +
    +

    The second argument is the userdata passed to session::add_torrent() or +torrent_handle::add_extension().

    +

    The function should return a boost::shared_ptr<torrent_plugin> which +may or may not be 0. If it is a null pointer, 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 boost::shared_ptr) and pass +it in to session::add_extension().

    +
    +
    +

    custom alerts

    +

    Since plugins are running within internal libtorrent threads, one convenient +way to communicate with the client is to post custom alerts.

    +

    The expected interface of any alert, apart from deriving from the alert +base class, looks like this:

    +
    +static const int alert_type = <unique alert ID>;
    +virtual int type() const { return alert_type; }
    +
    +virtual std::string message() const;
    +
    +virtual std::auto_ptr<alert> clone() const
    +{ return std::auto_ptr<alert>(new name(*this)); }
    +
    +static const int static_category = <bitmask of alert::category_t flags>;
    +virtual int category() const { return static_category; }
    +
    +virtual char const* what() const { return <string literal of the name of this alert>; }
    +
    +

    The alert_type is used for the type-checking in alert_cast. It must +not collide with any other alert. The built-in alerts in libtorrent will +not use alert type IDs greater than user_alert_id. When defining your +own alert, make sure it's greater than this constant.

    +

    type() is the run-time equivalence of the alert_type.

    +

    The message() virtual function is expected to construct a useful +string representation of the alert and the event or data it represents. +Something convenient to put in a log file for instance.

    +

    clone() is used internally to copy alerts. The suggested implementation +of simply allocating a new instance as a copy of *this is all that's +expected.

    +

    The static category is required for checking whether or not the category +for a specific alert is enabled or not, without instantiating the alert. +The category virtual function is the run-time equivalence.

    +

    The what() virtual function may simply be a string literal of the class +name of your alert.

    +

    For more information, see the alert section.

    +
    +

    plugin

    +

    Declared in "libtorrent/extensions.hpp"

    +

    this is the base class for a session plugin. One primary feature +is that it is notified of all torrents that are added to the session, +and can add its own torrent_plugins.

    +
    +struct plugin
    +{
    +   virtual boost::uint32_t implemented_features ();
    +   virtual boost::shared_ptr<torrent_plugin> new_torrent (torrent_handle const&, void*);
    +   virtual void added (session_handle);
    +   virtual void register_dht_extensions (dht_extensions_t&);
    +   virtual void on_alert (alert const*);
    +   virtual bool on_unknown_torrent (sha1_hash const& /* info_hash */
    +      , peer_connection_handle const& /* pc */, add_torrent_params& /* p */);
    +   virtual void on_tick ();
    +   virtual bool on_optimistic_unchoke (std::vector<peer_connection_handle>& /* peers */);
    +   virtual void save_state (entry&) const;
    +   virtual void load_state (bdecode_node const&);
    +
    +   enum feature_flags_t
    +   {
    +      optimistic_unchoke_feature,
    +      tick_feature,
    +   };
    +};
    +
    +
    +

    implemented_features()

    +
    +virtual boost::uint32_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.

    +
    +
    +

    new_torrent()

    +
    +virtual boost::shared_ptr<torrent_plugin> new_torrent (torrent_handle const&, void*);
    +
    +

    this is called by the session every time a new torrent is added. +The torrent* points to the internal torrent object created +for the new torrent. The void* is the userdata pointer as +passed in via add_torrent_params.

    +

    If the plugin returns a torrent_plugin instance, it will be added +to the new torrent. Otherwise, return an empty shared_ptr to a +torrent_plugin (the default).

    +
    +
    +

    added()

    +
    +virtual void added (session_handle);
    +
    +

    called when plugin is added to a session

    +
    +
    +

    register_dht_extensions()

    +
    +virtual void register_dht_extensions (dht_extensions_t&);
    +
    +

    called after a plugin is added +allows the plugin to register DHT requests it would like to handle

    +
    +
    +

    on_alert()

    +
    +virtual void on_alert (alert const*);
    +
    +

    called when an alert is posted alerts that are filtered are not posted

    +
    +
    +

    on_unknown_torrent()

    +
    +virtual bool on_unknown_torrent (sha1_hash const& /* info_hash */
    +      , peer_connection_handle const& /* pc */, add_torrent_params& /* p */);
    +
    +

    return true if the add_torrent_params should be added

    +
    +
    +

    on_tick()

    +
    +virtual void on_tick ();
    +
    +

    called once per second

    +
    +
    +

    on_optimistic_unchoke()

    +
    +virtual bool on_optimistic_unchoke (std::vector<peer_connection_handle>& /* peers */);
    +
    +

    called when choosing peers to optimistically unchoke. peer's will be +unchoked in the order they appear in the given vector. if +the plugin returns true then the ordering provided will be used and no +other plugin will be allowed to change it. If your plugin expects this +to be called, make sure to include the flag +optimistic_unchoke_feature in the return value from +implemented_features().

    +
    +
    +

    save_state()

    +
    +virtual void save_state (entry&) const;
    +
    +

    called when saving settings state

    +
    +
    +

    load_state()

    +
    +virtual void load_state (bdecode_node const&);
    +
    +

    called when loading settings state

    +
    +
    +

    enum feature_flags_t

    +

    Declared in "libtorrent/extensions.hpp"

    + +++++ + + + + + + + + + + + + + + + + +
    namevaluedescription
    optimistic_unchoke_feature1include 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.
    tick_feature2include this bit if your plugin needs to have on_tick() called
    +
    +
    +
    +

    torrent_plugin

    +

    Declared in "libtorrent/extensions.hpp"

    +

    Torrent plugins are associated with a single torrent and have a number +of functions called at certain events. Many of its functions have the +ability to change or override the default libtorrent behavior.

    +
    +struct torrent_plugin
    +{
    +   virtual boost::shared_ptr<peer_plugin> new_connection (peer_connection_handle const&);
    +   virtual void on_piece_pass (int /*index*/);
    +   virtual void on_piece_failed (int /*index*/);
    +   virtual void tick ();
    +   virtual bool on_resume ();
    +   virtual bool on_pause ();
    +   virtual void on_files_checked ();
    +   virtual void on_state (int /*s*/);
    +   virtual void on_unload ();
    +   virtual void on_load ();
    +   virtual void on_add_peer (tcp::endpoint const&,
    +      int /*src*/, int /*flags*/);
    +};
    +
    +
    +

    new_connection()

    +
    +virtual boost::shared_ptr<peer_plugin> new_connection (peer_connection_handle const&);
    +
    +

    This function is called each time a new peer is connected to the torrent. You +may choose to ignore this by just returning a default constructed +shared_ptr (in which case you don't need to override this member +function).

    +

    If you need an extension to the peer connection (which most plugins do) you +are supposed to return an instance of your peer_plugin class. Which in +turn will have its hook functions called on event specific to that peer.

    +

    The peer_connection_handle will be valid as long as the shared_ptr +is being held by the torrent object. So, it is generally a good idea to not +keep a shared_ptr to your own peer_plugin. If you want to keep references +to it, use weak_ptr.

    +

    If this function throws an exception, the connection will be closed.

    + +
    +
    +

    on_piece_failed() on_piece_pass()

    +
    +virtual void on_piece_pass (int /*index*/);
    +virtual void on_piece_failed (int /*index*/);
    +
    +

    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.

    +
    +
    +

    tick()

    +
    +virtual void tick ();
    +
    +

    This hook is called approximately once per second. It is a way of making it +easy for plugins to do timed events, for sending messages or whatever.

    + +
    +
    +

    on_resume() on_pause()

    +
    +virtual bool on_resume ();
    +virtual bool on_pause ();
    +
    +

    These hooks are called when the torrent is paused and unpaused 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 unpause.

    +

    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.

    +
    +
    +

    on_files_checked()

    +
    +virtual void on_files_checked ();
    +
    +

    This function is called when the initial files of the torrent have been +checked. If there are no files to check, this function is called immediately.

    +

    i.e. This function is always called when the torrent is in a state where it +can start downloading.

    +
    +
    +

    on_state()

    +
    +virtual void on_state (int /*s*/);
    +
    +

    called when the torrent changes state +the state is one of torrent_status::state_t +enum members

    + +
    +
    +

    on_unload() on_load()

    +
    +virtual void on_unload ();
    +virtual void on_load ();
    +
    +

    called when the torrent is unloaded from RAM +and loaded again, respectively +unload is called right before the torrent is +unloaded and load is called right after it's +loaded. i.e. the full torrent state is available +when these callbacks are called.

    +
    +
    +

    on_add_peer()

    +
    +virtual void on_add_peer (tcp::endpoint const&,
    +      int /*src*/, int /*flags*/);
    +
    +

    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.

    +
    +
    +
    +

    peer_plugin

    +

    Declared in "libtorrent/extensions.hpp"

    +

    peer plugins are associated with a specific peer. A peer could be +both a regular bittorrent peer (bt_peer_connection) or one of the +web seed connections (web_peer_connection or http_seed_connection). +In order to only attach to certain peers, make your +torrent_plugin::new_connection only return a plugin for certain peer +connection types

    +
    +struct peer_plugin
    +{
    +   virtual char const* type () const;
    +   virtual void add_handshake (entry&);
    +   virtual void on_disconnect (error_code const& /*ec*/);
    +   virtual void on_connected ();
    +   virtual bool on_handshake (char const* /*reserved_bits*/);
    +   virtual bool on_extension_handshake (bdecode_node const&);
    +   virtual bool on_have (int /*index*/);
    +   virtual bool on_bitfield (bitfield const& /*bitfield*/);
    +   virtual bool on_have_all ();
    +   virtual bool on_reject (peer_request const&);
    +   virtual bool on_request (peer_request const&);
    +   virtual bool on_unchoke ();
    +   virtual bool on_interested ();
    +   virtual bool on_allowed_fast (int /*index*/);
    +   virtual bool on_have_none ();
    +   virtual bool on_choke ();
    +   virtual bool on_not_interested ();
    +   virtual bool on_piece (peer_request const& /*piece*/
    +      , disk_buffer_holder& /*data*/);
    +   virtual bool on_suggest (int /*index*/);
    +   virtual bool on_cancel (peer_request const&);
    +   virtual bool on_dont_have (int /*index*/);
    +   virtual void sent_unchoke ();
    +   virtual void sent_payload (int /* bytes */);
    +   virtual bool can_disconnect (error_code const& /*ec*/);
    +   virtual bool on_extended (int /*length*/, int /*msg*/,
    +      buffer::const_interval /*body*/);
    +   virtual bool on_unknown_message (int /*length*/, int /*msg*/,
    +      buffer::const_interval /*body*/);
    +   virtual void on_piece_pass (int /*index*/);
    +   virtual void on_piece_failed (int /*index*/);
    +   virtual void tick ();
    +   virtual bool write_request (peer_request const&);
    +};
    +
    +
    +

    type()

    +
    +virtual char const* type () const;
    +
    +

    This function is expected to return the name of +the plugin.

    +
    +
    +

    add_handshake()

    +
    +virtual void add_handshake (entry&);
    +
    +

    can add entries to the extension handshake +this is not called for web seeds

    +
    +
    +

    on_disconnect()

    +
    +virtual void on_disconnect (error_code const& /*ec*/);
    +
    +

    called when the peer is being disconnected.

    +
    +
    +

    on_connected()

    +
    +virtual void on_connected ();
    +
    +

    called when the peer is successfully connected. Note that +incoming connections will have been connected by the time +the peer plugin is attached to it, and won't have this hook +called.

    +
    +
    +

    on_handshake()

    +
    +virtual bool on_handshake (char const* /*reserved_bits*/);
    +
    +

    this is called when the initial BT 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

    +
    +
    +

    on_extension_handshake()

    +
    +virtual bool on_extension_handshake (bdecode_node const&);
    +
    +

    called when the extension handshake from the other end is received +if this returns false, it means that this extension isn't +supported by this peer. It will result in this peer_plugin +being removed from the peer_connection and destructed. +this is not called for web seeds

    + + + + + + + + + + + + + + +
    +
    +

    on_bitfield() on_have_none() on_suggest() on_unchoke() on_cancel() on_have() on_choke() on_piece() on_request() on_reject() on_not_interested() on_interested() on_allowed_fast() on_have_all() on_dont_have()

    +
    +virtual bool on_have (int /*index*/);
    +virtual bool on_bitfield (bitfield const& /*bitfield*/);
    +virtual bool on_have_all ();
    +virtual bool on_reject (peer_request const&);
    +virtual bool on_request (peer_request const&);
    +virtual bool on_unchoke ();
    +virtual bool on_interested ();
    +virtual bool on_allowed_fast (int /*index*/);
    +virtual bool on_have_none ();
    +virtual bool on_choke ();
    +virtual bool on_not_interested ();
    +virtual bool on_piece (peer_request const& /*piece*/
    +      , disk_buffer_holder& /*data*/);
    +virtual bool on_suggest (int /*index*/);
    +virtual bool on_cancel (peer_request const&);
    +virtual bool on_dont_have (int /*index*/);
    +
    +

    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.

    +
    +
    +

    sent_unchoke()

    +
    +virtual void sent_unchoke ();
    +
    +

    called after a choke message has been sent to the peer

    +
    +
    +

    sent_payload()

    +
    +virtual void sent_payload (int /* bytes */);
    +
    +

    called after piece data has been sent to the peer +this can be used for stats book keeping

    +
    +
    +

    can_disconnect()

    +
    +virtual bool can_disconnect (error_code const& /*ec*/);
    +
    +

    called when libtorrent think this peer should be disconnected. +if the plugin returns false, the peer will not be disconnected.

    +
    +
    +

    on_extended()

    +
    +virtual bool on_extended (int /*length*/, int /*msg*/,
    +      buffer::const_interval /*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.

    +
    +
    +

    on_unknown_message()

    +
    +virtual bool on_unknown_message (int /*length*/, int /*msg*/,
    +      buffer::const_interval /*body*/);
    +
    +

    this is not called for web seeds

    + +
    +
    +

    on_piece_failed() on_piece_pass()

    +
    +virtual void on_piece_pass (int /*index*/);
    +virtual void on_piece_failed (int /*index*/);
    +
    +

    called when a piece that this peer participated in either +fails or passes the hash_check

    +
    +
    +

    tick()

    +
    +virtual void tick ();
    +
    +

    called approximately once every second

    +
    +
    +

    write_request()

    +
    +virtual bool write_request (peer_request const&);
    +
    +

    called each time a request message is to be sent. If true +is returned, the original request message won't be sent and +no other plugin will have this function called.

    +
    +
    +
    +

    crypto_plugin

    +

    Declared in "libtorrent/extensions.hpp"

    +
    +struct crypto_plugin
    +{
    +   virtual void set_incoming_key (unsigned char const* key, int len) = 0;
    +   virtual void set_outgoing_key (unsigned char const* key, int len) = 0;
    +   virtual int encrypt (std::vector<boost::asio::mutable_buffer>& /*send_vec*/) = 0;
    +   virtual void decrypt (std::vector<boost::asio::mutable_buffer>& /*receive_vec*/
    +      , int& /* consume */, int& /*produce*/, int& /*packet_size*/) = 0;
    +};
    +
    +
    +

    encrypt()

    +
    +virtual int encrypt (std::vector<boost::asio::mutable_buffer>& /*send_vec*/) = 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 retrieved from the passed in vector. The +vector must be cleared if no additional data is to be inserted.

    +
    +
    +

    decrypt()

    +
    +virtual void decrypt (std::vector<boost::asio::mutable_buffer>& /*receive_vec*/
    +      , int& /* consume */, int& /*produce*/, int& /*packet_size*/) = 0;
    +
    +

    decrypt the provided buffers. +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

    +
    +
    +
    +

    create_smart_ban_plugin()

    +

    Declared in "libtorrent/extensions/smart_ban.hpp"

    +
    +boost::shared_ptr<torrent_plugin> create_smart_ban_plugin (torrent_handle const&, void*);
    +
    +

    constructor function for the smart ban extension. The extension keeps +track of the data peers have sent us for failing pieces and once the +piece completes and passes the hash check bans the peers that turned +out to have sent corrupt data. +This function can either be passed in the add_torrent_params::extensions +field, or via torrent_handle::add_extension().

    +
    +
    +

    create_ut_metadata_plugin()

    +

    Declared in "libtorrent/extensions/ut_metadata.hpp"

    +
    +boost::shared_ptr<torrent_plugin> create_ut_metadata_plugin (torrent_handle const&, void*);
    +
    +

    constructor function for the ut_metadata extension. The ut_metadata +extension allows peers to request the .torrent file (or more +specifically the 'info'-dictionary of the .torrent file) from each +other. This is the main building block in making magnet links work. +This extension is enabled by default unless explicitly disabled in +the session constructor.

    +

    This can either be passed in the add_torrent_params::extensions field, or +via torrent_handle::add_extension().

    +
    +
    +

    create_ut_pex_plugin()

    +

    Declared in "libtorrent/extensions/ut_pex.hpp"

    +
    +boost::shared_ptr<torrent_plugin> create_ut_pex_plugin (torrent_handle const&, void*);
    +
    +

    constructor function for the ut_pex extension. The ut_pex +extension allows peers to gossip about their connections, allowing +the swarm stay well connected and peers aware of more peers in the +swarm. This extension is enabled by default unless explicitly disabled in +the session constructor.

    +

    This can either be passed in the add_torrent_params::extensions field, or +via torrent_handle::add_extension().

    +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/reference-Settings.html b/docs/reference-Settings.html new file mode 100644 index 0000000..b36bf85 --- /dev/null +++ b/docs/reference-Settings.html @@ -0,0 +1,6800 @@ + + + + + + + + + + + + + + + +
    +
    + + + + +
    + + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.1.1
    +
    +

    Settings

    + +

    You have some control over session configuration through the session::apply_settings() +member function. To change one or more configuration options, create a settings_pack. +object and fill it with the settings to be set and pass it in to session::apply_settings().

    +

    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.

    +
    +

    dht_settings

    +

    Declared in "libtorrent/session_settings.hpp"

    +

    structure used to hold configuration options for the DHT

    +

    The dht_settings struct used to contain a service_port member to +control which port the DHT would listen on and send messages from. This +field is deprecated and ignored. libtorrent always tries to open the UDP +socket on the same port as the TCP socket.

    +
    +struct dht_settings
    +{
    +   dht_settings ();
    +
    +   int max_peers_reply;
    +   int search_branching;
    +   int max_fail_count;
    +   int max_torrents;
    +   int max_dht_items;
    +   int max_peers;
    +   int max_torrent_search_reply;
    +   bool restrict_routing_ips;
    +   bool restrict_search_ips;
    +   bool extended_routing_table;
    +   bool aggressive_lookups;
    +   bool privacy_lookups;
    +   bool enforce_node_id;
    +   bool ignore_dark_internet;
    +   int block_timeout;
    +   int block_ratelimit;
    +   bool read_only;
    +   int item_lifetime;
    +};
    +
    +
    +

    dht_settings()

    +
    +dht_settings ();
    +
    +

    initialized dht_settings to the default values

    +
    +
    max_peers_reply
    +
    the maximum number of peers to send in a reply to get_peers
    +
    +
    +
    search_branching
    +
    the number of concurrent search request the node will send when +announcing and refreshing the routing table. This parameter is called +alpha in the kademlia paper
    +
    +
    +
    max_fail_count
    +
    the maximum number of failed tries to contact a node before it is +removed from the routing table. If there are known working nodes that +are ready to replace a failing node, it will be replaced immediately, +this limit is only used to clear out nodes that don't have any node +that can replace them.
    +
    +
    +
    max_torrents
    +
    the total number of torrents to track from the DHT. This is simply an +upper limit to make sure malicious DHT nodes cannot make us allocate +an unbounded amount of memory.
    +
    +
    +
    max_dht_items
    +
    max number of items the DHT will store
    +
    +
    +
    max_peers
    +
    the max number of peers to store per torrent (for the DHT)
    +
    +
    +
    max_torrent_search_reply
    +
    the max number of torrents to return in a torrent search query to the +DHT
    +
    +
    +
    restrict_routing_ips
    +

    determines if the routing table entries should restrict entries to one +per IP. This defaults to true, which helps mitigate some attacks on +the DHT. It prevents adding multiple nodes with IPs with a very close +CIDR distance.

    +

    when set, nodes whose IP address that's in the same /24 (or /64 for +IPv6) range in the same routing table bucket. This is an attempt to +mitigate node ID spoofing attacks also restrict any IP to only have a +single entry in the whole routing table

    +
    +
    +
    +
    restrict_search_ips
    +
    determines if DHT searches should prevent adding nodes with IPs with +very close CIDR distance. This also defaults to true and helps +mitigate certain attacks on the DHT.
    +
    +
    +
    extended_routing_table
    +
    makes the first buckets in the DHT routing table fit 128, 64, 32 and +16 nodes respectively, as opposed to the standard size of 8. All other +buckets have size 8 still.
    +
    +
    +
    aggressive_lookups
    +
    slightly changes the lookup behavior in terms of how many outstanding +requests we keep. Instead of having branch factor be a hard limit, we +always keep branch factor outstanding requests to the closest nodes. +i.e. every time we get results back with closer nodes, we query them +right away. It lowers the lookup times at the cost of more outstanding +queries.
    +
    +
    +
    privacy_lookups
    +
    when set, perform lookups in a way that is slightly more expensive, +but which minimizes the amount of information leaked about you.
    +
    +
    +
    enforce_node_id
    +
    when set, node's whose IDs that are not correctly generated based on +its external IP are ignored. When a query arrives from such node, an +error message is returned with a message saying "invalid node ID".
    +
    +
    +
    ignore_dark_internet
    +
    ignore DHT messages from parts of the internet we wouldn't expect to +see any traffic from
    +
    +
    +
    block_timeout
    +
    the number of seconds a DHT node is banned if it exceeds the rate +limit. The rate limit is averaged over 10 seconds to allow for bursts +above the limit.
    +
    +
    +
    block_ratelimit
    +
    the max number of packets per second a DHT node is allowed to send +without getting banned.
    +
    +
    +
    read_only
    +
    when set, the other nodes won't keep this node in their routing +tables, it's meant for low-power and/or ephemeral devices that +cannot support the DHT, it is also useful for mobile devices which +are sensitive to network traffic and battery life. +this node no longer responds to 'query' messages, and will place a +'ro' key (value = 1) in the top-level message dictionary of outgoing +query messages.
    +
    +
    +
    item_lifetime
    +
    the number of seconds a immutable/mutable item will be expired. +default is 0, means never expires.
    +
    +
    +
    +
    +

    settings_pack

    +

    Declared in "libtorrent/settings_pack.hpp"

    +

    The settings_pack struct, contains the names of all settings as +enum values. These values are passed in to the set_str(), +set_int(), set_bool() functions, to specify the setting to +change.

    +

    These are the available settings:

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    user_agentstring"libtorrent/" LIBTORRENT_VERSION
    +

    this is the client identification to the tracker. The recommended +format of this string is: "ClientName/ClientVersion +libtorrent/libtorrentVersion". This name will not only be used when +making HTTP requests, but also when sending extended headers to +peers that support that extension. It may not contain r or n

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    announce_ipstring0
    +

    announce_ip is the ip address passed along to trackers as the +&ip= parameter. If left as the default, that parameter is +omitted.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    mmap_cachestring0
    +

    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.

    +

    Since this setting sets a hard upper limit on cache usage, it +cannot be combined with +settings_pack::contiguous_recv_buffer, since that feature +treats the cache_size setting as a soft (but still pretty hard) +limit. The result of combining the two is peers being disconnected +after failing to allocate more disk buffers.

    +

    This feature requires the mmap system call, on systems that +don't have mmap this setting is ignored.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    handshake_client_versionstring0
    +

    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

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    outgoing_interfacesstring""
    +

    sets the network interface this session will use when it opens +outgoing connections. By default, it binds outgoing connections to +INADDR_ANY and port 0 (i.e. let the OS decide). Ths parameter must +be a string containing one or more, comma separated, adapter names. +Adapter names on unix systems are of the form "eth0", "eth1", +"tun0", etc. When specifying multiple interfaces, they will be +assigned in round-robin order. 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. +Setting this to an empty string will disable binding of outgoing +connections.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    listen_interfacesstring"0.0.0.0:6881"
    +

    a comma-separated list of IP port-pairs. These +are the listen ports that will be opened for accepting incoming uTP +and TCP connections. It is possible to listen on multiple +IPs and multiple ports. Binding to port 0 will make the +operating system pick the port. The default is "0.0.0.0:6881", which +binds to all interfaces on port 6881. +if binding fails, the listen_failed_alert is posted, potentially +more than once. Once/if binding the listen socket(s) succeed, +listen_succeeded_alert is posted. +Each port will attempt to open both a UDP and a TCP listen socket, +to allow accepting uTP connections as well as TCP. If using the DHT, +this will also make the DHT use the same UDP ports.

    +
    +

    Note

    +

    The current support for opening arbitrary UDP sockets is limited. +In this version of libtorrent, there will only ever be two UDP +sockets, one for IPv4 and one for IPv6.

    +
    + +++++ + + + + + + + + + + + + +
    nametypedefault
    proxy_hostnamestring""
    +

    when using a poxy, this is the hostname where the proxy is running +see proxy_type.

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    proxy_usernamestring""
    proxy_passwordstring""
    +

    when using a proxy, these are the credentials (if any) to use whne +connecting to it. see proxy_type

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    i2p_hostnamestring""
    +

    sets the i2p SAM bridge to connect to. set the port with the +i2p_port setting.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    peer_fingerprintstring"-LT1110-"
    +

    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 +used as the peer-id

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    dht_bootstrap_nodesstring"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. This setting will contain one or more bootstrap nodes +by default.

    +

    Changing these after the DHT has been started may not have any +effect until the DHT is restarted.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    allow_multiple_connections_per_ipboolfalse
    +

    determines if connections from the same IP address as existing +connections should be rejected or not. Multiple connections from +the same IP address is not allowed by default, to prevent abusive +behavior by peers. It may be useful to allow such connections in +cases where simulations are run on the same machine, and all peers +in a swarm has the same IP address.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    send_redundant_havebooltrue
    +

    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. +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. +Default is false.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    lazy_bitfieldsboolfalse
    +

    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.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    use_dht_as_fallbackboolfalse
    +

    use_dht_as_fallback determines how the DHT is used. If this is +true, the DHT will only be used for torrents where all trackers in +its tracker list has failed. Either by an explicit error message or +a time out. This is false by default, which means the DHT is used +by default regardless of if the trackers fail or not.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    upnp_ignore_nonroutersboolfalse
    +

    upnp_ignore_nonrouters indicates whether or not the UPnP +implementation should ignore any broadcast response from a device +whose address is not the configured router for this machine. i.e. +it's a way to not talk to other people's routers by mistake.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    use_parole_modebooltrue
    +

    use_parole_mode specifies if parole mode should be used. Parole +mode means that peers that participate in pieces that fail the hash +check are put in a mode where they are only allowed to download +whole pieces. If the whole piece a peer in parole mode fails the +hash check, it is banned. If a peer participates in a piece that +passes the hash check, it is taken out of parole mode.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    use_read_cachebooltrue
    +

    enable and disable caching of blocks read from disk. the purpose of +the read cache is partly read-ahead of requests but also to avoid +reading blocks back from the disk multiple times for popular +pieces.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    dont_flush_write_cacheboolfalse
    +

    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

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    coalesce_readsboolfalse
    coalesce_writesboolfalse
    +

    allocate separate, contiguous, buffers for read and write calls. +Only used where writev/readv cannot be used will use more RAM but +may improve performance

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    auto_manage_prefer_seedsboolfalse
    +

    prefer seeding torrents when determining which torrents to give +active slots to, the default is false which gives preference to +downloading torrents

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    dont_count_slow_torrentsbooltrue
    +

    if dont_count_slow_torrents is true, torrents without any +payload transfers are not subject to the active_seeds and +active_downloads limits. This is intended to make it more +likely to utilize all available bandwidth, and avoid having +torrents that don't transfer anything block the active slots.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    close_redundant_connectionsbooltrue
    +

    close_redundant_connections specifies whether libtorrent should +close connections where both ends have no utility in keeping the +connection open. For instance if both ends have completed their +downloads, there's no point in keeping it open.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    prioritize_partial_piecesboolfalse
    +

    If prioritize_partial_pieces is true, partial pieces are picked +before pieces that are more rare. If false, rare pieces are always +prioritized, unless the number of partial pieces is growing out of +proportion.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    rate_limit_ip_overheadbooltrue
    +

    if set to true, the estimated TCP/IP overhead is drained from the +rate limiters, to avoid exceeding the limits with the total traffic

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    announce_to_all_tiersboolfalse
    announce_to_all_trackersboolfalse
    +

    announce_to_all_trackers controls how multi tracker torrents +are treated. If this is set to true, all trackers in the same tier +are announced to in parallel. If all trackers in tier 0 fails, all +trackers in tier 1 are announced as well. If it's set to false, the +behavior is as defined by the multi tracker specification. It +defaults to false, which is the same behavior previous versions of +libtorrent has had as well.

    +

    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. This is false by +default in order to comply with the multi-tracker specification.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    prefer_udp_trackersbooltrue
    +

    prefer_udp_trackers is true by default. It 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 trackers' tier is respected and there's no +preference of one protocol over another.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    strict_super_seedingboolfalse
    +

    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.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    disable_hash_checksboolfalse
    +

    when set to true, all data downloaded from peers will be assumed to +be correct, and not tested to match the hashes in the torrent this +is only useful for simulation and testing purposes (typically +combined with disabled_storage)

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    allow_i2p_mixedboolfalse
    +

    if this is true, i2p torrents are allowed to also get peers from +other sources than the tracker, and connect to regular IPs, not +providing any anonymization. This may be useful if the user is not +interested in the anonymization of i2p, but still wants to be able +to connect to i2p peers.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    low_prio_diskbooltrue
    +

    low_prio_disk determines if the disk I/O should use a normal or +low priority policy. This defaults to true, which 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.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    volatile_read_cacheboolfalse
    +

    volatile_read_cache, if this is set to true, read cache blocks +that are hit by peer read requests are removed from the disk cache +to free up more space. This is useful if you don't expect the disk +cache to create any cache hits from other peers than the one who +triggered the cache line to be read into the cache in the first +place.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    guided_read_cacheboolfalse
    +

    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.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    no_atime_storagebooltrue
    +

    no_atime_storage this is a linux-only option and passes in the +O_NOATIME to open() when opening files. This may lead to +some disk performance improvements.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    incoming_starts_queued_torrentsboolfalse
    +

    incoming_starts_queued_torrents defaults to false. If a torrent +has been paused by the auto managed feature in libtorrent, i.e. the +torrent is paused and auto managed, this feature affects whether or +not it is automatically started on an incoming connection. The main +reason to queue torrents, is not to make them unavailable, but to +save on the overhead of announcing to the trackers, the DHT and to +avoid spreading one's unchoke slots too thin. If a peer managed to +find us, even though we're no in the torrent anymore, this setting +can make us start the torrent and serve it.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    report_true_downloadedboolfalse
    +

    when set to true, the downloaded counter sent to trackers will +include the actual number of payload bytes downloaded including +redundant bytes. If set to false, it will not include any redundancy +bytes

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    strict_end_game_modebooltrue
    +

    strict_end_game_mode defaults to true, and controls when a +block may be requested twice. If this is true, a block may only +be requested twice when there's ay least one request to every piece +that's left to download in the torrent. This may slow down progress +on some pieces sometimes, but it may also avoid downloading a lot +of redundant bytes. If this is false, libtorrent attempts to +use each peer connection to its max, by always requesting +something, even if it means requesting something that has been +requested from another peer already.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    broadcast_lsdbooltrue
    +

    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.

    + + + + +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
    nametypedefault
    enable_outgoing_utpbooltrue
    enable_incoming_utpbooltrue
    enable_outgoing_tcpbooltrue
    enable_incoming_tcpbooltrue
    +

    when set to true, libtorrent will try to make outgoing utp +connections controls whether libtorrent will accept incoming +connections or make outgoing connections of specific type.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    ignore_resume_timestampsboolfalse
    +

    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. This defaults to +false, which 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.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    no_recheck_incomplete_resumeboolfalse
    +

    no_recheck_incomplete_resume determines if the storage should +check the whole files when resume data is incomplete or missing or +whether it should simply assume we don't have any of the data. By +default, this is determined by the existence of any of the files. +By setting this setting to true, the files won't be checked, but +will go straight to download mode.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    anonymous_modeboolfalse
    +

    anonymous_mode defaults to false. When set to true, the client +tries to hide its identity to a certain degree. The peer-ID will no +longer include the client's fingerprint. The user-agent will be +reset to an empty string. Trackers will only be used if they are +using a proxy server. The listen sockets are closed, and incoming +connections will only be accepted through a SOCKS5 or I2P proxy (if +a peer proxy is set up and is run on the same machine as the +tracker proxy). Since no incoming connections are accepted, +NAT-PMP, UPnP, DHT and local peer discovery are all turned off when +this setting is enabled.

    +

    If you're using I2P, it might make sense to enable anonymous mode +as well.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    report_web_seed_downloadsbooltrue
    +

    specifies whether downloads from web seeds is reported to the +tracker or not. Defaults to on. Turning it off also excludes web +seed traffic from other stats and download rate reporting via the +libtorrent API.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    announce_double_natboolfalse
    +

    set to true if uTP connections should be rate limited This option +is DEPRECATED, please use set_peer_class_filter() instead. +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

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    seeding_outgoing_connectionsbooltrue
    +

    seeding_outgoing_connections determines if seeding (and +finished) torrents should attempt to make outgoing connections or +not. By default this is true. It may be set to false in very +specific applications where the cost of making outgoing connections +is high, and there are no or small benefits of doing so. For +instance, if no nodes are behind a firewall or a NAT, seeds don't +need to make outgoing connections.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    no_connect_privileged_portsboolfalse
    +

    when this is true, libtorrent will not attempt to make outgoing +connections to peers whose port is < 1024. This is a safety +precaution to avoid being part of a DDoS attack

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    smooth_connectsbooltrue
    +

    smooth_connects is true by default, which means the number of +connection attempts per second may be limited to below the +connection_speed, in case we're close to bump up against the +limit of number of connections. The intention of this setting is to +more evenly distribute our connection attempts over time, instead +of attempting to connect in batches, and timing them out in +batches.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    always_send_user_agentboolfalse
    +

    always send user-agent in every web seed request. If false, only +the first request per http connection will include the user agent

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    apply_ip_filter_to_trackersbooltrue
    +

    apply_ip_filter_to_trackers defaults to true. It determines +whether the IP filter applies to trackers as well as peers. If this +is set to false, trackers are exempt from the IP filter (if there +is one). If no IP filter is set, this setting is irrelevant.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    use_disk_read_aheadbooltrue
    +

    use_disk_read_ahead defaults to true and 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.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    lock_filesboolfalse
    +

    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.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    contiguous_recv_bufferbooltrue
    +

    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.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    ban_web_seedsbooltrue
    +

    when true, web seeds sending bad data will be banned

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    allow_partial_disk_writesbooltrue
    +

    when set to false, the write_cache_line_size will apply across +piece boundaries. this is a bad idea unless the piece picker also +is configured to have an affinity to pick pieces belonging to the +same write cache line as is configured in the disk cache.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    force_proxyboolfalse
    +

    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). This setting also disabled peer +country lookups, since those are done via DNS lookups that aren't +supported by proxies.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    support_share_modebooltrue
    +

    if false, prevents libtorrent to advertise share-mode support

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    support_merkle_torrentsbooltrue
    +

    if this is false, don't advertise support for the Tribler merkle +tree piece message

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    report_redundant_bytesbooltrue
    +

    if this is true, the number of redundant bytes is sent to the +tracker

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    listen_system_port_fallbackbooltrue
    +

    if this is true, libtorrent will fall back to listening on a port +chosen by the operating system (i.e. binding to port 0). If a +failure is preferred, set this to false.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    use_disk_cache_poolbooltrue
    +

    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.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    announce_crypto_supportbooltrue
    +

    when this is true, and incoming encrypted connections are enabled, +&supportcrypt=1 is included in http tracker announces

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    enable_upnpbooltrue
    +

    Starts and stops the UPnP service. When started, the listen port +and the DHT port are attempted to be forwarded on local UPnP router +devices.

    +

    The upnp object returned by start_upnp() can be used to add and +remove arbitrary port mappings. Mapping status is returned through +the portmap_alert and the portmap_error_alert. The object will be +valid until stop_upnp() is called. See upnp and nat pmp.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    enable_natpmpbooltrue
    +

    Starts and stops the NAT-PMP service. When started, the listen port +and the DHT port are attempted to be forwarded on the router +through NAT-PMP.

    +

    The natpmp object returned by start_natpmp() can be used to add +and remove arbitrary port mappings. Mapping status is returned +through the portmap_alert and the portmap_error_alert. The object +will be valid until stop_natpmp() is called. See +upnp and nat pmp.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    enable_lsdbooltrue
    +

    Starts and stops Local Service Discovery. This service will +broadcast the infohashes of all the non-private torrents on the +local network to look for peers on the same swarm within multicast +reach.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    enable_dhtbooltrue
    +

    starts the dht node and makes the trackerless service available to +torrents.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    prefer_rc4boolfalse
    +

    if the allowed encryption level is both, setting this to true will +prefer rc4 if both methods are offered, plaintext otherwise

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    proxy_hostnamesbooltrue
    +

    if true, hostname lookups are done via the configured proxy (if +any). This is only supported by SOCKS5 and HTTP.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    proxy_peer_connectionsbooltrue
    +

    if true, peer connections are made (and accepted) over the +configured proxy, if any. Web seeds as well as regular bittorrent +peer connections are considered "peer connections". Anything +transporting actual torrent payload (trackers and DHT traffic are +not considered peer connections).

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    auto_sequentialbooltrue
    +

    if this setting is true, torrents with a very high availability of +pieces (and seeds) are downloaded sequentially. This is more +efficient for the disk I/O. With many seeds, the download order is +unlikely to matter anyway

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    proxy_tracker_connectionsbooltrue
    +

    if true, tracker connections are made over the configured proxy, if +any.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    tracker_completion_timeoutint30
    +

    tracker_completion_timeout is the number of seconds the tracker +connection will wait from when it sent the request until it +considers the tracker to have timed-out. Default value is 60 +seconds.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    tracker_receive_timeoutint10
    +

    tracker_receive_timeout is the number of seconds to wait to +receive any data from the tracker. If no data is received for this +number of seconds, the tracker will be considered as having timed +out. If a tracker is down, this is the kind of timeout that will +occur.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    stop_tracker_timeoutint5
    +

    the time 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

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    tracker_maximum_response_lengthint1024*1024
    +

    this is the maximum number of bytes in a tracker response. If a +response size passes this number of bytes it will be rejected and +the connection will be closed. On gzipped responses this size is +measured on the uncompressed data. So, if you get 20 bytes of gzip +response that'll expand to 2 megabytes, it will be interrupted +before the entire response has been uncompressed (assuming the +limit is lower than 2 megs).

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    piece_timeoutint20
    +

    the number of seconds from a request is sent until it times out if +no piece response is returned.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    request_timeoutint60
    +

    the number of seconds one block (16kB) is expected to be received +within. If it's not, the block is requested from a different peer

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    request_queue_timeint3
    +

    the length of the request queue given in the number of seconds it +should take for the other end to send all the pieces. i.e. the +actual number of requests depends on the download rate and this +number.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    max_allowed_in_request_queueint500
    +

    the number of outstanding block requests a peer is allowed to queue +up in the client. If a peer sends more requests than this (before +the first one has been sent) the last request will be dropped. the +higher this is, the faster upload speeds the client can get to a +single peer.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    max_out_request_queueint500
    +

    max_out_request_queue is the maximum number of outstanding +requests to send to a peer. This limit takes precedence over +request_queue_time. i.e. no matter the download speed, the +number of outstanding requests will never exceed this limit.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    whole_pieces_thresholdint20
    +

    if a whole piece can be downloaded in this number of seconds, or +less, the peer_connection will prefer to request whole pieces at a +time from this peer. The benefit of this is to better utilize disk +caches by doing localized accesses and also to make it easier to +identify bad peers if a piece fails the hash check.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    peer_timeoutint120
    +

    peer_timeout is the number of seconds the peer connection +should wait (for any activity on the peer connection) before +closing it due to time out. This defaults to 120 seconds, since +that's what's specified in the protocol specification. After half +the time out, a keep alive message is sent.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    urlseed_timeoutint20
    +

    same as peer_timeout, but only applies to url-seeds. this is +usually set lower, because web servers are expected to be more +reliable.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    urlseed_pipeline_sizeint5
    +

    controls the pipelining size of url-seeds. i.e. the number of HTTP +request to keep outstanding before waiting for the first one to +complete. It's common for web servers to limit this to a relatively +low number, like 5

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    urlseed_wait_retryint30
    +

    time to wait until a new retry of a web seed takes place

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    file_pool_sizeint40
    +

    sets the upper limit on the total number of files this session will +keep open. The reason why files are left open at all is that some +anti virus software hooks on every file close, and scans the file +for viruses. deferring the closing of the files will be the +difference between a usable system and a completely hogged down +system. Most operating systems also has a limit on the total number +of file descriptors a process may have open. It is usually a good +idea to find this limit and set the number of connections and the +number of files limits so their sum is slightly below it.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    max_failcountint3
    +

    max_failcount is the maximum times we try to connect to a peer +before stop connecting again. If a peer succeeds, the failcounter +is reset. If a peer is retrieved from a peer source (other than +DHT) the failcount is decremented by one, allowing another try.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    min_reconnect_timeint60
    +

    the number of seconds to wait to reconnect to a peer. this time is +multiplied with the failcount.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    peer_connect_timeoutint15
    +

    peer_connect_timeout the number of seconds to wait after a +connection attempt is initiated to a peer until it is considered as +having timed out. This setting is especially important in case the +number of half-open connections are limited, since stale half-open +connection may delay the connection of other peers considerably.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    connection_speedint10
    +

    connection_speed is the number of connection attempts that are +made per second. If a number < 0 is specified, it will default to +200 connections per second. If 0 is specified, it means don't make +outgoing connections at all.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    inactivity_timeoutint600
    +

    if a peer is uninteresting and uninterested for longer than this +number of seconds, it will be disconnected. default is 10 minutes

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    unchoke_intervalint15
    +

    unchoke_interval is the number of seconds between +chokes/unchokes. On this interval, peers are re-evaluated for being +choked/unchoked. This is defined as 30 seconds in the protocol, and +it should be significantly longer than what it takes for TCP to +ramp up to it's max rate.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    optimistic_unchoke_intervalint30
    +

    optimistic_unchoke_interval is the number of seconds between +each optimistic unchoke. On this timer, the currently +optimistically unchoked peer will change.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    num_wantint200
    +

    num_want is the number of peers we want from each tracker +request. It defines what is sent as the &num_want= parameter to +the tracker.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    initial_picker_thresholdint4
    +

    initial_picker_threshold specifies the number of pieces we need +before we switch to rarest first picking. This defaults to 4, which +means the 4 first pieces in any torrent are picked at random, the +following pieces are picked in rarest first order.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    allowed_fast_set_sizeint10
    +

    the number of allowed pieces to send to peers that supports the +fast extensions

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    suggest_modeintsettings_pack::no_piece_suggestions
    +

    suggest_mode controls whether or not libtorrent will send out +suggest messages to create a bias of its peers to request certain +pieces. The modes are:

    +
      +
    • no_piece_suggestsions which is the default and will not send +out suggest messages.
    • +
    • suggest_read_cache which will send out suggest messages for +the most recent pieces that are in the read cache.
    • +
    + +++++ + + + + + + + + + + + + +
    nametypedefault
    max_queued_disk_bytesint1024 * 1024
    +

    max_queued_disk_bytes is the number maximum number of bytes, to +be written to disk, that can wait in the disk I/O thread queue. +This queue is only for waiting for the disk I/O thread to receive +the job and either write it to disk or insert it in the write +cache. When this limit is reached, the peer connections will stop +reading data from their sockets, until the disk thread catches up. +Setting this too low will severely limit your download rate.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    handshake_timeoutint10
    +

    the number of seconds to wait for a handshake response from a peer. +If no response is received within this time, the peer is +disconnected.

    + + + +++++ + + + + + + + + + + + + + + + + + + + + +
    nametypedefault
    send_buffer_low_watermarkint10 * 1024
    send_buffer_watermarkint500 * 1024
    send_buffer_watermark_factorint50
    +

    send_buffer_low_watermark the minimum send buffer target size +(send buffer includes bytes pending being read from disk). For good +and snappy seeding performance, set this fairly high, to at least +fit a few blocks. This is essentially the initial window size which +will determine how fast we can ramp up the send rate

    +

    if the send buffer has fewer bytes than send_buffer_watermark, +we'll read another 16kB block onto it. If set too small, upload +rate capacity will suffer. If set too high, memory will be wasted. +The actual watermark may be lower than this in case the upload rate +is low, this is the upper limit.

    +

    the current upload rate to a peer is multiplied by this factor to +get the send buffer watermark. The factor is specified as a +percentage. i.e. 50 -> 0.5 This product is clamped to the +send_buffer_watermark setting to not exceed the max. For high +speed upload, this should be set to a greater value than 100. For +high capacity connections, setting this higher can improve upload +performance and disk throughput. Setting it too high may waste RAM +and create a bias towards read jobs over write jobs.

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    choking_algorithmintsettings_pack::fixed_slots_choker
    seed_choking_algorithmintsettings_pack::round_robin
    +

    choking_algorithm specifies which algorithm to use to determine +which peers to unchoke.

    +

    The options for choking algorithms are:

    +
      +
    • fixed_slots_choker is the traditional choker with a fixed +number of unchoke slots (as specified by +session::set_max_uploads()).
    • +
    • rate_based_choker 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.
    • +
    • bittyrant_choker attempts to optimize download rate by +finding the reciprocation rate of each peer individually and +prefers peers that gives the highest return on investment. It +still allocates all upload capacity, but shuffles it around to +the best peers first. For this choker to be efficient, you need +to set a global upload rate limit +(session::set_upload_rate_limit()). For more information +about this choker, see the paper. This choker is not fully +implemented nor tested.
    • +
    +

    seed_choking_algorithm controls the seeding unchoke behavior. +The available options are:

    +
      +
    • round_robin which round-robins the peers that are unchoked +when seeding. This distributes the upload bandwidht uniformly and +fairly. It minimizes the ability for a peer to download everything +without redistributing it.
    • +
    • fastest_upload unchokes the peers we can send to the fastest. +This might be a bit more reliable in utilizing all available +capacity.
    • +
    • anti_leech 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.
    • +
    + + + +++++ + + + + + + + + + + + + + + + + + + + + +
    nametypedefault
    cache_sizeint1024
    cache_buffer_chunk_sizeint0
    cache_expiryint300
    +

    cache_size is the disk write and read cache. It is specified +in units of 16 KiB blocks. Buffers that are part of a peer's send +or receive buffer also count against this limit. Send and receive +buffers will never be denied to be allocated, but they will cause +the actual cached blocks to be flushed or evicted. If this is set +to -1, the cache size is automatically set to the amount of +physical RAM available in the machine divided by 8. If the amount +of physical RAM cannot be determined, it's set to 1024 (= 16 MiB).

    +

    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_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. Default is 60 second.

    +

    On 32 bit builds, the effective cache size will be limited to 3/4 of +2 GiB to avoid exceeding the virtual address space limit.

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    disk_io_write_modeintsettings_pack::enable_os_cache
    disk_io_read_modeintsettings_pack::enable_os_cache
    +

    determines how files are opened when they're in read only mode +versus read and write mode. The options are:

    +
    +
    enable_os_cache
    +
    This is the default and files are opened normally, with the OS +caching reads and writes.
    +
    disable_os_cache
    +
    This opens all files in no-cache mode. This corresponds to the +OS not letting blocks for the files linger in the cache. This +makes sense in order to avoid the bittorrent client to +potentially evict all other processes' cache by simply handling +high throughput and large files. If libtorrent's read cache is +disabled, enabling this may reduce performance.
    +
    +

    One reason to disable caching is that it may help the operating +system from growing its file cache indefinitely.

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    outgoing_portint0
    num_outgoing_portsint0
    +

    this is the first port to use for binding outgoing connections to. +This is useful for users that have routers that allow QoS settings +based on local port. when binding outgoing connections to specific +ports, num_outgoing_ports is the size of the range. It should +be more than a few

    +
    +

    Warning

    +

    setting outgoing ports will limit the ability to keep +multiple connections to the same client, even for different +torrents. It is not recommended to change this setting. Its main +purpose is to use as an escape hatch for cheap routers with QoS +capability but can only classify flows based on port numbers.

    +
    +

    It is a range instead of a single port because of the problems with +failing to reconnect to peers if a previous socket to that peer and +port is in TIME_WAIT state.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    peer_tosint0
    +

    peer_tos determines the TOS byte set in the IP header of every +packet sent to peers (including web seeds). The default value for +this is 0x0 (no marking). One potentially useful TOS mark is +0x20, this represents the QBone scavenger service. For more +details, see QBSS.

    + + + + + + + + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametypedefault
    active_downloadsint3
    active_seedsint5
    active_checkingint1
    active_dht_limitint88
    active_tracker_limitint1600
    active_lsd_limitint60
    active_limitint15
    active_loaded_limitint100
    +

    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. By default this is set to 88, which is no more than one +DHT announce every 10 seconds.

    +

    active_tracker_limit is the max number of torrents to announce +to their trackers. By default this is 360, which is no more than +one announce every 5 seconds.

    +

    active_lsd_limit is the max number of torrents to announce to +the local network over the local service discovery protocol. By +default this is 80, which is no more than one announce every 5 +seconds (assuming the default announce interval of 5 minutes).

    +

    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_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.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    auto_manage_intervalint30
    +

    auto_manage_interval is the number of seconds between the +torrent queue is updated, and rotated.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    seed_time_limitint24 * 60 * 60
    +

    this is the limit on the time a torrent has been an active seed +(specified in seconds) before it is considered having met the seed +limit criteria. See queuing.

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    auto_scrape_intervalint1800
    auto_scrape_min_intervalint300
    +

    auto_scrape_interval is the number of seconds between scrapes +of queued torrents (auto managed and paused torrents). Auto managed +torrents that are paused, are scraped regularly in order to keep +track of their downloader/seed ratio. This ratio is used to +determine which torrents to seed and which to pause.

    +

    auto_scrape_min_interval is the minimum number of seconds +between any automatic scrape (regardless of torrent). In case there +are a large number of paused auto managed torrents, this puts a +limit on how often a scrape request is sent.

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    max_peerlist_sizeint3000
    max_paused_peerlist_sizeint1000
    +

    max_peerlist_size is the maximum number of peers in the list of +known peers. These peers are not necessarily connected, so this +number should be much greater than the maximum number of connected +peers. Peers are evicted from the cache when the list grows passed +90% of this limit, and once the size hits the limit, peers are no +longer added to the list. If this limit is set to 0, there is no +limit on how many peers we'll keep in the peer list.

    +

    max_paused_peerlist_size is the max peer list size used for +torrents that are paused. This default to the same as +max_peerlist_size, but can be used to save memory for paused +torrents, since it's not as important for them to keep a large peer +list.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    min_announce_intervalint5 * 60
    +

    this is the minimum allowed announce interval for a tracker. This +is specified in seconds and is used as a sanity check on what is +returned from a tracker. It mitigates hammering misconfigured +trackers.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    auto_manage_startupint60
    +

    this is the number of seconds a torrent is considered active after +it was started, regardless of upload and download speed. This is so +that newly started torrents are not considered inactive until they +have a fair chance to start downloading.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    seeding_piece_quotaint20
    +

    seeding_piece_quota is the number of pieces to send to a peer, +when seeding, before rotating in another peer to the unchoke set. +It defaults to 3 pieces, which means that when seeding, any peer +we've sent more than this number of pieces to will be unchoked in +favour of a choked peer.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    max_rejectsint50
    +

    TODO: deprecate this +max_rejects is the number of piece requests we will reject in a +row while a peer is choked before the peer is considered abusive +and is disconnected.

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    recv_socket_buffer_sizeint0
    send_socket_buffer_sizeint0
    +

    recv_socket_buffer_size and send_socket_buffer_size +specifies the buffer sizes set on peer sockets. 0 (which is the +default) means the OS default (i.e. don't change the buffer sizes). +The socket buffer sizes are changed using setsockopt() with +SOL_SOCKET/SO_RCVBUF and SO_SNDBUFFER.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    file_checks_delay_per_blockint0
    +

    file_checks_delay_per_block is the number of milliseconds to +sleep in between disk read operations when checking torrents. This +defaults to 0, but 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.

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    read_cache_line_sizeint32
    write_cache_line_sizeint16
    +

    read_cache_line_size is the number of blocks to read into the +read cache when a read cache miss occurs. Setting this to 0 is +essentially the same thing as disabling read cache. The number of +blocks read into the read cache is always capped by the piece +boundary.

    +

    When a piece in the write cache has write_cache_line_size +contiguous blocks in it, they will be flushed. Setting this to 1 +effectively disables the write cache.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    optimistic_disk_retryint10 * 60
    +

    optimistic_disk_retry is the number of seconds from a disk +write errors occur on a torrent until libtorrent will take it out +of the upload mode, to test if the error condition has been fixed.

    +

    libtorrent will only do this automatically for auto managed +torrents.

    +

    You can explicitly take a torrent out of upload only mode using +set_upload_mode().

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    max_suggest_piecesint10
    +

    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. +It defaults to 10.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    local_service_announce_intervalint5 * 60
    +

    local_service_announce_interval is the time between local +network announces for a torrent. By default, when local service +discovery is enabled a torrent announces itself every 5 minutes. +This interval is specified in seconds.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    dht_announce_intervalint15 * 60
    +

    dht_announce_interval is the number of seconds between +announcing torrents to the distributed hash table (DHT).

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    udp_tracker_token_expiryint60
    +

    udp_tracker_token_expiry is the number of seconds libtorrent +will keep UDP tracker connection tokens around for. This is +specified to be 60 seconds, and defaults to that. The higher this +value is, the fewer packets have to be sent to the UDP tracker. In +order for higher values to work, the tracker needs to be configured +to match the expiration time for tokens.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    default_cache_min_ageint1
    +

    default_cache_min_age is the minimum number of seconds any read +cache line is kept in the cache. This defaults to one second but +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.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    num_optimistic_unchoke_slotsint0
    +

    num_optimistic_unchoke_slots is the number of optimistic +unchoke slots to use. It defaults to 0, which means automatic. +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. When this is set to 0, libtorrent opens up 20% of your +allowed upload slots as optimistic unchoke slots.

    + + + +++++ + + + + + + + + + + + + + + + + + + + + +
    nametypedefault
    default_est_reciprocation_rateint16000
    increase_est_reciprocation_rateint20
    decrease_est_reciprocation_rateint3
    +

    default_est_reciprocation_rate is the assumed reciprocation +rate from peers when using the BitTyrant choker. This defaults to +14 kiB/s. 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 defaults to 20%. +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 default to 3%. This only applies +to the BitTyrant choker.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    max_pex_peersint50
    +

    the max number of peers we accept from pex messages from a single +peer. this limits the number of concurrent peers any of our peers +claims to be connected to. If they claim to be connected to more +than this, we'll ignore any peer that exceeds this limit

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    tick_intervalint500
    +

    tick_interval specifies the number of milliseconds between +internal ticks. This is the frequency with which bandwidth quota is +distributed to peers. It should not be more than one second (i.e. +1000 ms). Setting this to a low value (around 100) means higher +resolution bandwidth quota distribution, setting it to a higher +value saves CPU cycles.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    share_mode_targetint3
    +

    share_mode_target specifies the target share ratio for share +mode torrents. This defaults to 3, meaning we'll try to upload 3 +times as much as we download. Setting this very high, will make it +very conservative and you might end up not downloading anything +ever (and not affecting your share ratio). It does not make any +sense to set this any lower than 2. For instance, if only 3 peers +need to download the rarest piece, it's impossible to download a +single piece and upload it more than 3 times. If the +share_mode_target is set to more than 3, nothing is downloaded.

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    upload_rate_limitint0
    download_rate_limitint0
    +

    upload_rate_limit, download_rate_limit, +local_upload_rate_limit and local_download_rate_limit sets +the session-global limits of upload and download rate limits, in +bytes per second. The local rates refer to peers on the local +network. By default peers on the local network are not rate +limited.

    +

    These rate limits are only used for local peers (peers within the +same subnet as the client itself) and it is only used when +ignore_limits_on_local_network is set to true (which it is by +default). These rate limits default to unthrottled, but can be +useful in case you want to treat local peers preferentially, but +not quite unthrottled.

    +

    A value of 0 means unlimited.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    dht_upload_rate_limitint4000
    +

    dht_upload_rate_limit sets the rate limit on the DHT. This is +specified in bytes per second and defaults to 4000. For busy boxes +with lots of torrents that requires more DHT traffic, this should +be raised.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    unchoke_slots_limitint8
    +

    unchoke_slots_limit is the max number of unchoked peers in the +session. The number of unchoke slots may be ignored depending on +what choking_algorithm is set to.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    connections_limitint200
    +

    connections_limit sets a global limit on the number of +connections opened. The number of connections is set to a hard +minimum of at least two per torrent, so if you set a too low +connections limit, and open too many torrents, the limit will not +be met.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    connections_slackint10
    +

    connections_slack is the the number of incoming connections +exceeding the connection limit to accept in order to potentially +replace existing ones.

    + + + + + + + + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametypedefault
    utp_target_delayint100
    utp_gain_factorint3000
    utp_min_timeoutint500
    utp_syn_resendsint2
    utp_fin_resendsint2
    utp_num_resendsint3
    utp_connect_timeoutint3000
    utp_loss_multiplierint50
    +

    utp_target_delay is the target delay for uTP sockets in +milliseconds. A high value will make uTP connections more +aggressive and cause longer queues in the upload bottleneck. It +cannot be too low, since the noise in the measurements would cause +it to send too slow. The default is 50 milliseconds. +utp_gain_factor is the number of bytes the uTP congestion +window can increase at the most in one RTT. This defaults to 300 +bytes. 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. This defaults to 500 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 +lossed 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. By default it's set to 50 (i.e. cut in half). Do not +change this value unless you know what you're doing. Never set it +higher than 100.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    mixed_mode_algorithmintsettings_pack::peer_proportional
    +

    The mixed_mode_algorithm determines how to treat TCP +connections when there are uTP connections. Since uTP is designed +to yield to TCP, there's an inherent problem when using swarms that +have both TCP and uTP connections. If nothing is done, uTP +connections would often be starved out for bandwidth by the TCP +connections. This mode is prefer_tcp. The peer_proportional +mode simply looks at the current throughput and rate limits all TCP +connections to their proportional share based on how many of the +connections are TCP. This works best if uTP connections are not +rate limited by the global rate limiter (which they aren't by +default).

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    listen_queue_sizeint5
    +

    listen_queue_size is the value passed in to listen() for the +listen socket. It is the number of outstanding incoming connections +to queue up while we're not actively waiting for a connection to be +accepted. The default is 5 which 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 listen_on() is called again (or for the first time).

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    torrent_connect_boostint10
    +

    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.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    alert_queue_sizeint1000
    +

    alert_queue_size is the maximum number of alerts queued up +internally. If alerts are not popped, the queue will eventually +fill up to this level.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    max_metadata_sizeint3 * 1024 * 10240
    +

    max_metadata_size is the maximum allowed size (in bytes) to be +received by the metadata extension, i.e. magnet links.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    checking_mem_usageint256
    +

    the number of blocks to keep outstanding at any given time when +checking torrents. Higher numbers give faster re-checks but uses +more memory. Specified in number of 16 kiB blocks

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    predictive_piece_announceint0
    +

    if set to > 0, pieces will be announced to other peers before they +are fully downloaded (and before they are hash checked). The +intention is to gain 1.5 potential round trip times per downloaded +piece. When non-zero, this indicates how many milliseconds in +advance pieces should be announced, before they are expected to be +completed.

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    aio_threadsint4
    aio_maxint300
    +

    for some aio back-ends, aio_threads specifies the number of +io-threads to use, and aio_max the max number of outstanding +jobs.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    network_threadsint0
    +

    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.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    ssl_listenint0
    +

    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.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    tracker_backoffint250
    +

    tracker_backoff determines how aggressively to back off from +retrying failing trackers. This value determines x in the +following formula, determining the number of seconds to wait until +the next retry:

    +
    +delay = 5 + 5 * x / 100 * fails^2
    +

    This setting may be useful to make libtorrent more or less +aggressive in hitting trackers.

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    share_ratio_limitint200
    seed_time_ratio_limitint700
    +

    when a seeding torrent reaches either the share ratio (bytes up / +bytes down) or the seed time ratio (seconds as seed / seconds as +downloader) or the seed time limit (seconds as seed) it is +considered done, and it will leave room for other torrents these +are specified as percentages

    + + + +++++ + + + + + + + + + + + + + + + + + + + + +
    nametypedefault
    peer_turnoverint4
    peer_turnover_cutoffint90
    peer_turnover_intervalint300
    +

    peer_turnover is the percentage of peers to disconnect every +turnover peer_turnover_interval (if we're at the peer limit), this +is specified in percent when we are connected to more than limit * +peer_turnover_cutoff peers disconnect peer_turnover fraction of the +peers. It is specified in percent peer_turnover_interval is the +interval (in seconds) between optimistic disconnects if the +disconnects happen and how many peers are disconnected is +controlled by peer_turnover and peer_turnover_cutoff

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    connect_seed_every_n_downloadint10
    +

    this setting controls the priority of downloading torrents over +seeding or finished torrents when it comes to making peer +connections. Peer connections are throttled by the connection_speed +and the half-open connection limit. This makes peer connections a +limited resource. Torrents that still have pieces to download are +prioritized by default, to avoid having many seeding torrents use +most of the connection attempts and only give one peer every now +and then to the downloading torrent. libtorrent will loop over the +downloading torrents to connect a peer each, and every n:th +connection attempt, a finished torrent is picked to be allowed to +connect to a peer. This setting controls n.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    max_http_recv_buffer_sizeint4*1024*204
    +

    the max number of bytes to allow an HTTP response to be when +announcing to trackers or downloading .torrent files via the +url provided in add_torrent_params.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    max_retry_port_bindint10
    +

    if binding to a specific port fails, should the port be incremented +by one and tried again? This setting specifies how many times to +retry a failed port bind

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    alert_maskintalert::error_notification
    +

    a bitmask combining flags from alert::category_t defining which +kinds of alerts to receive

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    out_enc_policyintsettings_pack::pe_enabled
    in_enc_policyintsettings_pack::pe_enabled
    +

    control the settings for incoming and outgoing connections +respectively. see enc_policy enum for the available options. +Keep in mind that protocol encryption degrades performance in +several respects:

    +
      +
    1. It prevents "zero copy" disk buffers being sent to peers, since +each peer needs to mutate the data (i.e. encrypt it) the data +must be copied per peer connection rather than sending the same +buffer to multiple peers.
    2. +
    3. The encryption itself requires more CPU than plain bittorrent +protocol. The highest cost is the Diffie Hellman exchange on +connection setup.
    4. +
    5. The encryption handshake adds several round-trips to the +connection setup, and delays transferring data.
    6. +
    + +++++ + + + + + + + + + + + + +
    nametypedefault
    allowed_enc_levelintsettings_pack::pe_both
    +

    determines the encryption level of the connections. This setting +will adjust which encryption scheme is offered to the other peer, +as well as which encryption scheme is selected by the client. See +enc_level enum for options.

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    inactive_down_rateint2048
    inactive_up_rateint2048
    +

    the download and upload rate limits for a torrent to be considered +active by the queuing mechanism. A torrent whose download rate is +less than inactive_down_rate and whose upload rate is less than +inactive_up_rate for auto_manage_startup seconds, is +considered inactive, and another queued torrent may be started. +This logic is disabled if dont_count_slow_torrents is false.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    proxy_typeintsettings_pack::none
    +

    proxy to use, defaults to none. see proxy_type_t.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    proxy_portint0
    +

    the port of the proxy server

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    i2p_portint0
    +

    sets the i2p SAM bridge port to connect to. set the hostname with +the i2p_hostname setting.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    cache_size_volatileint256
    +

    this determines the max number of volatile disk cache blocks. If the +number of volatile blocks exceed this limit, other volatile blocks +will start to be evicted. A disk cache block is volatile if it has +low priority, and should be one of the first blocks to be evicted +under pressure. For instance, blocks pulled into the cache as the +result of calculating a piece hash are volatile. These blocks don't +represent potential interest among peers, so the value of keeping +them in the cache is limited.

    +
    +struct settings_pack
    +{
    +   void set_int (int name, int val);
    +   void set_str (int name, std::string val);
    +   void set_bool (int name, bool val);
    +   bool has_val (int name) const;
    +   void clear ();
    +   bool get_bool (int name) const;
    +   int get_int (int name) const;
    +   std::string get_str (int name) const;
    +
    +   enum type_bases
    +   {
    +      string_type_base,
    +      int_type_base,
    +      bool_type_base,
    +      type_mask,
    +      index_mask,
    +   };
    +
    +   enum string_types
    +   {
    +      user_agent,
    +      announce_ip,
    +      mmap_cache,
    +      handshake_client_version,
    +      outgoing_interfaces,
    +      listen_interfaces,
    +      proxy_hostname,
    +      proxy_username,
    +      proxy_password,
    +      i2p_hostname,
    +      peer_fingerprint,
    +      dht_bootstrap_nodes,
    +      max_string_setting_internal,
    +   };
    +
    +   enum bool_types
    +   {
    +      allow_multiple_connections_per_ip,
    +      deprecated1,
    +      send_redundant_have,
    +      lazy_bitfields,
    +      use_dht_as_fallback,
    +      upnp_ignore_nonrouters,
    +      use_parole_mode,
    +      use_read_cache,
    +      deprecated7,
    +      dont_flush_write_cache,
    +      deprecated10,
    +      coalesce_reads,
    +      coalesce_writes,
    +      auto_manage_prefer_seeds,
    +      dont_count_slow_torrents,
    +      close_redundant_connections,
    +      prioritize_partial_pieces,
    +      rate_limit_ip_overhead,
    +      announce_to_all_tiers,
    +      announce_to_all_trackers,
    +      prefer_udp_trackers,
    +      strict_super_seeding,
    +      deprecated8,
    +      disable_hash_checks,
    +      allow_i2p_mixed,
    +      low_prio_disk,
    +      volatile_read_cache,
    +      guided_read_cache,
    +      no_atime_storage,
    +      incoming_starts_queued_torrents,
    +      report_true_downloaded,
    +      strict_end_game_mode,
    +      broadcast_lsd,
    +      enable_outgoing_utp,
    +      enable_incoming_utp,
    +      enable_outgoing_tcp,
    +      enable_incoming_tcp,
    +      ignore_resume_timestamps,
    +      no_recheck_incomplete_resume,
    +      anonymous_mode,
    +      report_web_seed_downloads,
    +      deprecated2,
    +      announce_double_nat,
    +      seeding_outgoing_connections,
    +      no_connect_privileged_ports,
    +      smooth_connects,
    +      always_send_user_agent,
    +      apply_ip_filter_to_trackers,
    +      use_disk_read_ahead,
    +      lock_files,
    +      contiguous_recv_buffer,
    +      ban_web_seeds,
    +      allow_partial_disk_writes,
    +      force_proxy,
    +      support_share_mode,
    +      support_merkle_torrents,
    +      report_redundant_bytes,
    +      listen_system_port_fallback,
    +      use_disk_cache_pool,
    +      announce_crypto_support,
    +      enable_upnp,
    +      enable_natpmp,
    +      enable_lsd,
    +      enable_dht,
    +      prefer_rc4,
    +      proxy_hostnames,
    +      proxy_peer_connections,
    +      auto_sequential,
    +      proxy_tracker_connections,
    +      max_bool_setting_internal,
    +   };
    +
    +   enum int_types
    +   {
    +      tracker_completion_timeout,
    +      tracker_receive_timeout,
    +      stop_tracker_timeout,
    +      tracker_maximum_response_length,
    +      piece_timeout,
    +      request_timeout,
    +      request_queue_time,
    +      max_allowed_in_request_queue,
    +      max_out_request_queue,
    +      whole_pieces_threshold,
    +      peer_timeout,
    +      urlseed_timeout,
    +      urlseed_pipeline_size,
    +      urlseed_wait_retry,
    +      file_pool_size,
    +      max_failcount,
    +      min_reconnect_time,
    +      peer_connect_timeout,
    +      connection_speed,
    +      inactivity_timeout,
    +      unchoke_interval,
    +      optimistic_unchoke_interval,
    +      num_want,
    +      initial_picker_threshold,
    +      allowed_fast_set_size,
    +      suggest_mode,
    +      max_queued_disk_bytes,
    +      handshake_timeout,
    +      send_buffer_low_watermark,
    +      send_buffer_watermark,
    +      send_buffer_watermark_factor,
    +      choking_algorithm,
    +      seed_choking_algorithm,
    +      cache_size,
    +      cache_buffer_chunk_size,
    +      cache_expiry,
    +      deprecated11,
    +      disk_io_write_mode,
    +      disk_io_read_mode,
    +      outgoing_port,
    +      num_outgoing_ports,
    +      peer_tos,
    +      active_downloads,
    +      active_seeds,
    +      active_checking,
    +      active_dht_limit,
    +      active_tracker_limit,
    +      active_lsd_limit,
    +      active_limit,
    +      active_loaded_limit,
    +      auto_manage_interval,
    +      seed_time_limit,
    +      auto_scrape_interval,
    +      auto_scrape_min_interval,
    +      max_peerlist_size,
    +      max_paused_peerlist_size,
    +      min_announce_interval,
    +      auto_manage_startup,
    +      seeding_piece_quota,
    +      max_rejects,
    +      recv_socket_buffer_size,
    +      send_socket_buffer_size,
    +      file_checks_delay_per_block,
    +      read_cache_line_size,
    +      write_cache_line_size,
    +      optimistic_disk_retry,
    +      max_suggest_pieces,
    +      local_service_announce_interval,
    +      dht_announce_interval,
    +      udp_tracker_token_expiry,
    +      default_cache_min_age,
    +      num_optimistic_unchoke_slots,
    +      default_est_reciprocation_rate,
    +      increase_est_reciprocation_rate,
    +      decrease_est_reciprocation_rate,
    +      max_pex_peers,
    +      tick_interval,
    +      share_mode_target,
    +      upload_rate_limit,
    +      download_rate_limit,
    +      deprecated3,
    +      deprecated4,
    +      dht_upload_rate_limit,
    +      unchoke_slots_limit,
    +      deprecated5,
    +      connections_limit,
    +      connections_slack,
    +      utp_target_delay,
    +      utp_gain_factor,
    +      utp_min_timeout,
    +      utp_syn_resends,
    +      utp_fin_resends,
    +      utp_num_resends,
    +      utp_connect_timeout,
    +      deprecated6,
    +      utp_loss_multiplier,
    +      mixed_mode_algorithm,
    +      listen_queue_size,
    +      torrent_connect_boost,
    +      alert_queue_size,
    +      max_metadata_size,
    +      deprecated9,
    +      checking_mem_usage,
    +      predictive_piece_announce,
    +      aio_threads,
    +      aio_max,
    +      network_threads,
    +      ssl_listen,
    +      tracker_backoff,
    +      share_ratio_limit,
    +      seed_time_ratio_limit,
    +      peer_turnover,
    +      peer_turnover_cutoff,
    +      peer_turnover_interval,
    +      connect_seed_every_n_download,
    +      max_http_recv_buffer_size,
    +      max_retry_port_bind,
    +      alert_mask,
    +      out_enc_policy,
    +      in_enc_policy,
    +      allowed_enc_level,
    +      inactive_down_rate,
    +      inactive_up_rate,
    +      proxy_type,
    +      proxy_port,
    +      i2p_port,
    +      cache_size_volatile,
    +      max_int_setting_internal,
    +   };
    +
    +   enum settings_counts_t
    +   {
    +      num_string_settings,
    +      num_bool_settings,
    +      num_int_settings,
    +   };
    +
    +   enum suggest_mode_t
    +   {
    +      no_piece_suggestions,
    +      suggest_read_cache,
    +   };
    +
    +   enum choking_algorithm_t
    +   {
    +      fixed_slots_choker,
    +      rate_based_choker,
    +      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,
    +   };
    +
    +   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,
    +   };
    +};
    +
    +
    +

    enum type_bases

    +

    Declared in "libtorrent/settings_pack.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    string_type_base0 
    int_type_base16384 
    bool_type_base32768 
    type_mask49152 
    index_mask16383 
    +
    +
    +

    enum string_types

    +

    Declared in "libtorrent/settings_pack.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    user_agent this is the client identification to the tracker. The recommended +format of this string is: "ClientName/ClientVersion +libtorrent/libtorrentVersion". 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_ip1announce_ip is the ip address passed along to trackers as the +&ip= parameter. If left as the default, that parameter is +omitted.
    mmap_cache2

    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.

    +

    Since this setting sets a hard upper limit on cache usage, it +cannot be combined with +settings_pack::contiguous_recv_buffer, since that feature +treats the cache_size setting as a soft (but still pretty hard) +limit. The result of combining the two is peers being disconnected +after failing to allocate more disk buffers.

    +

    This feature requires the mmap system call, on systems that +don't have mmap this setting is ignored.

    +
    handshake_client_version3this 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
    outgoing_interfaces4sets the network interface this session will use when it opens +outgoing connections. By default, it binds outgoing connections to +INADDR_ANY and port 0 (i.e. let the OS decide). Ths parameter must +be a string containing one or more, comma separated, adapter names. +Adapter names on unix systems are of the form "eth0", "eth1", +"tun0", etc. When specifying multiple interfaces, they will be +assigned in round-robin order. 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. +Setting this to an empty string will disable binding of outgoing +connections.
    listen_interfaces5

    a comma-separated list of IP port-pairs. These +are the listen ports that will be opened for accepting incoming uTP +and TCP connections. It is possible to listen on multiple +IPs and multiple ports. Binding to port 0 will make the +operating system pick the port. The default is "0.0.0.0:6881", which +binds to all interfaces on port 6881.

    +

    if binding fails, the listen_failed_alert is posted, potentially +more than once. Once/if binding the listen socket(s) succeed, +listen_succeeded_alert is posted.

    +

    Each port will attempt to open both a UDP and a TCP listen socket, +to allow accepting uTP connections as well as TCP. If using the DHT, +this will also make the DHT use the same UDP ports.

    +
    +

    Note

    +

    The current support for opening arbitrary UDP sockets is limited. +In this version of libtorrent, there will only ever be two UDP +sockets, one for IPv4 and one for IPv6.

    +
    +
    proxy_hostname6when using a poxy, this is the hostname where the proxy is running +see proxy_type.
    proxy_username7when using a proxy, these are the credentials (if any) to use whne +connecting to it. see proxy_type
    proxy_password8 
    i2p_hostname9sets the i2p SAM bridge to connect to. set the port with the +i2p_port setting.
    peer_fingerprint10this 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 +used as the peer-id
    dht_bootstrap_nodes11

    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. This setting will contain one or more bootstrap nodes +by default.

    +

    Changing these after the DHT has been started may not have any +effect until the DHT is restarted.

    +
    max_string_setting_internal12 
    +
    +
    +

    enum bool_types

    +

    Declared in "libtorrent/settings_pack.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    allow_multiple_connections_per_ip determines if connections from the same IP address as existing +connections should be rejected or not. Multiple connections from +the same IP address is not allowed by default, to prevent abusive +behavior by peers. It may be useful to allow such connections in +cases where simulations are run on the same machine, and all peers +in a swarm has the same IP address.
    deprecated11if 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.
    send_redundant_have2send_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. +Default is false.
    lazy_bitfields3if 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.
    use_dht_as_fallback4use_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. This is false by default, which means the DHT is used +by default regardless of if the trackers fail or not.
    upnp_ignore_nonrouters5upnp_ignore_nonrouters indicates whether or not the UPnP +implementation should ignore any broadcast response from a device +whose address is not the configured router for this machine. i.e. +it's a way to not talk to other people's routers by mistake.
    use_parole_mode6use_parole_mode specifies if parole mode should be used. Parole +mode means that peers that participate in pieces that fail the hash +check are put in a mode where they are only allowed to download +whole pieces. If the whole piece a peer in parole mode fails the +hash check, it is banned. If a peer participates in a piece that +passes the hash check, it is taken out of parole mode.
    use_read_cache7enable 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.
    deprecated78 
    dont_flush_write_cache9this 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
    deprecated1010 
    coalesce_reads11allocate 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_writes12 
    auto_manage_prefer_seeds13prefer seeding torrents when determining which torrents to give +active slots to, the default is false which gives preference to +downloading torrents
    dont_count_slow_torrents14if 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_connections15close_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_pieces16If 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_overhead17if 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_tiers18

    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. It +defaults to false, which is the same behavior previous versions of +libtorrent has had as well.

    +

    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. This is false by +default in order to comply with the multi-tracker specification.

    +
    announce_to_all_trackers19 
    prefer_udp_trackers20prefer_udp_trackers is true by default. It 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 trackers' tier is respected and there's no +preference of one protocol over another.
    strict_super_seeding21strict_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.
    deprecated822 
    disable_hash_checks23when 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_mixed24if 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.
    low_prio_disk25low_prio_disk determines if the disk I/O should use a normal or +low priority policy. This defaults to true, which 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.
    volatile_read_cache26volatile_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.
    guided_read_cache27guided_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.
    no_atime_storage28no_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_torrents29incoming_starts_queued_torrents defaults to false. 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_downloaded30when 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_mode31strict_end_game_mode defaults to true, and controls when a +block may be requested twice. If this is true, a block may only +be requested twice when there's ay 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.
    broadcast_lsd32if 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.
    enable_outgoing_utp33when set to true, libtorrent will try to make outgoing utp +connections controls whether libtorrent will accept incoming +connections or make outgoing connections of specific type.
    enable_incoming_utp34 
    enable_outgoing_tcp35 
    enable_incoming_tcp36 
    ignore_resume_timestamps37ignore_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. This defaults to +false, which 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.
    no_recheck_incomplete_resume38no_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. By +default, this is determined by the existence of any of the files. +By setting this setting to true, the files won't be checked, but +will go straight to download mode.
    anonymous_mode39

    anonymous_mode defaults to false. When set to true, the client +tries to hide its identity to a certain degree. The peer-ID will no +longer include the client's fingerprint. The user-agent will be +reset to an empty string. Trackers will only be used if they are +using a proxy server. The listen sockets are closed, and incoming +connections will only be accepted through a SOCKS5 or I2P proxy (if +a peer proxy is set up and is run on the same machine as the +tracker proxy). Since no incoming connections are accepted, +NAT-PMP, UPnP, DHT and local peer discovery are all turned off when +this setting is enabled.

    +

    If you're using I2P, it might make sense to enable anonymous mode +as well.

    +
    report_web_seed_downloads40specifies whether downloads from web seeds is reported to the +tracker or not. Defaults to on. Turning it off also excludes web +seed traffic from other stats and download rate reporting via the +libtorrent API.
    deprecated241set to true if uTP connections should be rate limited This option +is DEPRECATED, please use set_peer_class_filter() instead.
    announce_double_nat42if 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
    seeding_outgoing_connections43seeding_outgoing_connections determines if seeding (and +finished) torrents should attempt to make outgoing connections or +not. By default this is true. 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_ports44when 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_connects45smooth_connects is true by default, which 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_agent46always 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_trackers47apply_ip_filter_to_trackers defaults to true. It 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.
    use_disk_read_ahead48use_disk_read_ahead defaults to true and 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.
    lock_files49lock_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.
    contiguous_recv_buffer50contiguous_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.
    ban_web_seeds51when true, web seeds sending bad data will be banned
    allow_partial_disk_writes52when 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.
    force_proxy53If 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). This setting also disabled peer +country lookups, since those are done via DNS lookups that aren't +supported by proxies.
    support_share_mode54if false, prevents libtorrent to advertise share-mode support
    support_merkle_torrents55if this is false, don't advertise support for the Tribler merkle +tree piece message
    report_redundant_bytes56if this is true, the number of redundant bytes is sent to the +tracker
    listen_system_port_fallback57if 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.
    use_disk_cache_pool58use_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.
    announce_crypto_support59when this is true, and incoming encrypted connections are enabled, +&supportcrypt=1 is included in http tracker announces
    enable_upnp60

    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_natpmp61

    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_lsd62Starts and stops Local Service Discovery. This service will +broadcast the infohashes of all the non-private torrents on the +local network to look for peers on the same swarm within multicast +reach.
    enable_dht63starts the dht node and makes the trackerless service available to +torrents.
    prefer_rc464if the allowed encryption level is both, setting this to true will +prefer rc4 if both methods are offered, plaintext otherwise
    proxy_hostnames65if true, hostname lookups are done via the configured proxy (if +any). This is only supported by SOCKS5 and HTTP.
    proxy_peer_connections66if 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_sequential67if 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_connections68if true, tracker connections are made over the configured proxy, if +any.
    max_bool_setting_internal69 
    +
    +
    +

    enum int_types

    +

    Declared in "libtorrent/settings_pack.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    tracker_completion_timeout 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. Default value is 60 +seconds.
    tracker_receive_timeout1tracker_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_timeout2the time 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
    tracker_maximum_response_length3this 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 megs).
    piece_timeout4the number of seconds from a request is sent until it times out if +no piece response is returned.
    request_timeout5the number of seconds one block (16kB) is expected to be received +within. If it's not, the block is requested from a different peer
    request_queue_time6the 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_queue7the 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_queue8max_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_threshold9if 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_timeout10peer_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. This defaults to 120 seconds, since +that's what's specified in the protocol specification. After half +the time out, a keep alive message is sent.
    urlseed_timeout11same 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_size12controls the pipelining size of url-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_retry13time to wait until a new retry of a web seed takes place
    file_pool_size14sets 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. It is usually a good +idea to find this limit and set the number of connections and the +number of files limits so their sum is slightly below it.
    max_failcount15max_failcount is the maximum times we try to connect to a peer +before stop connecting again. If a peer succeeds, the failcounter +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_time16the number of seconds to wait to reconnect to a peer. this time is +multiplied with the failcount.
    peer_connect_timeout17peer_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_speed18connection_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_timeout19if a peer is uninteresting and uninterested for longer than this +number of seconds, it will be disconnected. default is 10 minutes
    unchoke_interval20unchoke_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_interval21optimistic_unchoke_interval is the number of seconds between +each optimistic unchoke. On this timer, the currently +optimistically unchoked peer will change.
    num_want22num_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_threshold23initial_picker_threshold specifies the number of pieces we need +before we switch to rarest first picking. This defaults to 4, which +means the 4 first pieces in any torrent are picked at random, the +following pieces are picked in rarest first order.
    allowed_fast_set_size24the number of allowed pieces to send to peers that supports the +fast extensions
    suggest_mode25

    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_suggestsions which is the default and 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_bytes26max_queued_disk_bytes is the number 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_timeout27the 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_watermark28

    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 16kB 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_watermark29 
    send_buffer_watermark_factor30 
    choking_algorithm31

    choking_algorithm specifies which algorithm to use to determine +which peers to unchoke.

    +

    The options for choking algorithms are:

    +
      +
    • fixed_slots_choker is the traditional choker with a fixed +number of unchoke slots (as specified by +session::set_max_uploads()).
    • +
    • rate_based_choker 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.
    • +
    • bittyrant_choker attempts to optimize download rate by +finding the reciprocation rate of each peer individually and +prefers peers that gives the highest return on investment. It +still allocates all upload capacity, but shuffles it around to +the best peers first. For this choker to be efficient, you need +to set a global upload rate limit +(session::set_upload_rate_limit()). For more information +about this choker, see the paper. This choker is not fully +implemented nor tested.
    • +
    +

    seed_choking_algorithm controls the seeding unchoke behavior. +The available options are:

    +
      +
    • round_robin which round-robins the peers that are unchoked +when seeding. This distributes the upload bandwidht uniformly and +fairly. It minimizes the ability for a peer to download everything +without redistributing it.
    • +
    • fastest_upload unchokes the peers we can send to the fastest. +This might be a bit more reliable in utilizing all available +capacity.
    • +
    • anti_leech 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.
    • +
    +
    seed_choking_algorithm32 
    cache_size33

    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 to the amount of +physical RAM available in the machine divided by 8. If the amount +of physical RAM cannot be determined, it's set to 1024 (= 16 MiB).

    +

    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_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. Default is 60 second.

    +

    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_buffer_chunk_size34 
    cache_expiry35 
    deprecated1136 
    disk_io_write_mode37

    determines how files are opened when they're in read only mode +versus read and write mode. The options are:

    +
    +
    enable_os_cache
    +
    This is the default and files are opened normally, with the OS +caching reads and writes.
    +
    disable_os_cache
    +
    This opens all files in no-cache mode. This corresponds to the +OS not letting blocks for the files linger in the cache. This +makes sense in order to avoid the bittorrent client to +potentially evict all other processes' cache by simply handling +high throughput and large files. If libtorrent's read cache is +disabled, enabling this may reduce performance.
    +
    +

    One reason to disable caching is that it may help the operating +system from growing its file cache indefinitely.

    +
    disk_io_read_mode38 
    outgoing_port39

    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.

    +
    num_outgoing_ports40 
    peer_tos41peer_tos determines the TOS byte set in the IP header of every +packet sent to peers (including web seeds). The default value for +this is 0x0 (no marking). One potentially useful TOS mark is +0x20, this represents the QBone scavenger service. For more +details, see QBSS.
    active_downloads42

    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. By default this is set to 88, which is no more than one +DHT announce every 10 seconds.

    +

    active_tracker_limit is the max number of torrents to announce +to their trackers. By default this is 360, which is no more than +one announce every 5 seconds.

    +

    active_lsd_limit is the max number of torrents to announce to +the local network over the local service discovery protocol. By +default this is 80, which is no more than one announce every 5 +seconds (assuming the default announce interval of 5 minutes).

    +

    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_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_seeds43 
    active_checking44 
    active_dht_limit45 
    active_tracker_limit46 
    active_lsd_limit47 
    active_limit48 
    active_loaded_limit49 
    auto_manage_interval50auto_manage_interval is the number of seconds between the +torrent queue is updated, and rotated.
    seed_time_limit51this 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_interval52

    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_min_interval53 
    max_peerlist_size54

    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 default to the same as +max_peerlist_size, but can be used to save memory for paused +torrents, since it's not as important for them to keep a large peer +list.

    +
    max_paused_peerlist_size55 
    min_announce_interval56this 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 misconfigured +trackers.
    auto_manage_startup57this 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_quota58seeding_piece_quota is the number of pieces to send to a peer, +when seeding, before rotating in another peer to the unchoke set. +It defaults to 3 pieces, which means that when seeding, any peer +we've sent more than this number of pieces to will be unchoked in +favour of a choked peer.
    max_rejects59TODO: deprecate this +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_size60recv_socket_buffer_size and send_socket_buffer_size +specifies the buffer sizes set on peer sockets. 0 (which is the +default) 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.
    send_socket_buffer_size61 
    file_checks_delay_per_block62file_checks_delay_per_block is the number of milliseconds to +sleep in between disk read operations when checking torrents. This +defaults to 0, but 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.
    read_cache_line_size63

    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.

    +
    write_cache_line_size64 
    optimistic_disk_retry65

    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_pieces66max_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. +It defaults to 10.
    local_service_announce_interval67local_service_announce_interval is the time between local +network announces for a torrent. By default, when local service +discovery is enabled a torrent announces itself every 5 minutes. +This interval is specified in seconds.
    dht_announce_interval68dht_announce_interval is the number of seconds between +announcing torrents to the distributed hash table (DHT).
    udp_tracker_token_expiry69udp_tracker_token_expiry is the number of seconds libtorrent +will keep UDP tracker connection tokens around for. This is +specified to be 60 seconds, and defaults to that. 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.
    default_cache_min_age70default_cache_min_age is the minimum number of seconds any read +cache line is kept in the cache. This defaults to one second but +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.
    num_optimistic_unchoke_slots71num_optimistic_unchoke_slots is the number of optimistic +unchoke slots to use. It defaults to 0, which means automatic. +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. When this is set to 0, libtorrent opens up 20% of your +allowed upload slots as optimistic unchoke slots.
    default_est_reciprocation_rate72

    default_est_reciprocation_rate is the assumed reciprocation +rate from peers when using the BitTyrant choker. This defaults to +14 kiB/s. 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 defaults to 20%. +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 default to 3%. This only applies +to the BitTyrant choker.

    +
    increase_est_reciprocation_rate73 
    decrease_est_reciprocation_rate74 
    max_pex_peers75the 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_interval76tick_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_target77share_mode_target specifies the target share ratio for share +mode torrents. This defaults to 3, meaning 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_limit78

    upload_rate_limit, download_rate_limit, +local_upload_rate_limit and local_download_rate_limit sets +the session-global limits of upload and download rate limits, in +bytes per second. The local rates refer to peers on the local +network. By default peers on the local network are not rate +limited.

    +

    These rate limits are only used for local peers (peers within the +same subnet as the client itself) and it is only used when +ignore_limits_on_local_network is set to true (which it is by +default). These rate limits default to unthrottled, but can be +useful in case you want to treat local peers preferentially, but +not quite unthrottled.

    +

    A value of 0 means unlimited.

    +
    download_rate_limit79 
    deprecated380 
    deprecated481 
    dht_upload_rate_limit82dht_upload_rate_limit sets the rate limit on the DHT. This is +specified in bytes per second and defaults to 4000. For busy boxes +with lots of torrents that requires more DHT traffic, this should +be raised.
    unchoke_slots_limit83unchoke_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.
    deprecated584 
    connections_limit85connections_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_slack86connections_slack is the the number of incoming connections +exceeding the connection limit to accept in order to potentially +replace existing ones.
    utp_target_delay87

    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. The default is 50 milliseconds. +utp_gain_factor is the number of bytes the uTP congestion +window can increase at the most in one RTT. This defaults to 300 +bytes. 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. This defaults to 500 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 +lossed 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. By default it's set to 50 (i.e. cut in half). Do not +change this value unless you know what you're doing. Never set it +higher than 100.

    +
    utp_gain_factor88 
    utp_min_timeout89 
    utp_syn_resends90 
    utp_fin_resends91 
    utp_num_resends92 
    utp_connect_timeout93 
    deprecated694 
    utp_loss_multiplier95 
    mixed_mode_algorithm96The 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_size97listen_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. The default is 5 which 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 listen_on() is called again (or for the first time).
    torrent_connect_boost98torrent_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.
    alert_queue_size99alert_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.
    max_metadata_size100max_metadata_size is the maximum allowed size (in bytes) to be +received by the metadata extension, i.e. magnet links.
    deprecated9101 
    checking_mem_usage102the 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_announce103if 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_threads104for some aio back-ends, aio_threads specifies the number of +io-threads to use, and aio_max the max number of outstanding +jobs.
    aio_max105 
    network_threads106network_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.
    ssl_listen107ssl_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.
    tracker_backoff108

    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_limit109when 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
    seed_time_ratio_limit110 
    peer_turnover111peer_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_cutoff112 
    peer_turnover_interval113 
    connect_seed_every_n_download114this 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_size115the 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_bind116if 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_mask117a bitmask combining flags from alert::category_t defining which +kinds of alerts to receive
    out_enc_policy118

    control the settings for incoming and outgoing connections +respectively. see enc_policy enum for the available options. +Keep in mind that protocol encryption degrades performance in +several respects:

    +
      +
    1. It prevents "zero copy" disk buffers being sent to peers, since +each peer needs to mutate the data (i.e. encrypt it) the data +must be copied per peer connection rather than sending the same +buffer to multiple peers.
    2. +
    3. The encryption itself requires more CPU than plain bittorrent +protocol. The highest cost is the Diffie Hellman exchange on +connection setup.
    4. +
    5. The encryption handshake adds several round-trips to the +connection setup, and delays transferring data.
    6. +
    +
    in_enc_policy119 
    allowed_enc_level120determines 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_rate121the 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_up_rate122 
    proxy_type123proxy to use, defaults to none. see proxy_type_t.
    proxy_port124the port of the proxy server
    i2p_port125sets the i2p SAM bridge port to connect to. set the hostname with +the i2p_hostname setting.
    cache_size_volatile126this 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.
    max_int_setting_internal127 
    +
    +
    +

    enum settings_counts_t

    +

    Declared in "libtorrent/settings_pack.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    num_string_settings  
    num_bool_settings  
    num_int_settings  
    +
    +
    +

    enum suggest_mode_t

    +

    Declared in "libtorrent/settings_pack.hpp"

    + +++++ + + + + + + + + + + + + + + + + +
    namevaluedescription
    no_piece_suggestions0 
    suggest_read_cache1 
    +
    +
    +

    enum choking_algorithm_t

    +

    Declared in "libtorrent/settings_pack.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    fixed_slots_choker0 
    rate_based_choker2 
    bittyrant_choker3 
    +
    +
    +

    enum seed_choking_algorithm_t

    +

    Declared in "libtorrent/settings_pack.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    round_robin0 
    fastest_upload1 
    anti_leech2 
    +
    +
    +

    enum io_buffer_mode_t

    +

    Declared in "libtorrent/settings_pack.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    enable_os_cache0 
    deprecated1 
    disable_os_cache2 
    +
    +
    +

    enum bandwidth_mixed_algo_t

    +

    Declared in "libtorrent/settings_pack.hpp"

    + +++++ + + + + + + + + + + + + + + + + +
    namevaluedescription
    prefer_tcp0disables the mixed mode bandwidth balancing
    peer_proportional1does not throttle uTP, throttles TCP to the same proportion +of throughput as there are TCP connections
    +
    +
    +

    enum enc_policy

    +

    Declared in "libtorrent/settings_pack.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    pe_forced0Only encrypted connections are allowed. Incoming connections that +are not encrypted are closed and if the encrypted outgoing +connection fails, a non-encrypted retry will not be made.
    pe_enabled1encrypted connections are enabled, but non-encrypted connections +are allowed. An incoming non-encrypted connection will be accepted, +and if an outgoing encrypted connection fails, a non- encrypted +connection will be tried.
    pe_disabled2only non-encrypted connections are allowed.
    +
    +
    +

    enum enc_level

    +

    Declared in "libtorrent/settings_pack.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    pe_plaintext1use only plaintext encryption
    pe_rc42use only rc4 encryption
    pe_both3allow both
    +
    +
    +

    enum proxy_type_t

    +

    Declared in "libtorrent/settings_pack.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    none0This is the default, no proxy server is used, all other fields are +ignored.
    socks41The server is assumed to be a SOCKS4 server that requires a +username.
    socks52The server is assumed to be a SOCKS5 server (RFC 1928) that does +not require any authentication. The username and password are +ignored.
    socks5_pw3The server is assumed to be a SOCKS5 server that supports plain +text username and password authentication (RFC 1929). The +username and password specified may be sent to the proxy if it +requires.
    http4The server is assumed to be an HTTP proxy. If the transport used +for the connection is non-HTTP, the server is assumed to support +the CONNECT method. i.e. for web seeds and HTTP trackers, a plain +proxy will suffice. The proxy is assumed to not require +authorization. The username and password will not be used.
    http_pw5The server is assumed to be an HTTP proxy that requires user +authorization. The username and password will be sent to the proxy.
    i2p_proxy6route through a i2p SAM proxy
    + +
    +
    +
    +

    min_memory_usage() high_performance_seed()

    +

    Declared in "libtorrent/session.hpp"

    +
    +void min_memory_usage (settings_pack& set);
    +void high_performance_seed (settings_pack& set);
    +
    +

    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 inteded 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.

    + +
    +
    +

    setting_by_name() name_for_setting()

    +

    Declared in "libtorrent/settings_pack.hpp"

    +
    +char const* name_for_setting (int s);
    +int setting_by_name (std::string const& name);
    +
    +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/reference-Storage.html b/docs/reference-Storage.html new file mode 100644 index 0000000..a0b5966 --- /dev/null +++ b/docs/reference-Storage.html @@ -0,0 +1,643 @@ + + + + + + + + + + + + + + + +
    +
    + + + + +
    + + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.1.1
    +
    +

    Storage

    + +
    +

    file_slice

    +

    Declared in "libtorrent/file_storage.hpp"

    +

    represents a window of a file in a torrent.

    +

    The file_index refers to the index of the file (in the torrent_info). +To get the path and filename, use file_path() and give the file_index +as argument. The offset is the byte offset in the file where the range +starts, and size is the number of bytes this range is. The size + offset +will never be greater than the file size.

    +
    +struct file_slice
    +{
    +   int file_index;
    +   boost::int64_t offset;
    +   boost::int64_t size;
    +};
    +
    +
    +
    file_index
    +
    the index of the file
    +
    +
    +
    offset
    +
    the offset from the start of the file, in bytes
    +
    +
    +
    size
    +
    the size of the window, in bytes
    +
    +
    +
    +

    file_storage

    +

    Declared in "libtorrent/file_storage.hpp"

    +

    The file_storage class represents a file list and the piece +size. Everything necessary to interpret a regular bittorrent storage +file structure.

    +
    +class file_storage
    +{
    +   bool is_valid () const;
    +   void reserve (int num_files);
    +   void add_file (std::string const& path, boost::int64_t file_size, int file_flags = 0
    +      , std::time_t mtime = 0, std::string const& symlink_path = "");
    +   void add_file_borrow (char const* filename, int filename_len
    +      , std::string const& path, boost::int64_t file_size
    +      , boost::uint32_t file_flags = 0, char const* filehash = 0
    +      , boost::int64_t mtime = 0, std::string const& symlink_path = "");
    +   void rename_file (int index, std::string const& new_filename);
    +   std::vector<file_slice> map_block (int piece, boost::int64_t offset
    +      , int size) const;
    +   peer_request map_file (int file, boost::int64_t offset, int size) const;
    +   int num_files () const;
    +   boost::int64_t total_size () const;
    +   void set_num_pieces (int n);
    +   int num_pieces () const;
    +   void set_piece_length (int l);
    +   int piece_length () const;
    +   int piece_size (int index) const;
    +   std::string const& name () const;
    +   void set_name (std::string const& n);
    +   void swap (file_storage& ti);
    +   void unload ();
    +   bool is_loaded () const;
    +   void optimize (int pad_file_limit = -1, int alignment = -1
    +      , bool tail_padding = false);
    +   sha1_hash hash (int index) const;
    +   std::string file_name (int index) const;
    +   boost::int64_t file_offset (int index) const;
    +   time_t mtime (int index) const;
    +   bool pad_file_at (int index) const;
    +   std::string const& symlink (int index) const;
    +   std::string file_path (int index, std::string const& save_path = "") const;
    +   boost::int64_t file_size (int index) const;
    +   boost::uint32_t file_path_hash (int index, std::string const& save_path) const;
    +   void all_path_hashes (boost::unordered_set<boost::uint32_t>& table) const;
    +   std::vector<std::string> const& paths () const;
    +   int file_flags (int index) const;
    +   bool file_absolute_path (int index) const;
    +   int file_index_at_offset (boost::int64_t offset) const;
    +   char const* file_name_ptr (int index) const;
    +   int file_name_len (int index) const;
    +   void apply_pointer_offset (ptrdiff_t off);
    +
    +   enum flags_t
    +   {
    +      pad_file,
    +      attribute_hidden,
    +      attribute_executable,
    +      attribute_symlink,
    +   };
    +
    +   enum file_flags_t
    +   {
    +      flag_pad_file,
    +      flag_hidden,
    +      flag_executable,
    +      flag_symlink,
    +   };
    +};
    +
    +
    +

    is_valid()

    +
    +bool is_valid () const;
    +
    +

    returns true if the piece length has been initialized +on the file_storage. This is typically taken as a proxy +of whether the file_storage as a whole is initialized or +not.

    +
    +
    +

    reserve()

    +
    +void reserve (int num_files);
    +
    +

    allocates space for num_files in the internal file list. This can +be used to avoid reallocating the internal file list when the number +of files to be added is known up-front.

    + +
    +
    +

    add_file() add_file_borrow()

    +
    +void add_file (std::string const& path, boost::int64_t file_size, int file_flags = 0
    +      , std::time_t mtime = 0, std::string const& symlink_path = "");
    +void add_file_borrow (char const* filename, int filename_len
    +      , std::string const& path, boost::int64_t file_size
    +      , boost::uint32_t file_flags = 0, char const* filehash = 0
    +      , boost::int64_t mtime = 0, std::string const& symlink_path = "");
    +
    +

    Adds a file to the file storage. The add_file_borrow version +expects that filename points to a string of filename_len +bytes that 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 NULL, the filename from path is used and not +borrowed. In this case filename_len is ignored.

    +

    The path argument is the full path (in the torrent file) to +the file to add. Note that this is not supposed to be an absolute +path, but it is expected to include the name of the torrent as the +first path element.

    +

    file_size is the size of the file in bytes.

    +

    The file_flags argument sets attributes on the file. The file +attributes is an extension and may not work in all bittorrent clients.

    +

    For possible file attributes, see file_storage::flags_t.

    +

    The mtime argument is optional and can be set to 0. If non-zero, +it is the posix time of the last modification time of this file.

    +

    symlink_path is the path the file is a symlink to. To make this a +symlink you also need to set the file_storage::flag_symlink file flag.

    +

    If more files than one are added, certain restrictions to their paths +apply. In a multi-file file storage (torrent), all files must share +the same root directory.

    +

    That is, the first path element of all files must be the same. +This shared path element is also set to the name of the torrent. It +can be changed by calling set_name.

    +
    +
    +

    rename_file()

    +
    +void rename_file (int 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.

    +
    +
    +

    map_block()

    +
    +std::vector<file_slice> map_block (int piece, boost::int64_t offset
    +      , int size) const;
    +
    +

    returns a list of file_slice objects representing the portions of +files the specified piece index, byte offset and size range overlaps. +this is the inverse mapping of map_file().

    +

    Preconditions of this function is that the input range is within the +torrents address space. piece may not be negative and

    +
    +piece * piece_size + offset + size
    +

    may not exceed the total size of the torrent.

    +
    +
    +

    map_file()

    +
    +peer_request map_file (int file, boost::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 ove 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.

    +
    +
    +

    num_files()

    +
    +int num_files () const;
    +
    +

    returns the number of files in the file_storage

    +
    +
    +

    total_size()

    +
    +boost::int64_t total_size () const;
    +
    +

    returns the total number of bytes all the files in this torrent spans

    + +
    +
    +

    num_pieces() set_num_pieces()

    +
    +void set_num_pieces (int n);
    +int num_pieces () const;
    +
    +

    set and get the number of pieces in the torrent

    + +
    +
    +

    piece_length() set_piece_length()

    +
    +void set_piece_length (int l);
    +int piece_length () const;
    +
    +

    set and get the size of each piece in this torrent. This size is typically an even power +of 2. It doesn't have to be though. It should be divisible by 16kiB however.

    +
    +
    +

    piece_size()

    +
    +int piece_size (int 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.

    + +
    +
    +

    set_name() name()

    +
    +std::string const& name () const;
    +void set_name (std::string const& n);
    +
    +

    set and get the name of this torrent. For multi-file torrents, this is also +the name of the root directory all the files are stored in.

    +
    +
    +

    swap()

    +
    +void swap (file_storage& ti);
    +
    +

    swap all content of this with ti.

    +
    +
    +

    unload()

    +
    +void unload ();
    +
    +

    deallocates most of the memory used by this +instance, leaving it only partially usable

    +
    +
    +

    is_loaded()

    +
    +bool is_loaded () const;
    +
    +

    returns true when populated with at least one file

    +
    +
    +

    optimize()

    +
    +void optimize (int pad_file_limit = -1, int alignment = -1
    +      , bool tail_padding = false);
    +
    +

    if pad_file_limit >= 0, files larger than that limit will be padded, +default is to not add any padding (-1). The alignment specifies the +alignment files should be padded to. This defaults to the piece size +(-1) but it may also make sense to set it to 16 kiB, or something +divisible by 16 kiB. +If pad_file_limit is 0, every file will be padded (except empty ones). +tail_padding indicates whether aligned files also are padded at +the end to make them end aligned. This is required for mutable +torrents, since piece hashes are compared

    + + + + + + + +
    + +
    +

    file_path_hash()

    +
    +boost::uint32_t file_path_hash (int index, std::string const& save_path) const;
    +
    +

    returns the crc32 hash of file_path(index)

    +
    +
    +

    all_path_hashes()

    +
    +void all_path_hashes (boost::unordered_set<boost::uint32_t>& table) const;
    +
    +

    this will add the CRC32 hash of all directory entries to the table. No +filename will be included, just directories. Every depth of directories +are added separately to allow test for collisions with files at all +levels. i.e. if one path in the torrent is foo/bar/baz, the CRC32 +hashes for foo, foo/bar and foo/bar/baz will be added to +the set.

    +
    +
    +

    file_flags()

    +
    +int file_flags (int index) const;
    +
    +

    returns a bitmask of flags from file_flags_t that apply +to file at index.

    +
    +
    +

    file_absolute_path()

    +
    +bool file_absolute_path (int 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.

    +
    +
    +

    file_index_at_offset()

    +
    +int file_index_at_offset (boost::int64_t offset) const;
    +
    +

    returns the index of the file at the given offset in the torrent

    + +
    +
    +

    file_name_len() file_name_ptr()

    +
    +char const* file_name_ptr (int index) const;
    +int file_name_len (int index) const;
    +
    +

    low-level function. returns a pointer to the internal storage for +the filename. This string may not be null terminated! +the file_name_len() function returns the length of the filename.

    +
    +
    +

    apply_pointer_offset()

    +
    +void apply_pointer_offset (ptrdiff_t off);
    +
    +

    if the backing buffer changed for this storage, this is the pointer +offset to add to any pointers to make them point into the new buffer

    +
    +
    +

    enum flags_t

    +

    Declared in "libtorrent/file_storage.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    pad_file1the file is a pad file. It's required to contain zeroes +at it will not be saved to disk. Its purpose is to make +the following file start on a piece boundary.
    attribute_hidden2this file has the hidden attribute set. This is primarily +a windows attribute
    attribute_executable4this file has the executable attribute set.
    attribute_symlink8this file is a symbolic link. It should have a link +target string associated with it.
    +
    +
    +

    enum file_flags_t

    +

    Declared in "libtorrent/file_storage.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    flag_pad_file1this file is a pad file. The creator of the +torrent promises the file is entirely filled with +zeroes and does not need to be downloaded. The +purpose is just to align the next file to either +a block or piece boundary.
    flag_hidden2this file is hidden (sets the hidden attribute +on windows)
    flag_executable4this file is executable (sets the executable bit +on posix like systems)
    flag_symlink8this file is a symlink. The symlink target is +specified in a separate field
    +
    +
    +
    +

    storage_params

    +

    Declared in "libtorrent/storage_defs.hpp"

    +

    see default_storage::default_storage()

    +
    +struct storage_params
    +{
    +   storage_params ();
    +
    +   file_storage const* files;
    +   file_storage const* mapped_files;
    +   std::string path;
    +   file_pool* pool;
    +   storage_mode_t mode;
    +   std::vector<boost::uint8_t> const* priorities;
    +   torrent_info const* info;
    +};
    +
    +
    +
    +

    default_storage_constructor()

    +

    Declared in "libtorrent/storage_defs.hpp"

    +
    +storage_interface* default_storage_constructor (storage_params const&);
    +
    +

    the constructor function for the regular file storage. This is the +default value for add_torrent_params::storage.

    +
    +
    +

    disabled_storage_constructor()

    +

    Declared in "libtorrent/storage_defs.hpp"

    +
    +storage_interface* disabled_storage_constructor (storage_params const&);
    +
    +

    the constructor function for the disabled storage. This can be used for +testing and benchmarking. It will throw away any data written to +it and return garbage for anything read from it.

    +
    +
    +

    zero_storage_constructor()

    +

    Declared in "libtorrent/storage_defs.hpp"

    +
    +storage_interface* zero_storage_constructor (storage_params const&);
    +
    +
    +
    +

    enum storage_mode_t

    +

    Declared in "libtorrent/storage_defs.hpp"

    + +++++ + + + + + + + + + + + + + + + + +
    namevaluedescription
    storage_mode_allocate0All pieces will be written to their final position, all files will be +allocated in full when the torrent is first started. This is done with +fallocate() and similar calls. This mode minimizes fragmentation.
    storage_mode_sparse1All pieces will be written to the place where they belong and sparse files +will be used. This is the recommended, and default mode.
    +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/reference-Utility.html b/docs/reference-Utility.html new file mode 100644 index 0000000..a76ce21 --- /dev/null +++ b/docs/reference-Utility.html @@ -0,0 +1,491 @@ + + + + + + + + + + + + + + + +
    +
    + + + + +
    + + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.1.1
    +
    +

    Utility

    + +
    +

    bitfield

    +

    Declared in "libtorrent/bitfield.hpp"

    +

    The bitfield type stores any number of bits as a bitfield +in a heap allocated array.

    +
    +struct bitfield
    +{
    +   bitfield (int bits);
    +   bitfield (bitfield const& rhs);
    +   bitfield (bitfield&& rhs);
    +   bitfield (int bits, bool val);
    +   bitfield ();
    +   bitfield (char const* b, int bits);
    +   void assign (char const* b, int bits);
    +   bool operator[] (int index) const;
    +   bool get_bit (int index) const;
    +   void clear_bit (int index);
    +   void set_bit (int index);
    +   bool all_set () const;
    +   bool none_set () const;
    +   int size () const;
    +   int num_words () const;
    +   bool empty () const;
    +   char const* data () const;
    +   bitfield& operator= (bitfield const& rhs);
    +   int count () const;
    +};
    +
    +
    +

    bitfield()

    +
    +bitfield (int bits);
    +bitfield (bitfield const& rhs);
    +bitfield (bitfield&& rhs);
    +bitfield (int bits, bool val);
    +bitfield ();
    +bitfield (char const* b, int bits);
    +
    +

    constructs a new bitfield. The default constructor creates an empty +bitfield. bits is the size of the bitfield (specified in bits). +val is the value to initialize the bits to. If not specified +all bits are initialized to 0.

    +

    The constructor taking a pointer b and bits copies a bitfield +from the specified buffer, and bits number of bits (rounded up to +the nearest byte boundary).

    +
    +
    +

    assign()

    +
    +void assign (char const* b, int bits);
    +
    +

    copy bitfield from buffer b of bits number of bits, rounded up to +the nearest byte boundary.

    +
    +
    +

    operator[]()

    +
    +bool operator[] (int index) const;
    +
    +

    query bit at index. Returns true if bit is 1, otherwise false.

    + +
    +
    +

    set_bit() clear_bit()

    +
    +void clear_bit (int index);
    +void set_bit (int index);
    +
    +

    set bit at index to 0 (clear_bit) or 1 (set_bit).

    +
    +
    +

    all_set()

    +
    +bool all_set () const;
    +
    +

    returns true if all bits in the bitfield are set

    +
    +
    +

    size()

    +
    +int size () const;
    +
    +

    returns the size of the bitfield in bits.

    +
    +
    +

    empty()

    +
    +bool empty () const;
    +
    +

    returns true if the bitfield has zero size.

    +
    +
    +

    data()

    +
    +char const* data () const;
    +
    +

    returns a pointer to the internal buffer of the bitfield.

    +
    +
    +

    operator=()

    +
    +bitfield& operator= (bitfield const& rhs);
    +
    +

    copy operator

    +
    +
    +

    count()

    +
    +int count () const;
    +
    +

    count the number of bits in the bitfield that are set to 1.

    +
    +
    +
    +

    hasher

    +

    Declared in "libtorrent/hasher.hpp"

    +

    this is a SHA-1 hash class.

    +

    You use it by first instantiating it, then call update() to feed it +with data. i.e. you don't have to keep the entire buffer of which you want to +create the hash in memory. You can feed the hasher parts of it at a time. When +You have fed the hasher with all the data, you call final() and it +will return the sha1-hash of the data.

    +

    The constructor that takes a char const* and an integer will construct the +sha1 context and feed it the data passed in.

    +

    If you want to reuse the hasher object once you have created a hash, you have to +call reset() to reinitialize it.

    +

    The sha1-algorithm used was implemented by Steve Reid and released as public domain. +For more info, see src/sha1.cpp.

    +
    +class hasher
    +{
    +   hasher ();
    +   hasher (const char* data, int len);
    +   hasher& operator= (hasher const& h);
    +   hasher (hasher const& h);
    +   hasher& update (std::string const& data);
    +   hasher& update (const char* data, int len);
    +   sha1_hash final ();
    +   void reset ();
    +   ~hasher ();
    +};
    +
    +
    +

    hasher()

    +
    +hasher (const char* data, int len);
    +
    +

    this is the same as default constructing followed by a call to +update(data, len).

    +
    +
    +

    update()

    +
    +hasher& update (std::string const& data);
    +hasher& update (const char* data, int len);
    +
    +

    append the following bytes to what is being hashed

    +
    +
    +

    final()

    +
    +sha1_hash final ();
    +
    +

    returns the SHA-1 digest of the buffers previously passed to +update() and the hasher constructor.

    +
    +
    +

    reset()

    +
    +void reset ();
    +
    +

    restore the hasher state to be as if the hasher has just been +default constructed.

    +
    +
    +
    +

    sha1_hash

    +

    Declared in "libtorrent/sha1_hash.hpp"

    +

    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.

    +
    +class sha1_hash
    +{
    +   sha1_hash ();
    +   static sha1_hash max ();
    +   static sha1_hash min ();
    +   explicit sha1_hash (char const* s);
    +   void assign (char const* str);
    +   explicit sha1_hash (std::string const& s);
    +   void assign (std::string const& s);
    +   char* data ();
    +   char const* data () const;
    +   void clear ();
    +   bool is_all_zeros () const;
    +   sha1_hash& operator<<= (int n);
    +   sha1_hash& operator>>= (int n);
    +   bool operator!= (sha1_hash const& n) const;
    +   bool operator< (sha1_hash const& n) const;
    +   bool operator== (sha1_hash const& n) const;
    +   sha1_hash operator~ () const;
    +   sha1_hash operator^ (sha1_hash const& n) const;
    +   sha1_hash& operator^= (sha1_hash const& n);
    +   sha1_hash operator& (sha1_hash const& n) const;
    +   sha1_hash& operator&= (sha1_hash const& n);
    +   sha1_hash& operator|= (sha1_hash const& n);
    +   boost::uint8_t& operator[] (int i);
    +   boost::uint8_t const& operator[] (int i) const;
    +   const_iterator begin () const;
    +   iterator begin ();
    +   iterator end ();
    +   const_iterator end () const;
    +   std::string to_string () const;
    +};
    +
    +
    +

    sha1_hash()

    +
    +sha1_hash ();
    +
    +

    constructs an all-zero sha1-hash

    +
    +
    +

    max()

    +
    +static sha1_hash max ();
    +
    +

    returns an all-F sha1-hash. i.e. the maximum value +representable by a 160 bit number (20 bytes). This is +a static member function.

    +
    +
    +

    min()

    +
    +static sha1_hash min ();
    +
    +

    returns an all-zero sha1-hash. i.e. the minimum value +representable by a 160 bit number (20 bytes). This is +a static member function.

    + +
    +
    +

    assign() sha1_hash()

    +
    +explicit sha1_hash (char const* s);
    +void assign (char const* str);
    +explicit sha1_hash (std::string const& s);
    +void assign (std::string const& s);
    +
    +

    copies 20 bytes from the pointer provided, into the sha1-hash. +The passed in string MUST be at least 20 bytes. NULL terminators +are ignored, s is treated like a raw memory buffer.

    +
    +
    +

    clear()

    +
    +void clear ();
    +
    +

    set the sha1-hash to all zeroes.

    +
    +
    +

    is_all_zeros()

    +
    +bool is_all_zeros () const;
    +
    +

    return true if the sha1-hash is all zero.

    +
    +
    +

    operator<<=()

    +
    +sha1_hash& operator<<= (int n);
    +
    +

    shift left n bits.

    +
    +
    +

    operator>>=()

    +
    +sha1_hash& operator>>= (int n);
    +
    +

    shift r n bits.

    + + +
    +
    +

    operator!=() operator<() operator==()

    +
    +bool operator!= (sha1_hash const& n) const;
    +bool operator< (sha1_hash const& n) const;
    +bool operator== (sha1_hash const& n) const;
    +
    +

    standard comparison operators

    +
    +
    +

    operator~()

    +
    +sha1_hash operator~ () const;
    +
    +

    returns a bit-wise negated copy of the sha1-hash

    +
    +
    +

    operator^()

    +
    +sha1_hash operator^ (sha1_hash const& n) const;
    +
    +

    returns the bit-wise XOR of the two sha1-hashes.

    +
    +
    +

    operator^=()

    +
    +sha1_hash& operator^= (sha1_hash const& n);
    +
    +

    in-place bit-wise XOR with the passed in sha1_hash.

    +
    +
    +

    operator&()

    +
    +sha1_hash operator& (sha1_hash const& n) const;
    +
    +

    returns the bit-wise AND of the two sha1-hashes.

    +
    +
    +

    operator&=()

    +
    +sha1_hash& operator&= (sha1_hash const& n);
    +
    +

    in-place bit-wise AND of the passed in sha1_hash

    +
    +
    +

    operator|=()

    +
    +sha1_hash& operator|= (sha1_hash const& n);
    +
    +

    in-place bit-wise OR of the two sha1-hash.

    +
    +
    +

    operator[]()

    +
    +boost::uint8_t& operator[] (int i);
    +boost::uint8_t const& operator[] (int i) const;
    +
    +

    accessors for specific bytes

    + +
    +
    +

    begin() end()

    +
    +const_iterator begin () const;
    +iterator begin ();
    +iterator end ();
    +const_iterator end () const;
    +
    +

    start and end iterators for the hash. The value type +of these iterators is boost::uint8_t.

    +
    +
    +

    to_string()

    +
    +std::string to_string () const;
    +
    +

    return a copy of the 20 bytes representing the sha1-hash as a std::string. +It's still a binary string with 20 binary characters.

    +
    +
    +
    +

    hash_value()

    +

    Declared in "libtorrent/sha1_hash.hpp"

    +
    +inline std::size_t hash_value (sha1_hash const& b);
    +
    +
    +
    +

    operator<<()

    +

    Declared in "libtorrent/sha1_hash.hpp"

    +
    +inline std::ostream& operator<< (std::ostream& os, sha1_hash const& peer);
    +
    +

    print a sha1_hash object to an ostream as 40 hexadecimal digits

    +
    +
    +

    operator>>()

    +

    Declared in "libtorrent/sha1_hash.hpp"

    +
    +inline std::istream& operator>> (std::istream& is, sha1_hash& peer);
    +
    +

    read 40 hexadecimal digits from an istream into a sha1_hash

    +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/reference-ed25519.html b/docs/reference-ed25519.html new file mode 100644 index 0000000..1e8cf60 --- /dev/null +++ b/docs/reference-ed25519.html @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + +
    +
    + + + + +
    + + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.1.1
    +
    +

    ed25519

    + +
    +

    ed25519_create_seed()

    +

    Declared in "libtorrent/ed25519.hpp"

    +
    +void ed25519_create_seed (unsigned char *seed);
    +
    + + + + +
    +
    +

    ed25519_key_exchange() ed25519_create_keypair() ed25519_verify() ed25519_sign() ed25519_add_scalar()

    +

    Declared in "libtorrent/ed25519.hpp"

    +
    +void ed25519_add_scalar (unsigned char *public_key, unsigned char *private_key, const unsigned char *scalar);
    +void ed25519_key_exchange (unsigned char *shared_secret, const unsigned char *public_key, const unsigned char *private_key);
    +void ed25519_create_keypair (unsigned char *public_key, unsigned char *private_key, const unsigned char *seed);
    +int ed25519_verify (const unsigned char *signature, const unsigned char *message, size_t message_len, const unsigned char *private_key);
    +void ed25519_sign (unsigned char *signature, const unsigned char *message, size_t message_len, const unsigned char *public_key, const unsigned char *private_key);
    +
    +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/reference.html b/docs/reference.html new file mode 100644 index 0000000..fcca651 --- /dev/null +++ b/docs/reference.html @@ -0,0 +1,317 @@ + + + + + + +reference documentation + + + + + + + +
    +
    + + + + +
    +

    reference documentation

    + + + + +
    +

    Alerts

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/rst.css b/docs/rst.css new file mode 100644 index 0000000..c2d7875 --- /dev/null +++ b/docs/rst.css @@ -0,0 +1,241 @@ +.document a { + border: none; + color: black; +} + +.document a:hover { + background: none; +} + +.document a.reference { + color: #8D370A; + border-bottom: dotted 1px #8D370A; +} + +.document a.reference:hover { + border-bottom: solid 1px #8D370A; + background: #eee; +} + +div.section { + margin-bottom: 3em; +} + +div.section div.section div.section { + margin-bottom: 2em; +} + +div.section p, div.section ul, div.section dl { +} + +div.main-toc { + column-count: 4; + -webkit-column-count: 4; + -moz-column-count: 4; + column-width: 13em; + -webkit-column-width: 13em; + -moz-column-width: 13em; + border: 1px solid #999; + padding: 5px; + margin-bottom: 10px; +} + +.rubric +{ + margin-top: 5px; + margin-bottom: 5px; + font-size: 120%; + font-weight: bold; +} + +table.docinfo { + text-align: left; + float: right; + width: 200px; + margin-right: 0px; + margin-left: 20px; + margin-bottom: 20px; +} + +table.docinfo th { + border-top: none; + font-size: 72%; + padding-left: 10px; +} + +table.docinfo td { + padding-left: 10px; + font-size: 88%; +} + +table.docinfo tr.field td, table.docinfo tr.field th {display: none;} + +#h1.title { display: none; } + +dt { + margin-bottom: 0.5em; + color: #000; + font-weight: bold; +} + +dd { + margin-left: 2em; + margin-bottom: 1em; +} + +tt { + font: 1em "Courier New", "Courier"; + font-size: 110%; +} + +pre { + font-family: "Courier", monospace; + margin-right: 10px; + background: #C1E5F6; + border-left: solid 2px #6185A6; + border-right: solid 2px #6185A6; + padding: 5px 10px 5px 10px; + + background: #f6f6f6; + border: solid 1px #ddd; + margin: 1em 0; +} + +div.warning, div.note, div.important { + width: 80%; + margin: 1.5em auto; + background: #C1E5F6; + background: #F1FFF5; + border: solid 1px #D1DFD5; + padding: 5px 10px 5px 10px; +} + +p.admonition-title { + font-family: Georgia, "Lucida Grande"; + font-size: 128%; + letter-spacing: 2px; + text-transform: uppercase; + margin: 0 0 0.5em 0; + border-bottom: solid 1px #D1DFD5 +} + +h1 { font-size: 200%; } +h2 { font-size: 130%; } +h3 { font-size: 100%; font-style: italic; } + +h1.title { text-align: center; } + +table { margin-bottom: 1em; border-collapse: collapse; } +table, th, td { border: none; } + +th, td { padding: 0.3em; } + +th { + text-align: left; + background: #f0f0e0; + border-right: solid 1px #f0f0e0; + border-top: solid 1px #e8e8d8; + border-bottom: solid 1px #e8e8d8; +} + +td { + background: #f8f8e8; + border-right: solid 1px #f8f8e8; + border-bottom: solid 1px #e8e8d8; +} + +td td { + background: #e8e8d8; + border-right: solid 1px #e8e8d8; + border-bottom: solid 1px #d8d8c8; +} + +div.topic { + border-left: solid 1px #eee; + padding-left: 1em; + margin: 0 0 1.5em; +} + +p.topic-title { + font: 1.3em Georgia, "Times New Roman", serif; +} + +/* TOC */ + +div.contents { + border: none; +} + +#table-of-contents { + margin-left: 20px; + padding: 0 0 1em; + width: 200px; + float: right; + clear: right; + background: url('img/blue_bottom.png') no-repeat bottom left; + border-right: solid 1px #A1C5D6; +} + +#table-of-contents p { + font-family: Georgia, "Times New Roman", serif; + background: #A1C5D6 url('img/blue_top.png') no-repeat top left; + color: #AD370A; + padding: 0.5em; + margin: 0; +} + +#table-of-contents li { + margin: 0 0.5em 0 0.5em; +} + +#table-of-contents ul { + margin: 0; + padding: 0 0 0 0.8em; + list-style: none; + text-align: left; + line-height: 1.5em; +} + +#table-of-contents ul ul { + background: url('img/dotline.gif') repeat-y; +} + +#table-of-contents a.reference { + border: none; + font: 0.88em Tahoma; + font-weight: bold; + color: #000050; + margin-right: 1em; + background: url('img/minus.gif') no-repeat left 50%; + padding-left: 15px; +} + +#table-of-contents li li a.reference { + font-weight: normal; + background: none; + padding: 0; +} + +#table-of-contents a.reference:hover +{ + border-bottom: solid 1px #8D370A; +} + +#librarySidebar { + float: left; + width: 170px; +} + +#libraryBody { + border-left: solid 1px #eee; + padding-left: 10px; + margin-left: 178px; + margin-right: 10px; +} + +.keyword { font-weight: bold } +.string { color: #771; } +.comment { font-style: italic; color: #559; } +.preproc { font-style: italic; color: #959; } +.number { color: #595; } + diff --git a/docs/session_stats_peers.png b/docs/session_stats_peers.png new file mode 100644 index 0000000000000000000000000000000000000000..e134f310f3b8f04a15c4408702cd0739b0740e23 GIT binary patch literal 3902 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 + ++------------+--------+----------------------------------+ +| name | type | default | ++============+========+==================================+ +| user_agent | string | "libtorrent/" LIBTORRENT_VERSION | ++------------+--------+----------------------------------+ + +this is the client identification to the tracker. The recommended +format of this string is: "ClientName/ClientVersion +libtorrent/libtorrentVersion". 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 | 0 | ++-------------+--------+---------+ + +``announce_ip`` is the ip address passed along to trackers as the +``&ip=`` parameter. If left as the default, that parameter is +omitted. + +.. _mmap_cache: + +.. raw:: html + + + ++------------+--------+---------+ +| name | type | default | ++============+========+=========+ +| mmap_cache | string | 0 | ++------------+--------+---------+ + +``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. + +Since this setting sets a hard upper limit on cache usage, it +cannot be combined with +``settings_pack::contiguous_recv_buffer``, since that feature +treats the ``cache_size`` setting as a soft (but still pretty hard) +limit. The result of combining the two is peers being disconnected +after failing to allocate more disk buffers. + +This feature requires the ``mmap`` system call, on systems that +don't have ``mmap`` this setting is ignored. + +.. _handshake_client_version: + +.. raw:: html + + + ++--------------------------+--------+---------+ +| name | type | default | ++==========================+========+=========+ +| handshake_client_version | string | 0 | ++--------------------------+--------+---------+ + +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 + +.. _outgoing_interfaces: + +.. raw:: html + + + ++---------------------+--------+---------+ +| name | type | default | ++=====================+========+=========+ +| outgoing_interfaces | string | "" | ++---------------------+--------+---------+ + +sets the network interface this session will use when it opens +outgoing connections. By default, it binds outgoing connections to +INADDR_ANY and port 0 (i.e. let the OS decide). Ths parameter must +be a string containing one or more, comma separated, adapter names. +Adapter names on unix systems are of the form "eth0", "eth1", +"tun0", etc. When specifying multiple interfaces, they will be +assigned in round-robin order. 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. +Setting this to an empty string will disable binding of outgoing +connections. + +.. _listen_interfaces: + +.. raw:: html + + + ++-------------------+--------+----------------+ +| name | type | default | ++===================+========+================+ +| listen_interfaces | string | "0.0.0.0:6881" | ++-------------------+--------+----------------+ + +a comma-separated list of IP port-pairs. These +are the listen ports that will be opened for accepting incoming uTP +and TCP connections. It is possible to listen on multiple +IPs and multiple ports. Binding to port 0 will make the +operating system pick the port. The default is "0.0.0.0:6881", which +binds to all interfaces on port 6881. +if binding fails, the listen_failed_alert is posted, potentially +more than once. Once/if binding the listen socket(s) succeed, +listen_succeeded_alert is posted. +Each port will attempt to open both a UDP and a TCP listen socket, +to allow accepting uTP connections as well as TCP. If using the DHT, +this will also make the DHT use the same UDP ports. + +.. note:: + The current support for opening arbitrary UDP sockets is limited. + In this version of libtorrent, there will only ever be two UDP + sockets, one for IPv4 and one for IPv6. + +.. _proxy_hostname: + +.. raw:: html + + + ++----------------+--------+---------+ +| name | type | default | ++================+========+=========+ +| proxy_hostname | string | "" | ++----------------+--------+---------+ + +when using a poxy, this is the hostname where the proxy is running +see proxy_type. + +.. _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 whne +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 | "-LT1110-" | ++------------------+--------+------------+ + +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 +used as the peer-id + +.. _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. This setting will contain one or more bootstrap nodes +by default. + +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. Multiple connections from +the same IP address is not allowed by default, to prevent abusive +behavior by peers. It may be useful to allow such connections in +cases where simulations are run on the same machine, and all peers +in a swarm has the same IP address. + +.. _send_redundant_have: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| send_redundant_have | bool | true | ++---------------------+------+---------+ + +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. +``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. +Default is false. + +.. _lazy_bitfields: + +.. raw:: html + + + ++----------------+------+---------+ +| name | type | default | ++================+======+=========+ +| lazy_bitfields | bool | false | ++----------------+------+---------+ + +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. + +.. _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. This is false by default, which means the DHT is used +by default 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 the configured router for this machine. i.e. +it's a way to not talk to other people's routers by mistake. + +.. _use_parole_mode: + +.. raw:: html + + + ++-----------------+------+---------+ +| name | type | default | ++=================+======+=========+ +| use_parole_mode | bool | true | ++-----------------+------+---------+ + +``use_parole_mode`` specifies if parole mode should be used. Parole +mode means that peers that participate in pieces that fail the hash +check are put in a mode where they are only allowed to download +whole pieces. If the whole piece a peer in parole mode fails the +hash check, it is banned. If a peer participates in a piece that +passes the hash check, it is taken out of parole mode. + +.. _use_read_cache: + +.. raw:: html + + + ++----------------+------+---------+ +| name | type | default | ++================+======+=========+ +| use_read_cache | bool | true | ++----------------+------+---------+ + +enable and disable caching of blocks read from disk. the purpose of +the read cache is partly read-ahead of requests but also to avoid +reading blocks back from the disk multiple times for popular +pieces. + +.. _dont_flush_write_cache: + +.. raw:: html + + + ++------------------------+------+---------+ +| name | type | default | ++========================+======+=========+ +| dont_flush_write_cache | bool | false | ++------------------------+------+---------+ + +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 + +.. _coalesce_reads: + +.. _coalesce_writes: + +.. raw:: html + + + + ++-----------------+------+---------+ +| name | type | default | ++=================+======+=========+ +| coalesce_reads | bool | false | ++-----------------+------+---------+ +| coalesce_writes | bool | false | ++-----------------+------+---------+ + +allocate separate, contiguous, buffers for read and write calls. +Only used where writev/readv cannot be used will use more RAM but +may improve performance + +.. _auto_manage_prefer_seeds: + +.. raw:: html + + + ++--------------------------+------+---------+ +| name | type | default | ++==========================+======+=========+ +| auto_manage_prefer_seeds | bool | false | ++--------------------------+------+---------+ + +prefer seeding torrents when determining which torrents to give +active slots to, the default is false which gives 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. It +defaults to false, which is the same behavior previous versions of +libtorrent has had as well. + +``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. This is false by +default in order to comply with the multi-tracker specification. + +.. _prefer_udp_trackers: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| prefer_udp_trackers | bool | true | ++---------------------+------+---------+ + +``prefer_udp_trackers`` is true by default. It 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 trackers' tier is respected and there's no +preference of one protocol over another. + +.. _strict_super_seeding: + +.. raw:: html + + + ++----------------------+------+---------+ +| name | type | default | ++======================+======+=========+ +| strict_super_seeding | bool | false | ++----------------------+------+---------+ + +``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. + +.. _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. + +.. _low_prio_disk: + +.. raw:: html + + + ++---------------+------+---------+ +| name | type | default | ++===============+======+=========+ +| low_prio_disk | bool | true | ++---------------+------+---------+ + +``low_prio_disk`` determines if the disk I/O should use a normal or +low priority policy. This defaults to true, which 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. + +.. _volatile_read_cache: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| volatile_read_cache | bool | false | ++---------------------+------+---------+ + +``volatile_read_cache``, if this is set to true, read cache blocks +that are hit by peer read requests are removed from the disk cache +to free up more space. This is useful if you don't expect the disk +cache to create any cache hits from other peers than the one who +triggered the cache line to be read into the cache in the first +place. + +.. _guided_read_cache: + +.. raw:: html + + + ++-------------------+------+---------+ +| name | type | default | ++===================+======+=========+ +| guided_read_cache | bool | false | ++-------------------+------+---------+ + +``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. + +.. _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`` defaults to false. 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`` defaults to true, and controls when a +block may be requested twice. If this is ``true``, a block may only +be requested twice when there's ay 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. + +.. _broadcast_lsd: + +.. raw:: html + + + ++---------------+------+---------+ +| name | type | default | ++===============+======+=========+ +| broadcast_lsd | bool | true | ++---------------+------+---------+ + +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. + +.. _enable_outgoing_utp: + +.. _enable_incoming_utp: + +.. _enable_outgoing_tcp: + +.. _enable_incoming_tcp: + +.. raw:: html + + + + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| enable_outgoing_utp | bool | true | ++---------------------+------+---------+ +| enable_incoming_utp | bool | true | ++---------------------+------+---------+ +| enable_outgoing_tcp | bool | true | ++---------------------+------+---------+ +| enable_incoming_tcp | bool | true | ++---------------------+------+---------+ + +when set to true, libtorrent will try to make outgoing utp +connections controls whether libtorrent will accept incoming +connections or make outgoing connections of specific type. + +.. _ignore_resume_timestamps: + +.. raw:: html + + + ++--------------------------+------+---------+ +| name | type | default | ++==========================+======+=========+ +| ignore_resume_timestamps | bool | false | ++--------------------------+------+---------+ + +``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. This defaults to +false, which 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. + +.. _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. By +default, this is determined by the existence of any of the files. +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`` defaults to false. When set to true, the client +tries to hide its identity to a certain degree. The peer-ID will no +longer include the client's fingerprint. The user-agent will be +reset to an empty string. Trackers will only be used if they are +using a proxy server. The listen sockets are closed, and incoming +connections will only be accepted through a SOCKS5 or I2P proxy (if +a peer proxy is set up and is run on the same machine as the +tracker proxy). Since no incoming connections are accepted, +NAT-PMP, UPnP, DHT and local peer discovery are all turned off when +this setting is enabled. + +If you're using I2P, it might make sense to enable anonymous mode +as well. + +.. _report_web_seed_downloads: + +.. raw:: html + + + ++---------------------------+------+---------+ +| name | type | default | ++===========================+======+=========+ +| report_web_seed_downloads | bool | true | ++---------------------------+------+---------+ + +specifies whether downloads from web seeds is reported to the +tracker or not. Defaults to on. Turning it off also excludes web +seed traffic from other stats and download rate reporting via the +libtorrent API. + +.. _announce_double_nat: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| announce_double_nat | bool | false | ++---------------------+------+---------+ + +set to true if uTP connections should be rate limited This option +is *DEPRECATED*, please use set_peer_class_filter() instead. +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 + +.. _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. By default this is true. 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`` is true by default, which 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`` defaults to true. It 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. + +.. _use_disk_read_ahead: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| use_disk_read_ahead | bool | true | ++---------------------+------+---------+ + +``use_disk_read_ahead`` defaults to true and 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. + +.. _lock_files: + +.. raw:: html + + + ++------------+------+---------+ +| name | type | default | ++============+======+=========+ +| lock_files | bool | false | ++------------+------+---------+ + +``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. + +.. _contiguous_recv_buffer: + +.. raw:: html + + + ++------------------------+------+---------+ +| name | type | default | ++========================+======+=========+ +| contiguous_recv_buffer | bool | true | ++------------------------+------+---------+ + +``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. + +.. _ban_web_seeds: + +.. raw:: html + + + ++---------------+------+---------+ +| name | type | default | ++===============+======+=========+ +| ban_web_seeds | bool | true | ++---------------+------+---------+ + +when true, web seeds sending bad data will be banned + +.. _allow_partial_disk_writes: + +.. raw:: html + + + ++---------------------------+------+---------+ +| name | type | default | ++===========================+======+=========+ +| allow_partial_disk_writes | bool | true | ++---------------------------+------+---------+ + +when set to false, the ``write_cache_line_size`` will apply across +piece boundaries. this is a bad idea unless the piece picker also +is configured to have an affinity to pick pieces belonging to the +same write cache line as is configured in the disk cache. + +.. _force_proxy: + +.. raw:: html + + + ++-------------+------+---------+ +| name | type | default | ++=============+======+=========+ +| force_proxy | bool | false | ++-------------+------+---------+ + +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). This setting also disabled peer +country lookups, since those are done via DNS lookups that aren't +supported by proxies. + +.. _support_share_mode: + +.. raw:: html + + + ++--------------------+------+---------+ +| name | type | default | ++====================+======+=========+ +| support_share_mode | bool | true | ++--------------------+------+---------+ + +if false, prevents libtorrent to advertise share-mode support + +.. _support_merkle_torrents: + +.. raw:: html + + + ++-------------------------+------+---------+ +| name | type | default | ++=========================+======+=========+ +| support_merkle_torrents | bool | true | ++-------------------------+------+---------+ + +if this is false, don't advertise support for the Tribler merkle +tree piece message + +.. _report_redundant_bytes: + +.. raw:: html + + + ++------------------------+------+---------+ +| name | type | default | ++========================+======+=========+ +| report_redundant_bytes | bool | true | ++------------------------+------+---------+ + +if this is true, the number of redundant bytes is sent to the +tracker + +.. _listen_system_port_fallback: + +.. raw:: html + + + ++-----------------------------+------+---------+ +| name | type | default | ++=============================+======+=========+ +| listen_system_port_fallback | bool | true | ++-----------------------------+------+---------+ + +if this is true, libtorrent will fall back to listening on a port +chosen by the operating system (i.e. binding to port 0). If a +failure is preferred, set this to false. + +.. _use_disk_cache_pool: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| use_disk_cache_pool | bool | true | ++---------------------+------+---------+ + +``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. + +.. _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 infohashes 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, plaintext 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. + +.. _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. Default value is 60 +seconds. + +.. _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 | ++----------------------+------+---------+ + +the time 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 + +.. _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 megs). + +.. _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 (16kB) is expected to be received +within. If it's not, the block is requested from a different peer + +.. _request_queue_time: + +.. raw:: html + + + ++--------------------+------+---------+ +| name | type | default | ++====================+======+=========+ +| request_queue_time | int | 3 | ++--------------------+------+---------+ + +the length of the request queue given in the number of seconds it +should take for the other end to send all the pieces. i.e. the +actual number of requests depends on the download rate and this +number. + +.. _max_allowed_in_request_queue: + +.. raw:: html + + + ++------------------------------+------+---------+ +| name | type | default | ++==============================+======+=========+ +| max_allowed_in_request_queue | int | 500 | ++------------------------------+------+---------+ + +the number of outstanding block requests a peer is allowed to queue +up in the client. If a peer sends more requests than this (before +the first one has been sent) the last request will be dropped. the +higher this is, the faster upload speeds the client can get to a +single peer. + +.. _max_out_request_queue: + +.. raw:: html + + + ++-----------------------+------+---------+ +| name | type | default | ++=======================+======+=========+ +| max_out_request_queue | int | 500 | ++-----------------------+------+---------+ + +``max_out_request_queue`` is the maximum number of outstanding +requests to send to a peer. This limit takes precedence over +``request_queue_time``. i.e. no matter the download speed, the +number of outstanding requests will never exceed this limit. + +.. _whole_pieces_threshold: + +.. raw:: html + + + ++------------------------+------+---------+ +| name | type | default | ++========================+======+=========+ +| whole_pieces_threshold | int | 20 | ++------------------------+------+---------+ + +if a whole piece can be downloaded in this number of seconds, or +less, the peer_connection will prefer to request whole pieces at a +time from this peer. The benefit of this is to better utilize disk +caches by doing localized accesses and also to make it easier to +identify bad peers if a piece fails the hash check. + +.. _peer_timeout: + +.. raw:: html + + + ++--------------+------+---------+ +| name | type | default | ++==============+======+=========+ +| peer_timeout | int | 120 | ++--------------+------+---------+ + +``peer_timeout`` is the number of seconds the peer connection +should wait (for any activity on the peer connection) before +closing it due to time out. This defaults to 120 seconds, since +that's what's 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-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 | ++--------------------+------+---------+ + +time to wait until a new retry of a web seed takes place + +.. _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. It is usually a good +idea to find this limit and set the number of connections and the +number of files limits so their sum is slightly below it. + +.. _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 failcounter +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 | 10 | ++------------------+------+---------+ + +``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. default is 10 minutes + +.. _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. This defaults to 4, which +means the 4 first 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 | 10 | ++-----------------------+------+---------+ + +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_suggestsions`` which is the default and 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 number 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 16kB 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 +which peers to unchoke. + +The options for choking algorithms are: + +* ``fixed_slots_choker`` is the traditional choker with a fixed + number of unchoke slots (as specified by + ``session::set_max_uploads()``). + +* ``rate_based_choker`` 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. + +* ``bittyrant_choker`` attempts to optimize download rate by + finding the reciprocation rate of each peer individually and + prefers peers that gives the highest *return on investment*. It + still allocates all upload capacity, but shuffles it around to + the best peers first. For this choker to be efficient, you need + to set a global upload rate limit + (``session::set_upload_rate_limit()``). For more information + about this choker, see the paper_. This choker is not fully + implemented nor tested. + +.. _paper: http://bittyrant.cs.washington.edu/#papers + +``seed_choking_algorithm`` controls the seeding unchoke behavior. +The available options are: + +* ``round_robin`` which round-robins the peers that are unchoked + when seeding. This distributes the upload bandwidht uniformly and + fairly. It minimizes the ability for a peer to download everything + without redistributing it. + +* ``fastest_upload`` unchokes the peers we can send to the fastest. + This might be a bit more reliable in utilizing all available + capacity. + +* ``anti_leech`` 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. + +.. _cache_size: + +.. _cache_buffer_chunk_size: + +.. _cache_expiry: + +.. raw:: html + + + + + ++-------------------------+------+---------+ +| name | type | default | ++=========================+======+=========+ +| cache_size | int | 1024 | ++-------------------------+------+---------+ +| cache_buffer_chunk_size | int | 0 | ++-------------------------+------+---------+ +| cache_expiry | int | 300 | ++-------------------------+------+---------+ + +``cache_size`` is the disk write and read cache. It is specified +in units of 16 KiB blocks. Buffers that are part of a peer's send +or receive buffer also count against this limit. Send and receive +buffers will never be denied to be allocated, but they will cause +the actual cached blocks to be flushed or evicted. If this is set +to -1, the cache size is automatically set to the amount of +physical RAM available in the machine divided by 8. If the amount +of physical RAM cannot be determined, it's set to 1024 (= 16 MiB). + +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_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. Default is 60 second. + +On 32 bit builds, the effective cache size will be limited to 3/4 of +2 GiB to avoid exceeding the virtual address space limit. + +.. _disk_io_write_mode: + +.. _disk_io_read_mode: + +.. raw:: html + + + + ++--------------------+------+--------------------------------+ +| name | type | default | ++====================+======+================================+ +| disk_io_write_mode | int | settings_pack::enable_os_cache | ++--------------------+------+--------------------------------+ +| disk_io_read_mode | int | settings_pack::enable_os_cache | ++--------------------+------+--------------------------------+ + +determines how files are opened when they're in read only mode +versus read and write mode. The options are: + +enable_os_cache + This is the default and files are opened normally, with the OS + caching reads and writes. +disable_os_cache + This opens all files in no-cache mode. This corresponds to the + OS not letting blocks for the files linger in the cache. This + makes sense in order to avoid the bittorrent client to + potentially evict all other processes' cache by simply handling + high throughput and large files. If libtorrent's read cache is + disabled, enabling this may reduce performance. + +One reason to disable caching is that it may help the operating +system from growing its file cache indefinitely. + +.. _outgoing_port: + +.. _num_outgoing_ports: + +.. raw:: html + + + + ++--------------------+------+---------+ +| name | type | default | ++====================+======+=========+ +| outgoing_port | int | 0 | ++--------------------+------+---------+ +| num_outgoing_ports | int | 0 | ++--------------------+------+---------+ + +this is the first port to use for binding outgoing connections to. +This is useful for users that have routers that allow QoS settings +based on local port. when binding outgoing connections to specific +ports, ``num_outgoing_ports`` is the size of the range. It should +be more than a few + +.. warning:: setting outgoing ports will limit the ability to keep + multiple connections to the same client, even for different + torrents. It is not recommended to change this setting. Its main + purpose is to use as an escape hatch for cheap routers with QoS + capability but can only classify flows based on port numbers. + +It is a range instead of a single port because of the problems with +failing to reconnect to peers if a previous socket to that peer and +port is in ``TIME_WAIT`` state. + +.. _peer_tos: + +.. raw:: html + + + ++----------+------+---------+ +| name | type | default | ++==========+======+=========+ +| peer_tos | int | 0 | ++----------+------+---------+ + +``peer_tos`` determines the TOS byte set in the IP header of every +packet sent to peers (including web seeds). The default value for +this is ``0x0`` (no marking). One potentially useful TOS mark is +``0x20``, this represents the *QBone scavenger service*. For more +details, see QBSS_. + +.. _`QBSS`: http://qbone.internet2.edu/qbss/ + +.. _active_downloads: + +.. _active_seeds: + +.. _active_checking: + +.. _active_dht_limit: + +.. _active_tracker_limit: + +.. _active_lsd_limit: + +.. _active_limit: + +.. _active_loaded_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 | 15 | ++----------------------+------+---------+ +| active_loaded_limit | int | 100 | ++----------------------+------+---------+ + +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. By default this is set to 88, which is no more than one +DHT announce every 10 seconds. + +``active_tracker_limit`` is the max number of torrents to announce +to their trackers. By default this is 360, which is no more than +one announce every 5 seconds. + +``active_lsd_limit`` is the max number of torrents to announce to +the local network over the local service discovery protocol. By +default this is 80, which is no more than one announce every 5 +seconds (assuming the default announce interval of 5 minutes). + +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_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_. + +.. _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 default to the same as +``max_peerlist_size``, but 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 misconfigured +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. +It defaults to 3 pieces, which means that when seeding, any peer +we've sent more than this number of pieces to will be unchoked in +favour of a choked peer. + +.. _max_rejects: + +.. raw:: html + + + ++-------------+------+---------+ +| name | type | default | ++=============+======+=========+ +| max_rejects | int | 50 | ++-------------+------+---------+ + +TODO: deprecate this +``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 | ++-------------------------+------+---------+ + +``recv_socket_buffer_size`` and ``send_socket_buffer_size`` +specifies the buffer sizes set on peer sockets. 0 (which is the +default) 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. + +.. _file_checks_delay_per_block: + +.. raw:: html + + + ++-----------------------------+------+---------+ +| name | type | default | ++=============================+======+=========+ +| file_checks_delay_per_block | int | 0 | ++-----------------------------+------+---------+ + +``file_checks_delay_per_block`` is the number of milliseconds to +sleep in between disk read operations when checking torrents. This +defaults to 0, but 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. + +.. _read_cache_line_size: + +.. _write_cache_line_size: + +.. raw:: html + + + + ++-----------------------+------+---------+ +| name | type | default | ++=======================+======+=========+ +| read_cache_line_size | int | 32 | ++-----------------------+------+---------+ +| write_cache_line_size | int | 16 | ++-----------------------+------+---------+ + +``read_cache_line_size`` is the number of blocks to read into the +read cache when a read cache miss occurs. Setting this to 0 is +essentially the same thing as disabling read cache. The number of +blocks read into the read cache is always capped by the piece +boundary. + +When a piece in the write cache has ``write_cache_line_size`` +contiguous blocks in it, they will be flushed. Setting this to 1 +effectively disables the write cache. + +.. _optimistic_disk_retry: + +.. raw:: html + + + ++-----------------------+------+---------+ +| name | type | default | ++=======================+======+=========+ +| optimistic_disk_retry | int | 10 * 60 | ++-----------------------+------+---------+ + +``optimistic_disk_retry`` is the number of seconds from a disk +write errors occur on a torrent until libtorrent will take it out +of the upload mode, to test if the error condition has been fixed. + +libtorrent will only do this automatically for auto managed +torrents. + +You can explicitly take a torrent out of upload only mode using +set_upload_mode(). + +.. _max_suggest_pieces: + +.. raw:: html + + + ++--------------------+------+---------+ +| name | type | default | ++====================+======+=========+ +| max_suggest_pieces | int | 10 | ++--------------------+------+---------+ + +``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. +It defaults to 10. + +.. _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. By default, when local service +discovery is enabled a torrent announces itself every 5 minutes. +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, and defaults to that. 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. + +.. _default_cache_min_age: + +.. raw:: html + + + ++-----------------------+------+---------+ +| name | type | default | ++=======================+======+=========+ +| default_cache_min_age | int | 1 | ++-----------------------+------+---------+ + +``default_cache_min_age`` is the minimum number of seconds any read +cache line is kept in the cache. This defaults to one second but +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. + +.. _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. It defaults to 0, which means automatic. +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. When this is set to 0, libtorrent opens up 20% of your +allowed upload slots as optimistic unchoke slots. + +.. _default_est_reciprocation_rate: + +.. _increase_est_reciprocation_rate: + +.. _decrease_est_reciprocation_rate: + +.. raw:: html + + + + + ++---------------------------------+------+---------+ +| name | type | default | ++=================================+======+=========+ +| default_est_reciprocation_rate | int | 16000 | ++---------------------------------+------+---------+ +| increase_est_reciprocation_rate | int | 20 | ++---------------------------------+------+---------+ +| decrease_est_reciprocation_rate | int | 3 | ++---------------------------------+------+---------+ + +``default_est_reciprocation_rate`` is the assumed reciprocation +rate from peers when using the BitTyrant choker. This defaults to +14 kiB/s. 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 defaults to 20%. +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 default to 3%. This only applies +to the BitTyrant choker. + +.. _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. This defaults to 3, meaning 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``, ``download_rate_limit``, +``local_upload_rate_limit`` and ``local_download_rate_limit`` sets +the session-global limits of upload and download rate limits, in +bytes per second. The local rates refer to peers on the local +network. By default peers on the local network are not rate +limited. + +These rate limits are only used for local peers (peers within the +same subnet as the client itself) and it is only used when +``ignore_limits_on_local_network`` is set to true (which it is by +default). These rate limits default to unthrottled, but can be +useful in case you want to treat local peers preferentially, but +not quite unthrottled. + +A value of 0 means unlimited. + +.. _dht_upload_rate_limit: + +.. raw:: html + + + ++-----------------------+------+---------+ +| name | type | default | ++=======================+======+=========+ +| dht_upload_rate_limit | int | 4000 | ++-----------------------+------+---------+ + +``dht_upload_rate_limit`` sets the rate limit on the DHT. This is +specified in bytes per second and defaults to 4000. For busy boxes +with lots of torrents that requires more DHT traffic, this should +be raised. + +.. _unchoke_slots_limit: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| unchoke_slots_limit | int | 8 | ++---------------------+------+---------+ + +``unchoke_slots_limit`` is the max number of unchoked peers in the +session. The number of unchoke slots may be ignored depending on +what ``choking_algorithm`` is set to. + +.. _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 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. The default is 50 milliseconds. +``utp_gain_factor`` is the number of bytes the uTP congestion +window can increase at the most in one RTT. This defaults to 300 +bytes. 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. This defaults to 500 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 +lossed 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``. By default it's set to 50 (i.e. cut in half). 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. The default is 5 which 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 listen_on() is called again (or for the first time). + +.. _torrent_connect_boost: + +.. raw:: html + + + ++-----------------------+------+---------+ +| name | type | default | ++=======================+======+=========+ +| torrent_connect_boost | int | 10 | ++-----------------------+------+---------+ + +``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. + +.. _alert_queue_size: + +.. raw:: html + + + ++------------------+------+---------+ +| name | type | default | ++==================+======+=========+ +| alert_queue_size | int | 1000 | ++------------------+------+---------+ + +``alert_queue_size`` is the maximum number of alerts queued up +internally. If alerts are not popped, the queue will eventually +fill up to this level. + +.. _max_metadata_size: + +.. raw:: html + + + ++-------------------+------+------------------+ +| name | type | default | ++===================+======+==================+ +| max_metadata_size | int | 3 * 1024 * 10240 | ++-------------------+------+------------------+ + +``max_metadata_size`` is the maximum allowed size (in bytes) to be +received by the metadata extension, i.e. magnet links. + +.. _checking_mem_usage: + +.. raw:: html + + + ++--------------------+------+---------+ +| name | type | default | ++====================+======+=========+ +| checking_mem_usage | int | 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: + +.. _aio_max: + +.. raw:: html + + + + ++-------------+------+---------+ +| name | type | default | ++=============+======+=========+ +| aio_threads | int | 4 | ++-------------+------+---------+ +| aio_max | int | 300 | ++-------------+------+---------+ + +for some aio back-ends, ``aio_threads`` specifies the number of +io-threads to use, and ``aio_max`` the max number of outstanding +jobs. + +.. _network_threads: + +.. raw:: html + + + ++-----------------+------+---------+ +| name | type | default | ++=================+======+=========+ +| network_threads | int | 0 | ++-----------------+------+---------+ + +``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. + +.. _ssl_listen: + +.. raw:: html + + + ++------------+------+---------+ +| name | type | default | ++============+======+=========+ +| ssl_listen | int | 0 | ++------------+------+---------+ + +``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. + +.. _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 + +.. _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 | alert::error_notification | ++------------+------+---------------------------+ + +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, defaults to none. see proxy_type_t. + +.. _proxy_port: + +.. raw:: html + + + ++------------+------+---------+ +| name | type | default | ++============+======+=========+ +| proxy_port | int | 0 | ++------------+------+---------+ + +the port of the proxy server + +.. _i2p_port: + +.. raw:: html + + + ++----------+------+---------+ +| name | type | default | ++==========+======+=========+ +| i2p_port | int | 0 | ++----------+------+---------+ + +sets the i2p_ SAM bridge port to connect to. set the hostname with +the ``i2p_hostname`` setting. + +.. _i2p: http://www.i2p2.de + +.. _cache_size_volatile: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| cache_size_volatile | int | 256 | ++---------------------+------+---------+ + +this determines the max number of volatile disk cache blocks. If the +number of volatile blocks exceed this limit, other volatile blocks +will start to be evicted. A disk cache block is volatile if it has +low priority, and should be one of the first blocks to be evicted +under pressure. For instance, blocks pulled into the cache as the +result of calculating a piece hash are volatile. These blocks don't +represent potential interest among peers, so the value of keeping +them in the cache is limited. + diff --git a/docs/stats_counters.rst b/docs/stats_counters.rst new file mode 100644 index 0000000..f3f8bfc --- /dev/null +++ b/docs/stats_counters.rst @@ -0,0 +1,2031 @@ +.. _peer.error_peers: + +.. _peer.disconnected_peers: + +.. raw:: html + + + + ++-------------------------+---------+ +| name | type | ++=========================+=========+ +| peer.error_peers | counter | ++-------------------------+---------+ +| peer.disconnected_peers | counter | ++-------------------------+---------+ + + +``error_peers`` is the total number of peer disconnects +caused by an error (not initiated by this client) and +disconnected initiated by this client (``disconnected_peers``). + +.. _peer.eof_peers: + +.. _peer.connreset_peers: + +.. _peer.connrefused_peers: + +.. _peer.connaborted_peers: + +.. _peer.notconnected_peers: + +.. _peer.perm_peers: + +.. _peer.buffer_peers: + +.. _peer.unreachable_peers: + +.. _peer.broken_pipe_peers: + +.. _peer.addrinuse_peers: + +.. _peer.no_access_peers: + +.. _peer.invalid_arg_peers: + +.. _peer.aborted_peers: + +.. raw:: html + + + + + + + + + + + + + + + ++-------------------------+---------+ +| name | type | ++=========================+=========+ +| peer.eof_peers | counter | ++-------------------------+---------+ +| peer.connreset_peers | counter | ++-------------------------+---------+ +| peer.connrefused_peers | counter | ++-------------------------+---------+ +| peer.connaborted_peers | counter | ++-------------------------+---------+ +| peer.notconnected_peers | counter | ++-------------------------+---------+ +| peer.perm_peers | counter | ++-------------------------+---------+ +| peer.buffer_peers | counter | ++-------------------------+---------+ +| peer.unreachable_peers | counter | ++-------------------------+---------+ +| peer.broken_pipe_peers | counter | ++-------------------------+---------+ +| peer.addrinuse_peers | counter | ++-------------------------+---------+ +| peer.no_access_peers | counter | ++-------------------------+---------+ +| peer.invalid_arg_peers | counter | ++-------------------------+---------+ +| peer.aborted_peers | counter | ++-------------------------+---------+ + + +these counters break down the peer errors into more specific +categories. These errors are what the underlying transport +reported (i.e. TCP or uTP) + +.. _peer.piece_requests: + +.. _peer.max_piece_requests: + +.. _peer.invalid_piece_requests: + +.. _peer.choked_piece_requests: + +.. _peer.cancelled_piece_requests: + +.. _peer.piece_rejects: + +.. raw:: html + + + + + + + + ++-------------------------------+---------+ +| name | type | ++===============================+=========+ +| peer.piece_requests | counter | ++-------------------------------+---------+ +| peer.max_piece_requests | counter | ++-------------------------------+---------+ +| peer.invalid_piece_requests | counter | ++-------------------------------+---------+ +| peer.choked_piece_requests | counter | ++-------------------------------+---------+ +| peer.cancelled_piece_requests | counter | ++-------------------------------+---------+ +| peer.piece_rejects | counter | ++-------------------------------+---------+ + + +the total number of incoming piece requests we've received followed +by the number of rejected piece requests for various reasons. +max_piece_requests mean we already had too many outstanding requests +from this peer, so we rejected it. cancelled_piece_requests are ones +where the other end explicitly asked for the piece to be rejected. + +.. _peer.error_incoming_peers: + +.. _peer.error_outgoing_peers: + +.. raw:: html + + + + ++---------------------------+---------+ +| name | type | ++===========================+=========+ +| peer.error_incoming_peers | counter | ++---------------------------+---------+ +| peer.error_outgoing_peers | counter | ++---------------------------+---------+ + + +these counters break down the peer errors into +whether they happen on incoming or outgoing peers. + +.. _peer.error_rc4_peers: + +.. _peer.error_encrypted_peers: + +.. raw:: html + + + + ++----------------------------+---------+ +| name | type | ++============================+=========+ +| peer.error_rc4_peers | counter | ++----------------------------+---------+ +| peer.error_encrypted_peers | counter | ++----------------------------+---------+ + + +these counters break down the peer errors into +whether they happen on encrypted peers (just +encrypted handshake) and rc4 peers (full stream +encryption). These can indicate whether encrypted +peers are more or less likely to fail + +.. _peer.error_tcp_peers: + +.. _peer.error_utp_peers: + +.. raw:: html + + + + ++----------------------+---------+ +| name | type | ++======================+=========+ +| peer.error_tcp_peers | counter | ++----------------------+---------+ +| peer.error_utp_peers | counter | ++----------------------+---------+ + + +these counters break down the peer errors into +whether they happen on uTP peers or TCP peers. +these may indicate whether one protocol is +more error prone + +.. _peer.connect_timeouts: + +.. _peer.uninteresting_peers: + +.. _peer.timeout_peers: + +.. _peer.no_memory_peers: + +.. _peer.too_many_peers: + +.. _peer.transport_timeout_peers: + +.. _peer.num_banned_peers: + +.. _peer.banned_for_hash_failure: + +.. _peer.connection_attempts: + +.. _peer.connection_attempt_loops: + +.. _peer.incoming_connections: + +.. raw:: html + + + + + + + + + + + + + ++-------------------------------+---------+ +| name | type | ++===============================+=========+ +| peer.connect_timeouts | counter | ++-------------------------------+---------+ +| peer.uninteresting_peers | counter | ++-------------------------------+---------+ +| peer.timeout_peers | counter | ++-------------------------------+---------+ +| peer.no_memory_peers | counter | ++-------------------------------+---------+ +| peer.too_many_peers | counter | ++-------------------------------+---------+ +| peer.transport_timeout_peers | counter | ++-------------------------------+---------+ +| peer.num_banned_peers | counter | ++-------------------------------+---------+ +| peer.banned_for_hash_failure | counter | ++-------------------------------+---------+ +| peer.connection_attempts | counter | ++-------------------------------+---------+ +| peer.connection_attempt_loops | counter | ++-------------------------------+---------+ +| peer.incoming_connections | counter | ++-------------------------------+---------+ + + +these counters break down the reasons to +disconnect peers. + +.. _peer.num_tcp_peers: + +.. _peer.num_socks5_peers: + +.. _peer.num_http_proxy_peers: + +.. _peer.num_utp_peers: + +.. _peer.num_i2p_peers: + +.. _peer.num_ssl_peers: + +.. _peer.num_ssl_socks5_peers: + +.. _peer.num_ssl_http_proxy_peers: + +.. _peer.num_ssl_utp_peers: + +.. _peer.num_peers_half_open: + +.. _peer.num_peers_connected: + +.. _peer.num_peers_up_interested: + +.. _peer.num_peers_down_interested: + +.. _peer.num_peers_up_unchoked_all: + +.. _peer.num_peers_up_unchoked_optimistic: + +.. _peer.num_peers_up_unchoked: + +.. _peer.num_peers_down_unchoked: + +.. _peer.num_peers_up_requests: + +.. _peer.num_peers_down_requests: + +.. _peer.num_peers_end_game: + +.. _peer.num_peers_up_disk: + +.. _peer.num_peers_down_disk: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + ++---------------------------------------+---------+ +| name | type | ++=======================================+=========+ +| peer.num_tcp_peers | counter | ++---------------------------------------+---------+ +| peer.num_socks5_peers | counter | ++---------------------------------------+---------+ +| peer.num_http_proxy_peers | counter | ++---------------------------------------+---------+ +| peer.num_utp_peers | counter | ++---------------------------------------+---------+ +| peer.num_i2p_peers | counter | ++---------------------------------------+---------+ +| peer.num_ssl_peers | counter | ++---------------------------------------+---------+ +| peer.num_ssl_socks5_peers | counter | ++---------------------------------------+---------+ +| peer.num_ssl_http_proxy_peers | counter | ++---------------------------------------+---------+ +| peer.num_ssl_utp_peers | counter | ++---------------------------------------+---------+ +| peer.num_peers_half_open | counter | ++---------------------------------------+---------+ +| peer.num_peers_connected | counter | ++---------------------------------------+---------+ +| peer.num_peers_up_interested | counter | ++---------------------------------------+---------+ +| peer.num_peers_down_interested | counter | ++---------------------------------------+---------+ +| peer.num_peers_up_unchoked_all | counter | ++---------------------------------------+---------+ +| peer.num_peers_up_unchoked_optimistic | counter | ++---------------------------------------+---------+ +| peer.num_peers_up_unchoked | counter | ++---------------------------------------+---------+ +| peer.num_peers_down_unchoked | counter | ++---------------------------------------+---------+ +| peer.num_peers_up_requests | counter | ++---------------------------------------+---------+ +| peer.num_peers_down_requests | counter | ++---------------------------------------+---------+ +| peer.num_peers_end_game | counter | ++---------------------------------------+---------+ +| peer.num_peers_up_disk | counter | ++---------------------------------------+---------+ +| peer.num_peers_down_disk | counter | ++---------------------------------------+---------+ + + +the number of peer connections for each kind of socket. +these counts include half-open (connecting) peers. +``num_peers_up_unchoked_all`` is the total number of unchoked peers, +whereas ``num_peers_up_unchoked`` only are unchoked peers that count +against the limit (i.e. excluding peers that are unchoked because the +limit doesn't apply to them). ``num_peers_up_unchoked_optimistic`` is +the number of optimistically unchoked peers. + +.. _net.on_read_counter: + +.. _net.on_write_counter: + +.. _net.on_tick_counter: + +.. _net.on_lsd_counter: + +.. _net.on_lsd_peer_counter: + +.. _net.on_udp_counter: + +.. _net.on_accept_counter: + +.. _net.on_disk_queue_counter: + +.. _net.on_disk_counter: + +.. raw:: html + + + + + + + + + + + ++---------------------------+---------+ +| name | type | ++===========================+=========+ +| net.on_read_counter | counter | ++---------------------------+---------+ +| net.on_write_counter | counter | ++---------------------------+---------+ +| net.on_tick_counter | counter | ++---------------------------+---------+ +| net.on_lsd_counter | counter | ++---------------------------+---------+ +| net.on_lsd_peer_counter | counter | ++---------------------------+---------+ +| net.on_udp_counter | counter | ++---------------------------+---------+ +| net.on_accept_counter | counter | ++---------------------------+---------+ +| net.on_disk_queue_counter | counter | ++---------------------------+---------+ +| net.on_disk_counter | counter | ++---------------------------+---------+ + + +These counters count the number of times the +network thread wakes up for each respective +reason. If these counters are very large, it +may indicate a performance issue, causing the +network thread to wake up too ofte, wasting CPU. +mitigate it by increasing buffers and limits +for the specific trigger that wakes up the +thread. + +.. _net.sent_payload_bytes: + +.. _net.sent_bytes: + +.. _net.sent_ip_overhead_bytes: + +.. _net.sent_tracker_bytes: + +.. _net.recv_payload_bytes: + +.. _net.recv_bytes: + +.. _net.recv_ip_overhead_bytes: + +.. _net.recv_tracker_bytes: + +.. raw:: html + + + + + + + + + + ++----------------------------+---------+ +| name | type | ++============================+=========+ +| net.sent_payload_bytes | counter | ++----------------------------+---------+ +| net.sent_bytes | counter | ++----------------------------+---------+ +| net.sent_ip_overhead_bytes | counter | ++----------------------------+---------+ +| net.sent_tracker_bytes | counter | ++----------------------------+---------+ +| net.recv_payload_bytes | counter | ++----------------------------+---------+ +| net.recv_bytes | counter | ++----------------------------+---------+ +| net.recv_ip_overhead_bytes | counter | ++----------------------------+---------+ +| net.recv_tracker_bytes | counter | ++----------------------------+---------+ + + +total number of bytes sent and received by the session + +.. _net.limiter_up_queue: + +.. _net.limiter_down_queue: + +.. raw:: html + + + + ++------------------------+---------+ +| name | type | ++========================+=========+ +| net.limiter_up_queue | counter | ++------------------------+---------+ +| net.limiter_down_queue | counter | ++------------------------+---------+ + + +the number of sockets currently waiting for upload and download +bandwidht from the rate limiter. + +.. _net.limiter_up_bytes: + +.. _net.limiter_down_bytes: + +.. raw:: html + + + + ++------------------------+---------+ +| name | type | ++========================+=========+ +| net.limiter_up_bytes | counter | ++------------------------+---------+ +| net.limiter_down_bytes | counter | ++------------------------+---------+ + + +the number of upload and download bytes waiting to be handed out from +the rate limiter. + +.. _net.recv_failed_bytes: + +.. raw:: html + + + ++-----------------------+---------+ +| name | type | ++=======================+=========+ +| net.recv_failed_bytes | counter | ++-----------------------+---------+ + + +the number of bytes downloaded that had to be discarded because they +failed the hash check + +.. _net.recv_redundant_bytes: + +.. raw:: html + + + ++--------------------------+---------+ +| name | type | ++==========================+=========+ +| net.recv_redundant_bytes | counter | ++--------------------------+---------+ + + +the number of downloaded bytes that were discarded because they +were downloaded multiple times (from different peers) + +.. _net.has_incoming_connections: + +.. raw:: html + + + ++------------------------------+---------+ +| name | type | ++==============================+=========+ +| net.has_incoming_connections | counter | ++------------------------------+---------+ + + +is false by default and set to true when +the first incoming connection is established +this is used to know if the client is behind +NAT or not. + +.. _ses.num_checking_torrents: + +.. _ses.num_stopped_torrents: + +.. _ses.num_upload_only_torrents: + +.. _ses.num_downloading_torrents: + +.. _ses.num_seeding_torrents: + +.. _ses.num_queued_seeding_torrents: + +.. _ses.num_queued_download_torrents: + +.. _ses.num_error_torrents: + +.. raw:: html + + + + + + + + + + ++----------------------------------+---------+ +| name | type | ++==================================+=========+ +| ses.num_checking_torrents | counter | ++----------------------------------+---------+ +| ses.num_stopped_torrents | counter | ++----------------------------------+---------+ +| ses.num_upload_only_torrents | counter | ++----------------------------------+---------+ +| ses.num_downloading_torrents | counter | ++----------------------------------+---------+ +| ses.num_seeding_torrents | counter | ++----------------------------------+---------+ +| ses.num_queued_seeding_torrents | counter | ++----------------------------------+---------+ +| ses.num_queued_download_torrents | counter | ++----------------------------------+---------+ +| ses.num_error_torrents | counter | ++----------------------------------+---------+ + + +these gauges count the number of torrents in +different states. Each torrent only belongs to +one of these states. For torrents that could +belong to multiple of these, the most prominent +in picked. For instance, a torrent with an error +counts as an error-torrent, regardless of its other +state. + +.. _ses.non_filter_torrents: + +.. raw:: html + + + ++-------------------------+---------+ +| name | type | ++=========================+=========+ +| ses.non_filter_torrents | counter | ++-------------------------+---------+ + + +the number of torrents that don't have the +IP filter applied to them. + +.. _ses.num_loaded_torrents: + +.. _ses.num_pinned_torrents: + +.. raw:: html + + + + ++-------------------------+---------+ +| name | type | ++=========================+=========+ +| ses.num_loaded_torrents | counter | ++-------------------------+---------+ +| ses.num_pinned_torrents | counter | ++-------------------------+---------+ + + +the number of torrents that are currently loaded + +.. _ses.num_piece_passed: + +.. _ses.num_piece_failed: + +.. _ses.num_have_pieces: + +.. _ses.num_total_pieces_added: + +.. raw:: html + + + + + + ++----------------------------+---------+ +| name | type | ++============================+=========+ +| ses.num_piece_passed | counter | ++----------------------------+---------+ +| ses.num_piece_failed | counter | ++----------------------------+---------+ +| ses.num_have_pieces | counter | ++----------------------------+---------+ +| ses.num_total_pieces_added | counter | ++----------------------------+---------+ + + +these count the number of times a piece has passed the +hash check, the number of times a piece was successfully +written to disk and the number of total possible pieces +added by adding torrents. e.g. when adding a torrent with +1000 piece, num_total_pieces_added is incremented by 1000. + +.. _ses.torrent_evicted_counter: + +.. raw:: html + + + ++-----------------------------+---------+ +| name | type | ++=============================+=========+ +| ses.torrent_evicted_counter | counter | ++-----------------------------+---------+ + + +this counts the number of times a torrent has been +evicted (only applies when `dynamic loading of torrent files`_ +is enabled). + +.. _ses.num_unchoke_slots: + +.. raw:: html + + + ++-----------------------+---------+ +| name | type | ++=======================+=========+ +| ses.num_unchoke_slots | counter | ++-----------------------+---------+ + + +the number of allowed unchoked peers + +.. _ses.num_incoming_choke: + +.. _ses.num_incoming_unchoke: + +.. _ses.num_incoming_interested: + +.. _ses.num_incoming_not_interested: + +.. _ses.num_incoming_have: + +.. _ses.num_incoming_bitfield: + +.. _ses.num_incoming_request: + +.. _ses.num_incoming_piece: + +.. _ses.num_incoming_cancel: + +.. _ses.num_incoming_dht_port: + +.. _ses.num_incoming_suggest: + +.. _ses.num_incoming_have_all: + +.. _ses.num_incoming_have_none: + +.. _ses.num_incoming_reject: + +.. _ses.num_incoming_allowed_fast: + +.. _ses.num_incoming_ext_handshake: + +.. _ses.num_incoming_pex: + +.. _ses.num_incoming_metadata: + +.. _ses.num_incoming_extended: + +.. _ses.num_outgoing_choke: + +.. _ses.num_outgoing_unchoke: + +.. _ses.num_outgoing_interested: + +.. _ses.num_outgoing_not_interested: + +.. _ses.num_outgoing_have: + +.. _ses.num_outgoing_bitfield: + +.. _ses.num_outgoing_request: + +.. _ses.num_outgoing_piece: + +.. _ses.num_outgoing_cancel: + +.. _ses.num_outgoing_dht_port: + +.. _ses.num_outgoing_suggest: + +.. _ses.num_outgoing_have_all: + +.. _ses.num_outgoing_have_none: + +.. _ses.num_outgoing_reject: + +.. _ses.num_outgoing_allowed_fast: + +.. _ses.num_outgoing_ext_handshake: + +.. _ses.num_outgoing_pex: + +.. _ses.num_outgoing_metadata: + +.. _ses.num_outgoing_extended: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++---------------------------------+---------+ +| name | type | ++=================================+=========+ +| ses.num_incoming_choke | counter | ++---------------------------------+---------+ +| ses.num_incoming_unchoke | counter | ++---------------------------------+---------+ +| ses.num_incoming_interested | counter | ++---------------------------------+---------+ +| ses.num_incoming_not_interested | counter | ++---------------------------------+---------+ +| ses.num_incoming_have | counter | ++---------------------------------+---------+ +| ses.num_incoming_bitfield | counter | ++---------------------------------+---------+ +| ses.num_incoming_request | counter | ++---------------------------------+---------+ +| ses.num_incoming_piece | counter | ++---------------------------------+---------+ +| ses.num_incoming_cancel | counter | ++---------------------------------+---------+ +| ses.num_incoming_dht_port | counter | ++---------------------------------+---------+ +| ses.num_incoming_suggest | counter | ++---------------------------------+---------+ +| ses.num_incoming_have_all | counter | ++---------------------------------+---------+ +| ses.num_incoming_have_none | counter | ++---------------------------------+---------+ +| ses.num_incoming_reject | counter | ++---------------------------------+---------+ +| ses.num_incoming_allowed_fast | counter | ++---------------------------------+---------+ +| ses.num_incoming_ext_handshake | counter | ++---------------------------------+---------+ +| ses.num_incoming_pex | counter | ++---------------------------------+---------+ +| ses.num_incoming_metadata | counter | ++---------------------------------+---------+ +| ses.num_incoming_extended | counter | ++---------------------------------+---------+ +| ses.num_outgoing_choke | counter | ++---------------------------------+---------+ +| ses.num_outgoing_unchoke | counter | ++---------------------------------+---------+ +| ses.num_outgoing_interested | counter | ++---------------------------------+---------+ +| ses.num_outgoing_not_interested | counter | ++---------------------------------+---------+ +| ses.num_outgoing_have | counter | ++---------------------------------+---------+ +| ses.num_outgoing_bitfield | counter | ++---------------------------------+---------+ +| ses.num_outgoing_request | counter | ++---------------------------------+---------+ +| ses.num_outgoing_piece | counter | ++---------------------------------+---------+ +| ses.num_outgoing_cancel | counter | ++---------------------------------+---------+ +| ses.num_outgoing_dht_port | counter | ++---------------------------------+---------+ +| ses.num_outgoing_suggest | counter | ++---------------------------------+---------+ +| ses.num_outgoing_have_all | counter | ++---------------------------------+---------+ +| ses.num_outgoing_have_none | counter | ++---------------------------------+---------+ +| ses.num_outgoing_reject | counter | ++---------------------------------+---------+ +| ses.num_outgoing_allowed_fast | counter | ++---------------------------------+---------+ +| ses.num_outgoing_ext_handshake | counter | ++---------------------------------+---------+ +| ses.num_outgoing_pex | counter | ++---------------------------------+---------+ +| ses.num_outgoing_metadata | counter | ++---------------------------------+---------+ +| ses.num_outgoing_extended | counter | ++---------------------------------+---------+ + + +bittorrent message counters. These counters are incremented +every time a message of the corresponding type is received from +or sent to a bittorrent peer. + +.. _ses.waste_piece_timed_out: + +.. _ses.waste_piece_cancelled: + +.. _ses.waste_piece_unknown: + +.. _ses.waste_piece_seed: + +.. _ses.waste_piece_end_game: + +.. _ses.waste_piece_closing: + +.. raw:: html + + + + + + + + ++---------------------------+---------+ +| name | type | ++===========================+=========+ +| ses.waste_piece_timed_out | counter | ++---------------------------+---------+ +| ses.waste_piece_cancelled | counter | ++---------------------------+---------+ +| ses.waste_piece_unknown | counter | ++---------------------------+---------+ +| ses.waste_piece_seed | counter | ++---------------------------+---------+ +| ses.waste_piece_end_game | counter | ++---------------------------+---------+ +| ses.waste_piece_closing | counter | ++---------------------------+---------+ + + +the number of wasted downloaded bytes by reason of the bytes being +wasted. + +.. _picker.piece_picker_partial_loops: + +.. _picker.piece_picker_suggest_loops: + +.. _picker.piece_picker_sequential_loops: + +.. _picker.piece_picker_reverse_rare_loops: + +.. _picker.piece_picker_rare_loops: + +.. _picker.piece_picker_rand_start_loops: + +.. _picker.piece_picker_rand_loops: + +.. _picker.piece_picker_busy_loops: + +.. raw:: html + + + + + + + + + + ++----------------------------------------+---------+ +| name | type | ++========================================+=========+ +| picker.piece_picker_partial_loops | counter | ++----------------------------------------+---------+ +| picker.piece_picker_suggest_loops | counter | ++----------------------------------------+---------+ +| picker.piece_picker_sequential_loops | counter | ++----------------------------------------+---------+ +| picker.piece_picker_reverse_rare_loops | counter | ++----------------------------------------+---------+ +| picker.piece_picker_rare_loops | counter | ++----------------------------------------+---------+ +| picker.piece_picker_rand_start_loops | counter | ++----------------------------------------+---------+ +| picker.piece_picker_rand_loops | counter | ++----------------------------------------+---------+ +| picker.piece_picker_busy_loops | counter | ++----------------------------------------+---------+ + + +the number of pieces considered while picking pieces + +.. _picker.reject_piece_picks: + +.. _picker.unchoke_piece_picks: + +.. _picker.incoming_redundant_piece_picks: + +.. _picker.incoming_piece_picks: + +.. _picker.end_game_piece_picks: + +.. _picker.snubbed_piece_picks: + +.. _picker.interesting_piece_picks: + +.. _picker.hash_fail_piece_picks: + +.. _disk.write_cache_blocks: + +.. _disk.read_cache_blocks: + +.. raw:: html + + + + + + + + + + + + ++---------------------------------------+---------+ +| name | type | ++=======================================+=========+ +| picker.reject_piece_picks | counter | ++---------------------------------------+---------+ +| picker.unchoke_piece_picks | counter | ++---------------------------------------+---------+ +| picker.incoming_redundant_piece_picks | counter | ++---------------------------------------+---------+ +| picker.incoming_piece_picks | counter | ++---------------------------------------+---------+ +| picker.end_game_piece_picks | counter | ++---------------------------------------+---------+ +| picker.snubbed_piece_picks | counter | ++---------------------------------------+---------+ +| picker.interesting_piece_picks | counter | ++---------------------------------------+---------+ +| picker.hash_fail_piece_picks | counter | ++---------------------------------------+---------+ +| disk.write_cache_blocks | counter | ++---------------------------------------+---------+ +| disk.read_cache_blocks | counter | ++---------------------------------------+---------+ + + +This breaks down the piece picks into the event that +triggered it + +.. _disk.request_latency: + +.. _disk.pinned_blocks: + +.. _disk.disk_blocks_in_use: + +.. _disk.queued_disk_jobs: + +.. _disk.num_running_disk_jobs: + +.. _disk.num_read_jobs: + +.. _disk.num_write_jobs: + +.. _disk.num_jobs: + +.. _disk.num_writing_threads: + +.. _disk.num_running_threads: + +.. _disk.blocked_disk_jobs: + +.. raw:: html + + + + + + + + + + + + + ++----------------------------+---------+ +| name | type | ++============================+=========+ +| disk.request_latency | counter | ++----------------------------+---------+ +| disk.pinned_blocks | counter | ++----------------------------+---------+ +| disk.disk_blocks_in_use | counter | ++----------------------------+---------+ +| disk.queued_disk_jobs | counter | ++----------------------------+---------+ +| disk.num_running_disk_jobs | counter | ++----------------------------+---------+ +| disk.num_read_jobs | counter | ++----------------------------+---------+ +| disk.num_write_jobs | counter | ++----------------------------+---------+ +| disk.num_jobs | counter | ++----------------------------+---------+ +| disk.num_writing_threads | counter | ++----------------------------+---------+ +| disk.num_running_threads | counter | ++----------------------------+---------+ +| disk.blocked_disk_jobs | counter | ++----------------------------+---------+ + + +the number of microseconds it takes from receiving a request from a +peer until we're sending the response back on the socket. + +.. _disk.queued_write_bytes: + +.. _disk.arc_mru_size: + +.. _disk.arc_mru_ghost_size: + +.. _disk.arc_mfu_size: + +.. _disk.arc_mfu_ghost_size: + +.. _disk.arc_write_size: + +.. _disk.arc_volatile_size: + +.. raw:: html + + + + + + + + + ++-------------------------+---------+ +| name | type | ++=========================+=========+ +| disk.queued_write_bytes | counter | ++-------------------------+---------+ +| disk.arc_mru_size | counter | ++-------------------------+---------+ +| disk.arc_mru_ghost_size | counter | ++-------------------------+---------+ +| disk.arc_mfu_size | counter | ++-------------------------+---------+ +| disk.arc_mfu_ghost_size | counter | ++-------------------------+---------+ +| disk.arc_write_size | counter | ++-------------------------+---------+ +| disk.arc_volatile_size | counter | ++-------------------------+---------+ + + +the number of bytes we have sent to the disk I/O +thread for writing. Every time we hear back from +the disk I/O thread with a completed write job, this +is updated to the number of bytes the disk I/O thread +is actually waiting for to be written (as opposed to +bytes just hanging out in the cache) + +.. _disk.num_blocks_written: + +.. _disk.num_blocks_read: + +.. raw:: html + + + + ++-------------------------+---------+ +| name | type | ++=========================+=========+ +| disk.num_blocks_written | counter | ++-------------------------+---------+ +| disk.num_blocks_read | counter | ++-------------------------+---------+ + + +the number of blocks written and read from disk in total. A block is +16 kiB. + +.. _disk.num_blocks_hashed: + +.. raw:: html + + + ++------------------------+---------+ +| name | type | ++========================+=========+ +| disk.num_blocks_hashed | counter | ++------------------------+---------+ + + +the total number of blocks run through SHA-1 hashing + +.. _disk.num_blocks_cache_hits: + +.. raw:: html + + + ++----------------------------+---------+ +| name | type | ++============================+=========+ +| disk.num_blocks_cache_hits | counter | ++----------------------------+---------+ + + +the number of blocks read from the disk cache + +.. _disk.num_write_ops: + +.. _disk.num_read_ops: + +.. raw:: html + + + + ++--------------------+---------+ +| name | type | ++====================+=========+ +| disk.num_write_ops | counter | ++--------------------+---------+ +| disk.num_read_ops | counter | ++--------------------+---------+ + + +the number of disk I/O operation for reads and writes. One disk +operation may transfer more then one block. + +.. _disk.num_read_back: + +.. raw:: html + + + ++--------------------+---------+ +| name | type | ++====================+=========+ +| disk.num_read_back | counter | ++--------------------+---------+ + + +the number of blocks that had to be read back from disk in order to +hash a piece (when verifying against the piece hash) + +.. _disk.disk_read_time: + +.. _disk.disk_write_time: + +.. _disk.disk_hash_time: + +.. _disk.disk_job_time: + +.. raw:: html + + + + + + ++----------------------+---------+ +| name | type | ++======================+=========+ +| disk.disk_read_time | counter | ++----------------------+---------+ +| disk.disk_write_time | counter | ++----------------------+---------+ +| disk.disk_hash_time | counter | ++----------------------+---------+ +| disk.disk_job_time | counter | ++----------------------+---------+ + + +cumulative time spent in various disk jobs, as well +as total for all disk jobs. Measured in microseconds + +.. _disk.num_fenced_read: + +.. _disk.num_fenced_write: + +.. _disk.num_fenced_hash: + +.. _disk.num_fenced_move_storage: + +.. _disk.num_fenced_release_files: + +.. _disk.num_fenced_delete_files: + +.. _disk.num_fenced_check_fastresume: + +.. _disk.num_fenced_save_resume_data: + +.. _disk.num_fenced_rename_file: + +.. _disk.num_fenced_stop_torrent: + +.. _disk.num_fenced_cache_piece: + +.. _disk.num_fenced_flush_piece: + +.. _disk.num_fenced_flush_hashed: + +.. _disk.num_fenced_flush_storage: + +.. _disk.num_fenced_trim_cache: + +.. _disk.num_fenced_file_priority: + +.. _disk.num_fenced_load_torrent: + +.. _disk.num_fenced_clear_piece: + +.. _disk.num_fenced_tick_storage: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + ++----------------------------------+---------+ +| name | type | ++==================================+=========+ +| disk.num_fenced_read | counter | ++----------------------------------+---------+ +| disk.num_fenced_write | counter | ++----------------------------------+---------+ +| disk.num_fenced_hash | counter | ++----------------------------------+---------+ +| disk.num_fenced_move_storage | counter | ++----------------------------------+---------+ +| disk.num_fenced_release_files | counter | ++----------------------------------+---------+ +| disk.num_fenced_delete_files | counter | ++----------------------------------+---------+ +| disk.num_fenced_check_fastresume | counter | ++----------------------------------+---------+ +| disk.num_fenced_save_resume_data | counter | ++----------------------------------+---------+ +| disk.num_fenced_rename_file | counter | ++----------------------------------+---------+ +| disk.num_fenced_stop_torrent | counter | ++----------------------------------+---------+ +| disk.num_fenced_cache_piece | counter | ++----------------------------------+---------+ +| disk.num_fenced_flush_piece | counter | ++----------------------------------+---------+ +| disk.num_fenced_flush_hashed | counter | ++----------------------------------+---------+ +| disk.num_fenced_flush_storage | counter | ++----------------------------------+---------+ +| disk.num_fenced_trim_cache | counter | ++----------------------------------+---------+ +| disk.num_fenced_file_priority | counter | ++----------------------------------+---------+ +| disk.num_fenced_load_torrent | counter | ++----------------------------------+---------+ +| disk.num_fenced_clear_piece | counter | ++----------------------------------+---------+ +| disk.num_fenced_tick_storage | counter | ++----------------------------------+---------+ + + +for each kind of disk job, a counter of how many jobs of that kind +are currently blocked by a disk fence + +.. _dht.dht_nodes: + +.. raw:: html + + + ++---------------+---------+ +| name | type | ++===============+=========+ +| dht.dht_nodes | counter | ++---------------+---------+ + + +The number of nodes in the DHT routing table + +.. _dht.dht_node_cache: + +.. raw:: html + + + ++--------------------+---------+ +| name | type | ++====================+=========+ +| dht.dht_node_cache | counter | ++--------------------+---------+ + + +The number of replacement nodes in the DHT routing table + +.. _dht.dht_torrents: + +.. raw:: html + + + ++------------------+---------+ +| name | type | ++==================+=========+ +| dht.dht_torrents | counter | ++------------------+---------+ + + +the number of torrents currently tracked by our DHT node + +.. _dht.dht_peers: + +.. raw:: html + + + ++---------------+---------+ +| name | type | ++===============+=========+ +| dht.dht_peers | counter | ++---------------+---------+ + + +the number of peers currently tracked by our DHT node + +.. _dht.dht_immutable_data: + +.. raw:: html + + + ++------------------------+---------+ +| name | type | ++========================+=========+ +| dht.dht_immutable_data | counter | ++------------------------+---------+ + + +the number of immutable data items tracked by our DHT node + +.. _dht.dht_mutable_data: + +.. raw:: html + + + ++----------------------+---------+ +| name | type | ++======================+=========+ +| dht.dht_mutable_data | counter | ++----------------------+---------+ + + +the number of mutable data items tracked by our DHT node + +.. _dht.dht_allocated_observers: + +.. raw:: html + + + ++-----------------------------+---------+ +| name | type | ++=============================+=========+ +| dht.dht_allocated_observers | counter | ++-----------------------------+---------+ + + +the number of RPC observers currently allocated + +.. _dht.dht_messages_in: + +.. _dht.dht_messages_out: + +.. raw:: html + + + + ++----------------------+---------+ +| name | type | ++======================+=========+ +| dht.dht_messages_in | counter | ++----------------------+---------+ +| dht.dht_messages_out | counter | ++----------------------+---------+ + + +the total number of DHT messages sent and received + +.. _dht.dht_messages_out_dropped: + +.. raw:: html + + + ++------------------------------+---------+ +| name | type | ++==============================+=========+ +| dht.dht_messages_out_dropped | counter | ++------------------------------+---------+ + + +the number of outgoing messages that failed to be +sent + +.. _dht.dht_bytes_in: + +.. _dht.dht_bytes_out: + +.. raw:: html + + + + ++-------------------+---------+ +| name | type | ++===================+=========+ +| dht.dht_bytes_in | counter | ++-------------------+---------+ +| dht.dht_bytes_out | counter | ++-------------------+---------+ + + +the total number of bytes sent and received by the DHT + +.. _dht.dht_ping_in: + +.. _dht.dht_ping_out: + +.. _dht.dht_find_node_in: + +.. _dht.dht_find_node_out: + +.. _dht.dht_get_peers_in: + +.. _dht.dht_get_peers_out: + +.. _dht.dht_announce_peer_in: + +.. _dht.dht_announce_peer_out: + +.. _dht.dht_get_in: + +.. _dht.dht_get_out: + +.. _dht.dht_put_in: + +.. _dht.dht_put_out: + +.. raw:: html + + + + + + + + + + + + + + ++---------------------------+---------+ +| name | type | ++===========================+=========+ +| dht.dht_ping_in | counter | ++---------------------------+---------+ +| dht.dht_ping_out | counter | ++---------------------------+---------+ +| dht.dht_find_node_in | counter | ++---------------------------+---------+ +| dht.dht_find_node_out | counter | ++---------------------------+---------+ +| dht.dht_get_peers_in | counter | ++---------------------------+---------+ +| dht.dht_get_peers_out | counter | ++---------------------------+---------+ +| dht.dht_announce_peer_in | counter | ++---------------------------+---------+ +| dht.dht_announce_peer_out | counter | ++---------------------------+---------+ +| dht.dht_get_in | counter | ++---------------------------+---------+ +| dht.dht_get_out | counter | ++---------------------------+---------+ +| dht.dht_put_in | counter | ++---------------------------+---------+ +| dht.dht_put_out | counter | ++---------------------------+---------+ + + +the number of DHT messages we've sent and received +by kind. + +.. _dht.dht_invalid_announce: + +.. _dht.dht_invalid_get_peers: + +.. _dht.dht_invalid_put: + +.. _dht.dht_invalid_get: + +.. raw:: html + + + + + + ++---------------------------+---------+ +| name | type | ++===========================+=========+ +| dht.dht_invalid_announce | counter | ++---------------------------+---------+ +| dht.dht_invalid_get_peers | counter | ++---------------------------+---------+ +| dht.dht_invalid_put | counter | ++---------------------------+---------+ +| dht.dht_invalid_get | counter | ++---------------------------+---------+ + + +the number of failed incoming DHT requests by kind of request + +.. _utp.utp_packet_loss: + +.. _utp.utp_timeout: + +.. _utp.utp_packets_in: + +.. _utp.utp_packets_out: + +.. _utp.utp_fast_retransmit: + +.. _utp.utp_packet_resend: + +.. _utp.utp_samples_above_target: + +.. _utp.utp_samples_below_target: + +.. _utp.utp_payload_pkts_in: + +.. _utp.utp_payload_pkts_out: + +.. _utp.utp_invalid_pkts_in: + +.. _utp.utp_redundant_pkts_in: + +.. raw:: html + + + + + + + + + + + + + + ++------------------------------+---------+ +| name | type | ++==============================+=========+ +| utp.utp_packet_loss | counter | ++------------------------------+---------+ +| utp.utp_timeout | counter | ++------------------------------+---------+ +| utp.utp_packets_in | counter | ++------------------------------+---------+ +| utp.utp_packets_out | counter | ++------------------------------+---------+ +| utp.utp_fast_retransmit | counter | ++------------------------------+---------+ +| utp.utp_packet_resend | counter | ++------------------------------+---------+ +| utp.utp_samples_above_target | counter | ++------------------------------+---------+ +| utp.utp_samples_below_target | counter | ++------------------------------+---------+ +| utp.utp_payload_pkts_in | counter | ++------------------------------+---------+ +| utp.utp_payload_pkts_out | counter | ++------------------------------+---------+ +| utp.utp_invalid_pkts_in | counter | ++------------------------------+---------+ +| utp.utp_redundant_pkts_in | counter | ++------------------------------+---------+ + + +uTP counters. Each counter represents the number of time each event +has occurred. + +.. _utp.num_utp_idle: + +.. _utp.num_utp_syn_sent: + +.. _utp.num_utp_connected: + +.. _utp.num_utp_fin_sent: + +.. _utp.num_utp_close_wait: + +.. _utp.num_utp_deleted: + +.. raw:: html + + + + + + + + ++------------------------+---------+ +| name | type | ++========================+=========+ +| utp.num_utp_idle | counter | ++------------------------+---------+ +| utp.num_utp_syn_sent | counter | ++------------------------+---------+ +| utp.num_utp_connected | counter | ++------------------------+---------+ +| utp.num_utp_fin_sent | counter | ++------------------------+---------+ +| utp.num_utp_close_wait | counter | ++------------------------+---------+ +| utp.num_utp_deleted | counter | ++------------------------+---------+ + + +the number of uTP sockets in each respective state + +.. _sock_bufs.socket_send_size3: + +.. _sock_bufs.socket_send_size4: + +.. _sock_bufs.socket_send_size5: + +.. _sock_bufs.socket_send_size6: + +.. _sock_bufs.socket_send_size7: + +.. _sock_bufs.socket_send_size8: + +.. _sock_bufs.socket_send_size9: + +.. _sock_bufs.socket_send_size10: + +.. _sock_bufs.socket_send_size11: + +.. _sock_bufs.socket_send_size12: + +.. _sock_bufs.socket_send_size13: + +.. _sock_bufs.socket_send_size14: + +.. _sock_bufs.socket_send_size15: + +.. _sock_bufs.socket_send_size16: + +.. _sock_bufs.socket_send_size17: + +.. _sock_bufs.socket_send_size18: + +.. _sock_bufs.socket_send_size19: + +.. _sock_bufs.socket_send_size20: + +.. _sock_bufs.socket_recv_size3: + +.. _sock_bufs.socket_recv_size4: + +.. _sock_bufs.socket_recv_size5: + +.. _sock_bufs.socket_recv_size6: + +.. _sock_bufs.socket_recv_size7: + +.. _sock_bufs.socket_recv_size8: + +.. _sock_bufs.socket_recv_size9: + +.. _sock_bufs.socket_recv_size10: + +.. _sock_bufs.socket_recv_size11: + +.. _sock_bufs.socket_recv_size12: + +.. _sock_bufs.socket_recv_size13: + +.. _sock_bufs.socket_recv_size14: + +.. _sock_bufs.socket_recv_size15: + +.. _sock_bufs.socket_recv_size16: + +.. _sock_bufs.socket_recv_size17: + +.. _sock_bufs.socket_recv_size18: + +.. _sock_bufs.socket_recv_size19: + +.. _sock_bufs.socket_recv_size20: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++------------------------------+---------+ +| name | type | ++==============================+=========+ +| sock_bufs.socket_send_size3 | counter | ++------------------------------+---------+ +| sock_bufs.socket_send_size4 | counter | ++------------------------------+---------+ +| sock_bufs.socket_send_size5 | counter | ++------------------------------+---------+ +| sock_bufs.socket_send_size6 | counter | ++------------------------------+---------+ +| sock_bufs.socket_send_size7 | counter | ++------------------------------+---------+ +| sock_bufs.socket_send_size8 | counter | ++------------------------------+---------+ +| sock_bufs.socket_send_size9 | counter | ++------------------------------+---------+ +| sock_bufs.socket_send_size10 | counter | ++------------------------------+---------+ +| sock_bufs.socket_send_size11 | counter | ++------------------------------+---------+ +| sock_bufs.socket_send_size12 | counter | ++------------------------------+---------+ +| sock_bufs.socket_send_size13 | counter | ++------------------------------+---------+ +| sock_bufs.socket_send_size14 | counter | ++------------------------------+---------+ +| sock_bufs.socket_send_size15 | counter | ++------------------------------+---------+ +| sock_bufs.socket_send_size16 | counter | ++------------------------------+---------+ +| sock_bufs.socket_send_size17 | counter | ++------------------------------+---------+ +| sock_bufs.socket_send_size18 | counter | ++------------------------------+---------+ +| sock_bufs.socket_send_size19 | counter | ++------------------------------+---------+ +| sock_bufs.socket_send_size20 | counter | ++------------------------------+---------+ +| sock_bufs.socket_recv_size3 | counter | ++------------------------------+---------+ +| sock_bufs.socket_recv_size4 | counter | ++------------------------------+---------+ +| sock_bufs.socket_recv_size5 | counter | ++------------------------------+---------+ +| sock_bufs.socket_recv_size6 | counter | ++------------------------------+---------+ +| sock_bufs.socket_recv_size7 | counter | ++------------------------------+---------+ +| sock_bufs.socket_recv_size8 | counter | ++------------------------------+---------+ +| sock_bufs.socket_recv_size9 | counter | ++------------------------------+---------+ +| sock_bufs.socket_recv_size10 | counter | ++------------------------------+---------+ +| sock_bufs.socket_recv_size11 | counter | ++------------------------------+---------+ +| sock_bufs.socket_recv_size12 | counter | ++------------------------------+---------+ +| sock_bufs.socket_recv_size13 | counter | ++------------------------------+---------+ +| sock_bufs.socket_recv_size14 | counter | ++------------------------------+---------+ +| sock_bufs.socket_recv_size15 | counter | ++------------------------------+---------+ +| sock_bufs.socket_recv_size16 | counter | ++------------------------------+---------+ +| sock_bufs.socket_recv_size17 | counter | ++------------------------------+---------+ +| sock_bufs.socket_recv_size18 | counter | ++------------------------------+---------+ +| sock_bufs.socket_recv_size19 | counter | ++------------------------------+---------+ +| sock_bufs.socket_recv_size20 | counter | ++------------------------------+---------+ + + +the buffer sizes accepted by +socket send and receive calls respectively. +The larger the buffers are, the more efficient, +because it reqire fewer system calls per byte. +The size is 1 << n, where n is the number +at the end of the counter name. i.e. +8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, +16384, 32768, 65536, 131072, 262144, 524288, 1048576 +bytes + diff --git a/docs/storage.png b/docs/storage.png new file mode 100644 index 0000000000000000000000000000000000000000..14f79370a45e15d0a1fcdbc2e54a3fc9e65d30ea GIT binary patch literal 4773 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 literal 0 HcmV?d00001 diff --git a/docs/streaming.html b/docs/streaming.html new file mode 100644 index 0000000..c6ebef4 --- /dev/null +++ b/docs/streaming.html @@ -0,0 +1,174 @@ + + + + + + +Streaming implementation + + + + + + + +
    +
    + + + + +
    +

    Streaming implementation

    + +

    This documents describes the algorithm libtorrent uses to satisfy time critical +piece requests, i.e. streaming.

    +
    +

    piece picking

    +

    The standard bittorrent piece picker is peer-centric. A peer unchokes us or we +complte 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 bitrate 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 timeuts, 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 bandwidht may be lost in redundant download (keep in +mind the zero-sum game).

    +

    It never makes sense to request a block twice from the same peer. There is logic +in place to prevent this.

    +
    +
    +

    optimizations

    +

    One optimization is to buffer all piece requests while looping over the time- +critical pieces and not send them until one round is complete. This increases +the chances that the request messages are coalesced into the same packet. +This in turn lowers the number of system calls and network overhead.

    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/streaming.rst b/docs/streaming.rst new file mode 100644 index 0000000..2222507 --- /dev/null +++ b/docs/streaming.rst @@ -0,0 +1,126 @@ +Streaming implementation +======================== + +This documents describes the algorithm libtorrent uses to satisfy time critical +piece requests, i.e. streaming. + +piece picking +------------- + +The standard bittorrent piece picker is peer-centric. A peer unchokes us or we +complte 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 bitrate 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 timeuts, 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 bandwidht may be lost in redundant download (keep in +mind the zero-sum game). + +It never makes sense to request a block twice from the same peer. There is logic +in place to prevent this. + +optimizations +------------- + +One optimization is to buffer all piece requests while looping over the time- +critical pieces and not send them until one round is complete. This increases +the chances that the request messages are coalesced into the same packet. +This in turn lowers the number of system calls and network overhead. + diff --git a/docs/style.css b/docs/style.css new file mode 100644 index 0000000..ce03d34 --- /dev/null +++ b/docs/style.css @@ -0,0 +1,154 @@ +.entry { min-height: 135px; } + +#main { + font-family: Verdana; + text-align: left; + margin-top: 10px; +} + +/* Base elements */ + +* {margin: 0; padding: 0;} +body, table { font: 0.9em Verdana, sans-serif;} + +h1, h2, h3 { + font-family: Georgia "Times New Roman", serif; + padding-bottom: 0.5em; + font-weight: bold; +} + +div.sidebar { + background: #f8f8e8; + float: right; + width: 20em; + margin-right: 1em; + border: solid 1px #e5e5d5; + padding: 1.3em; +} + +div.sidebar p.sidebar-title { + font: 1.3em Georgia; + border-bottom: solid 1px #e5e5d5; + padding-bottom: 0.5em; + margin: 0 0 0.5em 0; +} + +a { + text-decoration: none; + color: #8D370A; + border-bottom: dotted 1px #8D370A; +} + +ul, ol { line-height: 1.8em; } +ul { list-style: square; } +li { margin-left: 2.8em; } + +p, ul, ol, img {margin-bottom: 1em;} + +.align-right { + float: right; +} + +.document { + margin: 0px; +} + +div.section { + margin-bottom: 3em; +} + +div.section div.section div.section { + margin-bottom: 2em; +} + +div.section p, div.section ul, div.section dl { +} + +#container { + text-align: left; + max-width: 65em; + margin: 5px auto; + position: relative; + padding: 3px ; +} + +#header { + height: 116px; + width: 100%; + border: none; + margin-top: 1em; + margin-bottom: 1em; +} + +#header tr td { + border: none; +} + +#orange { + margin: 0; + padding: 0; + width: 159px; + height: 116px; + border-top-left-radius: 6px; + border-bottom-left-radius: 6px; + background: url('img/orange.png') no-repeat top left; +} + +#logo { + margin: 0; + padding: 0; + text-align: center; + color: white; + font-size: 50pt; + height: 116px; + font-family: Georgia; + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; + background: url('img/bg.png'); +} + +#gradient { + width: 100%; + clear: both; + height: 30px; + background: linear-gradient(#bbb, #ddd); +} + +#footer { + margin: 0px; + padding: 0px; + text-align: center; + background: #ddd; + width: 100%; + color: #777; + overflow: hidden; +} + +#footer table { + margin-left: auto; + margin-right: auto; + font-size: 1em; +} + +#footer tr td { + border: none; + background: none; + color: #777; + text-align: left; +} + +#footer a { + color: #777; +} + +#footer a:hover { + color: #000; +} + +#filler { + height: 200px; + background: linear-gradient(#ddd, #fff); +} + +li p, li li { font-size: 100%; } + diff --git a/docs/todo.html b/docs/todo.html new file mode 100644 index 0000000..8a570a3 --- /dev/null +++ b/docs/todo.html @@ -0,0 +1,10423 @@ + + + + +

    libtorrent todo-list

    +0 urgent +19 important +63 relevant +7 feasible +174 notes +
    relevance 3../src/file.cpp:538find out what error code is reported when the filesystem does not support hard links.
    relevance 3../src/peer_connection.cpp:2968instead of having to ask the torrent whether it's in graceful pause mode or not, the peers should keep that state (and the torrent should update them when it enters graceful pause). When a peer enters graceful pause mode, it should cancel all outstanding requests and clear its request queue.
    relevance 3../src/peer_connection.cpp:3850once peers are properly put in graceful pause mode, they can cancel all outstanding requests and this test can be removed.
    relevance 3../src/session_impl.cpp:3928it would probably make sense to have a separate list of peers that are eligible for optimistic unchoke, similar to the torrents perhaps this could even iterate over the pool allocators of torrent_peer objects. It could probably be done in a single pass and collect the n best candidates. maybe just a queue of peers would make even more sense, just pick the next peer in the queue for unchoking. It would be O(1).
    relevance 3../src/session_impl.cpp:3952peers should know whether their torrent is paused or not, instead of having to ask it over and over again
    relevance 3../src/session_impl.cpp:4229there should be a pre-calculated list of all peers eligible for unchoking
    relevance 3../src/torrent.cpp:9663this really needs to be moved to do_async_save_resume_data. flags need to be passed on
    relevance 3../src/upnp.cpp:94listen_interface is not used. It's meant to bind the broadcast socket. it would probably have to be changed to a vector of interfaces to bind to though, since the broadcast socket opens one socket per local interface by default
    relevance 3../src/kademlia/node_id.cpp:54the XORing should be done at full words instead of bytes
    relevance 3../src/kademlia/node_id.cpp:66the XORing should be done at full words instead of bytes
    relevance 3../src/kademlia/node_id.cpp:82the xoring should be done at full words and _builtin_clz() could be used as the last step
    relevance 3../src/kademlia/routing_table.cpp:698the call to compare_ip_cidr here is expensive. peel off some layers of abstraction here to make it quicker. Look at xoring and using _builtin_ctz()
    relevance 3../src/kademlia/rpc_manager.cpp:87move this into it's own .cpp file
    relevance 3../include/libtorrent/torrent.hpp:1359factor out the links (as well as update_list() to a separate class that torrent can inherit)
    relevance 3../include/libtorrent/torrent_handle.hpp:237consider replacing all the setters and getters for pause, resume, stop-when-ready, share-mode, upload-mode, super-seeding, apply-ip-filter, resolve-countries, pinned, sequential-download, seed-mode with just set_flags() and clear_flags() using the flags from add_torrent_params. Perhaps those flags should have a more generic name.
    relevance 3../include/libtorrent/torrent_handle.hpp:484unify url_seed and http_seed with just web_seed, using the web_seed_entry.
    relevance 3../include/libtorrent/web_peer_connection.hpp:131if we make this be a disk_buffer_holder instead we would save a copy use allocate_disk_receive_buffer and release_disk_receive_buffer
    relevance 3../include/libtorrent/kademlia/routing_table.hpp:99to improve memory locality and scanning performance, turn the routing table into a single vector with boundaries for the nodes instead. Perhaps replacement nodes should be in a separate vector.
    relevance 3../include/libtorrent/aux_/allocating_handler.hpp:77make sure the handlers we pass in are potentially movable!
    relevance 2../test/test_dht.cpp:514split this test up into smaller test cases
    relevance 2../test/test_dht.cpp:2141split this up into smaller test cases
    relevance 2../test/test_piece_picker.cpp:281split this up into smaller tests (where we print_title)
    relevance 2../test/test_storage.cpp:481split this test up into smaller parts
    relevance 2../src/alert.cpp:1514the salt here is allocated on the heap. It would be nice to allocate in in the stack_allocator
    relevance 2../src/alert_manager.cpp:90keep a count of the number of threads waiting. Only if it's > 0 notify them
    relevance 2../src/block_cache.cpp:1751turn these return values into enums returns -1: block not in cache -2: out of memory
    relevance 2../src/escape_string.cpp:209this should probably be moved into string_util.cpp
    relevance 2../src/file.cpp:567test this on a FAT volume to see what error we get!
    relevance 2../src/http_tracker_connection.cpp:381returning a bool here is redundant. Instead this function should return the peer_entry
    relevance 2../src/instantiate_connection.cpp:43peer_connection and tracker_connection should probably be flags
    relevance 2../src/instantiate_connection.cpp:44move this function into libtorrent::aux namespace
    relevance 2../src/peer_connection.cpp:2385this should probably be based on time instead of number of request messages. For a very high throughput connection, 300 may be a legitimate number of requests to have in flight when getting choked
    relevance 2../src/peer_connection.cpp:3132since we throw away the queue entry once we issue the disk job, this may happen. Instead, we should keep the queue entry around, mark it as having been requested from disk and once the disk job comes back, discard it if it has been cancelled. Maybe even be able to cancel disk jobs?
    relevance 2../src/peer_connection.cpp:4820use a deadline_timer for timeouts. Don't rely on second_tick()! Hook this up to connect timeout as well. This would improve performance because of less work in second_tick(), and might let use remove ticking entirely eventually
    relevance 2../src/peer_list.cpp:497it would be nice if there was a way to iterate over these torrent_peer objects in the order they are allocated in the pool instead. It would probably be more efficient
    relevance 2../src/piece_picker.cpp:1977make the 2048 limit configurable
    relevance 2../src/piece_picker.cpp:2608the first_block returned here is the largest free range, not the first-fit range, which would be better
    relevance 2../src/piece_picker.cpp:3386it would be nice if this could be folded into lock_piece() the main distinction is that this also maintains the m_num_passed counter and the passed_hash_check member Is there ever a case where we call write filed without also locking the piece? Perhaps write_failed() should imply locking it.
    relevance 2../src/receive_buffer.cpp:209should this take a boost::array<..., 2> instead? it could return the number of buffers added, just like reserve.
    relevance 2../src/session_impl.cpp:467is there a reason not to move all of this into init()? and just post it to the io_service?
    relevance 2../src/session_impl.cpp:1979the udp socket(s) should be using the same generic mechanism and not be restricted to a single one we should open a one listen socket for each entry in the listen_interfaces list
    relevance 2../src/session_impl.cpp:3705make a list for torrents that want to be announced on the DHT so we don't have to loop over all torrents, just to find the ones that want to announce
    relevance 2../src/session_impl.cpp:6250this should be factored into the udp socket, so we only have the code once
    relevance 2../src/session_impl.cpp:6949perhaps DHT logging should be disabled by TORRENT_DISABLE_LOGGING too
    relevance 2../src/storage.cpp:1086we probably need to do this unconditionally in this function. Even if the resume data file appears stale, we need to create these hard links, right?
    relevance 2../src/storage.cpp:1110is this risky? The upper layer will assume we have the whole file. Perhaps we should verify that at least the size of the file is correct
    relevance 2../src/torrent.cpp:683post alert
    relevance 2../src/torrent.cpp:1920add a unit test where we don't have metadata, connect to a peer that sends a bitfield that's too large, then we get the metadata
    relevance 2../src/torrent.cpp:4984abort lookups this torrent has made via the session host resolver interface
    relevance 2../src/torrent.cpp:8179if peer is a really good peer, maybe we shouldn't disconnect it perhaps this logic should be disabled if we have too many idle peers (with some definition of idle)
    relevance 2../src/torrent.cpp:10615this should probably be removed
    relevance 2../src/tracker_manager.cpp:200some of these arguments could probably be moved to the tracker request itself. like the ip_filter and settings
    relevance 2../src/udp_socket.cpp:809the udp_socket should really just be a single socket, and the session should support having more than one, just like with TCP sockets for now, just make bind failures non-fatal
    relevance 2../src/udp_tracker_connection.cpp:83support authentication here. tracker_req().auth
    relevance 2../src/ut_metadata.cpp:123if we were to initialize m_metadata_size lazily instead, we would probably be more efficient initialize m_metadata_size
    relevance 2../src/utp_socket_manager.cpp:244we may want to take ec into account here. possibly close connections quicker
    relevance 2../src/utp_stream.cpp:386it would be nice if not everything would have to be public here
    relevance 2../src/web_peer_connection.cpp:329do we really need a special case here? wouldn't the multi-file case handle single file torrents correctly too?
    relevance 2../src/web_peer_connection.cpp:551just 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
    relevance 2../src/web_peer_connection.cpp:603create a mapping of file-index to redirection URLs. Use that to form URLs instead. Support to reconnect to a new server without destructing this peer_connection
    relevance 2../src/kademlia/dht_storage.cpp:110make this configurable in dht_settings
    relevance 2../src/kademlia/node.cpp:658it would be nice to have a bias towards node-id prefixes that are missing in the bucket
    relevance 2../src/kademlia/node.cpp:742use the non deprecated function instead of this one
    relevance 2../src/kademlia/node.cpp:901find_node should write directly to the response entry
    relevance 2../src/kademlia/routing_table.cpp:132use the non deprecated function instead of this one
    relevance 2../src/kademlia/routing_table.cpp:991move the lowest priority nodes to the replacement bucket
    relevance 2../include/libtorrent/alert_types.hpp:1443should the alert baseclass have this object instead?
    relevance 2../include/libtorrent/broadcast_socket.hpp:53facto these functions out
    relevance 2../include/libtorrent/build_config.hpp:40instead of using a dummy function to cause link errors when incompatible build configurations are used, make the namespace name depend on the configuration, and have a using declaration in the headers to pull it into libtorrent.
    relevance 2../include/libtorrent/enum_net.hpp:151this could be done more efficiently by just looking up the interface with the given name, maybe even with if_nametoindex()
    relevance 2../include/libtorrent/heterogeneous_queue.hpp:56add emplace_back() version
    relevance 2../include/libtorrent/peer_connection.hpp:1118rename this target queue size
    relevance 2../include/libtorrent/piece_picker.hpp:594having 8 priority levels is probably excessive. It should probably be changed to 3 levels + dont-download
    relevance 2../include/libtorrent/proxy_base.hpp:260use the resolver interface that has a built-in cache
    relevance 2../include/libtorrent/session_handle.hpp:78the ip filter should probably be saved here too
    relevance 2../include/libtorrent/socks5_stream.hpp:186add async_connect() that takes a hostname and port as well
    relevance 2../include/libtorrent/tracker_manager.hpp:293this class probably doesn't need to have virtual functions.
    relevance 2../include/libtorrent/aux_/session_impl.hpp:1177the throttling of saving resume data could probably be factored out into a separate class
    relevance 2../include/libtorrent/aux_/session_interface.hpp:128make this interface a lot smaller. It could be split up into several smaller interfaces. Each subsystem could then limit the size of the mock object to test it.
    relevance 2../include/libtorrent/aux_/session_interface.hpp:137the IP voting mechanism should be factored out to its own class, not part of the session
    relevance 2../include/libtorrent/aux_/session_interface.hpp:162remove this. There's already get_resolver()
    relevance 2../include/libtorrent/aux_/session_interface.hpp:217factor out the thread pool for socket jobs into a separate class used to (potentially) issue socket write calls onto multiple threads
    relevance 1../src/disk_io_thread.cpp:219it would be nice to have the number of threads be set dynamically
    relevance 1../src/http_seed_connection.cpp:129in chunked encoding mode, this assert won't hold. the chunk headers should be subtracted from the receive_buffer_size
    relevance 1../src/session_impl.cpp:5627report the proper address of the router as the source IP of this understanding of our external address, instead of the empty address
    relevance 1../src/torrent.cpp:1245make this depend on the error and on the filesystem the files are being downloaded to. If the error is no_space_left_on_device and the filesystem doesn't support sparse files, only zero the priorities of the pieces that are at the tails of all files, leaving everything up to the highest written piece in each file
    relevance 1../src/torrent.cpp:7282save the send_stats state instead of throwing them away it may pose an issue when downgrading though
    relevance 1../src/torrent.cpp:8523should disconnect all peers that have the pieces we have not just seeds. It would be pretty expensive to check all pieces for all peers though
    relevance 1../include/libtorrent/ip_voter.hpp:124instead, have one instance per possible subnet, global IPv4, global IPv6, loopback, 192.168.x.x, 10.x.x.x, etc.
    relevance 0../test/test_block_cache.cpp:469test try_evict_blocks
    relevance 0../test/test_block_cache.cpp:470test evicting volatile pieces, to see them be removed
    relevance 0../test/test_block_cache.cpp:471test evicting dirty pieces
    relevance 0../test/test_block_cache.cpp:472test free_piece
    relevance 0../test/test_block_cache.cpp:473test abort_dirty
    relevance 0../test/test_block_cache.cpp:474test unaligned reads
    relevance 0../test/test_bloom_filter.cpp:130test size()
    relevance 0../test/test_bloom_filter.cpp:131test clear()
    relevance 0../test/test_dht.cpp:103ideally the mock_socket would contain this queue of packets, to make tests independent
    relevance 0../test/test_dht.cpp:420check to make sure the "best" items are stored
    relevance 0../test/test_dht.cpp:513test obfuscated_get_peers
    relevance 0../test/test_fast_extension.cpp:1020test sending invalid requests (out of bound piece index, offsets and sizes)
    relevance 0../test/test_file_progress.cpp:109test the update function too
    relevance 0../test/test_file_storage.cpp:214test file_storage::optimize
    relevance 0../test/test_file_storage.cpp:215test map_block
    relevance 0../test/test_file_storage.cpp:216test piece_size(int piece)
    relevance 0../test/test_file_storage.cpp:217test file_index_at_offset
    relevance 0../test/test_file_storage.cpp:218test file attributes
    relevance 0../test/test_file_storage.cpp:219test symlinks
    relevance 0../test/test_file_storage.cpp:220test pad_files
    relevance 0../test/test_file_storage.cpp:221test reorder_file (make sure internal_file_entry::swap() is used)
    relevance 0../test/test_peer_list.cpp:939test erasing peers
    relevance 0../test/test_peer_list.cpp:940test update_peer_port with allow_multiple_connections_per_ip and without
    relevance 0../test/test_peer_list.cpp:941test add i2p peers
    relevance 0../test/test_peer_list.cpp:942test allow_i2p_mixed
    relevance 0../test/test_peer_list.cpp:943test insert_peer failing with all error conditions
    relevance 0../test/test_peer_list.cpp:944test IPv6
    relevance 0../test/test_peer_list.cpp:945test connect_to_peer() failing
    relevance 0../test/test_peer_list.cpp:946test connection_closed
    relevance 0../test/test_peer_list.cpp:947connect candidates recalculation when incrementing failcount
    relevance 0../test/test_resolve_links.cpp:80test files with different piece size (negative test)
    relevance 0../test/test_resolve_links.cpp:83it would be nice to test resolving of more than just 2 files as well. like 3 single file torrents merged into one, resolving all 3 files.
    relevance 0../test/test_resume.cpp:233test what happens when loading a resume file with both piece priorities and file priorities (file prio should take presedence)
    relevance 0../test/test_resume.cpp:236make sure a resume file only ever contain file priorities OR piece priorities. Never both.
    relevance 0../test/test_resume.cpp:239generally save
    relevance 0../test/test_resume.cpp:696test all other resume flags here too. This would require returning more than just the torrent_status from test_resume_flags. Also http seeds and trackers for instance
    relevance 0../test/test_settings_pack.cpp:140load_pack_from_dict
    relevance 0../test/test_ssl.cpp:394test using a signed certificate with the wrong info-hash in DN
    relevance 0../test/test_ssl.cpp:492also test using a hash that refers to a valid torrent but that differs from the SNI hash
    relevance 0../test/test_timestamp_history.cpp:54test the case where we have > 120 samples (and have the base delay actually be updated)
    relevance 0../test/test_timestamp_history.cpp:55test the case where a sample is lower than the history entry but not lower than the base
    relevance 0../test/test_torrent_info.cpp:166test remap_files
    relevance 0../test/test_torrent_info.cpp:167merkle torrents. specifically torrent_info::add_merkle_nodes and torrent with "root hash"
    relevance 0../test/test_torrent_info.cpp:168torrent with 'p' (padfile) attribute
    relevance 0../test/test_torrent_info.cpp:169torrent with 'h' (hidden) attribute
    relevance 0../test/test_torrent_info.cpp:170torrent with 'x' (executable) attribute
    relevance 0../test/test_torrent_info.cpp:171torrent with 'l' (symlink) attribute
    relevance 0../test/test_torrent_info.cpp:172creating a merkle torrent (torrent_info::build_merkle_list)
    relevance 0../test/test_torrent_info.cpp:173torrent with multiple trackers in multiple tiers, making sure we shuffle them (how do you test shuffling?, load it multiple times and make sure it's in different order at least once)
    relevance 0../test/test_torrent_info.cpp:174torrents with a zero-length name
    relevance 0../test/test_torrent_info.cpp:175torrents with a merkle tree and add_merkle_nodes
    relevance 0../test/test_torrent_info.cpp:176torrent with a non-dictionary info-section
    relevance 0../test/test_torrent_info.cpp:177torrents with DHT nodes
    relevance 0../test/test_torrent_info.cpp:178torrent with url-list as a single string
    relevance 0../test/test_torrent_info.cpp:179torrent with http seed as a single string
    relevance 0../test/test_torrent_info.cpp:180torrent with a comment
    relevance 0../test/test_torrent_info.cpp:181torrent with an SSL cert
    relevance 0../test/test_torrent_info.cpp:182torrent with attributes (executable and hidden)
    relevance 0../test/test_torrent_info.cpp:183torrent_info::add_tracker
    relevance 0../test/test_torrent_info.cpp:184torrent_info::unload
    relevance 0../test/test_torrent_info.cpp:185torrent_info constructor that takes an invalid bencoded buffer
    relevance 0../test/test_torrent_info.cpp:186verify_encoding with a string that triggers character replacement
    relevance 0../test/test_tracker.cpp:53test scrape requests
    relevance 0../test/test_tracker.cpp:54test parse peers6
    relevance 0../test/test_tracker.cpp:55test parse tracker-id
    relevance 0../test/test_tracker.cpp:56test parse failure-reason
    relevance 0../test/test_tracker.cpp:57test all failure paths, including invalid bencoding not a dictionary no files entry in scrape response no info-hash entry in scrape response malformed peers in peer list of dictionaries uneven number of bytes in peers and peers6 string responses
    relevance 0../test/test_transfer.cpp:211these settings_pack tests belong in their own test
    relevance 0../test/test_transfer.cpp:290factor out the disk-full test into its own unit test
    relevance 0../test/test_upnp.cpp:108store the log and verify that some key messages are there
    relevance 0../src/block_cache.cpp:1068it's somewhat expensive to iterate over this linked list. Presumably because of the random access of memory. It would be nice if pieces with no evictable blocks weren't in this list
    relevance 0../src/block_cache.cpp:1139this should probably only be done every n:th time
    relevance 0../src/block_cache.cpp:1839create a holder for refcounts that automatically decrement
    relevance 0../src/bt_peer_connection.cpp:700this could be optimized using knuth morris pratt
    relevance 0../src/bt_peer_connection.cpp:2254if we're finished, send upload_only message
    relevance 0../src/choker.cpp:347optimize this using partial_sort or something. We don't need to sort the entire list
    relevance 0../src/choker.cpp:350make the comparison function a free function and move it into this cpp file
    relevance 0../src/choker.cpp:355make configurable
    relevance 0../src/choker.cpp:369make configurable
    relevance 0../src/create_torrent.cpp:294this should probably be optional
    relevance 0../src/disk_buffer_pool.cpp:257perhaps we should sort the buffers here?
    relevance 0../src/disk_io_thread.cpp:883it would be nice to optimize this by having the cache pieces also ordered by
    relevance 0../src/disk_io_thread.cpp:926instead of doing a lookup each time through the loop, save cached_piece_entry pointers with piece_refcount incremented to pin them
    relevance 0../src/disk_io_thread.cpp:1115instead of doing this. pass in the settings to each storage_interface call. Each disk thread could hold its most recent understanding of the settings in a shared_ptr, and update it every time it wakes up from a job. That way each access to the settings won't require a mutex to be held.
    relevance 0../src/disk_io_thread.cpp:1160a potentially more efficient solution would be to have a special queue for retry jobs, that's only ever run when a job completes, in any thread. It would only work if counters::num_running_disk_jobs > 0
    relevance 0../src/disk_io_thread.cpp:1831maybe the tailqueue_iterator should contain a pointer-pointer instead and have an unlink function
    relevance 0../src/disk_io_thread.cpp:2097this is potentially very expensive. One way to solve it would be to have a fence for just this one piece.
    relevance 0../src/disk_io_thread.cpp:2352we should probably just hang the job on the piece and make sure the hasher gets kicked
    relevance 0../src/disk_io_thread.cpp:2422introduce a holder class that automatically increments and decrements the piece_refcount
    relevance 0../src/disk_io_thread.cpp:2674it would be nice to not have to lock the mutex every turn through this loop
    relevance 0../src/enum_net.cpp:278get the MTU (and other interesting metrics) from the rt_msghdr instead
    relevance 0../src/file_progress.cpp:137it would be nice to not depend on alert_manager here
    relevance 0../src/metadata_transfer.cpp:365this is not safe. The torrent could be unloaded while we're still sending the metadata
    relevance 0../src/packet_buffer.cpp:180use compare_less_wrap for this comparison as well
    relevance 0../src/part_file.cpp:252what do we do if someone is currently reading from the disk from this piece? does it matter? Since we won't actively erase the data from disk, but it may be overwritten soon, it's probably not that big of a deal
    relevance 0../src/part_file.cpp:375instead of rebuilding the whole file header and flushing it, update the slot entries as we go
    relevance 0../src/peer_connection.cpp:525it would be neat to be able to print this straight into the alert's stack allocator
    relevance 0../src/peer_connection.cpp:1036this should be the global download rate
    relevance 0../src/peer_connection.cpp:3380sort the allowed fast set in priority order
    relevance 0../src/peer_connection.cpp:6199The stats checks can not be honored when authenticated encryption is in use because we may have encrypted data which we cannot authenticate yet
    relevance 0../src/piece_picker.cpp:2056this could probably be optimized by incrementally calling partial_sort to sort one more element in the list. Because chances are that we'll just need a single piece, and once we've picked from it we're done. Sorting the rest of the list in that case is a waste of time.
    relevance 0../src/piece_picker.cpp:2578when expanding pieces for cache stripe reasons, the !downloading condition doesn't make much sense
    relevance 0../src/session_impl.cpp:527there's no rule here to make uTP connections not have the global or local rate limits apply to it. This used to be the default.
    relevance 0../src/session_impl.cpp:1658it would be nice to reserve() these vectors up front
    relevance 0../src/session_impl.cpp:1895instead of having a special case for this, just make the default listen interfaces be "0.0.0.0:6881,[::]:6881" and use the generic path. That would even allow for not listening at all.
    relevance 0../src/session_impl.cpp:2918should this function take a shared_ptr instead?
    relevance 0../src/session_impl.cpp:3289have a separate list for these connections, instead of having to loop through all of them
    relevance 0../src/session_impl.cpp:3322this should apply to all bandwidth channels
    relevance 0../src/session_impl.cpp:4113use a lower limit than m_settings.connections_limit to allocate the to 10% or so of connection slots for incoming connections
    relevance 0../src/session_impl.cpp:4265post a message to have this happen immediately instead of waiting for the next tick
    relevance 0../src/session_impl.cpp:4653it might be a nice feature here to limit the number of torrents to send in a single update. By just posting the first n torrents, they would nicely be round-robined because the torrent lists are always pushed back. Perhaps the status_update_alert could even have a fixed array of n entries rather than a vector, to further improve memory locality.
    relevance 0../src/session_impl.cpp:5010this logic could probably be less spaghetti looking by being moved to a function with early exits
    relevance 0../src/session_impl.cpp:5591perhaps this function should not exist when logging is disabled
    relevance 0../src/storage.cpp:912make this more generic to not just work if files have been renamed, but also if they have been merged into a single file for instance maybe use the same format as .torrent files and reuse some code from torrent_info
    relevance 0../src/storage.cpp:1208ideally, if we end up copying files because of a move across volumes, the source should not be deleted until they've all been copied. That would let us rollback with higher confidence.
    relevance 0../src/string_util.cpp:60warning C4146: unary minus operator applied to unsigned type, result still unsigned
    relevance 0../src/torrent.cpp:102factor out cache_status to its own header
    relevance 0../src/torrent.cpp:470if the existing torrent doesn't have metadata, insert the metadata we just downloaded into it.
    relevance 0../src/torrent.cpp:582if the existing torrent doesn't have metadata, insert the metadata we just downloaded into it.
    relevance 0../src/torrent.cpp:1575is verify_peer_cert called once per certificate in the chain, and this function just tells us which depth we're at right now? If so, the comment makes sense. any certificate that isn't the leaf (i.e. the one presented by the peer) should be accepted automatically, given preverified is true. The leaf certificate need to be verified to make sure its DN matches the info-hash
    relevance 0../src/torrent.cpp:1997instead of creating the picker up front here, maybe this whole section should move to need_picker()
    relevance 0../src/torrent.cpp:2071this could be optimized by looking up which files are complete and just look at those
    relevance 0../src/torrent.cpp:2087this could be optimized by looking up which files are complete and just look at those
    relevance 0../src/torrent.cpp:2253there may be peer extensions relying on the torrent extension still being alive. Only do this if there are no peers. And when the last peer is disconnected, if the torrent is unloaded, clear the extensions m_extensions.clear();
    relevance 0../src/torrent.cpp:2958this pattern is repeated in a few places. Factor this into a function and generalize the concept of a torrent having a dedicated listen port
    relevance 0../src/torrent.cpp:3818add one peer per IP the hostname resolves to
    relevance 0../src/torrent.cpp:4760update suggest_piece?
    relevance 0../src/torrent.cpp:4904really, we should just keep the picker around in this case to maintain the availability counters
    relevance 0../src/torrent.cpp:6978make this more generic to not just work if files have been renamed, but also if they have been merged into a single file for instance maybe use the same format as .torrent files and reuse some code from torrent_info The mapped_files needs to be read both in the network thread and in the disk thread, since they both have their own mapped files structures which are kept in sync
    relevance 0../src/torrent.cpp:7111if this is a merkle torrent and we can't restore the tree, we need to wipe all the bits in the have array, but not necessarily we might want to do a full check to see if we have all the pieces. This is low priority since almost no one uses merkle torrents
    relevance 0../src/torrent.cpp:7355make this more generic to not just work if files have been renamed, but also if they have been merged into a single file for instance. using file_base
    relevance 0../src/torrent.cpp:9611add a flag to ignore stats, and only care about resume data for content. For unchanged files, don't trigger a load of the metadata just to save an empty resume data file
    relevance 0../src/torrent.cpp:11256instead of resorting the whole list, insert the peers directly into the right place
    relevance 0../src/torrent_peer.cpp:188how do we deal with our external address changing?
    relevance 0../src/udp_socket.cpp:324it would be nice to detect this on posix systems also
    relevance 0../src/udp_socket.cpp:872use the system resolver_interface here
    relevance 0../src/ut_metadata.cpp:320we really need to increment the refcounter on the torrent while this buffer is still in the peer's send buffer
    relevance 0../src/utp_stream.cpp:1769this loop is not very efficient. It could be fixed by having a separate list of sequence numbers that need resending
    relevance 0../src/web_connection_base.cpp:81introduce a web-seed default class which has a low download priority
    relevance 0../src/kademlia/dht_storage.cpp:426c++11 use a lambda here instead
    relevance 0../src/kademlia/node.cpp:723in the future, this function should update all the dht related counter. For now, it just update the storage related ones.
    relevance 0../src/kademlia/put_data.cpp:97what if o is not an isntance of put_data_observer? This need to be redesigned for better type saftey.
    relevance 0../include/libtorrent/alert_types.hpp:171Once the backwards compatibility of clone() is removed, and once C++11 is required, this can be simplified to just say = delete
    relevance 0../include/libtorrent/announce_entry.hpp:97include the number of peers received from this tracker, at last announce
    relevance 0../include/libtorrent/block_cache.hpp:223make this 32 bits and to count seconds since the block cache was created
    relevance 0../include/libtorrent/config.hpp:359Make this count Unicode characters instead of bytes on windows
    relevance 0../include/libtorrent/file.hpp:175move this into a separate header file, TU pair
    relevance 0../include/libtorrent/heterogeneous_queue.hpp:184if this throws, should we do anything?
    relevance 0../include/libtorrent/identify_client.hpp:50hide these declarations when deprecaated functions are disabled, and expose them internally in a header under aux_.
    relevance 0../include/libtorrent/peer_connection.hpp:209make this a raw pointer (to save size in the first cache line) and make the constructor take a raw pointer. torrent objects should always outlive their peers
    relevance 0../include/libtorrent/peer_connection.hpp:1046factor this out into its own class with a virtual interface torrent and session should implement this interface
    relevance 0../include/libtorrent/peer_connection_interface.hpp:47make this interface smaller!
    relevance 0../include/libtorrent/performance_counters.hpp:139should keepalives be in here too? how about dont-have, share-mode, upload-only
    relevance 0../include/libtorrent/performance_counters.hpp:450some space could be saved here by making gauges 32 bits
    relevance 0../include/libtorrent/performance_counters.hpp:451restore these to regular integers. Instead have one copy of the counters per thread and collect them at convenient synchronization points
    relevance 0../include/libtorrent/piece_picker.hpp:756should this be allocated lazily?
    relevance 0../include/libtorrent/proxy_base.hpp:174it would be nice to remember the bind port and bind once we know where the proxy is m_sock.bind(endpoint, ec);
    relevance 0../include/libtorrent/receive_buffer.hpp:272Detect when the start of the next crpyto packet is aligned with the start of piece data and the crpyto packet is at least as large as the piece data. With a little extra work we could receive directly into a disk buffer in that case.
    relevance 0../include/libtorrent/session_handle.hpp:682add get_peer_class_type_filter() as well
    relevance 0../include/libtorrent/settings_pack.hpp:1117deprecate this ``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.
    relevance 0../include/libtorrent/torrent.hpp:195make this a raw pointer. perhaps keep the shared_ptr around further down the object to maintain an owner
    relevance 0../include/libtorrent/torrent.hpp:1256this wastes 5 bits per file
    relevance 0../include/libtorrent/torrent.hpp:1314These two bitfields should probably be coalesced into one
    relevance 0../include/libtorrent/torrent_info.hpp:117there may be some opportunities to optimize the size if torrent_info. specifically to turn some std::string and std::vector into pointers
    relevance 0../include/libtorrent/tracker_manager.hpp:395this should be unique_ptr in the future
    relevance 0../include/libtorrent/upnp.hpp:131support using the windows API for UPnP operations as well
    relevance 0../include/libtorrent/utp_stream.hpp:424implement blocking write. Low priority since it's not used (yet)
    relevance 0../include/libtorrent/kademlia/item.hpp:61since this is a public function, it should probably be moved out of this header and into one with other public functions.
    relevance 0../include/libtorrent/aux_/session_impl.hpp:868should this be renamed m_outgoing_interfaces?
    relevance 0../include/libtorrent/aux_/session_impl.hpp:920replace this by a proper asio timer
    relevance 0../include/libtorrent/aux_/session_impl.hpp:925replace this by a proper asio timer
    relevance 0../include/libtorrent/aux_/session_impl.hpp:932replace this by a proper asio timer
    relevance 0../include/libtorrent/aux_/session_interface.hpp:241it would be nice to not have this be part of session_interface
    relevance 0../include/libtorrent/aux_/session_settings.hpp:78make this a bitfield
    \ No newline at end of file diff --git a/docs/troubleshooting.dot b/docs/troubleshooting.dot new file mode 100644 index 0000000..c26931e --- /dev/null +++ b/docs/troubleshooting.dot @@ -0,0 +1,108 @@ +digraph no_download { + + node [shape=box]; + + node_peers [label="Do you have any peers?\n(torrent_status::num_peers)"]; + node_unchoked [label="Have any of the peers unchoked you?\n(peer_info::flags & peer_info::remote_unchoked)"]; + node_error [label="Does the torrent have an error state?\n(torrent_status::error)"]; + node_paused [label="Is the torrent paused?\n(torrent_status::paused)"]; + node_end_error [label="The error string in the torrent describes what\nwent wrong in the torrent. This is typically\nindicative of a fatal error that, once resolved,\nrequires you to call torrent_handle::clear_error()\nto clear before resuming"]; + node_tracker [label="Do you have any trackers in the torrent?\n(torrent_handle::trackers())"]; + node_auto [label="Is the torrent auto managed?\n(torrent_status::auto_managed)"]; + node_outstanding_reqs [label="Do you have any outstanding requests to any peer?\n(peer_info::download_queue_length)"]; + node_interested [label="Are you interested in any peers?\n(peer_info::flags & peer_info::interesting)"]; + node_tracker_peers [label="Did any tracker return any peers?\n(tracker_reply_alert::num_peers)"]; + + node_dht_enabled [label="Is DHT enabled?\n(session::is_dht_running())"]; + + node_dht_nodes [label="Do you see more than 5 DHT nodes?\n(session_status::dht_nodes)"] + + node_peers_for_sure [label="Do you know for sure the torrent has peers?"]; + + node_peers_connected [label="Were any of the peers connected to?\n(peer_info::flags & peer_info::connecting)"]; + + node_end_wireshark_tracker [label="Wireshark the tracker announce\nfrom all your peers and make sure\nthey all send identical info-hashes."]; + + node_connect_speed [label="connect_speed, connection_limit, ip-filter"]; + + node_upload_mode [label="Is the torrent in upload mode?\n(torrent_status::upload_mode)"]; + node_bwstate [label="What is the peer read_state set to?\n(peer_info::read_state)"]; + + node_dl_limit [label="There is a download rate limit in affect on your peers.\nDo you have a download rate limit set?\n(settings_pack::download_rate_limit)"]; + + node_dl_disk [label="Peers are blocked waiting on the disk.\nThis typically means your disk is overloaded"]; + + node_dl_socket [label="The peer is listening on the socket,\nbut no data is coming down"]; + +// end states + + node_end_queued [label="This means the torrent is 'queued'. i.e. it will\nbe started once the torrents in front of it\ncompletes downloading. To know the queue\norder, see torrent_status::queue_position. To\nconfigure the number of simultaneous downloads,\nsee settings_pack::active_limit and\nsettings_pack::active_downloads."]; + + node_end_stopped [label="This means the torrent is\n'stopped'. To start it call\ntorrent_handle::resume()."]; + + node_end_no_peer_source [label="You don't have any peers and you don't\nhave a way to acquire peers. You either\nneed a torrent with a tracker or you need\nDHT to be enabled. The DHT will not help\nfor private torrents"]; + + node_end_dht_broken [label="The DHT is probably not working correctly.\nYou might want to add a DHT bootstrap node\nthrough session::add_dht_router(), or have a\ntorrent with DHT nodes in it, or peer\nconnections of peers that are part of the DHT\nnetwork."]; + + node_end_no_peers [label="The torrent you are trying to\ndownload may not have any peers,\n and all peers you may see are stale\nand not responding anymore."]; + + node_end_supply_demand [label="There might be a much higher demand\nthan supply in this torrent. Waiting\naround will probably get you unchoked\neventually."]; + + node_end_flash_crowd [label="The torrent might have a very large number\nof peers and only very few seeds. This sometimes\nhappens with torrents that gain popularity\nvery fast, much faster than the initial seed\nand early peers can keep up distributing\nto. Typically when this happens all your peers,\nand you, will get pieces in lock-step."]; + + node_end_no_download [label="This means the torrent is configured to not\ndownload anything. If you want to download,\ntake the torrent out of upload only mode. If\nthe disk the torrent is downloading to is full,\nor if writing to the disk failed in some way, the\ntorrent may have switched into upload mode\nautomatically"]; + + node_peers -> node_error [label="no"]; + node_peers -> node_unchoked [label="yes"]; + + node_error -> node_end_error [label="yes"]; + node_error -> node_paused [label="no"]; + + node_paused -> node_auto [label="yes"]; + node_paused -> node_tracker [label="no"]; + + node_auto -> node_end_queued [label="yes"]; + node_auto -> node_end_stopped [label="no"]; + + node_tracker -> node_tracker_peers [label="yes"]; + node_tracker -> node_dht_enabled [label="no"]; + + node_tracker_peers -> node_peers_for_sure [label="no"]; + node_tracker_peers -> node_peers_connected [label="yes"]; + + node_peers_for_sure -> node_end_wireshark_tracker [label="yes"]; + node_peers_for_sure -> node_dht_enabled [label="no"]; + + node_peers_connected -> node_connect_speed [label="no"]; + node_peers_connected -> node_end_no_peers [label="yes"]; + + node_dht_enabled -> node_end_no_peer_source [label="no"]; + node_dht_enabled -> node_dht_nodes [label="yes"]; + + node_dht_nodes -> node_end_dht_broken [label="no"]; + node_dht_nodes -> node_end_no_peers [label="yes"]; + + node_unchoked -> node_outstanding_reqs [label="yes"]; + node_unchoked -> node_interested [label="no"]; + + node_outstanding_reqs -> node_bwstate [label="yes"]; + node_outstanding_reqs -> node_upload_mode [label="no"]; + + node_upload_mode -> node_end_flash_crowd [label="no"]; + node_upload_mode -> node_end_no_download [label="yes"]; + + node_interested -> node_upload_mode [label="no"]; + node_interested -> node_end_supply_demand [label="yes"]; + + node_bwstate -> "?" [label="peer_info::bw_idle"]; + node_bwstate -> node_dl_limit [label="peer_info::bw_limit"]; + node_bwstate -> node_dl_socket [label="peer_info::bw_network"]; + node_bwstate -> node_dl_disk [label="peer_info::bw_disk"]; + + node_dl_limit -> "Your download rate limit may be set too low.\nKeep in mind that it is specified in bytes\nper second." [label="yes"]; + node_dl_limit -> "mixed mode?" [label="no"]; + + node_dl_disk -> "e" [label="yes"]; + node_dl_disk -> "f" [label="no"]; +} + diff --git a/docs/troubleshooting.html b/docs/troubleshooting.html new file mode 100644 index 0000000..4798381 --- /dev/null +++ b/docs/troubleshooting.html @@ -0,0 +1,90 @@ + + + + + + +libtorrent manual + + + + + + + + +
    +
    + + + + +
    +

    libtorrent manual

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

    The following troubleshooting chart may help in finding out why torrents fail +to download. It is not complete, please submit suggestions via mail to +arvid@libtorrent.org or to the mailing list. Ideally in the form of patches +against docs/troubleshooting.dot.

    +

    thumb

    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/troubleshooting.png b/docs/troubleshooting.png new file mode 100644 index 0000000000000000000000000000000000000000..4e4f44ce4697e1582c220c271d2136d1a6837609 GIT binary patch literal 373382 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

    <*b~99^+xXv_pV>L&~YdK{a5`%j{LJCGbP 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�DxsQyaYl( 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 $Ngj=T|DZ=cgVx?ezR|RXh7p8!6zCcC=({iHzRB zHZ7xvK%0Lu&B|)Rgk7_41|etli11pdHZn{4{4c+NOK4o+FjN8WqMT&L$ zz#D9zH7S(om98i$f;)8i=04^ZKvV)NJ@m7Mg@uA1IulI=;o&Nv@Q=m!MtM6MojLpF z#f#O*bJl==FamIc_P?yK(#+7-3>~>(w_{3Im#?QjGjwI@hV97jyh$yqFZ54*p)tKX z4{to67B|4X|E}crxTL<|=I=6F?_st%RNJw+;Y+uqwh4as#3$<6ait{igv(=M&v z>9Ol5d3YFZ>27!X;^r>UDf9GRnU+uP{(VaK`MTcW{ zq+N>;mNy~D`Tn)|qfa`tvK=yZY%n$LZ32$x;p>GK^%!SaZyK>Cji*dMP_p&7N2!^4Z%lP1t z(STLK(Xq{1N0A?#`&{Rmw8w*SiEyLM8n@y4gSW2*>K*lMjfLF#xG(a~@Y6OwZT>GJ z|KZdxXLDP`ulV`L^kXyY%Ft}ivt@{`3;RwQLW(K)|I!%>eJTtiGTct#-sRtY6lZ~s zjqZ2l@5(HuTeoh#Eh%YydT&BPc46O}5O-+29e(p*=caWP%LJ1+3q14JPZ}TM@M2{} zme9}7Z#+$)aJ(vdR&-%p5^r51U6}ReeoscRX!q{@KsopEH-{;9FLbRT;4S(7_oP!L zd7~7|sE!C_6j9`cB@etjJrP*Z&S^ehqukKY@I1J&v`7EnN=wVizj=(fG|ngc>Q#Ty z4Rr}jsfVU?R+j6)+)?Myza@81W7~C(dm(#nARzZKNzj4*vrzrBOb2&0$ z_g5+=t|$SoTWMo7fV0B#i50d)cc8XU=$N?t6U% zYnXg`bLxdd9U70bgO16;=fciAI(g7#B5FeH^HlU4r%LY%G@Zcj@4M`Mq4RX3M^ds| zyU

    <4OWYoEUk=6F5fSRVjyN!}d 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�&r7`LrBt)<#IilL9_^AKcO|Qu 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ÚwUNIF;&A=cZ34=QX6v_E2w~qV-%=k185CXkJ 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%0D*|J<)U z`ClUYc~zPRtq1H!IDdH3;jmvaCV{bVaBvJRvf3_CV;585&rYVm4;&8{H!lYl5AfCC z;uhu;66OQ`3JP;^vFfPJ{2u{Zd#I(E=l>Vr75J|}f$W(TAONQzt143_Z4~%FFj*(+ literal 0 HcmV?d00001 diff --git a/docs/tuning.html b/docs/tuning.html new file mode 100644 index 0000000..0c936cf --- /dev/null +++ b/docs/tuning.html @@ -0,0 +1,516 @@ + + + + + + +libtorrent manual + + + + + + + + +

    +
    + + + + +
    +

    libtorrent manual

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

    tuning libtorrent

    +

    libtorrent expose most parameters used in the bittorrent engine for +customization through the settings_pack. This makes it possible to +test and tweak the parameters for certain algorithms to make a client +that fits a wide range of needs. From low memory embedded devices to +servers seeding thousands of torrents. The default settings in libtorrent +are tuned for an end-user bittorrent client running on a normal desktop +computer.

    +

    This document describes techniques to benchmark libtorrent performance +and how parameters are likely to affect it.

    +
    +
    +

    reducing memory footprint

    +

    These are things you can do to reduce the memory footprint of libtorrent. You get +some of this by basing your default settings_pack on the min_memory_usage() +setting preset function.

    +

    Keep in mind that lowering memory usage will affect performance, always profile +and benchmark your settings to determine if it's worth the trade-off.

    +

    The typical buffer usage of libtorrent, for a single download, with the cache +size set to 256 blocks (256 * 16 kiB = 4 MiB) is:

    +
    +read cache:      128.6 (2058 kiB)
    +write cache:     103.5 (1656 kiB)
    +receive buffers: 7.3   (117 kiB)
    +send buffers:    4.8   (77 kiB)
    +hash temp:       0.001 (19 Bytes)
    +
    +

    The receive buffers is proportional to the number of connections we make, and is +limited by the total number of connections in the session (default is 200).

    +

    The send buffers is proportional to the number of upload slots that are allowed +in the session. The default is auto configured based on the observed upload rate.

    +

    The read and write cache can be controlled (see section below).

    +

    The "hash temp" entry size depends on whether or not hashing is optimized for +speed or memory usage. In this test run it was optimized for memory usage.

    +
    +

    disable disk cache

    +

    The bulk of the memory libtorrent will use is used for the disk cache. To save +the absolute most amount of memory, you can disable the cache by setting +settings_pack::cache_size to 0. You might want to consider using the cache +but just disable caching read operations. You do this by settings +settings_pack::use_read_cache to false. This is the main factor in how much +memory will be used by the client. Keep in mind that you will degrade performance +by disabling the cache. You should benchmark the disk access in order to make an +informed trade-off.

    +
    +
    +

    remove torrents

    +

    Torrents that have been added to libtorrent will inevitably use up memory, even +when it's paused. A paused torrent will not use any peer connection objects or +any send or receive buffers though. Any added torrent holds the entire .torrent +file in memory, it also remembers the entire list of peers that it's heard about +(which can be fairly long unless it's capped). It also retains information about +which blocks and pieces we have on disk, which can be significant for torrents +with many pieces.

    +

    If you need to minimize the memory footprint, consider removing torrents from +the session rather than pausing them. This will likely only make a difference +when you have a very large number of torrents in a session.

    +

    The downside of removing them is that they will no longer be auto-managed. Paused +auto managed torrents are scraped periodically, to determine which torrents are +in the greatest need of seeding, and libtorrent will prioritize to seed those.

    +
    +
    +

    socket buffer sizes

    +

    You can make libtorrent explicitly set the kernel buffer sizes of all its peer +sockets. If you set this to a low number, you may see reduced throughput, especially +for high latency connections. It is however an opportunity to save memory per +connection, and might be worth considering if you have a very large number of +peer connections. This memory will not be visible in your process, this sets +the amount of kernel memory is used for your sockets.

    +

    Change this by setting settings_pack::recv_socket_buffer_size and +settings_pack::send_socket_buffer_size.

    +
    +
    +

    peer list size

    +

    The default maximum for the peer list is 4000 peers. For IPv4 peers, each peer +entry uses 32 bytes, which ends up using 128 kB per torrent. If seeding 4 popular +torrents, the peer lists alone uses about half a megabyte.

    +

    The default limit is the same for paused torrents as well, so if you have a +large number of paused torrents (that are popular) it will be even more +significant.

    +

    If you're short of memory, you should consider lowering the limit. 500 is probably +enough. You can do this by setting settings_pack::max_peerlist_size to +the max number of peers you want in a torrent's peer list. This limit applies per +torrent. For 5 torrents, the total number of peers in peerlists 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.

    +
    +
    +

    optimize hashing for memory usage

    +

    When libtorrent is doing hash checks of a file, or when it re-reads a piece that +was just completed to verify its hash, there are two options. The default one +is optimized for speed, which allocates buffers for the entire piece, reads in +the whole piece in one read call, then hashes it.

    +

    The second option is to optimize for memory usage instead, where a single buffer +is allocated, and the piece is read one block at a time, hashing it as each +block is read from the file. For low memory environments, this latter approach +is recommended. Change this by settings settings_pack::optimize_hashing_for_speed +to false. This will significantly reduce peak memory usage, especially for +torrents with very large pieces.

    +
    +
    +

    reduce executable size

    +

    Compilers generally add a significant number of bytes to executables that make use +of C++ exceptions. By disabling exceptions (-fno-exceptions on GCC), you can +reduce the executable size with up to 45%. In order to build without exception +support, you need to patch parts of boost.

    +

    Also make sure to optimize for size when compiling.

    +

    Another way of reducing the executable size is to disable code that isn't used. +There are a number of TORRENT_* macros that control which features are included +in libtorrent. If these macros are used to strip down libtorrent, make sure the same +macros are defined when building libtorrent as when linking against it. If these +are different the structures will look different from the libtorrent side and from +the client side and memory corruption will follow.

    +

    One, probably, safe macro to define is TORRENT_NO_DEPRECATE which removes all +deprecated functions and struct members. As long as no deprecated functions are +relied upon, this should be a simple way to eliminate a little bit of code.

    +

    For all available options, see the building libtorrent secion.

    +
    +
    +
    +

    play nice with the disk

    +

    When checking a torrent, libtorrent will try to read as fast as possible from the disk. +The only thing that might hold it back is a CPU that is slow at calculating SHA-1 hashes, +but typically the file checking is limited by disk read speed. Most operating systems +today do not prioritize disk access based on the importance of the operation, this means +that checking a torrent might delay other disk accesses, such as virtual memory swapping +or just loading file by other (interactive) applications.

    +

    In order to play nicer with the disk, and leave some spare time for it to service other +processes that might be of higher importance to the end-user, you can introduce a sleep +between the disc accesses. This is a direct tradeoff between how fast you can check a +torrent and how soft you will hit the disk.

    +

    You control this by setting the settings_pack::file_checks_delay_per_block to greater +than zero. This number is the number of milliseconds to sleep between each read of 16 kiB.

    +

    The sleeps are not necessarily in between each 16 kiB block (it might be read in larger chunks), +but the number will be multiplied by the number of blocks that were read, to maintain the +same semantics.

    +
    +
    +

    high performance seeding

    +

    In the case of a high volume seed, there are two main concerns. Performance and scalability. +This translates into high send rates, and low memory and CPU usage per peer connection.

    +
    +

    file pool

    +

    libtorrent keeps an LRU file cache. Each file that is opened, is stuck in the cache. The main +purpose of this is because of anti-virus software that hooks on file-open and file close to +scan the file. Anti-virus software that does that will significantly increase the cost of +opening and closing files. However, for a high performance seed, the file open/close might +be so frequent that it becomes a significant cost. It might therefore be a good idea to allow +a large file descriptor cache. Adjust this though settings_pack::file_pool_size.

    +

    Don't forget to set a high rlimit for file descriptors in your process as well. This limit +must be high enough to keep all connections and files open.

    +
    +
    +

    disk cache

    +

    You typically want to set the cache size to as high as possible. The +settings_pack::cache_size is specified in 16 kiB blocks. Since you're seeding, +the cache would be useless unless you also set settings_pack::use_read_cache +to true.

    +

    In order to increase the possibility of read cache hits, set the +settings_pack::cache_expiry to a large number. This won't degrade anything as +long as the client is only seeding, and not downloading any torrents.

    +

    There's a guided cache mode. This means the size of the read cache line that's +stored in the cache is determined based on the upload rate to the peer that +triggered the read operation. The idea being that slow peers don't use up a +disproportional amount of space in the cache. This is enabled through +settings_pack::guided_read_cache.

    +

    In cases where the assumption is that the cache is only used as a read-ahead, and that no +other peer will ever request the same block while it's still in the cache, the read +cache can be set to be volatile. This means that every block that is requested out of +the read cache is removed immediately. This saves a significant amount of cache space +which can be used as read-ahead for other peers. To enable volatile read cache, set +settings_pack::volatile_read_cache to true.

    +
    +
    +

    SSD as level 2 cache

    +

    It is possible to introduce a second level of cache, below the RAM disk cache. This is done +by setting settings_pack::mmap_cache to a file path pointing to the SSD drive, and +increasing the settings_pack::cache_size to the number of 16 kiB blocks would fit +on the drive (or less).

    +

    This will allocate disk buffers (for reading and writing) from a memory region that has +been mapped to the specified file. If the drive this file lives on is not significantly +faster than the destination drive, performance will be degraded. The point is to take +advantage primarily of the fast read speed from SSD drives and use it to extend the read +cache, improving seed performance.

    +

    Which parts of the cache that actually live in RAM is determined by the operating system.

    +

    Note that when using this feature, any block which ends up being pulled from the mmapped +file will be considered a cache hit.

    +
    +
    +

    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 adviced to experiment with the +session_setting::mixed_mode_algorithm, setting it to settings_pack::prefer_tcp. +This setting entirely disables the balancing and unthrottles all connections. On a typical +home connection, this would mean that none of the benefits of uTP would be preserved +(the modem's send buffer would be full at all times) and uTP connections would for the most +part be squashed by the TCP traffic.

    +
    +
    +

    send buffer low watermark

    +

    libtorrent uses a low watermark for send buffers to determine when a new piece should +be requested from the disk I/O subsystem, to be appended to the send buffer. The low +watermark is determined based on the send rate of the socket. It needs to be large +enough to not draining the socket's send buffer before the disk operation completes.

    +

    The watermark is bound to a max value, to avoid buffer sizes growing out of control. +The default max send buffer size might not be enough to sustain very high upload rates, +and you might have to increase it. It's specified in bytes in +settings_pack::send_buffer_watermark.

    +
    +
    +

    peers

    +

    First of all, in order to allow many connections, set the global connection limit +high, session::set_max_connections(). Also set the upload rate limit to +infinite, session::set_upload_rate_limit(), passing 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 +settings_pack::auto_upload_slots and set the number of upload slots to a large +number via session::set_max_uploads(), or use -1 (which means infinite).

    +
    +
    +

    torrent limits

    +

    To seed thousands of torrents, you need to increase the settings_pack::active_limit +and settings_pack::active_seeds.

    +
    +
    +

    SHA-1 hashing

    +

    When downloading at very high rates, it is possible to have the CPU be the +bottleneck for passing every downloaded byte through SHA-1. In order to enable +calculating SHA-1 hashes in parallel, on multi-core systems, set +settings_pack::aio_threads to the number of threads libtorrent should +perform I/O and do SHA-1 hashing in. Only if that thread is close to saturating +one core does it make sense to increase the number of threads.

    +
    +
    +
    +

    scalability

    +

    In order to make more efficient use of the libtorrent interface when running a large +number of torrents simultaneously, one can use the session::get_torrent_status() call +together with session::refresh_torrent_status(). 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). 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). 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. If you have a lot of torrents, you might want to update the status +of only certain torrents. For instance, you might only be interested in torrents that +are being downloaded.

    +

    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.

    +

    A more efficient way however, would be to subscribe to status alert notifications, and +update your list based on these alerts. There are alerts for when torrents are added, removed, +paused, resumed, completed etc. Doing this ensures that you only query status for the +minimal set of torrents you are actually interested in.

    +
    +
    +

    benchmarking

    +

    There is a bunch of built-in instrumentation of libtorrent that can be used to get an insight +into what it's doing and how well it performs. This instrumentation is enabled by defining +preprocessor symbols when building.

    +

    There are also a number of scripts that parses the log files and generates graphs (requires +gnuplot and python).

    +
    +

    disk metrics

    +

    To enable disk I/O instrumentation, define TORRENT_DISK_STATS when building. When built +with this configuration libtorrent will create three log files, measuring various aspects of +the disk I/O. The following table is an overview of these files and what they measure.

    + ++++ + + + + + + + + + + +
    filenamedescription
    file_access.logThis is a low level log of read and write operations, with +timestamps and file offsets. The file offsets are byte +offsets in the torrent (not in any particular file, in the +case of a multi-file torrent). This can be used as an +estimate of the physical drive location. The purpose of +this log is to identify the amount of seeking the drive has +to do.
    +
    +

    file_access.log

    +

    The disk access log is a binary file that can be parsed and converted to human +readable by the script tools/parse_access_log.py. This tool produces a +graphical representation of the disk access and requires gnuplot.

    +
    +
    +
    +
    +

    understanding the disk threads

    +

    This section is somewhat outdated, there are potentially more than one disk +thread

    +

    All disk operations are funneled through a separate thread, referred to as the +disk thread. The main interface to the disk thread is a queue where disk jobs +are posted, and the results of these jobs are then posted back on the main +thread's io_service.

    +

    A disk job is essentially one of:

    +
      +
    1. +
      write this block to disk, i.e. a write job. For the most part this is just a
      +

      matter of sticking the block in the disk cache, but if we've run out of +cache space or completed a whole piece, we'll also flush blocks to disk. +This is typically very fast, since the OS just sticks these buffers in its +write cache which will be flushed at a later time, presumably when the drive +head will pass the place on the platter where the blocks go.

      +
      +
      +
    2. +
    3. +
      read this block from disk. The first thing that happens is we look in the
      +

      cache to see if the block is already in RAM. If it is, we'll return +immediately with this block. If it's a cache miss, we'll have to hit the +disk. Here we decide to defer this job. We find the physical offset on the +drive for this block and insert the job in an ordered queue, sorted by the +physical location. At a later time, once we don't have any more non-read +jobs left in the queue, we pick one read job out of the ordered queue and +service it. The order we pick jobs out of the queue is according to an +elevator cursor moving up and down along the ordered queue of read jobs. If +we have enough space in the cache we'll read read_cache_line_size number of +blocks and stick those in the cache. This defaults to 32 blocks. If the +system supports asynchronous I/O (Windows, Linux, Mac OS X, BSD, Solars for +instance), jobs will be issued immediately to the OS. This especially +increases read throughput, since the OS has a much greater flexibility to +reorder the read jobs.

      +
      +
      +
    4. +
    +

    Other disk job consist of operations that needs to be synchronized with the +disk I/O, like renaming files, closing files, flushing the cache, updating the +settings etc. These are relatively rare though.

    +
    +
    +

    contributions

    +

    If you have added instrumentation for some part of libtorrent that is not +covered here, or if you have improved any of the parser scrips, please consider +contributing it back to the project.

    +

    If you have run tests and found that some algorithm or default value in +libtorrent is suboptimal, please contribute that knowledge back as well, to +allow us to improve the library.

    +

    If you have additional suggestions on how to tune libtorrent for any specific +use case, please let us know and we'll update this document.

    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/tuning.rst b/docs/tuning.rst new file mode 100644 index 0000000..b1dcf19 --- /dev/null +++ b/docs/tuning.rst @@ -0,0 +1,463 @@ +================= +libtorrent manual +================= + +:Author: Arvid Norberg, arvid@libtorrent.org +:Version: 1.1.1 + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +tuning libtorrent +================= + +libtorrent expose most parameters used in the bittorrent engine for +customization through the ``settings_pack``. This makes it possible to +test and tweak the parameters for certain algorithms to make a client +that fits a wide range of needs. From low memory embedded devices to +servers seeding thousands of torrents. The default settings in libtorrent +are tuned for an end-user bittorrent client running on a normal desktop +computer. + +This document describes techniques to benchmark libtorrent performance +and how parameters are likely to affect it. + +reducing memory footprint +========================= + +These are things you can do to reduce the memory footprint of libtorrent. You get +some of this by basing your default ``settings_pack`` on the ``min_memory_usage()`` +setting preset function. + +Keep in mind that lowering memory usage will affect performance, always profile +and benchmark your settings to determine if it's worth the trade-off. + +The typical buffer usage of libtorrent, for a single download, with the cache +size set to 256 blocks (256 * 16 kiB = 4 MiB) is:: + + read cache: 128.6 (2058 kiB) + write cache: 103.5 (1656 kiB) + receive buffers: 7.3 (117 kiB) + send buffers: 4.8 (77 kiB) + hash temp: 0.001 (19 Bytes) + +The receive buffers is proportional to the number of connections we make, and is +limited by the total number of connections in the session (default is 200). + +The send buffers is proportional to the number of upload slots that are allowed +in the session. The default is auto configured based on the observed upload rate. + +The read and write cache can be controlled (see section below). + +The "hash temp" entry size depends on whether or not hashing is optimized for +speed or memory usage. In this test run it was optimized for memory usage. + +disable disk cache +------------------ + +The bulk of the memory libtorrent will use is used for the disk cache. To save +the absolute most amount of memory, you can disable the cache by setting +``settings_pack::cache_size`` to 0. You might want to consider using the cache +but just disable caching read operations. You do this by settings +``settings_pack::use_read_cache`` to false. This is the main factor in how much +memory will be used by the client. Keep in mind that you will degrade performance +by disabling the cache. You should benchmark the disk access in order to make an +informed trade-off. + +remove torrents +--------------- + +Torrents that have been added to libtorrent will inevitably use up memory, even +when it's paused. A paused torrent will not use any peer connection objects or +any send or receive buffers though. Any added torrent holds the entire .torrent +file in memory, it also remembers the entire list of peers that it's heard about +(which can be fairly long unless it's capped). It also retains information about +which blocks and pieces we have on disk, which can be significant for torrents +with many pieces. + +If you need to minimize the memory footprint, consider removing torrents from +the session rather than pausing them. This will likely only make a difference +when you have a very large number of torrents in a session. + +The downside of removing them is that they will no longer be auto-managed. Paused +auto managed torrents are scraped periodically, to determine which torrents are +in the greatest need of seeding, and libtorrent will prioritize to seed those. + +socket buffer sizes +------------------- + +You can make libtorrent explicitly set the kernel buffer sizes of all its peer +sockets. If you set this to a low number, you may see reduced throughput, especially +for high latency connections. It is however an opportunity to save memory per +connection, and might be worth considering if you have a very large number of +peer connections. This memory will not be visible in your process, this sets +the amount of kernel memory is used for your sockets. + +Change this by setting ``settings_pack::recv_socket_buffer_size`` and +``settings_pack::send_socket_buffer_size``. + +peer list size +-------------- + +The default maximum for the peer list is 4000 peers. For IPv4 peers, each peer +entry uses 32 bytes, which ends up using 128 kB per torrent. If seeding 4 popular +torrents, the peer lists alone uses about half a megabyte. + +The default limit is the same for paused torrents as well, so if you have a +large number of paused torrents (that are popular) it will be even more +significant. + +If you're short of memory, you should consider lowering the limit. 500 is probably +enough. You can do this by setting ``settings_pack::max_peerlist_size`` to +the max number of peers you want in a torrent's peer list. This limit applies per +torrent. For 5 torrents, the total number of peers in peerlists 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. + +optimize hashing for memory usage +--------------------------------- + +When libtorrent is doing hash checks of a file, or when it re-reads a piece that +was just completed to verify its hash, there are two options. The default one +is optimized for speed, which allocates buffers for the entire piece, reads in +the whole piece in one read call, then hashes it. + +The second option is to optimize for memory usage instead, where a single buffer +is allocated, and the piece is read one block at a time, hashing it as each +block is read from the file. For low memory environments, this latter approach +is recommended. Change this by settings ``settings_pack::optimize_hashing_for_speed`` +to false. This will significantly reduce peak memory usage, especially for +torrents with very large pieces. + +reduce executable size +---------------------- + +Compilers generally add a significant number of bytes to executables that make use +of C++ exceptions. By disabling exceptions (-fno-exceptions on GCC), you can +reduce the executable size with up to 45%. In order to build without exception +support, you need to patch parts of boost. + +Also make sure to optimize for size when compiling. + +Another way of reducing the executable size is to disable code that isn't used. +There are a number of ``TORRENT_*`` macros that control which features are included +in libtorrent. If these macros are used to strip down libtorrent, make sure the same +macros are defined when building libtorrent as when linking against it. If these +are different the structures will look different from the libtorrent side and from +the client side and memory corruption will follow. + +One, probably, safe macro to define is ``TORRENT_NO_DEPRECATE`` which removes all +deprecated functions and struct members. As long as no deprecated functions are +relied upon, this should be a simple way to eliminate a little bit of code. + +For all available options, see the `building libtorrent`_ secion. + +.. _`building libtorrent`: building.html + +play nice with the disk +======================= + +When checking a torrent, libtorrent will try to read as fast as possible from the disk. +The only thing that might hold it back is a CPU that is slow at calculating SHA-1 hashes, +but typically the file checking is limited by disk read speed. Most operating systems +today do not prioritize disk access based on the importance of the operation, this means +that checking a torrent might delay other disk accesses, such as virtual memory swapping +or just loading file by other (interactive) applications. + +In order to play nicer with the disk, and leave some spare time for it to service other +processes that might be of higher importance to the end-user, you can introduce a sleep +between the disc accesses. This is a direct tradeoff between how fast you can check a +torrent and how soft you will hit the disk. + +You control this by setting the ``settings_pack::file_checks_delay_per_block`` to greater +than zero. This number is the number of milliseconds to sleep between each read of 16 kiB. + +The sleeps are not necessarily in between each 16 kiB block (it might be read in larger chunks), +but the number will be multiplied by the number of blocks that were read, to maintain the +same semantics. + +high performance seeding +======================== + +In the case of a high volume seed, there are two main concerns. Performance and scalability. +This translates into high send rates, and low memory and CPU usage per peer connection. + +file pool +--------- + +libtorrent keeps an LRU file cache. Each file that is opened, is stuck in the cache. The main +purpose of this is because of anti-virus software that hooks on file-open and file close to +scan the file. Anti-virus software that does that will significantly increase the cost of +opening and closing files. However, for a high performance seed, the file open/close might +be so frequent that it becomes a significant cost. It might therefore be a good idea to allow +a large file descriptor cache. Adjust this though ``settings_pack::file_pool_size``. + +Don't forget to set a high rlimit for file descriptors in your process as well. This limit +must be high enough to keep all connections and files open. + +disk cache +---------- + +You typically want to set the cache size to as high as possible. The +``settings_pack::cache_size`` is specified in 16 kiB blocks. Since you're seeding, +the cache would be useless unless you also set ``settings_pack::use_read_cache`` +to true. + +In order to increase the possibility of read cache hits, set the +``settings_pack::cache_expiry`` to a large number. This won't degrade anything as +long as the client is only seeding, and not downloading any torrents. + +There's a *guided cache* mode. This means the size of the read cache line that's +stored in the cache is determined based on the upload rate to the peer that +triggered the read operation. The idea being that slow peers don't use up a +disproportional amount of space in the cache. This is enabled through +``settings_pack::guided_read_cache``. + +In cases where the assumption is that the cache is only used as a read-ahead, and that no +other peer will ever request the same block while it's still in the cache, the read +cache can be set to be *volatile*. This means that every block that is requested out of +the read cache is removed immediately. This saves a significant amount of cache space +which can be used as read-ahead for other peers. To enable volatile read cache, set +``settings_pack::volatile_read_cache`` to true. + +SSD as level 2 cache +-------------------- + +It is possible to introduce a second level of cache, below the RAM disk cache. This is done +by setting ``settings_pack::mmap_cache`` to a file path pointing to the SSD drive, and +increasing the ``settings_pack::cache_size`` to the number of 16 kiB blocks would fit +on the drive (or less). + +This will allocate disk buffers (for reading and writing) from a memory region that has +been mapped to the specified file. If the drive this file lives on is not significantly +faster than the destination drive, performance will be degraded. The point is to take +advantage primarily of the fast read speed from SSD drives and use it to extend the read +cache, improving seed performance. + +Which parts of the cache that actually live in RAM is determined by the operating system. + +Note that when using this feature, any block which ends up being pulled from the mmapped +file will be considered a cache hit. + +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 adviced to experiment with the +``session_setting::mixed_mode_algorithm``, setting it to ``settings_pack::prefer_tcp``. +This setting entirely disables the balancing and unthrottles 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, ``session::set_max_connections()``. Also set the upload rate limit to +infinite, ``session::set_upload_rate_limit()``, passing 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 +``settings_pack::auto_upload_slots`` and set the number of upload slots to a large +number via ``session::set_max_uploads()``, or use -1 (which means infinite). + +torrent limits +-------------- + +To seed thousands of torrents, you need to increase the ``settings_pack::active_limit`` +and ``settings_pack::active_seeds``. + +SHA-1 hashing +------------- + +When downloading at very high rates, it is possible to have the CPU be the +bottleneck for passing every downloaded byte through SHA-1. In order to enable +calculating SHA-1 hashes in parallel, on multi-core systems, set +``settings_pack::aio_threads`` to the number of threads libtorrent should +perform I/O and do SHA-1 hashing in. Only if that thread is close to saturating +one core does it make sense to increase the number of threads. + +scalability +=========== + +In order to make more efficient use of the libtorrent interface when running a large +number of torrents simultaneously, one can use the ``session::get_torrent_status()`` call +together with ``session::refresh_torrent_status()``. 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). 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). ``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. If you have a lot of torrents, you might want to update the status +of only certain torrents. For instance, you might only be interested in torrents that +are being downloaded. + +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. + +A more efficient way however, would be to subscribe to status alert notifications, and +update your list based on these alerts. There are alerts for when torrents are added, removed, +paused, resumed, completed etc. Doing this ensures that you only query status for the +minimal set of torrents you are actually interested in. + +benchmarking +============ + +There is a bunch of built-in instrumentation of libtorrent that can be used to get an insight +into what it's doing and how well it performs. This instrumentation is enabled by defining +preprocessor symbols when building. + +There are also a number of scripts that parses the log files and generates graphs (requires +gnuplot and python). + +disk metrics +------------ + +To enable disk I/O instrumentation, define ``TORRENT_DISK_STATS`` when building. When built +with this configuration libtorrent will create three log files, measuring various aspects of +the disk I/O. The following table is an overview of these files and what they measure. + ++--------------------------+--------------------------------------------------------------+ +| filename | description | ++==========================+==============================================================+ +| ``file_access.log`` | This is a low level log of read and write operations, with | +| | timestamps and file offsets. The file offsets are byte | +| | offsets in the torrent (not in any particular file, in the | +| | case of a multi-file torrent). This can be used as an | +| | estimate of the physical drive location. The purpose of | +| | this log is to identify the amount of seeking the drive has | +| | to do. | +| | | ++--------------------------+--------------------------------------------------------------+ + +file_access.log +''''''''''''''' + +The disk access log is a binary file that can be parsed and converted to human +readable by the script ``tools/parse_access_log.py``. This tool produces a +graphical representation of the disk access and requires ``gnuplot``. + +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 is suboptimal, please contribute that knowledge back as well, to +allow us to improve the library. + +If you have additional suggestions on how to tune libtorrent for any specific +use case, please let us know and we'll update this document. + diff --git a/docs/tutorial.html b/docs/tutorial.html new file mode 100644 index 0000000..c61da1b --- /dev/null +++ b/docs/tutorial.html @@ -0,0 +1,473 @@ + + + + + + +libtorrent manual + + + + + + + + +
    +
    + + + + +
    +

    libtorrent manual

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

    tutorial

    +

    The fundamental feature of starting and downloading torrents in libtorrent is +achieved by creating a session, which provides the context and a container for +torrents. This is done with via the session class, most of its interface is +documented under session_handle though.

    +

    To add a torrent to the session, you fill in an add_torrent_params object and +pass it either to add_torrent() or async_add_torrent().

    +

    add_torrent() is a blocking call which returns a torrent_handle.

    +

    For example:

    +
    +#include <libtorrent/session.hpp>
    +#include <libtorrent/add_torrent_params.hpp>
    +#include <libtorrent/torrent_handle.hpp>
    +
    +namespace lt = libtorrent;
    +int main(int argc, char const* argv[])
    +{
    +        if (argc != 2) {
    +                fprintf(stderr, "usage: %s <magnet-url>\n");
    +                return 1;
    +        }
    +        lt::session ses;
    +
    +        lt::add_torrent_params atp;
    +        atp.url = 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 programatically react to certain events, use alert_cast<> to attempt +a down cast of an alert object to a more specific type.

    +

    In order to print out events from libtorrent as well as exiting when the torrent +completes downloading, we can poll the session for alerts periodically and print +them out, as well as listening for the torrent_finished_alert, which is posted +when a torrent completes.

    +
    +#include <iostream>
    +#include <thread>
    +#include <chrono>
    +
    +#include <libtorrent/session.hpp>
    +#include <libtorrent/add_torrent_params.hpp>
    +#include <libtorrent/torrent_handle.hpp>
    +#include <libtorrent/alert_types.hpp>
    +
    +namespace lt = libtorrent;
    +int main(int argc, char const* argv[])
    +{
    +  if (argc != 2) {
    +    std::cerr << "usage: " << argv[0] << " <magnet-url>" << std::endl;
    +    return 1;
    +  }
    +  lt::session ses;
    +
    +  lt::add_torrent_params atp;
    +  atp.url = argv[1];
    +  atp.save_path = "."; // save in current dir
    +  lt::torrent_handle h = ses.add_torrent(atp);
    +
    +  for (;;) {
    +    std::vector<lt::alert*> alerts;
    +    ses.pop_alerts(&alerts);
    +
    +    for (lt::alert const* a : alerts) {
    +      std::cout << a->message() << std::endl;
    +      // if we receive the finished alert or an error, we're done
    +      if (lt::alert_cast<lt::torrent_finished_alert>(a)) {
    +        goto done;
    +      }
    +      if (lt::alert_cast<lt::torrent_error_alert>(a)) {
    +        goto done;
    +      }
    +    }
    +    std::this_thread::sleep_for(std::chrono::milliseconds(200));
    +  }
    +  done:
    +  std::cout << "done, shutting down" << std::endl;
    +}
    +
    +
    +

    alert masks

    +

    The output from this program will be quite verbose, which is probably a good +starting point to get some understanding of what's going on. Alerts are +categorized into alert categories. Each category can be enabled and disabled +independently via the alert mask.

    +

    The alert mask is a configuration option offered by libtorrent. There are many +configuration options, see settings_pack. The alert_mask setting is an integer +of the category flags ORed together.

    +

    For instance, to only see the most pertinent alerts, the session can be +constructed like this:

    +
    +lt::settings_pack pack;
    +pack.set_int(lt::settings_pack::alert_mask
    +        , lt::alert::error_notification
    +        | lt::alert::storage_notification
    +        | lt::alert::status_notification);
    +
    +lt::session ses(pack);
    +
    +

    Configuration options can be updated after the session is started by calling +apply_settings(). Some settings are best set before starting the session +though, like listen_interfaces, to avoid race conditions. If you start the +session with the default settings and then immediately change them, there will +still be a window where the default settings apply.

    +

    Changing the settings may trigger listen sockets to close and re-open and +NAT-PMP, UPnP updates to be sent. For this reason, it's typically a good idea +to batch settings updates into a single call.

    +
    +
    +

    session destruction

    +

    The session destructor is blocking by default. When shutting down, trackers +will need to be contacted to stop torrents and other outstanding operations +need to be cancelled. Shutting down can sometimes take several seconds, +primarily because of trackers that are unresponsive (and time out) and also +DNS servers that are unresponsive. DNS lookups are especially difficult to +abort when stalled.

    +

    In order to be able to start destruction an wait for it asynchronously, one +can call session::abort().

    +

    This call returns a session_proxy object, which is a handle keeping the session +state alive while destructing it. It deliberately does not provide any of the +session operations, since it's shutting down.

    +

    After having a session_proxy object, the session destructor does not block. +However, the session_proxy destructor will.

    +

    This can be used to shut down multiple sessions or other parts of the +application in parallel.

    +
    +
    +

    asynchronous operations

    +

    Essentially any call to a member function of session or torrent_handle that +returns a value is a blocking synchronous call. Meaning it will post a message +to the main libtorrent thread and wait for a response. Such calls may be +expensive, and in applications where stalls should be avoided (such as user +interface threads), blocking calls should be avoided.

    +

    In the example above, session::add_torrent() returns a torrent_handle and is +thus blocking. For higher efficiency, async_add_torrent() will post a message +to the main thread to add a torrent, and post the resulting torrent_handle back +in an alert (add_torrent_alert). This is especially useful when adding a lot +of torrents in quick succession, as there's no stall in between calls.

    +

    In the example above, we don't actually use the torrent_handle for anything, so +converting it to use async_add_torrent() is just a matter of replacing the +add_torrent() call with async_add_torrent().

    +
    +
    +

    torrent_status_updates

    +

    To get updates to the status of torrents, call post_torrent_updates() on the +session object. This will cause libtorrent to post a state_update_alert +containing torrent_status objects for all torrents whose status has changed +since the last call to post_torrent_updates().

    +

    The state_update_alert looks something like this:

    +
    +struct state_update_alert : alert
    +{
    +        virtual std::string message() const;
    +        std::vector<torrent_status> status;
    +};
    +
    +

    The status field only contains the torrent_status for torrents with +updates since the last call. It may be empty if no torrent has updated its +state. This feature is critical for scalability.

    +

    See the torrent_status object for more information on what is in there. +Perhaps the most interesting fields are total_payload_download, +total_payload_upload, num_peers and state.

    +
    +
    +

    resuming torrents

    +

    Since bittorrent downloads pieces of files in random order, it's not trivial to +resume a partial download. When resuming a download, the bittorrent engine must +restore the state of the downloading torrent, specifically which parts of the +file(s) are downloaded. There are two approaches to doing this:

    +
      +
    1. read every piece of the downloaded files from disk and compare it against its +expected hash.
    2. +
    3. save to disk the state of which pieces (and partial pieces) are downloaded, +and load it back in again when resuming.
    4. +
    +

    If no resume data is provided with a torrent that's added, libtorrent will +employ (1) by default.

    +

    To save resume data, call save_resume_data() on the torrent_handle object. +This will ask libtorrent to generate the resume data and post it back in +a save_resume_data_alert. If generating the resume data fails for any reason, +a save_resume_data_failed_alert is posted instead.

    +

    Exactly one of those alerts will be posted for every call to +save_resume_data(). This is an important property when shutting down a +session with multiple torrents, every resume alert must be handled before +resuming with shut down. Any torrent may fail to save resume data, so the client +would need to keep a count of the outstanding resume files, decremented on +either save_resume_data_alert or save_resume_data_failed_alert.

    +

    The save_resume_data_alert looks something like this:

    +
    +struct save_resume_data_alert : torrent_alert
    +{
    +        virtual std::string message() const;
    +
    +        // points to the resume data.
    +        boost::shared_ptr<entry> resume_data;
    +};
    +
    +

    resume_data points to an entry object. This represents a node or a tree of +nodes in a bencoded structure, which is the native encoding scheme in +bittorrent. It can be encoded into a byte buffer or file using bencode().

    +

    When adding a torrent with resume data, set the add_torrent_params::resume_data +to contain the bencoded buffer of the resume data.

    +
    +
    +

    example

    +

    Here's an updated version of the above example with the following updates:

    +
      +
    1. not using blocking calls
    2. +
    3. printing torrent status updates rather than the raw log
    4. +
    5. saving and loading resume files
    6. +
    +
    +#include <iostream>
    +#include <thread>
    +#include <chrono>
    +#include <fstream>
    +
    +#include <libtorrent/session.hpp>
    +#include <libtorrent/add_torrent_params.hpp>
    +#include <libtorrent/torrent_handle.hpp>
    +#include <libtorrent/alert_types.hpp>
    +#include <libtorrent/bencode.hpp>
    +#include <libtorrent/torrent_status.hpp>
    +
    +namespace lt = libtorrent;
    +using clk = std::chrono::steady_clock;
    +
    +// return the name of a torrent status enum
    +char const* state(lt::torrent_status::state_t s)
    +{
    +  switch(s) {
    +    case lt::torrent_status::checking_files: return "checking";
    +    case lt::torrent_status::downloading_metadata: return "dl metadata";
    +    case lt::torrent_status::downloading: return "downloading";
    +    case lt::torrent_status::finished: return "finished";
    +    case lt::torrent_status::seeding: return "seeding";
    +    case lt::torrent_status::allocating: return "allocating";
    +    case lt::torrent_status::checking_resume_data: return "checking resume";
    +    default: return "<>";
    +  }
    +}
    +
    +int main(int argc, char const* argv[])
    +{
    +  if (argc != 2) {
    +    std::cerr << "usage: " << argv[0] << " <magnet-url>" << std::endl;
    +    return 1;
    +  }
    +
    +  lt::settings_pack pack;
    +  pack.set_int(lt::settings_pack::alert_mask
    +    , lt::alert::error_notification
    +    | lt::alert::storage_notification
    +    | lt::alert::status_notification);
    +
    +  lt::session ses(pack);
    +  lt::add_torrent_params atp;
    +  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);
    +  atp.resume_data.assign(std::istream_iterator<char>(ifs)
    +    , std::istream_iterator<char>());
    +  atp.url = argv[1];
    +  atp.save_path = "."; // save in current dir
    +  ses.async_add_torrent(atp);
    +
    +  // this is the handle we'll set once we get the notification of it being
    +  // added
    +  lt::torrent_handle h;
    +  for (;;) {
    +    std::vector<lt::alert*> alerts;
    +    ses.pop_alerts(&alerts);
    +
    +    for (lt::alert const* a : alerts) {
    +      if (auto at = lt::alert_cast<lt::add_torrent_alert>(a)) {
    +        h = at->handle;
    +      }
    +      // if we receive the finished alert or an error, we're done
    +      if (lt::alert_cast<lt::torrent_finished_alert>(a)) {
    +        h.save_resume_data();
    +        goto done;
    +      }
    +      if (lt::alert_cast<lt::torrent_error_alert>(a)) {
    +        std::cout << a->message() << std::endl;
    +        goto done;
    +      }
    +
    +      // when resume data is ready, save it
    +      if (auto rd = lt::alert_cast<lt::save_resume_data_alert>(a)) {
    +        std::ofstream of(".resume_file", std::ios_base::binary);
    +        of.unsetf(std::ios_base::skipws);
    +        lt::bencode(std::ostream_iterator<char>(of)
    +          , *rd->resume_data);
    +      }
    +
    +      if (auto st = lt::alert_cast<lt::state_update_alert>(a)) {
    +        if (st->status.empty()) continue;
    +
    +        // we only have a single torrent, so we know which one
    +        // the status is for
    +        lt::torrent_status const& s = st->status[0];
    +        std::cout << "\r" << state(s.state) << " "
    +          << (s.download_payload_rate / 1000) << " kB/s "
    +          << (s.total_done / 1000) << " kB ("
    +          << (s.progress_ppm / 10000) << "%) downloaded\x1b[K";
    +        std::cout.flush();
    +      }
    +    }
    +    std::this_thread::sleep_for(std::chrono::milliseconds(200));
    +
    +    // ask the session to post a state_update_alert, to update our
    +    // state output for the torrent
    +    ses.post_torrent_updates();
    +
    +    // save resume data once every 30 seconds
    +    if (clk::now() - last_save_resume > std::chrono::seconds(30)) {
    +      h.save_resume_data();
    +      last_save_resume = clk::now();
    +    }
    +  }
    +
    +  // TODO: ideally we should save resume data here
    +
    +done:
    +  std::cout << "\ndone, shutting down" << std::endl;
    +}
    +
    +
    +
    +

    torrent files

    +

    To add torrent files to a session (as opposed to a magnet link), it must first +be loaded into a torrent_info object.

    +

    The torrent_info object can be created either by filename a buffer or a +bencoded structure. When adding by filename, there's a sanity check limit on the +size of the file, for adding arbitrarily large torrents, load the file outside +of the constructor.

    +

    The torrent_info object provides an opportunity to query information about the +.torrent file as well as mutating it before adding it to the session.

    +
    +
    +

    bencoding

    +

    bencoded structures is the default data storage format used by bittorrent, such +as .torrent files, tracker announce and scrape responses and some wire protocol +extensions. libtorrent provides an efficient framework for decoding bencoded +data through bdecode() function.

    +

    There are two separate mechanisms for encoding and decoding. When decoding, +use the bdecode() function that returns a bdecode_node. When encoding, use +bencode() taking an entry object.

    +

    The key property of bdecode() is that it does not copy any data out of the +buffer that was parsed. It builds the tree structures of references pointing +into the buffer. The buffer must stay alive and valid for as long as the +bdecode_node is in use.

    +

    For performance details on bdecode(), see the blog post about it.

    +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/tutorial.rst b/docs/tutorial.rst new file mode 100644 index 0000000..47cd267 --- /dev/null +++ b/docs/tutorial.rst @@ -0,0 +1,310 @@ +================= +libtorrent manual +================= + +:Author: Arvid Norberg, arvid@libtorrent.org +:Version: 1.1.1 + +.. 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 + + namespace lt = libtorrent; + 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; + atp.url = 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 programatically react to certain events, use `alert_cast<>`_ to attempt +a down cast of an alert object to a more specific type. + +In order to print out events from libtorrent as well as exiting when the torrent +completes downloading, we can poll the session for alerts periodically and print +them out, as well as listening for the torrent_finished_alert_, which is posted +when a torrent completes. + +.. include:: ../examples/bt-get.cpp + :code: c++ + :tab-width: 2 + :start-after: */ + +alert masks +----------- + +The output from this program will be quite verbose, which is probably a good +starting point to get some understanding of what's going on. Alerts are +categorized into alert categories. Each category can be enabled and disabled +independently via the *alert mask*. + +The alert mask is a configuration option offered by libtorrent. There are many +configuration options, see settings_pack_. The alert_mask_ setting is an integer +of the `category flags`_ ORed together. + +For instance, to only see the most pertinent alerts, the session can be +constructed like this: + +.. code:: c++ + + lt::settings_pack pack; + pack.set_int(lt::settings_pack::alert_mask + , lt::alert::error_notification + | lt::alert::storage_notification + | lt::alert::status_notification); + + lt::session ses(pack); + +Configuration options can be updated after the session is started by calling +`apply_settings()`_. Some settings are best set before starting the session +though, like listen_interfaces_, to avoid race conditions. If you start the +session with the default settings and then immediately change them, there will +still be a window where the default settings apply. + +Changing the settings may trigger listen sockets to close and re-open and +NAT-PMP, UPnP updates to be sent. For this reason, it's typically a good idea +to batch settings updates into a single call. + +session destruction +------------------- + +The session destructor is blocking by default. When shutting down, trackers +will need to be contacted to stop torrents and other outstanding operations +need to be cancelled. Shutting down can sometimes take several seconds, +primarily because of trackers that are unresponsive (and time out) and also +DNS servers that are unresponsive. DNS lookups are especially difficult to +abort when stalled. + +In order to be able to start destruction an wait for it asynchronously, one +can call `session::abort()`_. + +This call returns a session_proxy_ object, which is a handle keeping the session +state alive while destructing it. It deliberately does not provide any of the +session operations, since it's shutting down. + +After having a session_proxy_ object, the session destructor does not block. +However, the session_proxy_ destructor *will*. + +This can be used to shut down multiple sessions or other parts of the +application in parallel. + +asynchronous operations +----------------------- + +Essentially any call to a member function of session_ or torrent_handle_ that +returns a value is a blocking synchronous call. Meaning it will post a message +to the main libtorrent thread and wait for a response. Such calls may be +expensive, and in applications where stalls should be avoided (such as user +interface threads), blocking calls should be avoided. + +In the example above, session::add_torrent() returns a torrent_handle_ and is +thus blocking. For higher efficiency, `async_add_torrent()`_ will post a message +to the main thread to add a torrent, and post the resulting torrent_handle_ back +in an alert (add_torrent_alert_). This is especially useful when adding a lot +of torrents in quick succession, as there's no stall in between calls. + +In the example above, we don't actually use the torrent_handle_ for anything, so +converting it to use `async_add_torrent()`_ is just a matter of replacing the +`add_torrent()`_ call with `async_add_torrent()`_. + +torrent_status_updates +---------------------- + +To get updates to the status of torrents, call `post_torrent_updates()`_ on the +session object. This will cause libtorrent to post a state_update_alert_ +containing torrent_status_ objects for all torrents whose status has *changed* +since the last call to `post_torrent_updates()`_. + +The state_update_alert_ looks something like this: + +.. code:: c++ + + struct state_update_alert : alert + { + virtual std::string message() const; + std::vector status; + }; + +The ``status`` field only contains the torrent_status_ for torrents with +updates since the last call. It may be empty if no torrent has updated its +state. This feature is critical for scalability_. + +See the torrent_status_ object for more information on what is in there. +Perhaps the most interesting fields are ``total_payload_download``, +``total_payload_upload``, ``num_peers`` and ``state``. + +resuming torrents +----------------- + +Since bittorrent downloads pieces of files in random order, it's not trivial to +resume a partial download. When resuming a download, the bittorrent engine must +restore the state of the downloading torrent, specifically which parts of the +file(s) are downloaded. There are two approaches to doing this: + +1. read every piece of the downloaded files from disk and compare it against its + expected hash. +2. save to disk the state of which pieces (and partial pieces) are downloaded, + and load it back in again when resuming. + +If no resume data is provided with a torrent that's added, libtorrent will +employ (1) by default. + +To save resume data, call `save_resume_data()`_ on the torrent_handle_ object. +This will ask libtorrent to generate the resume data and post it back in +a save_resume_data_alert_. If generating the resume data fails for any reason, +a save_resume_data_failed_alert_ is posted instead. + +Exactly one of those alerts will be posted for every call to +`save_resume_data()`_. This is an important property when shutting down a +session with multiple torrents, every resume alert must be handled before +resuming with shut down. Any torrent may fail to save resume data, so the client +would need to keep a count of the outstanding resume files, decremented on +either save_resume_data_alert_ or save_resume_data_failed_alert_. + +The save_resume_data_alert_ looks something like this: + +.. code:: c++ + + struct save_resume_data_alert : torrent_alert + { + virtual std::string message() const; + + // points to the resume data. + boost::shared_ptr resume_data; + }; + +``resume_data`` points to an entry_ object. This represents a node or a tree of +nodes in a bencoded_ structure, which is the native encoding scheme in +bittorrent. It can be encoded into a byte buffer or file using `bencode()`_. + +When adding a torrent with resume data, set the `add_torrent_params::resume_data`_ +to contain the bencoded buffer of the resume data. + +example +------- + +Here's an updated version of the above example with the following updates: + +1. not using blocking calls +2. printing torrent status updates rather than the raw log +3. saving and loading resume files + +.. include:: ../examples/bt-get2.cpp + :code: c++ + :tab-width: 2 + :start-after: */ + +torrent files +------------- + +To add torrent files to a session (as opposed to a magnet link), it must first +be loaded into a torrent_info_ object. + +The torrent_info_ object can be created either by filename a buffer or a +bencoded structure. When adding by filename, there's a sanity check limit on the +size of the file, for adding arbitrarily large torrents, load the file outside +of the constructor. + +The torrent_info_ object provides an opportunity to query information about the +.torrent file as well as mutating it before adding it to the session. + +bencoding +--------- + +bencoded_ structures is the default data storage format used by bittorrent, such +as .torrent files, tracker announce and scrape responses and some wire protocol +extensions. libtorrent provides an efficient framework for decoding bencoded +data through `bdecode()`_ function. + +There are two separate mechanisms for *encoding* and *decoding*. When decoding, +use the `bdecode()`_ function that returns a bdecode_node_. When encoding, use +`bencode()`_ taking an entry_ object. + +The key property of `bdecode()`_ is that it does not copy any data out of the +buffer that was parsed. It builds the tree structures of references pointing +into the buffer. The buffer must stay alive and valid for as long as the +bdecode_node_ is in use. + +For performance details on `bdecode()`_, see the `blog post about it`__. + +__ http://blog.libtorrent.org/2015/03/bdecode-parsers/ + +.. _session: reference-Core.html#session +.. _session_handle: reference-Core.html#session_handle +.. _add_torrent_params: reference-Core.html#add_torrent_params +.. _`add_torrent()`: reference-Core.html#add_torrent() +.. _`async_add_torrent()`: reference-Core.html#add_torrent() +.. _torrent_handle: reference-Core.html#torrent_handle +.. _`pop_alerts()`: reference-Core.html#pop_alerts() +.. _`alert`: reference-Alerts.html#alert +.. _`alert_cast<>`: reference-Alerts.html#alert_cast() +.. _torrent_finished_alert: reference-Alerts.html#torrent-finished-alert +.. _listen_interfaces: reference-Settings.html#listen_interfaces +.. _`add_torrent_alert`: reference-Alerts.html#add-torrent-alert +.. _settings_pack: reference-Settings.html#settings_pack +.. _alert_mask: reference-Settings.html#alert_mask +.. _`category flags`: reference-Alerts.html#category_t +.. _`apply_settings()`: reference-Core.html#apply_settings() +.. _`session::abort()`: reference-Core.html#abort() +.. _session_proxy: reference-Core.html#session_proxy +.. _`post_torrent_updates()`: reference-Core.html#post_torrent_updates() +.. _torrent_status: reference-Core.html#torrent_status +.. _state_update_alert: reference-Alerts.html#state_update_alert +.. _scalability: http://blog.libtorrent.org/2011/11/scalable-interfaces/ +.. _`save_resume_data()`: reference-Core.html#save_resume_data() +.. _save_resume_data_alert: reference-Alerts.html#save_resume_data_alert +.. _save_resume_data_failed_alert: reference-Alerts.html#save_resume_data_failed_alert +.. _bencoded: https://en.wikipedia.org/wiki/Bencode +.. _entry: reference-Bencoding.html#entry +.. _`bencode()`: reference-Bencoding.html#bencode() +.. _torrent_info: reference-Core.html#torrent_info +.. _`add_torrent_params::resume_data`: reference-Core.html#resume_data +.. _`bdecode()`: reference-Bdecoding.html#bdecode() +.. _bdecode_node: reference-Bdecoding.html#bdecode-node + + diff --git a/docs/udp_tracker_protocol.html b/docs/udp_tracker_protocol.html new file mode 100644 index 0000000..c111f5b --- /dev/null +++ b/docs/udp_tracker_protocol.html @@ -0,0 +1,605 @@ + + + + + + +Bittorrent udp-tracker protocol extension + + + + + + + + +
    +
    + + + + +
    +

    Bittorrent udp-tracker protocol extension

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

    introduction

    +

    A tracker with the protocol "udp://" in its URI +is supposed to be contacted using this protocol.

    +

    This protocol is supported by +xbt-tracker.

    +

    For additional information and descritptions of +the terminology used in this document, see +the protocol specification

    +

    All values are sent in network byte order (big endian). The sizes +are specified with ANSI-C standard types.

    +

    If no response to a request is received within 15 seconds, resend +the request. If no reply has been received after 60 seconds, stop +retrying.

    +
    +
    +

    connecting

    +

    Client sends packet:

    + +++++ + + + + + + + + + + + + + + + + + + + + +
    sizenamedescription
    int64_tconnection_idMust be initialized to 0x41727101980 +in network byte order. This will +identify the protocol.
    int32_taction0 for a connection request
    int32_ttransaction_idRandomized by client.
    +

    Server replies with packet:

    + +++++ + + + + + + + + + + + + + + + + + + + + +
    sizenamedescription
    int32_tactionDescribes the type of packet, in this +case it should be 0, for connect. +If 3 (for error) see errors.
    int32_ttransaction_idMust match the transaction_id sent +from the client.
    int64_tconnection_idA connection id, this is used when +further information is exchanged with +the tracker, to identify you. +This connection id can be reused for +multiple requests, but if it's cached +for too long, it will not be valid +anymore.
    +
    +
    +

    announcing

    +

    Client sends packet:

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    sizenamedescription
    int64_tconnection_idThe connection id acquired from +establishing the connection.
    int32_tactionAction. in this case, 1 for announce. +See actions.
    int32_ttransaction_idRandomized by client.
    int8_t[20]info_hashThe info-hash of the torrent you want +announce yourself in.
    int8_t[20]peer_idYour peer id.
    int64_tdownloadedThe number of byte you've downloaded +in this session.
    int64_tleftThe number of bytes you have left to +download until you're finished.
    int64_tuploadedThe number of bytes you have uploaded +in this session.
    int32_tevent

    The event, one of

    +
    +
      +
    • none = 0
    • +
    • completed = 1
    • +
    • started = 2
    • +
    • stopped = 3
    • +
    +
    +
    uint32_tipYour ip address. Set to 0 if you want +the tracker to use the sender of +this udp packet.
    uint32_tkeyA unique key that is randomized by the +client.
    int32_tnum_wantThe maximum number of peers you want +in the reply. Use -1 for default.
    uint16_tportThe port you're listening on.
    uint16_textensionsSee extensions
    +

    Server replies with packet:

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    sizenamedescription
    int32_tactionThe action this is a reply to. Should +in this case be 1 for announce. +If 3 (for error) see errors. +See actions.
    int32_ttransaction_idMust match the transaction_id sent +in the announce request.
    int32_tintervalthe number of seconds you should wait +until reannouncing yourself.
    int32_tleechersThe number of peers in the swarm that +has not finished downloading.
    int32_tseedersThe number of peers in the swarm that +has finished downloading and are +seeding.
    +

    The rest of the server reply is a variable number of the following structure:

    + +++++ + + + + + + + + + + + + + + + + +
    sizenamedescription
    int32_tipThe ip of a peer in the swarm.
    uint16_tportThe peer's listen port.
    +
    +
    +

    scraping

    +

    Client sends packet:

    + +++++ + + + + + + + + + + + + + + + + + + + + +
    sizenamedescription
    int64_tconnection_idThe connection id retreived from the +establishing of the connection.
    int32_tactionThe action, in this case, 2 for +scrape. See actions.
    int32_ttransaction_idRandomized by client.
    +

    The following structure is repeated for each info-hash to scrape, but limited by +the MTU.

    + +++++ + + + + + + + + + + + + +
    sizenamedescription
    int8_t[20]info_hashThe info hash that is to be scraped.
    +

    Server replies with packet:

    + +++++ + + + + + + + + + + + + + + + + +
    sizenamedescription
    int32_tactionThe action, should in this case be +2 for scrape. +If 3 (for error) see errors.
    int32_ttransaction_idMust match the sent transaction id.
    +

    The rest of the packet contains the following structures once for each info-hash +you asked in the scrape request.

    + +++++ + + + + + + + + + + + + + + + + + + + + +
    sizenamedescription
    int32_tcompleteThe current number of connected seeds.
    int32_tdownloadedThe number of times this torrent has +been downloaded.
    int32_tincompleteThe current number of connected +leechers.
    +
    +
    +

    errors

    +

    In case of a tracker error,

    +

    server replies packet:

    + +++++ + + + + + + + + + + + + + + + + + + + + +
    sizenamedescription
    int32_tactionThe action, in this case 3, for error. +See actions.
    int32_ttransaction_idMust match the transaction_id sent +from the client.
    int8_t[]error_stringThe rest of the packet is a string +describing the error.
    +
    +
    +

    actions

    +

    The action fields has the following encoding:

    +
    +
      +
    • connect = 0
    • +
    • announce = 1
    • +
    • scrape = 2
    • +
    • error = 3 (only in server replies)
    • +
    +
    +
    +
    +

    extensions

    +

    The extensions field is a bitmask. The following +bits are assigned:

    +
    + +
    +

    If multiple bits are present in the extension field, the extension +bodies are appended to the packet in the order of least significant +bit first. For instance, if both bit 1 and 2 are set, the extension +represented by bit 1 comes first, followed by the extension represented +by bit 2.

    +
    +

    authentication

    +

    The packet will have an authentication part +appended to it. It has the following format:

    + +++++ + + + + + + + + + + + + + + + + + + + + +
    sizenamedescription
    int8_tusername_lengthThe number of characters in the +username.
    int8_t[]usernameThe username, the number of characters +as specified in the previous field.
    uint8_t[8]passwd_hashsha1(packet + sha1(password)) +The packet in this case means the +entire packet except these 8 bytes +that are the password hash. These are +the 8 first bytes (most significant) +from the 20 bytes hash calculated.
    +
    +
    +
    +

    request string

    +

    The request string extension is meant to allow torrent creators pass along +cookies back to the tracker. This can be useful for authenticating that a +torrent is allowed to be tracked by a tracker for instance. It could also +be used to authenticate users by generating torrents with unique tokens +in the tracker URL for each user. The extension body has the following format:

    + +++++ + + + + + + + + + + + + + + + + +
    sizenamedescription
    int8_trequest lengthThe number of bytes in the request +string.
    int8_t[]request stringThe string that comes after the host- +name and port in the udp tracker URL. +Typically this starts with "/announce" +The bittorrent client is not expected +to append query string arguments for +stats reporting, like "uploaded" and +"downloaded" since this is already +reported in the udp tracker protocol. +However, the client is free to add +arguments as extensions.
    +
    +
    +

    credits

    +

    Protocol designed by Olaf van der Spek and extended by Arvid Norberg

    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/udp_tracker_protocol.rst b/docs/udp_tracker_protocol.rst new file mode 100644 index 0000000..c3efb00 --- /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 descritptions 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 reannouncing 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 retreived 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/utp.html b/docs/utp.html new file mode 100644 index 0000000..ad112c4 --- /dev/null +++ b/docs/utp.html @@ -0,0 +1,370 @@ + + + + + + +libtorrent manual + + + + + + + + +
    +
    + + + + +
    +

    libtorrent manual

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

    uTP

    +

    uTP (uTorrent transport protocol) is a transport protocol which uses one-way +delay measurements for its congestion controller. This article is about uTP +in general and specifically about libtorrent's implementation of it.

    +
    +

    rationale

    +

    One of the most common problems users are experiencing using bittorrent is +that their internet "stops working". This can be caused by a number of things, +for example:

    +
      +
    1. a home router that crashes or slows down when its NAT pin-hole +table overflows, triggered by DHT or simply many TCP connections.
    2. +
    3. a home router that crashes or slows down by UDP traffic (caused by +the DHT)
    4. +
    5. a home DSL or cable modem having its send buffer filled up by outgoing +data, and the buffer fits seconds worth of bytes. This adds seconds +of delay on interactive traffic. For a web site that needs 10 round +trips to load this may mean 10s of seconds of delay to load compared +to without bittorrent. Skype or other delay sensitive applications +would be affected even more.
    6. +
    +

    This document will cover (3).

    +

    Typically this is solved by asking the user to enter a number of bytes +that the client is allowed to send per second (i.e. setting an upload +rate limit). The common recommendation is to set this limit to 80% of the +uplink's capacity. This is to leave some headroom for things like TCP +ACKs as well as the user's interactive use of the connection such as +browsing the web or checking email.

    +

    There are two major drawbacks with this technique:

    +
      +
    1. The user needs to actively make this setting (very few protocols +require the user to provide this sort of information). This also +means the user needs to figure out what its up-link capacity is. +This is unfortunately a number that many ISPs are not advertizing +(because it's often much lower than the download capacity) which +might make it hard to find.
    2. +
    3. The 20% headroom is wasted most of the time. Whenever the user +is not using the internet connection for anything, those extra 20% +could have been used by bittorrent to upload, but they're already +allocated for interactive traffic. On top of that, 20% of the up-link +is often not enough to give a good and responsive browsing experience.
    4. +
    +

    The ideal bandwidth allocation would be to use 100% for bittorrent when +there is no interactive cross traffic, and 100% for interactive traffic +whenever there is any. This would not waste any bandwidth while the user +is idling, and it would make for a much better experience when the user +is using the internet connection for other things.

    +

    This is what uTP does.

    +
    +
    +

    TCP

    +

    The reason TCP will fill the send buffer, and cause the delay on all traffic, +is because its congestion control is only based on packet loss (and timeout).

    +

    Since the modem is buffering, packets won't get dropped until the entire queue +is full, and no more packets will fit. The packets will be dropped, TCP will +detect this within an RTT or so. When TCP notices a packet loss, it will slow +down its send rate and the queue will start to drain again. However, TCP will +immediately start to ramp up its send rate again until the buffer is full and +it detects packet loss again.

    +

    TCP is designed to fully utilize the link capacity, without causing congestion. +Whenever it sense congestion (through packet loss) it backs off. TCP is not +designed to keep delays low. When you get the first packet loss (assuming the +kind of queue described above, tail-queue) it is already too late. Your queue +is full and you have the maximum amount of delay your modem can provide.

    +

    TCP controls its send rate by limiting the number of bytes in-flight at any +given time. This limit is called congestion window (cwnd for short). During +steady state, the congestion window is constantly increasing linearly. Each +packet that is successfully transferred will increase cwnd.

    +
    +            cwnd
    +send_rate = ----
    +            RTT
    +
    +

    Send rate is proportional to cwnd divided by RTT. A smaller cwnd will cause +the send rate to be lower and a larger cwnd will cause the send rate to be +higher.

    +

    Using a congestion window instead of controlling the rate directly is simple +because it also introduces an upper bound for memory usage for packets that +haven't been ACKed yet and needs to be kept around.

    +

    The behavior of TCP, where it bumps up against the ceiling, backs off and then +starts increasing again until it hits the ceiling again, forms a saw tooth shape. +If the modem wouldn't have any send buffer at all, a single TCP stream would +not be able to fully utilize the link because of this behavior, since it would +only fully utilize the link right before the packet loss and the back-off.

    +
    +
    +

    LEDBAT congestion controller

    +

    The congestion controller in uTP is called LEDBAT, which also is an IETF working +group attempting to standardize it. The congestion controller, on top of reacting +to packet loss the same way TCP does, also reacts to changes in delays.

    +

    For any uTP (or LEDBAT) implementation, there is a target delay. This is the +amount of delay that is acceptable, and is in fact targeted for the connection. +The target delay is defined to 25 ms in LEDBAT, uTorrent uses 100 ms and +libtorrent uses 75 ms. Whenever a delay measurement is lower than the target, +cwnd is increased proportional to (target_delay - delay). Whenever the measurement +is higher than the target, cwnd is decreased proportional to (delay - target_delay).

    +

    It can simply be expressed as:

    +
    +cwnd += gain * (target_delay - delay)
    +
    +cwnd_thumb.png +

    Similarly to TCP, this is scaled so that the increase is evened out over one RTT.

    +

    The linear controller will adjust the cwnd more for delays that are far off the +target, and less for delays that are close to the target. This makes it converge +at the target delay. Although, due to noise there is almost always some amount of +oscillation. This oscillation is typically smaller than the saw tooth TCP forms.

    +

    The figure to the right shows how (TCP) cross traffic causese uTP to essentially +entirely stop sending anything. Its delay measurements are mostly well above the target +during this time. The cross traffic is only a single TCP stream in this test.

    +

    As soon as the cross traffic ceases, uTP will pick up its original send rate within +a second.

    +

    Since uTP constantly measures the delay, with every single packet, the reaction time +to cross traffic causing delays is a single RTT (typically a fraction of a second).

    +
    +
    +

    one way delays

    +

    uTP measures the delay imposed on packets being sent to the other end +of the connection. This measurement only includes buffering delay along +the link, not propagation delay (the speed of light times distance) nor +the routing delay (the time routers spend figuring out where to forward +the packet). It does this by always comparing all measurements to a +baseline measurement, to cancel out any fixed delay. By focusing on the +variable delay along a link, it will specifically detect points where +there might be congestion, since those points will have buffers.

    +delays_thumb.png +

    Delay on the return link is explicitly not included in the delay measurement. +This is because in a peer-to-peer application, the other end is likely to also +be connected via a modem, with the same send buffer restrictions as we assume +for the sending side. The other end having its send queue full is not an indication +of congestion on the path going the other way.

    +

    In order to measure one way delays for packets, we cannot rely on clocks being +synchronized, especially not at the microsecond level. Instead, the actual time +it takes for a packet to arrive at the destination is not measured, only the changes +in the transit time is measured.

    +

    Each packet that is sent includes a time stamp of the current time, in microseconds, +of the sending machine. The receiving machine calculates the difference between its +own timestamp and the one in the packet and sends this back in the ACK. This difference, +since it is in microseconds, will essentially be a random 32 bit number. However, +the difference will stay somewhat similar over time. Any changes in this difference +indicates that packets are either going through faster or slower.

    +

    In order to measure the one-way buffering delay, a base delay is established. The +base delay is the lowest ever seen value of the time stamp difference. Each delay +sample we receive back, is compared against the base delay and the delay is the +difference.

    +

    This is the delay that's fed into the congestion controller.

    +

    A histogram of typical delay measurements is shown to the right. This is from +a transfer between a cable modem connection and a DSL connection.

    +

    The details of the delay measurements are slightly more complicated since the +values needs to be able to wrap (cross the 2^32 boundry and start over at 0).

    +
    +
    +

    Path MTU discovery

    +

    MTU is short for Maximum Transfer Unit and describes the largest packet size that +can be sent over a link. Any datagrams which size exceeds this limit will either +be fragmented or dropped. A fragmented datagram means that the payload is split up +in multiple packets, each with its own individual packet header.

    +

    There are several reasons to avoid sending datagrams that get fragmented:

    +
      +
    1. A fragmented datagram is more likely to be lost. If any fragment is lost, +the whole datagram is dropped.
    2. +
    3. Bandwidth is likely to be wasted. If the datagram size is not divisible +by the MTU the last packet will not contain as much payload as it could, and the +payload over protocol header ratio decreases.
    4. +
    5. It's expensive to fragment datagrams. Few routers are optimized to handle large +numbers of fragmented packets. Datagrams that have to fragment are likely to +be delayed significantly, and contribute to more CPU being used on routers. +Typically fragmentation (and other advanced IP features) are implemented in +software (slow) and not hardware (fast).
    6. +
    +

    The path MTU is the lowest MTU of any link along a path from two endpoints on the +internet. The MTU bottleneck isn't necessarily at one of the endpoints, but can +be anywhere in between.

    +

    The most common MTU is 1500 bytes, which is the largest packet size for ethernet +networks. Many home DSL connections, however, tunnel IP through PPPoE (Point to +Point Protocol over Ethernet. Yes, that is the old dial-up modem protocol). This +protocol uses up 8 bytes per packet for its own header.

    +

    If the user happens to be on an internet connection over a VPN, it will add another +layer, with its own packet headers.

    +

    In short; if you would pick the largest possible packet size on an ethernet network, +1472, and stick with it, you would be quite likely to generate fragments for a lot +of connections. The fragments that will be created will be very small and especially +inflate the overhead waste.

    +

    The other approach of picking a very conservative packet size, that would be very +unlikely to get fragmented has the following drawbacks:

    +
      +
    1. People on good, normal, networks will be penalized with a small packet size. +Both in terms of router load but also bandwidth waste.
    2. +
    3. Software routers are typically not limited by the number of bytes they can route, +but the number of packets. Small packets means more of them, and more load on +software routers.
    4. +
    +

    The solution to the problem of finding the optimal packet size, is to dynamically +adjust the packet size and search for the largest size that can make it through +without being fragmented along the path.

    +

    To help do this, you can set the DF bit (Don't Fragment) in your Datagrams. This +asks routers that otherwise would fragment packets to instead drop them, and send +back an ICMP message reporting the MTU of the link the packet couldn't fit. With +this message, it's very simple to discover the path MTU. You simply mark your packets +not to be fragmented, and change your packet size whenever you receive the ICMP +packet-too-big message.

    +

    Unfortunately it's not quite that simple. There are a significant number of firewalls +in the wild blocking all ICMP messages. This means we can't rely on them, we also have +to guess that a packet was dropped because of its size. This is done by only marking +certain packets with DF, and if all other packets go through, except for the MTU probes, +we know that we need to lower our packet sizes.

    +

    If we set up bounds for the path MTU (say the minimum internet MTU, 576 and ethernet's 1500), +we can do a binary search for the MTU. This would let us find it in just a few round-trips.

    +

    On top of this, libtorrent has an optimization where it figures out which interface a +uTP connection will be sent over, and initialize the MTU ceiling to that interface's MTU. +This means that a VPN tunnel would advertize its MTU as lower, and the uTP connection would +immediately know to send smaller packets, no search required. It also has the side-effect +of being able to use much larger packet sizes for non-ethernet interfaces or ethernet links +with jumbo frames.

    +
    +
    +

    clock drift

    +our_delay_base_thumb.png +

    Clock drift is clocks progressing at different rates. It's different from clock +skew which means clocks set to different values (but which may progress at the same +rate).

    +

    Any clock drift between the two machines involved in a uTP transfer will result +in systematically inflated or deflated delay measurements.

    +

    This can be solved by letting the base delay be the lowest seen sample in the last +n minutes. This is a trade-off between seeing a single packet go straight through +the queue, with no delay, and the amount of clock drift one can assume on normal computers.

    +

    It turns out that it's fairly safe to assume that one of your packets will in fact go +straight through without any significant delay, once every 20 minutes or so. However, +the clock drift between normal computers can be as much as 17 ms in 10 minutes. 17 ms +is quite significant, especially if your target delay is 25 ms (as in the LEDBAT spec).

    +

    Clocks progresses at different rates depending on temperature. This means computers +running hot are likely to have a clock drift compared to computers running cool.

    +

    So, by updating the delay base periodically based on the lowest seen sample, you'll either +end up changing it upwards (artificaially making the delay samples appear small) without +the congestion or delay actually having changed, or you'll end up with a significant clock +drift and have artificially low samples because of that.

    +

    The solution to this problem is based on the fact that the clock drift is only a problem +for one of the sides of the connection. Only when your delay measurements keep increasing +is it a problem. If your delay measurements keep decreasing, the samples will simply push +down the delay base along with it. With this in mind, we can simply keep track of the +other end's delay measurements as well, applying the same logic to it. Whenever the +other end's base delay is adjusted downwards, we adjust our base delay upwards by the same +amount.

    +

    This will accurately keep the base delay updated with the clock drift and improve +the delay measurements. The figure on the right shows the absolute timestamp differences +along with the base delay. The slope of the measurements is caused by clock drift.

    +

    For more information on the clock drift compensation, see the slides from BitTorrent's +presentation at IPTPS10.

    +
    +
    +

    features

    +

    libtorrent's uTP implementation includes the following features:

    +
      +
    • Path MTU discovery, including jumbo frames and detecting restricted +MTU tunnels. Binary search packet sizes to find the largest non-fragmented.
    • +
    • Selective ACK. The ability to acknowledge individual packets in the +event of packet loss
    • +
    • Fast resend. The first time a packet is lost, it's resent immediately. +Triggered by duplicate ACKs.
    • +
    • Nagle's algorithm. Minimize protocol overhead by attempting to lump +full packets of payload together before sending a packet.
    • +
    • Delayed ACKs to minimize protocol overhead.
    • +
    • Microsecond resolution timestamps.
    • +
    • Advertised receive window, to support download rate limiting.
    • +
    • Correct handling of wrapping sequence numbers.
    • +
    • Easy configuration of target-delay, gain-factor, timeouts, delayed-ack +and socket buffers.
    • +
    +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/utp.rst b/docs/utp.rst new file mode 100644 index 0000000..f92dd2e --- /dev/null +++ b/docs/utp.rst @@ -0,0 +1,347 @@ +================= +libtorrent manual +================= + +:Author: Arvid Norberg, arvid@libtorrent.org +:Version: 1.1.1 + +.. 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 advertizing + (because it's often much lower than the download capacity) which + might make it hard to find. + +2. The 20% headroom is wasted most of the time. Whenever the user + is not using the internet connection for anything, those extra 20% + could have been used by bittorrent to upload, but they're already + allocated for interactive traffic. On top of that, 20% of the up-link + is often not enough to give a good and responsive browsing experience. + +The ideal bandwidth allocation would be to use 100% for bittorrent when +there is no interactive cross traffic, and 100% for interactive traffic +whenever there is any. This would not waste any bandwidth while the user +is idling, and it would make for a much better experience when the user +is using the internet connection for other things. + +This is what uTP does. + +TCP +--- + +The reason TCP will fill the send buffer, and cause the delay on all traffic, +is because its congestion control is *only* based on packet loss (and timeout). + +Since the modem is buffering, packets won't get dropped until the entire queue +is full, and no more packets will fit. The packets will be dropped, TCP will +detect this within an RTT or so. When TCP notices a packet loss, it will slow +down its send rate and the queue will start to drain again. However, TCP will +immediately start to ramp up its send rate again until the buffer is full and +it detects packet loss again. + +TCP is designed to fully utilize the link capacity, without causing congestion. +Whenever it sense congestion (through packet loss) it backs off. TCP is not +designed to keep delays low. When you get the first packet loss (assuming the +kind of queue described above, tail-queue) it is already too late. Your queue +is full and you have the maximum amount of delay your modem can provide. + +TCP controls its send rate by limiting the number of bytes in-flight at any +given time. This limit is called congestion window (*cwnd* for short). During +steady state, the congestion window is constantly increasing linearly. Each +packet that is successfully transferred will increase cwnd. + +:: + + cwnd + send_rate = ---- + RTT + + +Send rate is proportional to cwnd divided by RTT. A smaller cwnd will cause +the send rate to be lower and a larger cwnd will cause the send rate to be +higher. + +Using a congestion window instead of controlling the rate directly is simple +because it also introduces an upper bound for memory usage for packets that +haven't been ACKed yet and needs to be kept around. + +The behavior of TCP, where it bumps up against the ceiling, backs off and then +starts increasing again until it hits the ceiling again, forms a saw tooth shape. +If the modem wouldn't have any send buffer at all, a single TCP stream would +not be able to fully utilize the link because of this behavior, since it would +only fully utilize the link right before the packet loss and the back-off. + +LEDBAT congestion controller +---------------------------- + +The congestion controller in uTP is called LEDBAT_, which also is an IETF working +group attempting to standardize it. The congestion controller, on top of reacting +to packet loss the same way TCP does, also reacts to changes in delays. + +For any uTP (or LEDBAT_) implementation, there is a target delay. This is the +amount of delay that is acceptable, and is in fact targeted for the connection. +The target delay is defined to 25 ms in LEDBAT_, uTorrent uses 100 ms and +libtorrent uses 75 ms. Whenever a delay measurement is lower than the target, +cwnd is increased proportional to (target_delay - delay). Whenever the measurement +is higher than the target, cwnd is decreased proportional to (delay - target_delay). + +It can simply be expressed as:: + + cwnd += gain * (target_delay - delay) + +.. image:: cwnd_thumb.png + :target: cwnd.png + :align: right + +Similarly to TCP, this is scaled so that the increase is evened out over one RTT. + +The linear controller will adjust the cwnd more for delays that are far off the +target, and less for delays that are close to the target. This makes it converge +at the target delay. Although, due to noise there is almost always some amount of +oscillation. This oscillation is typically smaller than the saw tooth TCP forms. + +The figure to the right shows how (TCP) cross traffic causese uTP to essentially +entirely stop sending anything. Its delay measurements are mostly well above the target +during this time. The cross traffic is only a single TCP stream in this test. + +As soon as the cross traffic ceases, uTP will pick up its original send rate within +a second. + +Since uTP constantly measures the delay, with every single packet, the reaction time +to cross traffic causing delays is a single RTT (typically a fraction of a second). + +one way delays +-------------- + +uTP measures the delay imposed on packets being sent to the other end +of the connection. This measurement only includes buffering delay along +the link, not propagation delay (the speed of light times distance) nor +the routing delay (the time routers spend figuring out where to forward +the packet). It does this by always comparing all measurements to a +baseline measurement, to cancel out any fixed delay. By focusing on the +variable delay along a link, it will specifically detect points where +there might be congestion, since those points will have buffers. + +.. image:: delays_thumb.png + :target: delays.png + :align: right + +Delay on the return link is explicitly not included in the delay measurement. +This is because in a peer-to-peer application, the other end is likely to also +be connected via a modem, with the same send buffer restrictions as we assume +for the sending side. The other end having its send queue full is not an indication +of congestion on the path going the other way. + +In order to measure one way delays for packets, we cannot rely on clocks being +synchronized, especially not at the microsecond level. Instead, the actual time +it takes for a packet to arrive at the destination is not measured, only the changes +in the transit time is measured. + +Each packet that is sent includes a time stamp of the current time, in microseconds, +of the sending machine. The receiving machine calculates the difference between its +own timestamp and the one in the packet and sends this back in the ACK. This difference, +since it is in microseconds, will essentially be a random 32 bit number. However, +the difference will stay somewhat similar over time. Any changes in this difference +indicates that packets are either going through faster or slower. + +In order to measure the one-way buffering delay, a base delay is established. The +base delay is the lowest ever seen value of the time stamp difference. Each delay +sample we receive back, is compared against the base delay and the delay is the +difference. + +This is the delay that's fed into the congestion controller. + +A histogram of typical delay measurements is shown to the right. This is from +a transfer between a cable modem connection and a DSL connection. + +The details of the delay measurements are slightly more complicated since the +values needs to be able to wrap (cross the 2^32 boundry 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 advertize its MTU as lower, and the uTP connection would +immediately know to send smaller packets, no search required. It also has the side-effect +of being able to use much larger packet sizes for non-ethernet interfaces or ethernet links +with jumbo frames. + +clock drift +----------- + +.. image:: our_delay_base_thumb.png + :target: our_delay_base.png + :align: right + +Clock drift is clocks progressing at different rates. It's different from clock +skew which means clocks set to different values (but which may progress at the same +rate). + +Any clock drift between the two machines involved in a uTP transfer will result +in systematically inflated or deflated delay measurements. + +This can be solved by letting the base delay be the lowest seen sample in the last +*n* minutes. This is a trade-off between seeing a single packet go straight through +the queue, with no delay, and the amount of clock drift one can assume on normal computers. + +It turns out that it's fairly safe to assume that one of your packets will in fact go +straight through without any significant delay, once every 20 minutes or so. However, +the clock drift between normal computers can be as much as 17 ms in 10 minutes. 17 ms +is quite significant, especially if your target delay is 25 ms (as in the LEDBAT_ spec). + +Clocks progresses at different rates depending on temperature. This means computers +running hot are likely to have a clock drift compared to computers running cool. + +So, by updating the delay base periodically based on the lowest seen sample, you'll either +end up changing it upwards (artificaially making the delay samples appear small) without +the congestion or delay actually having changed, or you'll end up with a significant clock +drift and have artificially low samples because of that. + +The solution to this problem is based on the fact that the clock drift is only a problem +for one of the sides of the connection. Only when your delay measurements keep increasing +is it a problem. If your delay measurements keep decreasing, the samples will simply push +down the delay base along with it. With this in mind, we can simply keep track of the +other end's delay measurements as well, applying the same logic to it. Whenever the +other end's base delay is adjusted downwards, we adjust our base delay upwards by the same +amount. + +This will accurately keep the base delay updated with the clock drift and improve +the delay measurements. The figure on the right shows the absolute timestamp differences +along with the base delay. The slope of the measurements is caused by clock drift. + +For more information on the clock drift compensation, see the slides from BitTorrent's +presentation at IPTPS10_. + +.. _IPTPS10: http://www.usenix.org/event/iptps10/tech/slides/cohen.pdf +.. _LEDBAT: https://datatracker.ietf.org/doc/draft-ietf-ledbat-congestion/ + +features +-------- + +libtorrent's uTP implementation includes the following features: + +* Path MTU discovery, including jumbo frames and detecting restricted + MTU tunnels. Binary search packet sizes to find the largest non-fragmented. +* Selective ACK. The ability to acknowledge individual packets in the + event of packet loss +* Fast resend. The first time a packet is lost, it's resent immediately. + Triggered by duplicate ACKs. +* Nagle's algorithm. Minimize protocol overhead by attempting to lump + full packets of payload together before sending a packet. +* Delayed ACKs to minimize protocol overhead. +* Microsecond resolution timestamps. +* Advertised receive window, to support download rate limiting. +* Correct handling of wrapping sequence numbers. +* Easy configuration of target-delay, gain-factor, timeouts, delayed-ack + and socket buffers. + diff --git a/docs/utp_stack.diagram b/docs/utp_stack.diagram new file mode 100644 index 0000000..b3b8085 --- /dev/null +++ b/docs/utp_stack.diagram @@ -0,0 +1,9 @@ ++------------------------+ +| "BitTorrent protocol" | ++------------------------+ +| "SSL" | ++------------+-----------+ +| "TCP" | "uTP" | +| +-----------+ +| | "UDP" | ++------------+-----------+ diff --git a/docs/utp_stack.png b/docs/utp_stack.png new file mode 100644 index 0000000000000000000000000000000000000000..ad524a4fe6271782f0e3672c59ed08d3b123c560 GIT binary patch literal 833 zcmeAS@N?(olHy`uVBq!ia0y~yU}Ofe6F8WF+^O(*+iJGWx`(69qGxvXg{zkOHdjgAx3S-y&U(VM{)7P@g zBvzXLNdFSaKI?WwX)#%{!nc3! zCfSVX_tw?e9z0|^b5^nS*-3^m$MYu_yr^57{GsT~!sEfaznuMiktyc>Kig-ThdHa= zPp4OuT>m-6b@Sr?edj{iU;C_2$og}7pUbS(&wpC(&kMv{V-y+P5+E_~*s@BeV14K1R%ctsXOBS;N7_w^<}Sm{cY)w5m8<`ZnXP z>%*B>42z>*%}6x$Y;X};01QI8e_M_^dC%Lm_WvpKOKN)zZPwiS=XP+#6nCC|v&<4F zetVePUwpsE>|yWBYYO{1_6T}bB!^}P)IEEg{Q6j5jO=_qg6GZ$?B!hT33ZVYXTY1!Vz+cE9?rV0>|Rk_eIzYD zqk8s*Ywx$cU%H09CU|4_LWv9siQM$rwXWbncvJ7uDvh2cWqqbd%;_)|6eTIc60m9dun@Zo?cO&JDJbuzWIim+dtMKYz78P z9rJsoJ0ET4s>C!!&v!?2*^crrskhu~v)@*fFBQ9_R?DYBe)|3PrkZo}DSY?|e*nxhR(0+gmAU_WR|YuYyA1?g6mK0D2ue^ABri XC)c)+jVt(oS%bmT)z4*}Q$iB}>&c2V literal 0 HcmV?d00001 diff --git a/docs/write_disk_buffers.diagram b/docs/write_disk_buffers.diagram new file mode 100644 index 0000000..24a3aa7 --- /dev/null +++ b/docs/write_disk_buffers.diagram @@ -0,0 +1,15 @@ + + "decrypt in place" "move buffer ref." ++------------------+ "(no copy)" +--------------+ "(no copy)" +--------------+ +| "receive buffer" +--=------------>| "plain text" +--=------------->| "disk cache" | ++------------------+ | "buffer" | +------+-------+ + ^ +--------------+ | + | "read() on socket" "write() to file" | + | "(copy)" "(copy)" | +---=----|---------------------------------=---------------------------------|--=---- + | "kernel space" | + | v ++-------+---------+ +---------------------+ +| "socket kernel" | | "kernel page cache" | +| "buffer" | | | ++-----------------+ +---------------------+ diff --git a/docs/write_disk_buffers.png b/docs/write_disk_buffers.png new file mode 100644 index 0000000000000000000000000000000000000000..92d1e3a6bb09cabfb89aa9cb04588986e1b3521e GIT binary patch literal 3127 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 I0D$)Y06UXIumAu6 literal 0 HcmV?d00001 diff --git a/ed25519/readme.md b/ed25519/readme.md new file mode 100644 index 0000000..b202892 --- /dev/null +++ b/ed25519/readme.md @@ -0,0 +1,165 @@ +Ed25519 +======= + +This is a portable implementation of [Ed25519](http://ed25519.cr.yp.to/) based +on the SUPERCOP "ref10" implementation. Additionally there is key exchanging +and scalar addition included to further aid building a PKI using Ed25519. All +code is in the public domain. + +All code is pure ANSI C without any dependencies, except for the random seed +generation which uses standard OS cryptography APIs (`CryptGenRandom` on +Windows, `/dev/urandom` on nix). If you wish to be entirely portable define +`ED25519_NO_SEED`. This disables the `ed25519_create_seed` function, so if your +application requires key generation you must supply your own seeding function +(which is simply a 256 bit (32 byte) cryptographic random number generator). + + +Performance +----------- + +On a Windows machine with an Intel Pentium B970 @ 2.3GHz I got the following +speeds (running on only one a single core): + + Seed generation: 64us (15625 per second) + Key generation: 88us (11364 per second) + Message signing (short message): 87us (11494 per second) + Message verifying (short message): 228us (4386 per second) + Scalar addition: 100us (10000 per second) + Key exchange: 220us (4545 per second) + +The speeds on other machines may vary. Sign/verify times will be higher with +longer messages. The implementation significantly benefits from 64 bit +architectures, if possible compile as 64 bit. + + +Usage +----- + +Simply add all .c and .h files in the `src/` folder to your project and include +`ed25519.h` in any file you want to use the API. If you prefer to use a shared +library, only copy `ed25519.h` and define `ED25519_DLL` before importing. A +windows DLL is pre-built. + +There are no defined types for seeds, private keys, public keys, shared secrets +or signatures. Instead simple `unsigned char` buffers are used with the +following sizes: + +```c +unsigned char seed[32]; +unsigned char signature[64]; +unsigned char public_key[32]; +unsigned char private_key[64]; +unsigned char scalar[32]; +unsigned char shared_secret[32]; +``` + +API +--- + +```c +int ed25519_create_seed(unsigned char *seed); +``` + +Creates a 32 byte random seed in `seed` for key generation. `seed` must be a +writable 32 byte buffer. Returns 0 on success, and nonzero on failure. + +```c +void ed25519_create_keypair(unsigned char *public_key, unsigned char *private_key, const unsigned char *seed); +``` + +Creates a new key pair from the given seed. `public_key` must be a writable 32 +byte buffer, `private_key` must be a writable 64 byte buffer and `seed` must be +a 32 byte buffer. + +```c +void ed25519_sign(unsigned char *signature, + const unsigned char *message, size_t message_len, + const unsigned char *public_key, const unsigned char *private_key); +``` + +Creates a signature of the given message with the given key pair. `signature` +must be a writable 64 byte buffer. `message` must have at least `message_len` +bytes to be read. + +```c +int ed25519_verify(const unsigned char *signature, + const unsigned char *message, size_t message_len, + const unsigned char *public_key); +``` + +Verifies the signature on the given message using `public_key`. `signature` +must be a readable 64 byte buffer. `message` must have at least `message_len` +bytes to be read. Returns 1 if the signature matches, 0 otherwise. + +```c +void ed25519_add_scalar(unsigned char *public_key, unsigned char *private_key, + const unsigned char *scalar); +``` + +Adds `scalar` to the given key pair where scalar is a 32 byte buffer (possibly +generated with `ed25519_create_seed`), generating a new key pair. You can +calculate the public key sum without knowing the private key and vice versa by +passing in `NULL` for the key you don't know. This is useful for enforcing +randomness on a key pair by a third party while only knowing the public key, +among other things. Warning: the last bit of the scalar is ignored - if +comparing scalars make sure to clear it with `scalar[31] &= 127`. + + +```c +void ed25519_key_exchange(unsigned char *shared_secret, + const unsigned char *public_key, const unsigned char *private_key); +``` + +Performs a key exchange on the given public key and private key, producing a +shared secret. It is recommended to hash the shared secret before using it. +`shared_secret` must be a 32 byte writable buffer where the shared secret will +be stored. + +Example +------- + +```c +unsigned char seed[32], public_key[32], private_key[64], signature[64]; +unsigned char other_public_key[32], other_private_key[64], shared_secret[32]; +const unsigned char message[] = "TEST MESSAGE"; + +/* create a random seed, and a key pair out of that seed */ +if (ed25519_create_seed(seed)) { + printf("error while generating seed\n"); + exit(1); +} + +ed25519_create_keypair(public_key, private_key, seed); + +/* create signature on the message with the key pair */ +ed25519_sign(signature, message, strlen(message), public_key, private_key); + +/* verify the signature */ +if (ed25519_verify(signature, message, strlen(message), public_key)) { + printf("valid signature\n"); +} else { + printf("invalid signature\n"); +} + +/* create a dummy keypair to use for a key exchange, normally you'd only have +the public key and receive it through some communication channel */ +if (ed25519_create_seed(seed)) { + printf("error while generating seed\n"); + exit(1); +} + +ed25519_create_keypair(other_public_key, other_private_key, seed); + +/* do a key exchange with other_public_key */ +ed25519_key_exchange(shared_secret, other_public_key, private_key); + +/* + the magic here is that ed25519_key_exchange(shared_secret, public_key, + other_private_key); would result in the same shared_secret +*/ + +``` + +License +------- +All code is in the public domain. diff --git a/ed25519/src/add_scalar.cpp b/ed25519/src/add_scalar.cpp new file mode 100644 index 0000000..c0e191a --- /dev/null +++ b/ed25519/src/add_scalar.cpp @@ -0,0 +1,59 @@ +// ignore warnings in this file +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include "libtorrent/ed25519.hpp" +#include "ge.h" +#include "sc.h" + + +/* 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); + } + + /* 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 wether or not we know the private + key, but this information seems rather useless - if this is important pass + public_key and private_key seperately in 2 function calls */ + if (private_key) { + ge_scalarmult_base(&A, private_key); + } else { + /* unpack public key into T */ + ge_frombytes_negate_vartime(&public_key_unpacked, public_key); + fe_neg(public_key_unpacked.X, public_key_unpacked.X); // undo negate + fe_neg(public_key_unpacked.T, public_key_unpacked.T); // undo negate + ge_p3_to_cached(&T, &public_key_unpacked); + + /* calculate n*B */ + ge_scalarmult_base(&nB, n); + + /* A = n*B + T */ + ge_add(&A_p1p1, &nB, &T); + ge_p1p1_to_p3(&A, &A_p1p1); + } + + /* pack public key */ + ge_p3_tobytes(public_key, &A); + } +} diff --git a/ed25519/src/fe.cpp b/ed25519/src/fe.cpp new file mode 100644 index 0000000..809f0c3 --- /dev/null +++ b/ed25519/src/fe.cpp @@ -0,0 +1,1504 @@ +// 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; +} + + + +/* + 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 -= carry9 << 25; + carry1 = (h1 + (i64) (1 << 24)) >> 25; + h2 += carry1; + h1 -= carry1 << 25; + carry3 = (h3 + (i64) (1 << 24)) >> 25; + h4 += carry3; + h3 -= carry3 << 25; + carry5 = (h5 + (i64) (1 << 24)) >> 25; + h6 += carry5; + h5 -= carry5 << 25; + carry7 = (h7 + (i64) (1 << 24)) >> 25; + h8 += carry7; + h7 -= carry7 << 25; + carry0 = (h0 + (i64) (1 << 25)) >> 26; + h1 += carry0; + h0 -= carry0 << 26; + carry2 = (h2 + (i64) (1 << 25)) >> 26; + h3 += carry2; + h2 -= carry2 << 26; + carry4 = (h4 + (i64) (1 << 25)) >> 26; + h5 += carry4; + h4 -= carry4 << 26; + carry6 = (h6 + (i64) (1 << 25)) >> 26; + h7 += carry6; + h6 -= carry6 << 26; + carry8 = (h8 + (i64) (1 << 25)) >> 26; + h9 += carry8; + h8 -= 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); + + for (i = 1; i < 1; ++i) { + fe_sq(t0, t0); + } + + 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); + + for (i = 1; i < 1; ++i) { + fe_sq(t2, t2); + } + + 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 -= carry0 << 26; + carry4 = (h4 + (i64) (1 << 25)) >> 26; + h5 += carry4; + h4 -= carry4 << 26; + + carry1 = (h1 + (i64) (1 << 24)) >> 25; + h2 += carry1; + h1 -= carry1 << 25; + carry5 = (h5 + (i64) (1 << 24)) >> 25; + h6 += carry5; + h5 -= carry5 << 25; + + carry2 = (h2 + (i64) (1 << 25)) >> 26; + h3 += carry2; + h2 -= carry2 << 26; + carry6 = (h6 + (i64) (1 << 25)) >> 26; + h7 += carry6; + h6 -= carry6 << 26; + + carry3 = (h3 + (i64) (1 << 24)) >> 25; + h4 += carry3; + h3 -= carry3 << 25; + carry7 = (h7 + (i64) (1 << 24)) >> 25; + h8 += carry7; + h7 -= carry7 << 25; + + carry4 = (h4 + (i64) (1 << 25)) >> 26; + h5 += carry4; + h4 -= carry4 << 26; + carry8 = (h8 + (i64) (1 << 25)) >> 26; + h9 += carry8; + h8 -= carry8 << 26; + + carry9 = (h9 + (i64) (1 << 24)) >> 25; + h0 += carry9 * 19; + h9 -= carry9 << 25; + + carry0 = (h0 + (i64) (1 << 25)) >> 26; + h1 += carry0; + h0 -= 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 -= carry9 << 25; + carry1 = (h1 + (i64) (1<<24)) >> 25; h2 += carry1; h1 -= carry1 << 25; + carry3 = (h3 + (i64) (1<<24)) >> 25; h4 += carry3; h3 -= carry3 << 25; + carry5 = (h5 + (i64) (1<<24)) >> 25; h6 += carry5; h5 -= carry5 << 25; + carry7 = (h7 + (i64) (1<<24)) >> 25; h8 += carry7; h7 -= carry7 << 25; + + carry0 = (h0 + (i64) (1<<25)) >> 26; h1 += carry0; h0 -= carry0 << 26; + carry2 = (h2 + (i64) (1<<25)) >> 26; h3 += carry2; h2 -= carry2 << 26; + carry4 = (h4 + (i64) (1<<25)) >> 26; h5 += carry4; h4 -= carry4 << 26; + carry6 = (h6 + (i64) (1<<25)) >> 26; h7 += carry6; h6 -= carry6 << 26; + carry8 = (h8 + (i64) (1<<25)) >> 26; h9 += carry8; h8 -= 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); + + for (i = 1; i < 1; ++i) { + fe_sq(t0, t0); + } + + 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); + + for (i = 1; i < 1; ++i) { + 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 -= carry0 << 26; + carry4 = (h4 + (i64) (1 << 25)) >> 26; + h5 += carry4; + h4 -= carry4 << 26; + carry1 = (h1 + (i64) (1 << 24)) >> 25; + h2 += carry1; + h1 -= carry1 << 25; + carry5 = (h5 + (i64) (1 << 24)) >> 25; + h6 += carry5; + h5 -= carry5 << 25; + carry2 = (h2 + (i64) (1 << 25)) >> 26; + h3 += carry2; + h2 -= carry2 << 26; + carry6 = (h6 + (i64) (1 << 25)) >> 26; + h7 += carry6; + h6 -= carry6 << 26; + carry3 = (h3 + (i64) (1 << 24)) >> 25; + h4 += carry3; + h3 -= carry3 << 25; + carry7 = (h7 + (i64) (1 << 24)) >> 25; + h8 += carry7; + h7 -= carry7 << 25; + carry4 = (h4 + (i64) (1 << 25)) >> 26; + h5 += carry4; + h4 -= carry4 << 26; + carry8 = (h8 + (i64) (1 << 25)) >> 26; + h9 += carry8; + h8 -= carry8 << 26; + carry9 = (h9 + (i64) (1 << 24)) >> 25; + h0 += carry9 * 19; + h9 -= carry9 << 25; + carry0 = (h0 + (i64) (1 << 25)) >> 26; + h1 += carry0; + h0 -= 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 -= carry0 << 26; + carry4 = (h4 + (i64) (1 << 25)) >> 26; + h5 += carry4; + h4 -= carry4 << 26; + carry1 = (h1 + (i64) (1 << 24)) >> 25; + h2 += carry1; + h1 -= carry1 << 25; + carry5 = (h5 + (i64) (1 << 24)) >> 25; + h6 += carry5; + h5 -= carry5 << 25; + carry2 = (h2 + (i64) (1 << 25)) >> 26; + h3 += carry2; + h2 -= carry2 << 26; + carry6 = (h6 + (i64) (1 << 25)) >> 26; + h7 += carry6; + h6 -= carry6 << 26; + carry3 = (h3 + (i64) (1 << 24)) >> 25; + h4 += carry3; + h3 -= carry3 << 25; + carry7 = (h7 + (i64) (1 << 24)) >> 25; + h8 += carry7; + h7 -= carry7 << 25; + carry4 = (h4 + (i64) (1 << 25)) >> 26; + h5 += carry4; + h4 -= carry4 << 26; + carry8 = (h8 + (i64) (1 << 25)) >> 26; + h9 += carry8; + h8 -= carry8 << 26; + carry9 = (h9 + (i64) (1 << 24)) >> 25; + h0 += carry9 * 19; + h9 -= carry9 << 25; + carry0 = (h0 + (i64) (1 << 25)) >> 26; + h1 += carry0; + h0 -= 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 -= carry0 << 26; + carry1 = h1 >> 25; + h2 += carry1; + h1 -= carry1 << 25; + carry2 = h2 >> 26; + h3 += carry2; + h2 -= carry2 << 26; + carry3 = h3 >> 25; + h4 += carry3; + h3 -= carry3 << 25; + carry4 = h4 >> 26; + h5 += carry4; + h4 -= carry4 << 26; + carry5 = h5 >> 25; + h6 += carry5; + h5 -= carry5 << 25; + carry6 = h6 >> 26; + h7 += carry6; + h6 -= carry6 << 26; + carry7 = h7 >> 25; + h8 += carry7; + h7 -= carry7 << 25; + carry8 = h8 >> 26; + h9 += carry8; + h8 -= carry8 << 26; + carry9 = h9 >> 25; + h9 -= carry9 << 25; + + /* h10 = carry9 */ + /* + Goal: Output h0+...+2^255 h10-2^255 q, which is between 0 and 2^255-20. + Have h0+...+2^230 h9 between 0 and 2^255-1; + evidently 2^255 h10-2^255 q = 0. + Goal: Output h0+...+2^230 h9. + */ + s[0] = (unsigned char) ((h0 >> 0) & 0xff); + s[1] = (unsigned char) ((h0 >> 8) & 0xff); + s[2] = (unsigned char) ((h0 >> 16) & 0xff); + s[3] = (unsigned char) (((h0 >> 24) | (h1 << 2)) & 0xff); + s[4] = (unsigned char) ((h1 >> 6) & 0xff); + s[5] = (unsigned char) ((h1 >> 14) & 0xff); + s[6] = (unsigned char) (((h1 >> 22) | (h2 << 3)) & 0xff); + s[7] = (unsigned char) ((h2 >> 5) & 0xff); + s[8] = (unsigned char) ((h2 >> 13) & 0xff); + s[9] = (unsigned char) (((h2 >> 21) | (h3 << 5)) & 0xff); + s[10] = (unsigned char) ((h3 >> 3) & 0xff); + s[11] = (unsigned char) ((h3 >> 11) & 0xff); + s[12] = (unsigned char) (((h3 >> 19) | (h4 << 6)) & 0xff); + s[13] = (unsigned char) ((h4 >> 2) & 0xff); + s[14] = (unsigned char) ((h4 >> 10) & 0xff); + s[15] = (unsigned char) ((h4 >> 18) & 0xff); + s[16] = (unsigned char) ((h5 >> 0) & 0xff); + s[17] = (unsigned char) ((h5 >> 8) & 0xff); + s[18] = (unsigned char) ((h5 >> 16) & 0xff); + s[19] = (unsigned char) (((h5 >> 24) | (h6 << 1)) & 0xff); + s[20] = (unsigned char) ((h6 >> 7) & 0xff); + s[21] = (unsigned char) ((h6 >> 15) & 0xff); + s[22] = (unsigned char) (((h6 >> 23) | (h7 << 3)) & 0xff); + s[23] = (unsigned char) ((h7 >> 5) & 0xff); + s[24] = (unsigned char) ((h7 >> 13) & 0xff); + s[25] = (unsigned char) (((h7 >> 21) | (h8 << 4)) & 0xff); + s[26] = (unsigned char) ((h8 >> 4) & 0xff); + s[27] = (unsigned char) ((h8 >> 12) & 0xff); + s[28] = (unsigned char) (((h8 >> 20) | (h9 << 6)) & 0xff); + s[29] = (unsigned char) ((h9 >> 2) & 0xff); + s[30] = (unsigned char) ((h9 >> 10) & 0xff); + s[31] = (unsigned char) ((h9 >> 18) & 0xff); +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif // _MSC_VER + diff --git a/ed25519/src/fe.h b/ed25519/src/fe.h new file mode 100644 index 0000000..fbe47dc --- /dev/null +++ b/ed25519/src/fe.h @@ -0,0 +1,41 @@ +#ifndef FE_H +#define FE_H + +#include "fixedint.h" + + +/* + fe means field element. + Here the field is \Z/(2^255-19). + An element t, entries t[0]...t[9], represents the integer + t[0]+2^26 t[1]+2^51 t[2]+2^77 t[3]+2^102 t[4]+...+2^230 t[9]. + Bounds on each t[i] vary depending on context. +*/ + + +typedef i32 fe[10]; + + +void fe_0(fe h); +void fe_1(fe h); + +void fe_frombytes(fe h, const unsigned char *s); +void fe_tobytes(unsigned char *s, const fe h); + +void fe_copy(fe h, const fe f); +int fe_isnegative(const fe f); +int fe_isnonzero(const fe f); +void fe_cmov(fe f, const fe g, unsigned int b); +void fe_cswap(fe f, fe g, unsigned int b); + +void fe_neg(fe h, const fe f); +void fe_add(fe h, const fe f, const fe g); +void fe_invert(fe out, const fe z); +void fe_sq(fe h, const fe f); +void fe_sq2(fe h, const fe f); +void fe_mul(fe h, const fe f, const fe g); +void fe_mul121666(fe h, fe f); +void fe_pow22523(fe out, const fe z); +void fe_sub(fe h, const fe f, const fe g); + +#endif diff --git a/ed25519/src/fixedint.h b/ed25519/src/fixedint.h new file mode 100644 index 0000000..3032a95 --- /dev/null +++ b/ed25519/src/fixedint.h @@ -0,0 +1,6 @@ +#include + +typedef boost::uint64_t u64; +typedef boost::int64_t i64; +typedef boost::int32_t i32; + diff --git a/ed25519/src/ge.cpp b/ed25519/src/ge.cpp new file mode 100644 index 0000000..c6fc004 --- /dev/null +++ b/ed25519/src/ge.cpp @@ -0,0 +1,470 @@ +// 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, 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) { + ge_precomp minust; + unsigned char bnegative = negative(b); + unsigned char babs = b - (((-bnegative) & b) << 1); + fe_1(t->yplusx); + fe_1(t->yminusx); + fe_0(t->xy2d); + cmov(t, &base[pos][0], equal(babs, 1)); + cmov(t, &base[pos][1], equal(babs, 2)); + cmov(t, &base[pos][2], equal(babs, 3)); + cmov(t, &base[pos][3], equal(babs, 4)); + cmov(t, &base[pos][4], equal(babs, 5)); + cmov(t, &base[pos][5], equal(babs, 6)); + cmov(t, &base[pos][6], equal(babs, 7)); + cmov(t, &base[pos][7], equal(babs, 8)); + fe_copy(minust.yplusx, t->yminusx); + fe_copy(minust.yminusx, t->yplusx); + fe_neg(minust.xy2d, t->xy2d); + cmov(t, &minust, bnegative); +} + +/* +h = a * B +where a = a[0]+256*a[1]+...+256^31 a[31] +B is the Ed25519 base point (x,4/5) with x positive. + +Preconditions: + a[31] <= 127 +*/ + +void ge_scalarmult_base(ge_p3 *h, const unsigned char *a) { + signed char e[64]; + signed char carry; + ge_p1p1 r; + ge_p2 s; + ge_precomp t; + int i; + + for (i = 0; i < 32; ++i) { + e[2 * i + 0] = (a[i] >> 0) & 15; + e[2 * i + 1] = (a[i] >> 4) & 15; + } + + /* each e[i] is between 0 and 15 */ + /* e[63] is between 0 and 7 */ + carry = 0; + + for (i = 0; i < 63; ++i) { + e[i] += carry; + carry = e[i] + 8; + carry >>= 4; + e[i] -= carry << 4; + } + + e[63] += carry; + /* each e[i] is between -8 and 8 */ + ge_p3_0(h); + + for (i = 1; i < 64; i += 2) { + select(&t, i / 2, e[i]); + ge_madd(&r, h, &t); + ge_p1p1_to_p3(h, &r); + } + + ge_p3_dbl(&r, h); + ge_p1p1_to_p2(&s, &r); + ge_p2_dbl(&r, &s); + ge_p1p1_to_p2(&s, &r); + ge_p2_dbl(&r, &s); + ge_p1p1_to_p2(&s, &r); + ge_p2_dbl(&r, &s); + ge_p1p1_to_p3(h, &r); + + for (i = 0; i < 64; i += 2) { + select(&t, i / 2, e[i]); + ge_madd(&r, h, &t); + ge_p1p1_to_p3(h, &r); + } +} + + +/* +r = p - q +*/ + +void ge_sub(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q) { + fe t0; + + fe_add(r->X, p->Y, p->X); + fe_sub(r->Y, p->Y, p->X); + fe_mul(r->Z, r->X, q->YminusX); + fe_mul(r->Y, r->Y, q->YplusX); + fe_mul(r->T, q->T2d, p->T); + fe_mul(r->X, p->Z, q->Z); + fe_add(t0, r->X, r->X); + fe_sub(r->X, r->Z, r->Y); + fe_add(r->Y, r->Z, r->Y); + fe_sub(r->Z, t0, r->T); + fe_add(r->T, t0, r->T); +} + + +void ge_tobytes(unsigned char *s, const ge_p2 *h) { + fe recip; + fe x; + fe y; + fe_invert(recip, h->Z); + fe_mul(x, h->X, recip); + fe_mul(y, h->Y, recip); + fe_tobytes(s, y); + s[31] ^= fe_isnegative(x) << 7; +} diff --git a/ed25519/src/ge.h b/ed25519/src/ge.h new file mode 100644 index 0000000..17fde2d --- /dev/null +++ b/ed25519/src/ge.h @@ -0,0 +1,74 @@ +#ifndef GE_H +#define GE_H + +#include "fe.h" + + +/* +ge means group element. + +Here the group is the set of pairs (x,y) of field elements (see fe.h) +satisfying -x^2 + y^2 = 1 + d x^2y^2 +where d = -121665/121666. + +Representations: + ge_p2 (projective): (X:Y:Z) satisfying x=X/Z, y=Y/Z + ge_p3 (extended): (X:Y:Z:T) satisfying x=X/Z, y=Y/Z, XY=ZT + ge_p1p1 (completed): ((X:Z),(Y:T)) satisfying x=X/Z, y=Y/T + ge_precomp (Duif): (y+x,y-x,2dxy) +*/ + +typedef struct { + fe X; + fe Y; + fe Z; +} ge_p2; + +typedef struct { + fe X; + fe Y; + fe Z; + fe T; +} ge_p3; + +typedef struct { + fe X; + fe Y; + fe Z; + fe T; +} ge_p1p1; + +typedef struct { + fe yplusx; + fe yminusx; + fe xy2d; +} ge_precomp; + +typedef struct { + fe YplusX; + fe YminusX; + fe Z; + fe T2d; +} ge_cached; + +void ge_p3_tobytes(unsigned char *s, const ge_p3 *h); +void ge_tobytes(unsigned char *s, const ge_p2 *h); +int ge_frombytes_negate_vartime(ge_p3 *h, const unsigned char *s); + +void ge_add(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q); +void ge_sub(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q); +void ge_double_scalarmult_vartime(ge_p2 *r, const unsigned char *a, const ge_p3 *A, const unsigned char *b); +void ge_madd(ge_p1p1 *r, const ge_p3 *p, const ge_precomp *q); +void ge_msub(ge_p1p1 *r, const ge_p3 *p, const ge_precomp *q); +void ge_scalarmult_base(ge_p3 *h, const unsigned char *a); + +void ge_p1p1_to_p2(ge_p2 *r, const ge_p1p1 *p); +void ge_p1p1_to_p3(ge_p3 *r, const ge_p1p1 *p); +void ge_p2_0(ge_p2 *h); +void ge_p2_dbl(ge_p1p1 *r, const ge_p2 *p); +void ge_p3_0(ge_p3 *h); +void ge_p3_dbl(ge_p1p1 *r, const ge_p3 *p); +void ge_p3_to_cached(ge_cached *r, const ge_p3 *p); +void ge_p3_to_p2(ge_p2 *r, const ge_p3 *p); + +#endif diff --git a/ed25519/src/key_exchange.cpp b/ed25519/src/key_exchange.cpp new file mode 100644 index 0000000..1d728ed --- /dev/null +++ b/ed25519/src/key_exchange.cpp @@ -0,0 +1,83 @@ +// ignore warnings in this file +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include "libtorrent/ed25519.hpp" +#include "fe.h" + +void ed25519_key_exchange(unsigned char *shared_secret + , const unsigned char *public_key, const unsigned char *private_key) { + unsigned char e[32]; + unsigned int i; + + fe x1; + fe x2; + fe z2; + fe x3; + fe z3; + fe tmp0; + fe tmp1; + + int pos; + unsigned int swap; + unsigned int b; + + /* copy the private key and make sure it's valid */ + for (i = 0; i < 32; ++i) { + e[i] = private_key[i]; + } + + e[0] &= 248; + e[31] &= 63; + e[31] |= 64; + + /* unpack the public key and convert edwards to montgomery */ + /* due to CodesInChaos: montgomeryX = (edwardsY + 1)*inverse(1 - edwardsY) mod p */ + fe_frombytes(x1, public_key); + fe_1(tmp1); + fe_add(tmp0, x1, tmp1); + fe_sub(tmp1, tmp1, x1); + fe_invert(tmp1, tmp1); + fe_mul(x1, tmp0, tmp1); + + fe_1(x2); + fe_0(z2); + fe_copy(x3, x1); + fe_1(z3); + + swap = 0; + for (pos = 254; pos >= 0; --pos) { + b = e[pos / 8] >> (pos & 7); + b &= 1; + swap ^= b; + fe_cswap(x2, x3, swap); + fe_cswap(z2, z3, swap); + swap = b; + + /* from montgomery.h */ + fe_sub(tmp0, x3, z3); + fe_sub(tmp1, x2, z2); + fe_add(x2, x2, z2); + fe_add(z2, x3, z3); + fe_mul(z3, tmp0, x2); + fe_mul(z2, z2, tmp1); + fe_sq(tmp0, tmp1); + fe_sq(tmp1, x2); + fe_add(x3, z3, z2); + fe_sub(z2, z3, z2); + fe_mul(x2, tmp1, tmp0); + fe_sub(tmp1, tmp1, tmp0); + fe_sq(z2, z2); + fe_mul121666(z3, tmp1); + fe_sq(x3, x3); + fe_add(tmp0, tmp0, z3); + fe_mul(z3, x1, z2); + fe_mul(z2, tmp1, tmp0); + } + + fe_cswap(x2, x3, swap); + fe_cswap(z2, z3, swap); + + fe_invert(z2, z2); + fe_mul(x2, x2, z2); + fe_tobytes(shared_secret, x2); +} diff --git a/ed25519/src/keypair.cpp b/ed25519/src/keypair.cpp new file mode 100644 index 0000000..90d8dbc --- /dev/null +++ b/ed25519/src/keypair.cpp @@ -0,0 +1,19 @@ +// ignore warnings in this file +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include "libtorrent/ed25519.hpp" +#include "sha512.h" +#include "ge.h" + + +void ed25519_create_keypair(unsigned char *public_key, unsigned char *private_key, const unsigned char *seed) { + ge_p3 A; + + sha512(seed, 32, private_key); + private_key[0] &= 248; + private_key[31] &= 63; + private_key[31] |= 64; + + ge_scalarmult_base(&A, private_key); + ge_p3_tobytes(public_key, &A); +} diff --git a/ed25519/src/precomp_data.h b/ed25519/src/precomp_data.h new file mode 100644 index 0000000..ea00e4c --- /dev/null +++ b/ed25519/src/precomp_data.h @@ -0,0 +1,1392 @@ +static 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 ge_precomp base[32][8] = { + { + { + { 25967493, -14356035, 29566456, 3660896, -12694345, 4014787, 27544626, -11754271, -6079156, 2047605 }, + { -12545711, 934262, -2722910, 3049990, -727428, 9406986, 12720692, 5043384, 19500929, -15469378 }, + { -8738181, 4489570, 9688441, -14785194, 10184609, -12363380, 29287919, 11864899, -24514362, -4438546 }, + }, + { + { -12815894, -12976347, -21581243, 11784320, -25355658, -2750717, -11717903, -3814571, -358445, -10211303 }, + { -21703237, 6903825, 27185491, 6451973, -29577724, -9554005, -15616551, 11189268, -26829678, -5319081 }, + { 26966642, 11152617, 32442495, 15396054, 14353839, -12752335, -3128826, -9541118, -15472047, -4166697 }, + }, + { + { 15636291, -9688557, 24204773, -7912398, 616977, -16685262, 27787600, -14772189, 28944400, -1550024 }, + { 16568933, 4717097, -11556148, -1102322, 15682896, -11807043, 16354577, -11775962, 7689662, 11199574 }, + { 30464156, -5976125, -11779434, -15670865, 23220365, 15915852, 7512774, 10017326, -17749093, -9920357 }, + }, + { + { -17036878, 13921892, 10945806, -6033431, 27105052, -16084379, -28926210, 15006023, 3284568, -6276540 }, + { 23599295, -8306047, -11193664, -7687416, 13236774, 10506355, 7464579, 9656445, 13059162, 10374397 }, + { 7798556, 16710257, 3033922, 2874086, 28997861, 2835604, 32406664, -3839045, -641708, -101325 }, + }, + { + { 10861363, 11473154, 27284546, 1981175, -30064349, 12577861, 32867885, 14515107, -15438304, 10819380 }, + { 4708026, 6336745, 20377586, 9066809, -11272109, 6594696, -25653668, 12483688, -12668491, 5581306 }, + { 19563160, 16186464, -29386857, 4097519, 10237984, -4348115, 28542350, 13850243, -23678021, -15815942 }, + }, + { + { -15371964, -12862754, 32573250, 4720197, -26436522, 5875511, -19188627, -15224819, -9818940, -12085777 }, + { -8549212, 109983, 15149363, 2178705, 22900618, 4543417, 3044240, -15689887, 1762328, 14866737 }, + { -18199695, -15951423, -10473290, 1707278, -17185920, 3916101, -28236412, 3959421, 27914454, 4383652 }, + }, + { + { 5153746, 9909285, 1723747, -2777874, 30523605, 5516873, 19480852, 5230134, -23952439, -15175766 }, + { -30269007, -3463509, 7665486, 10083793, 28475525, 1649722, 20654025, 16520125, 30598449, 7715701 }, + { 28881845, 14381568, 9657904, 3680757, -20181635, 7843316, -31400660, 1370708, 29794553, -1409300 }, + }, + { + { 14499471, -2729599, -33191113, -4254652, 28494862, 14271267, 30290735, 10876454, -33154098, 2381726 }, + { -7195431, -2655363, -14730155, 462251, -27724326, 3941372, -6236617, 3696005, -32300832, 15351955 }, + { 27431194, 8222322, 16448760, -3907995, -18707002, 11938355, -32961401, -2970515, 29551813, 10109425 }, + }, + }, + { + { + { -13657040, -13155431, -31283750, 11777098, 21447386, 6519384, -2378284, -1627556, 10092783, -4764171 }, + { 27939166, 14210322, 4677035, 16277044, -22964462, -12398139, -32508754, 12005538, -17810127, 12803510 }, + { 17228999, -15661624, -1233527, 300140, -1224870, -11714777, 30364213, -9038194, 18016357, 4397660 }, + }, + { + { -10958843, -7690207, 4776341, -14954238, 27850028, -15602212, -26619106, 14544525, -17477504, 982639 }, + { 29253598, 15796703, -2863982, -9908884, 10057023, 3163536, 7332899, -4120128, -21047696, 9934963 }, + { 5793303, 16271923, -24131614, -10116404, 29188560, 1206517, -14747930, 4559895, -30123922, -10897950 }, + }, + { + { -27643952, -11493006, 16282657, -11036493, 28414021, -15012264, 24191034, 4541697, -13338309, 5500568 }, + { 12650548, -1497113, 9052871, 11355358, -17680037, -8400164, -17430592, 12264343, 10874051, 13524335 }, + { 25556948, -3045990, 714651, 2510400, 23394682, -10415330, 33119038, 5080568, -22528059, 5376628 }, + }, + { + { -26088264, -4011052, -17013699, -3537628, -6726793, 1920897, -22321305, -9447443, 4535768, 1569007 }, + { -2255422, 14606630, -21692440, -8039818, 28430649, 8775819, -30494562, 3044290, 31848280, 12543772 }, + { -22028579, 2943893, -31857513, 6777306, 13784462, -4292203, -27377195, -2062731, 7718482, 14474653 }, + }, + { + { 2385315, 2454213, -22631320, 46603, -4437935, -15680415, 656965, -7236665, 24316168, -5253567 }, + { 13741529, 10911568, -33233417, -8603737, -20177830, -1033297, 33040651, -13424532, -20729456, 8321686 }, + { 21060490, -2212744, 15712757, -4336099, 1639040, 10656336, 23845965, -11874838, -9984458, 608372 }, + }, + { + { -13672732, -15087586, -10889693, -7557059, -6036909, 11305547, 1123968, -6780577, 27229399, 23887 }, + { -23244140, -294205, -11744728, 14712571, -29465699, -2029617, 12797024, -6440308, -1633405, 16678954 }, + { -29500620, 4770662, -16054387, 14001338, 7830047, 9564805, -1508144, -4795045, -17169265, 4904953 }, + }, + { + { 24059557, 14617003, 19037157, -15039908, 19766093, -14906429, 5169211, 16191880, 2128236, -4326833 }, + { -16981152, 4124966, -8540610, -10653797, 30336522, -14105247, -29806336, 916033, -6882542, -2986532 }, + { -22630907, 12419372, -7134229, -7473371, -16478904, 16739175, 285431, 2763829, 15736322, 4143876 }, + }, + { + { 2379352, 11839345, -4110402, -5988665, 11274298, 794957, 212801, -14594663, 23527084, -16458268 }, + { 33431127, -11130478, -17838966, -15626900, 8909499, 8376530, -32625340, 4087881, -15188911, -14416214 }, + { 1767683, 7197987, -13205226, -2022635, -13091350, 448826, 5799055, 4357868, -4774191, -16323038 }, + }, + }, + { + { + { 6721966, 13833823, -23523388, -1551314, 26354293, -11863321, 23365147, -3949732, 7390890, 2759800 }, + { 4409041, 2052381, 23373853, 10530217, 7676779, -12885954, 21302353, -4264057, 1244380, -12919645 }, + { -4421239, 7169619, 4982368, -2957590, 30256825, -2777540, 14086413, 9208236, 15886429, 16489664 }, + }, + { + { 1996075, 10375649, 14346367, 13311202, -6874135, -16438411, -13693198, 398369, -30606455, -712933 }, + { -25307465, 9795880, -2777414, 14878809, -33531835, 14780363, 13348553, 12076947, -30836462, 5113182 }, + { -17770784, 11797796, 31950843, 13929123, -25888302, 12288344, -30341101, -7336386, 13847711, 5387222 }, + }, + { + { -18582163, -3416217, 17824843, -2340966, 22744343, -10442611, 8763061, 3617786, -19600662, 10370991 }, + { 20246567, -14369378, 22358229, -543712, 18507283, -10413996, 14554437, -8746092, 32232924, 16763880 }, + { 9648505, 10094563, 26416693, 14745928, -30374318, -6472621, 11094161, 15689506, 3140038, -16510092 }, + }, + { + { -16160072, 5472695, 31895588, 4744994, 8823515, 10365685, -27224800, 9448613, -28774454, 366295 }, + { 19153450, 11523972, -11096490, -6503142, -24647631, 5420647, 28344573, 8041113, 719605, 11671788 }, + { 8678025, 2694440, -6808014, 2517372, 4964326, 11152271, -15432916, -15266516, 27000813, -10195553 }, + }, + { + { -15157904, 7134312, 8639287, -2814877, -7235688, 10421742, 564065, 5336097, 6750977, -14521026 }, + { 11836410, -3979488, 26297894, 16080799, 23455045, 15735944, 1695823, -8819122, 8169720, 16220347 }, + { -18115838, 8653647, 17578566, -6092619, -8025777, -16012763, -11144307, -2627664, -5990708, -14166033 }, + }, + { + { -23308498, -10968312, 15213228, -10081214, -30853605, -11050004, 27884329, 2847284, 2655861, 1738395 }, + { -27537433, -14253021, -25336301, -8002780, -9370762, 8129821, 21651608, -3239336, -19087449, -11005278 }, + { 1533110, 3437855, 23735889, 459276, 29970501, 11335377, 26030092, 5821408, 10478196, 8544890 }, + }, + { + { 32173121, -16129311, 24896207, 3921497, 22579056, -3410854, 19270449, 12217473, 17789017, -3395995 }, + { -30552961, -2228401, -15578829, -10147201, 13243889, 517024, 15479401, -3853233, 30460520, 1052596 }, + { -11614875, 13323618, 32618793, 8175907, -15230173, 12596687, 27491595, -4612359, 3179268, -9478891 }, + }, + { + { 31947069, -14366651, -4640583, -15339921, -15125977, -6039709, -14756777, -16411740, 19072640, -9511060 }, + { 11685058, 11822410, 3158003, -13952594, 33402194, -4165066, 5977896, -5215017, 473099, 5040608 }, + { -20290863, 8198642, -27410132, 11602123, 1290375, -2799760, 28326862, 1721092, -19558642, -3131606 }, + }, + }, + { + { + { 7881532, 10687937, 7578723, 7738378, -18951012, -2553952, 21820786, 8076149, -27868496, 11538389 }, + { -19935666, 3899861, 18283497, -6801568, -15728660, -11249211, 8754525, 7446702, -5676054, 5797016 }, + { -11295600, -3793569, -15782110, -7964573, 12708869, -8456199, 2014099, -9050574, -2369172, -5877341 }, + }, + { + { -22472376, -11568741, -27682020, 1146375, 18956691, 16640559, 1192730, -3714199, 15123619, 10811505 }, + { 14352098, -3419715, -18942044, 10822655, 32750596, 4699007, -70363, 15776356, -28886779, -11974553 }, + { -28241164, -8072475, -4978962, -5315317, 29416931, 1847569, -20654173, -16484855, 4714547, -9600655 }, + }, + { + { 15200332, 8368572, 19679101, 15970074, -31872674, 1959451, 24611599, -4543832, -11745876, 12340220 }, + { 12876937, -10480056, 33134381, 6590940, -6307776, 14872440, 9613953, 8241152, 15370987, 9608631 }, + { -4143277, -12014408, 8446281, -391603, 4407738, 13629032, -7724868, 15866074, -28210621, -8814099 }, + }, + { + { 26660628, -15677655, 8393734, 358047, -7401291, 992988, -23904233, 858697, 20571223, 8420556 }, + { 14620715, 13067227, -15447274, 8264467, 14106269, 15080814, 33531827, 12516406, -21574435, -12476749 }, + { 236881, 10476226, 57258, -14677024, 6472998, 2466984, 17258519, 7256740, 8791136, 15069930 }, + }, + { + { 1276410, -9371918, 22949635, -16322807, -23493039, -5702186, 14711875, 4874229, -30663140, -2331391 }, + { 5855666, 4990204, -13711848, 7294284, -7804282, 1924647, -1423175, -7912378, -33069337, 9234253 }, + { 20590503, -9018988, 31529744, -7352666, -2706834, 10650548, 31559055, -11609587, 18979186, 13396066 }, + }, + { + { 24474287, 4968103, 22267082, 4407354, 24063882, -8325180, -18816887, 13594782, 33514650, 7021958 }, + { -11566906, -6565505, -21365085, 15928892, -26158305, 4315421, -25948728, -3916677, -21480480, 12868082 }, + { -28635013, 13504661, 19988037, -2132761, 21078225, 6443208, -21446107, 2244500, -12455797, -8089383 }, + }, + { + { -30595528, 13793479, -5852820, 319136, -25723172, -6263899, 33086546, 8957937, -15233648, 5540521 }, + { -11630176, -11503902, -8119500, -7643073, 2620056, 1022908, -23710744, -1568984, -16128528, -14962807 }, + { 23152971, 775386, 27395463, 14006635, -9701118, 4649512, 1689819, 892185, -11513277, -15205948 }, + }, + { + { 9770129, 9586738, 26496094, 4324120, 1556511, -3550024, 27453819, 4763127, -19179614, 5867134 }, + { -32765025, 1927590, 31726409, -4753295, 23962434, -16019500, 27846559, 5931263, -29749703, -16108455 }, + { 27461885, -2977536, 22380810, 1815854, -23033753, -3031938, 7283490, -15148073, -19526700, 7734629 }, + }, + }, + { + { + { -8010264, -9590817, -11120403, 6196038, 29344158, -13430885, 7585295, -3176626, 18549497, 15302069 }, + { -32658337, -6171222, -7672793, -11051681, 6258878, 13504381, 10458790, -6418461, -8872242, 8424746 }, + { 24687205, 8613276, -30667046, -3233545, 1863892, -1830544, 19206234, 7134917, -11284482, -828919 }, + }, + { + { 11334899, -9218022, 8025293, 12707519, 17523892, -10476071, 10243738, -14685461, -5066034, 16498837 }, + { 8911542, 6887158, -9584260, -6958590, 11145641, -9543680, 17303925, -14124238, 6536641, 10543906 }, + { -28946384, 15479763, -17466835, 568876, -1497683, 11223454, -2669190, -16625574, -27235709, 8876771 }, + }, + { + { -25742899, -12566864, -15649966, -846607, -33026686, -796288, -33481822, 15824474, -604426, -9039817 }, + { 10330056, 70051, 7957388, -9002667, 9764902, 15609756, 27698697, -4890037, 1657394, 3084098 }, + { 10477963, -7470260, 12119566, -13250805, 29016247, -5365589, 31280319, 14396151, -30233575, 15272409 }, + }, + { + { -12288309, 3169463, 28813183, 16658753, 25116432, -5630466, -25173957, -12636138, -25014757, 1950504 }, + { -26180358, 9489187, 11053416, -14746161, -31053720, 5825630, -8384306, -8767532, 15341279, 8373727 }, + { 28685821, 7759505, -14378516, -12002860, -31971820, 4079242, 298136, -10232602, -2878207, 15190420 }, + }, + { + { -32932876, 13806336, -14337485, -15794431, -24004620, 10940928, 8669718, 2742393, -26033313, -6875003 }, + { -1580388, -11729417, -25979658, -11445023, -17411874, -10912854, 9291594, -16247779, -12154742, 6048605 }, + { -30305315, 14843444, 1539301, 11864366, 20201677, 1900163, 13934231, 5128323, 11213262, 9168384 }, + }, + { + { -26280513, 11007847, 19408960, -940758, -18592965, -4328580, -5088060, -11105150, 20470157, -16398701 }, + { -23136053, 9282192, 14855179, -15390078, -7362815, -14408560, -22783952, 14461608, 14042978, 5230683 }, + { 29969567, -2741594, -16711867, -8552442, 9175486, -2468974, 21556951, 3506042, -5933891, -12449708 }, + }, + { + { -3144746, 8744661, 19704003, 4581278, -20430686, 6830683, -21284170, 8971513, -28539189, 15326563 }, + { -19464629, 10110288, -17262528, -3503892, -23500387, 1355669, -15523050, 15300988, -20514118, 9168260 }, + { -5353335, 4488613, -23803248, 16314347, 7780487, -15638939, -28948358, 9601605, 33087103, -9011387 }, + }, + { + { -19443170, -15512900, -20797467, -12445323, -29824447, 10229461, -27444329, -15000531, -5996870, 15664672 }, + { 23294591, -16632613, -22650781, -8470978, 27844204, 11461195, 13099750, -2460356, 18151676, 13417686 }, + { -24722913, -4176517, -31150679, 5988919, -26858785, 6685065, 1661597, -12551441, 15271676, -15452665 }, + }, + }, + { + { + { 11433042, -13228665, 8239631, -5279517, -1985436, -725718, -18698764, 2167544, -6921301, -13440182 }, + { -31436171, 15575146, 30436815, 12192228, -22463353, 9395379, -9917708, -8638997, 12215110, 12028277 }, + { 14098400, 6555944, 23007258, 5757252, -15427832, -12950502, 30123440, 4617780, -16900089, -655628 }, + }, + { + { -4026201, -15240835, 11893168, 13718664, -14809462, 1847385, -15819999, 10154009, 23973261, -12684474 }, + { -26531820, -3695990, -1908898, 2534301, -31870557, -16550355, 18341390, -11419951, 32013174, -10103539 }, + { -25479301, 10876443, -11771086, -14625140, -12369567, 1838104, 21911214, 6354752, 4425632, -837822 }, + }, + { + { -10433389, -14612966, 22229858, -3091047, -13191166, 776729, -17415375, -12020462, 4725005, 14044970 }, + { 19268650, -7304421, 1555349, 8692754, -21474059, -9910664, 6347390, -1411784, -19522291, -16109756 }, + { -24864089, 12986008, -10898878, -5558584, -11312371, -148526, 19541418, 8180106, 9282262, 10282508 }, + }, + { + { -26205082, 4428547, -8661196, -13194263, 4098402, -14165257, 15522535, 8372215, 5542595, -10702683 }, + { -10562541, 14895633, 26814552, -16673850, -17480754, -2489360, -2781891, 6993761, -18093885, 10114655 }, + { -20107055, -929418, 31422704, 10427861, -7110749, 6150669, -29091755, -11529146, 25953725, -106158 }, + }, + { + { -4234397, -8039292, -9119125, 3046000, 2101609, -12607294, 19390020, 6094296, -3315279, 12831125 }, + { -15998678, 7578152, 5310217, 14408357, -33548620, -224739, 31575954, 6326196, 7381791, -2421839 }, + { -20902779, 3296811, 24736065, -16328389, 18374254, 7318640, 6295303, 8082724, -15362489, 12339664 }, + }, + { + { 27724736, 2291157, 6088201, -14184798, 1792727, 5857634, 13848414, 15768922, 25091167, 14856294 }, + { -18866652, 8331043, 24373479, 8541013, -701998, -9269457, 12927300, -12695493, -22182473, -9012899 }, + { -11423429, -5421590, 11632845, 3405020, 30536730, -11674039, -27260765, 13866390, 30146206, 9142070 }, + }, + { + { 3924129, -15307516, -13817122, -10054960, 12291820, -668366, -27702774, 9326384, -8237858, 4171294 }, + { -15921940, 16037937, 6713787, 16606682, -21612135, 2790944, 26396185, 3731949, 345228, -5462949 }, + { -21327538, 13448259, 25284571, 1143661, 20614966, -8849387, 2031539, -12391231, -16253183, -13582083 }, + }, + { + { 31016211, -16722429, 26371392, -14451233, -5027349, 14854137, 17477601, 3842657, 28012650, -16405420 }, + { -5075835, 9368966, -8562079, -4600902, -15249953, 6970560, -9189873, 16292057, -8867157, 3507940 }, + { 29439664, 3537914, 23333589, 6997794, -17555561, -11018068, -15209202, -15051267, -9164929, 6580396 }, + }, + }, + { + { + { -12185861, -7679788, 16438269, 10826160, -8696817, -6235611, 17860444, -9273846, -2095802, 9304567 }, + { 20714564, -4336911, 29088195, 7406487, 11426967, -5095705, 14792667, -14608617, 5289421, -477127 }, + { -16665533, -10650790, -6160345, -13305760, 9192020, -1802462, 17271490, 12349094, 26939669, -3752294 }, + }, + { + { -12889898, 9373458, 31595848, 16374215, 21471720, 13221525, -27283495, -12348559, -3698806, 117887 }, + { 22263325, -6560050, 3984570, -11174646, -15114008, -566785, 28311253, 5358056, -23319780, 541964 }, + { 16259219, 3261970, 2309254, -15534474, -16885711, -4581916, 24134070, -16705829, -13337066, -13552195 }, + }, + { + { 9378160, -13140186, -22845982, -12745264, 28198281, -7244098, -2399684, -717351, 690426, 14876244 }, + { 24977353, -314384, -8223969, -13465086, 28432343, -1176353, -13068804, -12297348, -22380984, 6618999 }, + { -1538174, 11685646, 12944378, 13682314, -24389511, -14413193, 8044829, -13817328, 32239829, -5652762 }, + }, + { + { -18603066, 4762990, -926250, 8885304, -28412480, -3187315, 9781647, -10350059, 32779359, 5095274 }, + { -33008130, -5214506, -32264887, -3685216, 9460461, -9327423, -24601656, 14506724, 21639561, -2630236 }, + { -16400943, -13112215, 25239338, 15531969, 3987758, -4499318, -1289502, -6863535, 17874574, 558605 }, + }, + { + { -13600129, 10240081, 9171883, 16131053, -20869254, 9599700, 33499487, 5080151, 2085892, 5119761 }, + { -22205145, -2519528, -16381601, 414691, -25019550, 2170430, 30634760, -8363614, -31999993, -5759884 }, + { -6845704, 15791202, 8550074, -1312654, 29928809, -12092256, 27534430, -7192145, -22351378, 12961482 }, + }, + { + { -24492060, -9570771, 10368194, 11582341, -23397293, -2245287, 16533930, 8206996, -30194652, -5159638 }, + { -11121496, -3382234, 2307366, 6362031, -135455, 8868177, -16835630, 7031275, 7589640, 8945490 }, + { -32152748, 8917967, 6661220, -11677616, -1192060, -15793393, 7251489, -11182180, 24099109, -14456170 }, + }, + { + { 5019558, -7907470, 4244127, -14714356, -26933272, 6453165, -19118182, -13289025, -6231896, -10280736 }, + { 10853594, 10721687, 26480089, 5861829, -22995819, 1972175, -1866647, -10557898, -3363451, -6441124 }, + { -17002408, 5906790, 221599, -6563147, 7828208, -13248918, 24362661, -2008168, -13866408, 7421392 }, + }, + { + { 8139927, -6546497, 32257646, -5890546, 30375719, 1886181, -21175108, 15441252, 28826358, -4123029 }, + { 6267086, 9695052, 7709135, -16603597, -32869068, -1886135, 14795160, -7840124, 13746021, -1742048 }, + { 28584902, 7787108, -6732942, -15050729, 22846041, -7571236, -3181936, -363524, 4771362, -8419958 }, + }, + }, + { + { + { 24949256, 6376279, -27466481, -8174608, -18646154, -9930606, 33543569, -12141695, 3569627, 11342593 }, + { 26514989, 4740088, 27912651, 3697550, 19331575, -11472339, 6809886, 4608608, 7325975, -14801071 }, + { -11618399, -14554430, -24321212, 7655128, -1369274, 5214312, -27400540, 10258390, -17646694, -8186692 }, + }, + { + { 11431204, 15823007, 26570245, 14329124, 18029990, 4796082, -31446179, 15580664, 9280358, -3973687 }, + { -160783, -10326257, -22855316, -4304997, -20861367, -13621002, -32810901, -11181622, -15545091, 4387441 }, + { -20799378, 12194512, 3937617, -5805892, -27154820, 9340370, -24513992, 8548137, 20617071, -7482001 }, + }, + { + { -938825, -3930586, -8714311, 16124718, 24603125, -6225393, -13775352, -11875822, 24345683, 10325460 }, + { -19855277, -1568885, -22202708, 8714034, 14007766, 6928528, 16318175, -1010689, 4766743, 3552007 }, + { -21751364, -16730916, 1351763, -803421, -4009670, 3950935, 3217514, 14481909, 10988822, -3994762 }, + }, + { + { 15564307, -14311570, 3101243, 5684148, 30446780, -8051356, 12677127, -6505343, -8295852, 13296005 }, + { -9442290, 6624296, -30298964, -11913677, -4670981, -2057379, 31521204, 9614054, -30000824, 12074674 }, + { 4771191, -135239, 14290749, -13089852, 27992298, 14998318, -1413936, -1556716, 29832613, -16391035 }, + }, + { + { 7064884, -7541174, -19161962, -5067537, -18891269, -2912736, 25825242, 5293297, -27122660, 13101590 }, + { -2298563, 2439670, -7466610, 1719965, -27267541, -16328445, 32512469, -5317593, -30356070, -4190957 }, + { -30006540, 10162316, -33180176, 3981723, -16482138, -13070044, 14413974, 9515896, 19568978, 9628812 }, + }, + { + { 33053803, 199357, 15894591, 1583059, 27380243, -4580435, -17838894, -6106839, -6291786, 3437740 }, + { -18978877, 3884493, 19469877, 12726490, 15913552, 13614290, -22961733, 70104, 7463304, 4176122 }, + { -27124001, 10659917, 11482427, -16070381, 12771467, -6635117, -32719404, -5322751, 24216882, 5944158 }, + }, + { + { 8894125, 7450974, -2664149, -9765752, -28080517, -12389115, 19345746, 14680796, 11632993, 5847885 }, + { 26942781, -2315317, 9129564, -4906607, 26024105, 11769399, -11518837, 6367194, -9727230, 4782140 }, + { 19916461, -4828410, -22910704, -11414391, 25606324, -5972441, 33253853, 8220911, 6358847, -1873857 }, + }, + { + { 801428, -2081702, 16569428, 11065167, 29875704, 96627, 7908388, -4480480, -13538503, 1387155 }, + { 19646058, 5720633, -11416706, 12814209, 11607948, 12749789, 14147075, 15156355, -21866831, 11835260 }, + { 19299512, 1155910, 28703737, 14890794, 2925026, 7269399, 26121523, 15467869, -26560550, 5052483 }, + }, + }, + { + { + { -3017432, 10058206, 1980837, 3964243, 22160966, 12322533, -6431123, -12618185, 12228557, -7003677 }, + { 32944382, 14922211, -22844894, 5188528, 21913450, -8719943, 4001465, 13238564, -6114803, 8653815 }, + { 22865569, -4652735, 27603668, -12545395, 14348958, 8234005, 24808405, 5719875, 28483275, 2841751 }, + }, + { + { -16420968, -1113305, -327719, -12107856, 21886282, -15552774, -1887966, -315658, 19932058, -12739203 }, + { -11656086, 10087521, -8864888, -5536143, -19278573, -3055912, 3999228, 13239134, -4777469, -13910208 }, + { 1382174, -11694719, 17266790, 9194690, -13324356, 9720081, 20403944, 11284705, -14013818, 3093230 }, + }, + { + { 16650921, -11037932, -1064178, 1570629, -8329746, 7352753, -302424, 16271225, -24049421, -6691850 }, + { -21911077, -5927941, -4611316, -5560156, -31744103, -10785293, 24123614, 15193618, -21652117, -16739389 }, + { -9935934, -4289447, -25279823, 4372842, 2087473, 10399484, 31870908, 14690798, 17361620, 11864968 }, + }, + { + { -11307610, 6210372, 13206574, 5806320, -29017692, -13967200, -12331205, -7486601, -25578460, -16240689 }, + { 14668462, -12270235, 26039039, 15305210, 25515617, 4542480, 10453892, 6577524, 9145645, -6443880 }, + { 5974874, 3053895, -9433049, -10385191, -31865124, 3225009, -7972642, 3936128, -5652273, -3050304 }, + }, + { + { 30625386, -4729400, -25555961, -12792866, -20484575, 7695099, 17097188, -16303496, -27999779, 1803632 }, + { -3553091, 9865099, -5228566, 4272701, -5673832, -16689700, 14911344, 12196514, -21405489, 7047412 }, + { 20093277, 9920966, -11138194, -5343857, 13161587, 12044805, -32856851, 4124601, -32343828, -10257566 }, + }, + { + { -20788824, 14084654, -13531713, 7842147, 19119038, -13822605, 4752377, -8714640, -21679658, 2288038 }, + { -26819236, -3283715, 29965059, 3039786, -14473765, 2540457, 29457502, 14625692, -24819617, 12570232 }, + { -1063558, -11551823, 16920318, 12494842, 1278292, -5869109, -21159943, -3498680, -11974704, 4724943 }, + }, + { + { 17960970, -11775534, -4140968, -9702530, -8876562, -1410617, -12907383, -8659932, -29576300, 1903856 }, + { 23134274, -14279132, -10681997, -1611936, 20684485, 15770816, -12989750, 3190296, 26955097, 14109738 }, + { 15308788, 5320727, -30113809, -14318877, 22902008, 7767164, 29425325, -11277562, 31960942, 11934971 }, + }, + { + { -27395711, 8435796, 4109644, 12222639, -24627868, 14818669, 20638173, 4875028, 10491392, 1379718 }, + { -13159415, 9197841, 3875503, -8936108, -1383712, -5879801, 33518459, 16176658, 21432314, 12180697 }, + { -11787308, 11500838, 13787581, -13832590, -22430679, 10140205, 1465425, 12689540, -10301319, -13872883 }, + }, + }, + { + { + { 5414091, -15386041, -21007664, 9643570, 12834970, 1186149, -2622916, -1342231, 26128231, 6032912 }, + { -26337395, -13766162, 32496025, -13653919, 17847801, -12669156, 3604025, 8316894, -25875034, -10437358 }, + { 3296484, 6223048, 24680646, -12246460, -23052020, 5903205, -8862297, -4639164, 12376617, 3188849 }, + }, + { + { 29190488, -14659046, 27549113, -1183516, 3520066, -10697301, 32049515, -7309113, -16109234, -9852307 }, + { -14744486, -9309156, 735818, -598978, -20407687, -5057904, 25246078, -15795669, 18640741, -960977 }, + { -6928835, -16430795, 10361374, 5642961, 4910474, 12345252, -31638386, -494430, 10530747, 1053335 }, + }, + { + { -29265967, -14186805, -13538216, -12117373, -19457059, -10655384, -31462369, -2948985, 24018831, 15026644 }, + { -22592535, -3145277, -2289276, 5953843, -13440189, 9425631, 25310643, 13003497, -2314791, -15145616 }, + { -27419985, -603321, -8043984, -1669117, -26092265, 13987819, -27297622, 187899, -23166419, -2531735 }, + }, + { + { -21744398, -13810475, 1844840, 5021428, -10434399, -15911473, 9716667, 16266922, -5070217, 726099 }, + { 29370922, -6053998, 7334071, -15342259, 9385287, 2247707, -13661962, -4839461, 30007388, -15823341 }, + { -936379, 16086691, 23751945, -543318, -1167538, -5189036, 9137109, 730663, 9835848, 4555336 }, + }, + { + { -23376435, 1410446, -22253753, -12899614, 30867635, 15826977, 17693930, 544696, -11985298, 12422646 }, + { 31117226, -12215734, -13502838, 6561947, -9876867, -12757670, -5118685, -4096706, 29120153, 13924425 }, + { -17400879, -14233209, 19675799, -2734756, -11006962, -5858820, -9383939, -11317700, 7240931, -237388 }, + }, + { + { -31361739, -11346780, -15007447, -5856218, -22453340, -12152771, 1222336, 4389483, 3293637, -15551743 }, + { -16684801, -14444245, 11038544, 11054958, -13801175, -3338533, -24319580, 7733547, 12796905, -6335822 }, + { -8759414, -10817836, -25418864, 10783769, -30615557, -9746811, -28253339, 3647836, 3222231, -11160462 }, + }, + { + { 18606113, 1693100, -25448386, -15170272, 4112353, 10045021, 23603893, -2048234, -7550776, 2484985 }, + { 9255317, -3131197, -12156162, -1004256, 13098013, -9214866, 16377220, -2102812, -19802075, -3034702 }, + { -22729289, 7496160, -5742199, 11329249, 19991973, -3347502, -31718148, 9936966, -30097688, -10618797 }, + }, + { + { 21878590, -5001297, 4338336, 13643897, -3036865, 13160960, 19708896, 5415497, -7360503, -4109293 }, + { 27736861, 10103576, 12500508, 8502413, -3413016, -9633558, 10436918, -1550276, -23659143, -8132100 }, + { 19492550, -12104365, -29681976, -852630, -3208171, 12403437, 30066266, 8367329, 13243957, 8709688 }, + }, + }, + { + { + { 12015105, 2801261, 28198131, 10151021, 24818120, -4743133, -11194191, -5645734, 5150968, 7274186 }, + { 2831366, -12492146, 1478975, 6122054, 23825128, -12733586, 31097299, 6083058, 31021603, -9793610 }, + { -2529932, -2229646, 445613, 10720828, -13849527, -11505937, -23507731, 16354465, 15067285, -14147707 }, + }, + { + { 7840942, 14037873, -33364863, 15934016, -728213, -3642706, 21403988, 1057586, -19379462, -12403220 }, + { 915865, -16469274, 15608285, -8789130, -24357026, 6060030, -17371319, 8410997, -7220461, 16527025 }, + { 32922597, -556987, 20336074, -16184568, 10903705, -5384487, 16957574, 52992, 23834301, 6588044 }, + }, + { + { 32752030, 11232950, 3381995, -8714866, 22652988, -10744103, 17159699, 16689107, -20314580, -1305992 }, + { -4689649, 9166776, -25710296, -10847306, 11576752, 12733943, 7924251, -2752281, 1976123, -7249027 }, + { 21251222, 16309901, -2983015, -6783122, 30810597, 12967303, 156041, -3371252, 12331345, -8237197 }, + }, + { + { 8651614, -4477032, -16085636, -4996994, 13002507, 2950805, 29054427, -5106970, 10008136, -4667901 }, + { 31486080, 15114593, -14261250, 12951354, 14369431, -7387845, 16347321, -13662089, 8684155, -10532952 }, + { 19443825, 11385320, 24468943, -9659068, -23919258, 2187569, -26263207, -6086921, 31316348, 14219878 }, + }, + { + { -28594490, 1193785, 32245219, 11392485, 31092169, 15722801, 27146014, 6992409, 29126555, 9207390 }, + { 32382935, 1110093, 18477781, 11028262, -27411763, -7548111, -4980517, 10843782, -7957600, -14435730 }, + { 2814918, 7836403, 27519878, -7868156, -20894015, -11553689, -21494559, 8550130, 28346258, 1994730 }, + }, + { + { -19578299, 8085545, -14000519, -3948622, 2785838, -16231307, -19516951, 7174894, 22628102, 8115180 }, + { -30405132, 955511, -11133838, -15078069, -32447087, -13278079, -25651578, 3317160, -9943017, 930272 }, + { -15303681, -6833769, 28856490, 1357446, 23421993, 1057177, 24091212, -1388970, -22765376, -10650715 }, + }, + { + { -22751231, -5303997, -12907607, -12768866, -15811511, -7797053, -14839018, -16554220, -1867018, 8398970 }, + { -31969310, 2106403, -4736360, 1362501, 12813763, 16200670, 22981545, -6291273, 18009408, -15772772 }, + { -17220923, -9545221, -27784654, 14166835, 29815394, 7444469, 29551787, -3727419, 19288549, 1325865 }, + }, + { + { 15100157, -15835752, -23923978, -1005098, -26450192, 15509408, 12376730, -3479146, 33166107, -8042750 }, + { 20909231, 13023121, -9209752, 16251778, -5778415, -8094914, 12412151, 10018715, 2213263, -13878373 }, + { 32529814, -11074689, 30361439, -16689753, -9135940, 1513226, 22922121, 6382134, -5766928, 8371348 }, + }, + }, + { + { + { 9923462, 11271500, 12616794, 3544722, -29998368, -1721626, 12891687, -8193132, -26442943, 10486144 }, + { -22597207, -7012665, 8587003, -8257861, 4084309, -12970062, 361726, 2610596, -23921530, -11455195 }, + { 5408411, -1136691, -4969122, 10561668, 24145918, 14240566, 31319731, -4235541, 19985175, -3436086 }, + }, + { + { -13994457, 16616821, 14549246, 3341099, 32155958, 13648976, -17577068, 8849297, 65030, 8370684 }, + { -8320926, -12049626, 31204563, 5839400, -20627288, -1057277, -19442942, 6922164, 12743482, -9800518 }, + { -2361371, 12678785, 28815050, 4759974, -23893047, 4884717, 23783145, 11038569, 18800704, 255233 }, + }, + { + { -5269658, -1773886, 13957886, 7990715, 23132995, 728773, 13393847, 9066957, 19258688, -14753793 }, + { -2936654, -10827535, -10432089, 14516793, -3640786, 4372541, -31934921, 2209390, -1524053, 2055794 }, + { 580882, 16705327, 5468415, -2683018, -30926419, -14696000, -7203346, -8994389, -30021019, 7394435 }, + }, + { + { 23838809, 1822728, -15738443, 15242727, 8318092, -3733104, -21672180, -3492205, -4821741, 14799921 }, + { 13345610, 9759151, 3371034, -16137791, 16353039, 8577942, 31129804, 13496856, -9056018, 7402518 }, + { 2286874, -4435931, -20042458, -2008336, -13696227, 5038122, 11006906, -15760352, 8205061, 1607563 }, + }, + { + { 14414086, -8002132, 3331830, -3208217, 22249151, -5594188, 18364661, -2906958, 30019587, -9029278 }, + { -27688051, 1585953, -10775053, 931069, -29120221, -11002319, -14410829, 12029093, 9944378, 8024 }, + { 4368715, -3709630, 29874200, -15022983, -20230386, -11410704, -16114594, -999085, -8142388, 5640030 }, + }, + { + { 10299610, 13746483, 11661824, 16234854, 7630238, 5998374, 9809887, -16694564, 15219798, -14327783 }, + { 27425505, -5719081, 3055006, 10660664, 23458024, 595578, -15398605, -1173195, -18342183, 9742717 }, + { 6744077, 2427284, 26042789, 2720740, -847906, 1118974, 32324614, 7406442, 12420155, 1994844 }, + }, + { + { 14012521, -5024720, -18384453, -9578469, -26485342, -3936439, -13033478, -10909803, 24319929, -6446333 }, + { 16412690, -4507367, 10772641, 15929391, -17068788, -4658621, 10555945, -10484049, -30102368, -4739048 }, + { 22397382, -7767684, -9293161, -12792868, 17166287, -9755136, -27333065, 6199366, 21880021, -12250760 }, + }, + { + { -4283307, 5368523, -31117018, 8163389, -30323063, 3209128, 16557151, 8890729, 8840445, 4957760 }, + { -15447727, 709327, -6919446, -10870178, -29777922, 6522332, -21720181, 12130072, -14796503, 5005757 }, + { -2114751, -14308128, 23019042, 15765735, -25269683, 6002752, 10183197, -13239326, -16395286, -2176112 }, + }, + }, + { + { + { -19025756, 1632005, 13466291, -7995100, -23640451, 16573537, -32013908, -3057104, 22208662, 2000468 }, + { 3065073, -1412761, -25598674, -361432, -17683065, -5703415, -8164212, 11248527, -3691214, -7414184 }, + { 10379208, -6045554, 8877319, 1473647, -29291284, -12507580, 16690915, 2553332, -3132688, 16400289 }, + }, + { + { 15716668, 1254266, -18472690, 7446274, -8448918, 6344164, -22097271, -7285580, 26894937, 9132066 }, + { 24158887, 12938817, 11085297, -8177598, -28063478, -4457083, -30576463, 64452, -6817084, -2692882 }, + { 13488534, 7794716, 22236231, 5989356, 25426474, -12578208, 2350710, -3418511, -4688006, 2364226 }, + }, + { + { 16335052, 9132434, 25640582, 6678888, 1725628, 8517937, -11807024, -11697457, 15445875, -7798101 }, + { 29004207, -7867081, 28661402, -640412, -12794003, -7943086, 31863255, -4135540, -278050, -15759279 }, + { -6122061, -14866665, -28614905, 14569919, -10857999, -3591829, 10343412, -6976290, -29828287, -10815811 }, + }, + { + { 27081650, 3463984, 14099042, -4517604, 1616303, -6205604, 29542636, 15372179, 17293797, 960709 }, + { 20263915, 11434237, -5765435, 11236810, 13505955, -10857102, -16111345, 6493122, -19384511, 7639714 }, + { -2830798, -14839232, 25403038, -8215196, -8317012, -16173699, 18006287, -16043750, 29994677, -15808121 }, + }, + { + { 9769828, 5202651, -24157398, -13631392, -28051003, -11561624, -24613141, -13860782, -31184575, 709464 }, + { 12286395, 13076066, -21775189, -1176622, -25003198, 4057652, -32018128, -8890874, 16102007, 13205847 }, + { 13733362, 5599946, 10557076, 3195751, -5557991, 8536970, -25540170, 8525972, 10151379, 10394400 }, + }, + { + { 4024660, -16137551, 22436262, 12276534, -9099015, -2686099, 19698229, 11743039, -33302334, 8934414 }, + { -15879800, -4525240, -8580747, -2934061, 14634845, -698278, -9449077, 3137094, -11536886, 11721158 }, + { 17555939, -5013938, 8268606, 2331751, -22738815, 9761013, 9319229, 8835153, -9205489, -1280045 }, + }, + { + { -461409, -7830014, 20614118, 16688288, -7514766, -4807119, 22300304, 505429, 6108462, -6183415 }, + { -5070281, 12367917, -30663534, 3234473, 32617080, -8422642, 29880583, -13483331, -26898490, -7867459 }, + { -31975283, 5726539, 26934134, 10237677, -3173717, -605053, 24199304, 3795095, 7592688, -14992079 }, + }, + { + { 21594432, -14964228, 17466408, -4077222, 32537084, 2739898, 6407723, 12018833, -28256052, 4298412 }, + { -20650503, -11961496, -27236275, 570498, 3767144, -1717540, 13891942, -1569194, 13717174, 10805743 }, + { -14676630, -15644296, 15287174, 11927123, 24177847, -8175568, -796431, 14860609, -26938930, -5863836 }, + }, + }, + { + { + { 12962541, 5311799, -10060768, 11658280, 18855286, -7954201, 13286263, -12808704, -4381056, 9882022 }, + { 18512079, 11319350, -20123124, 15090309, 18818594, 5271736, -22727904, 3666879, -23967430, -3299429 }, + { -6789020, -3146043, 16192429, 13241070, 15898607, -14206114, -10084880, -6661110, -2403099, 5276065 }, + }, + { + { 30169808, -5317648, 26306206, -11750859, 27814964, 7069267, 7152851, 3684982, 1449224, 13082861 }, + { 10342826, 3098505, 2119311, 193222, 25702612, 12233820, 23697382, 15056736, -21016438, -8202000 }, + { -33150110, 3261608, 22745853, 7948688, 19370557, -15177665, -26171976, 6482814, -10300080, -11060101 }, + }, + { + { 32869458, -5408545, 25609743, 15678670, -10687769, -15471071, 26112421, 2521008, -22664288, 6904815 }, + { 29506923, 4457497, 3377935, -9796444, -30510046, 12935080, 1561737, 3841096, -29003639, -6657642 }, + { 10340844, -6630377, -18656632, -2278430, 12621151, -13339055, 30878497, -11824370, -25584551, 5181966 }, + }, + { + { 25940115, -12658025, 17324188, -10307374, -8671468, 15029094, 24396252, -16450922, -2322852, -12388574 }, + { -21765684, 9916823, -1300409, 4079498, -1028346, 11909559, 1782390, 12641087, 20603771, -6561742 }, + { -18882287, -11673380, 24849422, 11501709, 13161720, -4768874, 1925523, 11914390, 4662781, 7820689 }, + }, + { + { 12241050, -425982, 8132691, 9393934, 32846760, -1599620, 29749456, 12172924, 16136752, 15264020 }, + { -10349955, -14680563, -8211979, 2330220, -17662549, -14545780, 10658213, 6671822, 19012087, 3772772 }, + { 3753511, -3421066, 10617074, 2028709, 14841030, -6721664, 28718732, -15762884, 20527771, 12988982 }, + }, + { + { -14822485, -5797269, -3707987, 12689773, -898983, -10914866, -24183046, -10564943, 3299665, -12424953 }, + { -16777703, -15253301, -9642417, 4978983, 3308785, 8755439, 6943197, 6461331, -25583147, 8991218 }, + { -17226263, 1816362, -1673288, -6086439, 31783888, -8175991, -32948145, 7417950, -30242287, 1507265 }, + }, + { + { 29692663, 6829891, -10498800, 4334896, 20945975, -11906496, -28887608, 8209391, 14606362, -10647073 }, + { -3481570, 8707081, 32188102, 5672294, 22096700, 1711240, -33020695, 9761487, 4170404, -2085325 }, + { -11587470, 14855945, -4127778, -1531857, -26649089, 15084046, 22186522, 16002000, -14276837, -8400798 }, + }, + { + { -4811456, 13761029, -31703877, -2483919, -3312471, 7869047, -7113572, -9620092, 13240845, 10965870 }, + { -7742563, -8256762, -14768334, -13656260, -23232383, 12387166, 4498947, 14147411, 29514390, 4302863 }, + { -13413405, -12407859, 20757302, -13801832, 14785143, 8976368, -5061276, -2144373, 17846988, -13971927 }, + }, + }, + { + { + { -2244452, -754728, -4597030, -1066309, -6247172, 1455299, -21647728, -9214789, -5222701, 12650267 }, + { -9906797, -16070310, 21134160, 12198166, -27064575, 708126, 387813, 13770293, -19134326, 10958663 }, + { 22470984, 12369526, 23446014, -5441109, -21520802, -9698723, -11772496, -11574455, -25083830, 4271862 }, + }, + { + { -25169565, -10053642, -19909332, 15361595, -5984358, 2159192, 75375, -4278529, -32526221, 8469673 }, + { 15854970, 4148314, -8893890, 7259002, 11666551, 13824734, -30531198, 2697372, 24154791, -9460943 }, + { 15446137, -15806644, 29759747, 14019369, 30811221, -9610191, -31582008, 12840104, 24913809, 9815020 }, + }, + { + { -4709286, -5614269, -31841498, -12288893, -14443537, 10799414, -9103676, 13438769, 18735128, 9466238 }, + { 11933045, 9281483, 5081055, -5183824, -2628162, -4905629, -7727821, -10896103, -22728655, 16199064 }, + { 14576810, 379472, -26786533, -8317236, -29426508, -10812974, -102766, 1876699, 30801119, 2164795 }, + }, + { + { 15995086, 3199873, 13672555, 13712240, -19378835, -4647646, -13081610, -15496269, -13492807, 1268052 }, + { -10290614, -3659039, -3286592, 10948818, 23037027, 3794475, -3470338, -12600221, -17055369, 3565904 }, + { 29210088, -9419337, -5919792, -4952785, 10834811, -13327726, -16512102, -10820713, -27162222, -14030531 }, + }, + { + { -13161890, 15508588, 16663704, -8156150, -28349942, 9019123, -29183421, -3769423, 2244111, -14001979 }, + { -5152875, -3800936, -9306475, -6071583, 16243069, 14684434, -25673088, -16180800, 13491506, 4641841 }, + { 10813417, 643330, -19188515, -728916, 30292062, -16600078, 27548447, -7721242, 14476989, -12767431 }, + }, + { + { 10292079, 9984945, 6481436, 8279905, -7251514, 7032743, 27282937, -1644259, -27912810, 12651324 }, + { -31185513, -813383, 22271204, 11835308, 10201545, 15351028, 17099662, 3988035, 21721536, -3148940 }, + { 10202177, -6545839, -31373232, -9574638, -32150642, -8119683, -12906320, 3852694, 13216206, 14842320 }, + }, + { + { -15815640, -10601066, -6538952, -7258995, -6984659, -6581778, -31500847, 13765824, -27434397, 9900184 }, + { 14465505, -13833331, -32133984, -14738873, -27443187, 12990492, 33046193, 15796406, -7051866, -8040114 }, + { 30924417, -8279620, 6359016, -12816335, 16508377, 9071735, -25488601, 15413635, 9524356, -7018878 }, + }, + { + { 12274201, -13175547, 32627641, -1785326, 6736625, 13267305, 5237659, -5109483, 15663516, 4035784 }, + { -2951309, 8903985, 17349946, 601635, -16432815, -4612556, -13732739, -15889334, -22258478, 4659091 }, + { -16916263, -4952973, -30393711, -15158821, 20774812, 15897498, 5736189, 15026997, -2178256, -13455585 }, + }, + }, + { + { + { -8858980, -2219056, 28571666, -10155518, -474467, -10105698, -3801496, 278095, 23440562, -290208 }, + { 10226241, -5928702, 15139956, 120818, -14867693, 5218603, 32937275, 11551483, -16571960, -7442864 }, + { 17932739, -12437276, -24039557, 10749060, 11316803, 7535897, 22503767, 5561594, -3646624, 3898661 }, + }, + { + { 7749907, -969567, -16339731, -16464, -25018111, 15122143, -1573531, 7152530, 21831162, 1245233 }, + { 26958459, -14658026, 4314586, 8346991, -5677764, 11960072, -32589295, -620035, -30402091, -16716212 }, + { -12165896, 9166947, 33491384, 13673479, 29787085, 13096535, 6280834, 14587357, -22338025, 13987525 }, + }, + { + { -24349909, 7778775, 21116000, 15572597, -4833266, -5357778, -4300898, -5124639, -7469781, -2858068 }, + { 9681908, -6737123, -31951644, 13591838, -6883821, 386950, 31622781, 6439245, -14581012, 4091397 }, + { -8426427, 1470727, -28109679, -1596990, 3978627, -5123623, -19622683, 12092163, 29077877, -14741988 }, + }, + { + { 5269168, -6859726, -13230211, -8020715, 25932563, 1763552, -5606110, -5505881, -20017847, 2357889 }, + { 32264008, -15407652, -5387735, -1160093, -2091322, -3946900, 23104804, -12869908, 5727338, 189038 }, + { 14609123, -8954470, -6000566, -16622781, -14577387, -7743898, -26745169, 10942115, -25888931, -14884697 }, + }, + { + { 20513500, 5557931, -15604613, 7829531, 26413943, -2019404, -21378968, 7471781, 13913677, -5137875 }, + { -25574376, 11967826, 29233242, 12948236, -6754465, 4713227, -8940970, 14059180, 12878652, 8511905 }, + { -25656801, 3393631, -2955415, -7075526, -2250709, 9366908, -30223418, 6812974, 5568676, -3127656 }, + }, + { + { 11630004, 12144454, 2116339, 13606037, 27378885, 15676917, -17408753, -13504373, -14395196, 8070818 }, + { 27117696, -10007378, -31282771, -5570088, 1127282, 12772488, -29845906, 10483306, -11552749, -1028714 }, + { 10637467, -5688064, 5674781, 1072708, -26343588, -6982302, -1683975, 9177853, -27493162, 15431203 }, + }, + { + { 20525145, 10892566, -12742472, 12779443, -29493034, 16150075, -28240519, 14943142, -15056790, -7935931 }, + { -30024462, 5626926, -551567, -9981087, 753598, 11981191, 25244767, -3239766, -3356550, 9594024 }, + { -23752644, 2636870, -5163910, -10103818, 585134, 7877383, 11345683, -6492290, 13352335, -10977084 }, + }, + { + { -1931799, -5407458, 3304649, -12884869, 17015806, -4877091, -29783850, -7752482, -13215537, -319204 }, + { 20239939, 6607058, 6203985, 3483793, -18386976, -779229, -20723742, 15077870, -22750759, 14523817 }, + { 27406042, -6041657, 27423596, -4497394, 4996214, 10002360, -28842031, -4545494, -30172742, -4805667 }, + }, + }, + { + { + { 11374242, 12660715, 17861383, -12540833, 10935568, 1099227, -13886076, -9091740, -27727044, 11358504 }, + { -12730809, 10311867, 1510375, 10778093, -2119455, -9145702, 32676003, 11149336, -26123651, 4985768 }, + { -19096303, 341147, -6197485, -239033, 15756973, -8796662, -983043, 13794114, -19414307, -15621255 }, + }, + { + { 6490081, 11940286, 25495923, -7726360, 8668373, -8751316, 3367603, 6970005, -1691065, -9004790 }, + { 1656497, 13457317, 15370807, 6364910, 13605745, 8362338, -19174622, -5475723, -16796596, -5031438 }, + { -22273315, -13524424, -64685, -4334223, -18605636, -10921968, -20571065, -7007978, -99853, -10237333 }, + }, + { + { 17747465, 10039260, 19368299, -4050591, -20630635, -16041286, 31992683, -15857976, -29260363, -5511971 }, + { 31932027, -4986141, -19612382, 16366580, 22023614, 88450, 11371999, -3744247, 4882242, -10626905 }, + { 29796507, 37186, 19818052, 10115756, -11829032, 3352736, 18551198, 3272828, -5190932, -4162409 }, + }, + { + { 12501286, 4044383, -8612957, -13392385, -32430052, 5136599, -19230378, -3529697, 330070, -3659409 }, + { 6384877, 2899513, 17807477, 7663917, -2358888, 12363165, 25366522, -8573892, -271295, 12071499 }, + { -8365515, -4042521, 25133448, -4517355, -6211027, 2265927, -32769618, 1936675, -5159697, 3829363 }, + }, + { + { 28425966, -5835433, -577090, -4697198, -14217555, 6870930, 7921550, -6567787, 26333140, 14267664 }, + { -11067219, 11871231, 27385719, -10559544, -4585914, -11189312, 10004786, -8709488, -21761224, 8930324 }, + { -21197785, -16396035, 25654216, -1725397, 12282012, 11008919, 1541940, 4757911, -26491501, -16408940 }, + }, + { + { 13537262, -7759490, -20604840, 10961927, -5922820, -13218065, -13156584, 6217254, -15943699, 13814990 }, + { -17422573, 15157790, 18705543, 29619, 24409717, -260476, 27361681, 9257833, -1956526, -1776914 }, + { -25045300, -10191966, 15366585, 15166509, -13105086, 8423556, -29171540, 12361135, -18685978, 4578290 }, + }, + { + { 24579768, 3711570, 1342322, -11180126, -27005135, 14124956, -22544529, 14074919, 21964432, 8235257 }, + { -6528613, -2411497, 9442966, -5925588, 12025640, -1487420, -2981514, -1669206, 13006806, 2355433 }, + { -16304899, -13605259, -6632427, -5142349, 16974359, -10911083, 27202044, 1719366, 1141648, -12796236 }, + }, + { + { -12863944, -13219986, -8318266, -11018091, -6810145, -4843894, 13475066, -3133972, 32674895, 13715045 }, + { 11423335, -5468059, 32344216, 8962751, 24989809, 9241752, -13265253, 16086212, -28740881, -15642093 }, + { -1409668, 12530728, -6368726, 10847387, 19531186, -14132160, -11709148, 7791794, -27245943, 4383347 }, + }, + }, + { + { + { -28970898, 5271447, -1266009, -9736989, -12455236, 16732599, -4862407, -4906449, 27193557, 6245191 }, + { -15193956, 5362278, -1783893, 2695834, 4960227, 12840725, 23061898, 3260492, 22510453, 8577507 }, + { -12632451, 11257346, -32692994, 13548177, -721004, 10879011, 31168030, 13952092, -29571492, -3635906 }, + }, + { + { 3877321, -9572739, 32416692, 5405324, -11004407, -13656635, 3759769, 11935320, 5611860, 8164018 }, + { -16275802, 14667797, 15906460, 12155291, -22111149, -9039718, 32003002, -8832289, 5773085, -8422109 }, + { -23788118, -8254300, 1950875, 8937633, 18686727, 16459170, -905725, 12376320, 31632953, 190926 }, + }, + { + { -24593607, -16138885, -8423991, 13378746, 14162407, 6901328, -8288749, 4508564, -25341555, -3627528 }, + { 8884438, -5884009, 6023974, 10104341, -6881569, -4941533, 18722941, -14786005, -1672488, 827625 }, + { -32720583, -16289296, -32503547, 7101210, 13354605, 2659080, -1800575, -14108036, -24878478, 1541286 }, + }, + { + { 2901347, -1117687, 3880376, -10059388, -17620940, -3612781, -21802117, -3567481, 20456845, -1885033 }, + { 27019610, 12299467, -13658288, -1603234, -12861660, -4861471, -19540150, -5016058, 29439641, 15138866 }, + { 21536104, -6626420, -32447818, -10690208, -22408077, 5175814, -5420040, -16361163, 7779328, 109896 }, + }, + { + { 30279744, 14648750, -8044871, 6425558, 13639621, -743509, 28698390, 12180118, 23177719, -554075 }, + { 26572847, 3405927, -31701700, 12890905, -19265668, 5335866, -6493768, 2378492, 4439158, -13279347 }, + { -22716706, 3489070, -9225266, -332753, 18875722, -1140095, 14819434, -12731527, -17717757, -5461437 }, + }, + { + { -5056483, 16566551, 15953661, 3767752, -10436499, 15627060, -820954, 2177225, 8550082, -15114165 }, + { -18473302, 16596775, -381660, 15663611, 22860960, 15585581, -27844109, -3582739, -23260460, -8428588 }, + { -32480551, 15707275, -8205912, -5652081, 29464558, 2713815, -22725137, 15860482, -21902570, 1494193 }, + }, + { + { -19562091, -14087393, -25583872, -9299552, 13127842, 759709, 21923482, 16529112, 8742704, 12967017 }, + { -28464899, 1553205, 32536856, -10473729, -24691605, -406174, -8914625, -2933896, -29903758, 15553883 }, + { 21877909, 3230008, 9881174, 10539357, -4797115, 2841332, 11543572, 14513274, 19375923, -12647961 }, + }, + { + { 8832269, -14495485, 13253511, 5137575, 5037871, 4078777, 24880818, -6222716, 2862653, 9455043 }, + { 29306751, 5123106, 20245049, -14149889, 9592566, 8447059, -2077124, -2990080, 15511449, 4789663 }, + { -20679756, 7004547, 8824831, -9434977, -4045704, -3750736, -5754762, 108893, 23513200, 16652362 }, + }, + }, + { + { + { -33256173, 4144782, -4476029, -6579123, 10770039, -7155542, -6650416, -12936300, -18319198, 10212860 }, + { 2756081, 8598110, 7383731, -6859892, 22312759, -1105012, 21179801, 2600940, -9988298, -12506466 }, + { -24645692, 13317462, -30449259, -15653928, 21365574, -10869657, 11344424, 864440, -2499677, -16710063 }, + }, + { + { -26432803, 6148329, -17184412, -14474154, 18782929, -275997, -22561534, 211300, 2719757, 4940997 }, + { -1323882, 3911313, -6948744, 14759765, -30027150, 7851207, 21690126, 8518463, 26699843, 5276295 }, + { -13149873, -6429067, 9396249, 365013, 24703301, -10488939, 1321586, 149635, -15452774, 7159369 }, + }, + { + { 9987780, -3404759, 17507962, 9505530, 9731535, -2165514, 22356009, 8312176, 22477218, -8403385 }, + { 18155857, -16504990, 19744716, 9006923, 15154154, -10538976, 24256460, -4864995, -22548173, 9334109 }, + { 2986088, -4911893, 10776628, -3473844, 10620590, -7083203, -21413845, 14253545, -22587149, 536906 }, + }, + { + { 4377756, 8115836, 24567078, 15495314, 11625074, 13064599, 7390551, 10589625, 10838060, -15420424 }, + { -19342404, 867880, 9277171, -3218459, -14431572, -1986443, 19295826, -15796950, 6378260, 699185 }, + { 7895026, 4057113, -7081772, -13077756, -17886831, -323126, -716039, 15693155, -5045064, -13373962 }, + }, + { + { -7737563, -5869402, -14566319, -7406919, 11385654, 13201616, 31730678, -10962840, -3918636, -9669325 }, + { 10188286, -15770834, -7336361, 13427543, 22223443, 14896287, 30743455, 7116568, -21786507, 5427593 }, + { 696102, 13206899, 27047647, -10632082, 15285305, -9853179, 10798490, -4578720, 19236243, 12477404 }, + }, + { + { -11229439, 11243796, -17054270, -8040865, -788228, -8167967, -3897669, 11180504, -23169516, 7733644 }, + { 17800790, -14036179, -27000429, -11766671, 23887827, 3149671, 23466177, -10538171, 10322027, 15313801 }, + { 26246234, 11968874, 32263343, -5468728, 6830755, -13323031, -15794704, -101982, -24449242, 10890804 }, + }, + { + { -31365647, 10271363, -12660625, -6267268, 16690207, -13062544, -14982212, 16484931, 25180797, -5334884 }, + { -586574, 10376444, -32586414, -11286356, 19801893, 10997610, 2276632, 9482883, 316878, 13820577 }, + { -9882808, -4510367, -2115506, 16457136, -11100081, 11674996, 30756178, -7515054, 30696930, -3712849 }, + }, + { + { 32988917, -9603412, 12499366, 7910787, -10617257, -11931514, -7342816, -9985397, -32349517, 7392473 }, + { -8855661, 15927861, 9866406, -3649411, -2396914, -16655781, -30409476, -9134995, 25112947, -2926644 }, + { -2504044, -436966, 25621774, -5678772, 15085042, -5479877, -24884878, -13526194, 5537438, -13914319 }, + }, + }, + { + { + { -11225584, 2320285, -9584280, 10149187, -33444663, 5808648, -14876251, -1729667, 31234590, 6090599 }, + { -9633316, 116426, 26083934, 2897444, -6364437, -2688086, 609721, 15878753, -6970405, -9034768 }, + { -27757857, 247744, -15194774, -9002551, 23288161, -10011936, -23869595, 6503646, 20650474, 1804084 }, + }, + { + { -27589786, 15456424, 8972517, 8469608, 15640622, 4439847, 3121995, -10329713, 27842616, -202328 }, + { -15306973, 2839644, 22530074, 10026331, 4602058, 5048462, 28248656, 5031932, -11375082, 12714369 }, + { 20807691, -7270825, 29286141, 11421711, -27876523, -13868230, -21227475, 1035546, -19733229, 12796920 }, + }, + { + { 12076899, -14301286, -8785001, -11848922, -25012791, 16400684, -17591495, -12899438, 3480665, -15182815 }, + { -32361549, 5457597, 28548107, 7833186, 7303070, -11953545, -24363064, -15921875, -33374054, 2771025 }, + { -21389266, 421932, 26597266, 6860826, 22486084, -6737172, -17137485, -4210226, -24552282, 15673397 }, + }, + { + { -20184622, 2338216, 19788685, -9620956, -4001265, -8740893, -20271184, 4733254, 3727144, -12934448 }, + { 6120119, 814863, -11794402, -622716, 6812205, -15747771, 2019594, 7975683, 31123697, -10958981 }, + { 30069250, -11435332, 30434654, 2958439, 18399564, -976289, 12296869, 9204260, -16432438, 9648165 }, + }, + { + { 32705432, -1550977, 30705658, 7451065, -11805606, 9631813, 3305266, 5248604, -26008332, -11377501 }, + { 17219865, 2375039, -31570947, -5575615, -19459679, 9219903, 294711, 15298639, 2662509, -16297073 }, + { -1172927, -7558695, -4366770, -4287744, -21346413, -8434326, 32087529, -1222777, 32247248, -14389861 }, + }, + { + { 14312628, 1221556, 17395390, -8700143, -4945741, -8684635, -28197744, -9637817, -16027623, -13378845 }, + { -1428825, -9678990, -9235681, 6549687, -7383069, -468664, 23046502, 9803137, 17597934, 2346211 }, + { 18510800, 15337574, 26171504, 981392, -22241552, 7827556, -23491134, -11323352, 3059833, -11782870 }, + }, + { + { 10141598, 6082907, 17829293, -1947643, 9830092, 13613136, -25556636, -5544586, -33502212, 3592096 }, + { 33114168, -15889352, -26525686, -13343397, 33076705, 8716171, 1151462, 1521897, -982665, -6837803 }, + { -32939165, -4255815, 23947181, -324178, -33072974, -12305637, -16637686, 3891704, 26353178, 693168 }, + }, + { + { 30374239, 1595580, -16884039, 13186931, 4600344, 406904, 9585294, -400668, 31375464, 14369965 }, + { -14370654, -7772529, 1510301, 6434173, -18784789, -6262728, 32732230, -13108839, 17901441, 16011505 }, + { 18171223, -11934626, -12500402, 15197122, -11038147, -15230035, -19172240, -16046376, 8764035, 12309598 }, + }, + }, + { + { + { 5975908, -5243188, -19459362, -9681747, -11541277, 14015782, -23665757, 1228319, 17544096, -10593782 }, + { 5811932, -1715293, 3442887, -2269310, -18367348, -8359541, -18044043, -15410127, -5565381, 12348900 }, + { -31399660, 11407555, 25755363, 6891399, -3256938, 14872274, -24849353, 8141295, -10632534, -585479 }, + }, + { + { -12675304, 694026, -5076145, 13300344, 14015258, -14451394, -9698672, -11329050, 30944593, 1130208 }, + { 8247766, -6710942, -26562381, -7709309, -14401939, -14648910, 4652152, 2488540, 23550156, -271232 }, + { 17294316, -3788438, 7026748, 15626851, 22990044, 113481, 2267737, -5908146, -408818, -137719 }, + }, + { + { 16091085, -16253926, 18599252, 7340678, 2137637, -1221657, -3364161, 14550936, 3260525, -7166271 }, + { -4910104, -13332887, 18550887, 10864893, -16459325, -7291596, -23028869, -13204905, -12748722, 2701326 }, + { -8574695, 16099415, 4629974, -16340524, -20786213, -6005432, -10018363, 9276971, 11329923, 1862132 }, + }, + { + { 14763076, -15903608, -30918270, 3689867, 3511892, 10313526, -21951088, 12219231, -9037963, -940300 }, + { 8894987, -3446094, 6150753, 3013931, 301220, 15693451, -31981216, -2909717, -15438168, 11595570 }, + { 15214962, 3537601, -26238722, -14058872, 4418657, -15230761, 13947276, 10730794, -13489462, -4363670 }, + }, + { + { -2538306, 7682793, 32759013, 263109, -29984731, -7955452, -22332124, -10188635, 977108, 699994 }, + { -12466472, 4195084, -9211532, 550904, -15565337, 12917920, 19118110, -439841, -30534533, -14337913 }, + { 31788461, -14507657, 4799989, 7372237, 8808585, -14747943, 9408237, -10051775, 12493932, -5409317 }, + }, + { + { -25680606, 5260744, -19235809, -6284470, -3695942, 16566087, 27218280, 2607121, 29375955, 6024730 }, + { 842132, -2794693, -4763381, -8722815, 26332018, -12405641, 11831880, 6985184, -9940361, 2854096 }, + { -4847262, -7969331, 2516242, -5847713, 9695691, -7221186, 16512645, 960770, 12121869, 16648078 }, + }, + { + { -15218652, 14667096, -13336229, 2013717, 30598287, -464137, -31504922, -7882064, 20237806, 2838411 }, + { -19288047, 4453152, 15298546, -16178388, 22115043, -15972604, 12544294, -13470457, 1068881, -12499905 }, + { -9558883, -16518835, 33238498, 13506958, 30505848, -1114596, -8486907, -2630053, 12521378, 4845654 }, + }, + { + { -28198521, 10744108, -2958380, 10199664, 7759311, -13088600, 3409348, -873400, -6482306, -12885870 }, + { -23561822, 6230156, -20382013, 10655314, -24040585, -11621172, 10477734, -1240216, -3113227, 13974498 }, + { 12966261, 15550616, -32038948, -1615346, 21025980, -629444, 5642325, 7188737, 18895762, 12629579 }, + }, + }, + { + { + { 14741879, -14946887, 22177208, -11721237, 1279741, 8058600, 11758140, 789443, 32195181, 3895677 }, + { 10758205, 15755439, -4509950, 9243698, -4879422, 6879879, -2204575, -3566119, -8982069, 4429647 }, + { -2453894, 15725973, -20436342, -10410672, -5803908, -11040220, -7135870, -11642895, 18047436, -15281743 }, + }, + { + { -25173001, -11307165, 29759956, 11776784, -22262383, -15820455, 10993114, -12850837, -17620701, -9408468 }, + { 21987233, 700364, -24505048, 14972008, -7774265, -5718395, 32155026, 2581431, -29958985, 8773375 }, + { -25568350, 454463, -13211935, 16126715, 25240068, 8594567, 20656846, 12017935, -7874389, -13920155 }, + }, + { + { 6028182, 6263078, -31011806, -11301710, -818919, 2461772, -31841174, -5468042, -1721788, -2776725 }, + { -12278994, 16624277, 987579, -5922598, 32908203, 1248608, 7719845, -4166698, 28408820, 6816612 }, + { -10358094, -8237829, 19549651, -12169222, 22082623, 16147817, 20613181, 13982702, -10339570, 5067943 }, + }, + { + { -30505967, -3821767, 12074681, 13582412, -19877972, 2443951, -19719286, 12746132, 5331210, -10105944 }, + { 30528811, 3601899, -1957090, 4619785, -27361822, -15436388, 24180793, -12570394, 27679908, -1648928 }, + { 9402404, -13957065, 32834043, 10838634, -26580150, -13237195, 26653274, -8685565, 22611444, -12715406 }, + }, + { + { 22190590, 1118029, 22736441, 15130463, -30460692, -5991321, 19189625, -4648942, 4854859, 6622139 }, + { -8310738, -2953450, -8262579, -3388049, -10401731, -271929, 13424426, -3567227, 26404409, 13001963 }, + { -31241838, -15415700, -2994250, 8939346, 11562230, -12840670, -26064365, -11621720, -15405155, 11020693 }, + }, + { + { 1866042, -7949489, -7898649, -10301010, 12483315, 13477547, 3175636, -12424163, 28761762, 1406734 }, + { -448555, -1777666, 13018551, 3194501, -9580420, -11161737, 24760585, -4347088, 25577411, -13378680 }, + { -24290378, 4759345, -690653, -1852816, 2066747, 10693769, -29595790, 9884936, -9368926, 4745410 }, + }, + { + { -9141284, 6049714, -19531061, -4341411, -31260798, 9944276, -15462008, -11311852, 10931924, -11931931 }, + { -16561513, 14112680, -8012645, 4817318, -8040464, -11414606, -22853429, 10856641, -20470770, 13434654 }, + { 22759489, -10073434, -16766264, -1871422, 13637442, -10168091, 1765144, -12654326, 28445307, -5364710 }, + }, + { + { 29875063, 12493613, 2795536, -3786330, 1710620, 15181182, -10195717, -8788675, 9074234, 1167180 }, + { -26205683, 11014233, -9842651, -2635485, -26908120, 7532294, -18716888, -9535498, 3843903, 9367684 }, + { -10969595, -6403711, 9591134, 9582310, 11349256, 108879, 16235123, 8601684, -139197, 4242895 }, + }, + }, + { + { + { 22092954, -13191123, -2042793, -11968512, 32186753, -11517388, -6574341, 2470660, -27417366, 16625501 }, + { -11057722, 3042016, 13770083, -9257922, 584236, -544855, -7770857, 2602725, -27351616, 14247413 }, + { 6314175, -10264892, -32772502, 15957557, -10157730, 168750, -8618807, 14290061, 27108877, -1180880 }, + }, + { + { -8586597, -7170966, 13241782, 10960156, -32991015, -13794596, 33547976, -11058889, -27148451, 981874 }, + { 22833440, 9293594, -32649448, -13618667, -9136966, 14756819, -22928859, -13970780, -10479804, -16197962 }, + { -7768587, 3326786, -28111797, 10783824, 19178761, 14905060, 22680049, 13906969, -15933690, 3797899 }, + }, + { + { 21721356, -4212746, -12206123, 9310182, -3882239, -13653110, 23740224, -2709232, 20491983, -8042152 }, + { 9209270, -15135055, -13256557, -6167798, -731016, 15289673, 25947805, 15286587, 30997318, -6703063 }, + { 7392032, 16618386, 23946583, -8039892, -13265164, -1533858, -14197445, -2321576, 17649998, -250080 }, + }, + { + { -9301088, -14193827, 30609526, -3049543, -25175069, -1283752, -15241566, -9525724, -2233253, 7662146 }, + { -17558673, 1763594, -33114336, 15908610, -30040870, -12174295, 7335080, -8472199, -3174674, 3440183 }, + { -19889700, -5977008, -24111293, -9688870, 10799743, -16571957, 40450, -4431835, 4862400, 1133 }, + }, + { + { -32856209, -7873957, -5422389, 14860950, -16319031, 7956142, 7258061, 311861, -30594991, -7379421 }, + { -3773428, -1565936, 28985340, 7499440, 24445838, 9325937, 29727763, 16527196, 18278453, 15405622 }, + { -4381906, 8508652, -19898366, -3674424, -5984453, 15149970, -13313598, 843523, -21875062, 13626197 }, + }, + { + { 2281448, -13487055, -10915418, -2609910, 1879358, 16164207, -10783882, 3953792, 13340839, 15928663 }, + { 31727126, -7179855, -18437503, -8283652, 2875793, -16390330, -25269894, -7014826, -23452306, 5964753 }, + { 4100420, -5959452, -17179337, 6017714, -18705837, 12227141, -26684835, 11344144, 2538215, -7570755 }, + }, + { + { -9433605, 6123113, 11159803, -2156608, 30016280, 14966241, -20474983, 1485421, -629256, -15958862 }, + { -26804558, 4260919, 11851389, 9658551, -32017107, 16367492, -20205425, -13191288, 11659922, -11115118 }, + { 26180396, 10015009, -30844224, -8581293, 5418197, 9480663, 2231568, -10170080, 33100372, -1306171 }, + }, + { + { 15121113, -5201871, -10389905, 15427821, -27509937, -15992507, 21670947, 4486675, -5931810, -14466380 }, + { 16166486, -9483733, -11104130, 6023908, -31926798, -1364923, 2340060, -16254968, -10735770, -10039824 }, + { 28042865, -3557089, -12126526, 12259706, -3717498, -6945899, 6766453, -8689599, 18036436, 5803270 }, + }, + }, + { + { + { -817581, 6763912, 11803561, 1585585, 10958447, -2671165, 23855391, 4598332, -6159431, -14117438 }, + { -31031306, -14256194, 17332029, -2383520, 31312682, -5967183, 696309, 50292, -20095739, 11763584 }, + { -594563, -2514283, -32234153, 12643980, 12650761, 14811489, 665117, -12613632, -19773211, -10713562 }, + }, + { + { 30464590, -11262872, -4127476, -12734478, 19835327, -7105613, -24396175, 2075773, -17020157, 992471 }, + { 18357185, -6994433, 7766382, 16342475, -29324918, 411174, 14578841, 8080033, -11574335, -10601610 }, + { 19598397, 10334610, 12555054, 2555664, 18821899, -10339780, 21873263, 16014234, 26224780, 16452269 }, + }, + { + { -30223925, 5145196, 5944548, 16385966, 3976735, 2009897, -11377804, -7618186, -20533829, 3698650 }, + { 14187449, 3448569, -10636236, -10810935, -22663880, -3433596, 7268410, -10890444, 27394301, 12015369 }, + { 19695761, 16087646, 28032085, 12999827, 6817792, 11427614, 20244189, -1312777, -13259127, -3402461 }, + }, + { + { 30860103, 12735208, -1888245, -4699734, -16974906, 2256940, -8166013, 12298312, -8550524, -10393462 }, + { -5719826, -11245325, -1910649, 15569035, 26642876, -7587760, -5789354, -15118654, -4976164, 12651793 }, + { -2848395, 9953421, 11531313, -5282879, 26895123, -12697089, -13118820, -16517902, 9768698, -2533218 }, + }, + { + { -24719459, 1894651, -287698, -4704085, 15348719, -8156530, 32767513, 12765450, 4940095, 10678226 }, + { 18860224, 15980149, -18987240, -1562570, -26233012, -11071856, -7843882, 13944024, -24372348, 16582019 }, + { -15504260, 4970268, -29893044, 4175593, -20993212, -2199756, -11704054, 15444560, -11003761, 7989037 }, + }, + { + { 31490452, 5568061, -2412803, 2182383, -32336847, 4531686, -32078269, 6200206, -19686113, -14800171 }, + { -17308668, -15879940, -31522777, -2831, -32887382, 16375549, 8680158, -16371713, 28550068, -6857132 }, + { -28126887, -5688091, 16837845, -1820458, -6850681, 12700016, -30039981, 4364038, 1155602, 5988841 }, + }, + { + { 21890435, -13272907, -12624011, 12154349, -7831873, 15300496, 23148983, -4470481, 24618407, 8283181 }, + { -33136107, -10512751, 9975416, 6841041, -31559793, 16356536, 3070187, -7025928, 1466169, 10740210 }, + { -1509399, -15488185, -13503385, -10655916, 32799044, 909394, -13938903, -5779719, -32164649, -15327040 }, + }, + { + { 3960823, -14267803, -28026090, -15918051, -19404858, 13146868, 15567327, 951507, -3260321, -573935 }, + { 24740841, 5052253, -30094131, 8961361, 25877428, 6165135, -24368180, 14397372, -7380369, -6144105 }, + { -28888365, 3510803, -28103278, -1158478, -11238128, -10631454, -15441463, -14453128, -1625486, -6494814 }, + }, + }, + { + { + { 793299, -9230478, 8836302, -6235707, -27360908, -2369593, 33152843, -4885251, -9906200, -621852 }, + { 5666233, 525582, 20782575, -8038419, -24538499, 14657740, 16099374, 1468826, -6171428, -15186581 }, + { -4859255, -3779343, -2917758, -6748019, 7778750, 11688288, -30404353, -9871238, -1558923, -9863646 }, + }, + { + { 10896332, -7719704, 824275, 472601, -19460308, 3009587, 25248958, 14783338, -30581476, -15757844 }, + { 10566929, 12612572, -31944212, 11118703, -12633376, 12362879, 21752402, 8822496, 24003793, 14264025 }, + { 27713862, -7355973, -11008240, 9227530, 27050101, 2504721, 23886875, -13117525, 13958495, -5732453 }, + }, + { + { -23481610, 4867226, -27247128, 3900521, 29838369, -8212291, -31889399, -10041781, 7340521, -15410068 }, + { 4646514, -8011124, -22766023, -11532654, 23184553, 8566613, 31366726, -1381061, -15066784, -10375192 }, + { -17270517, 12723032, -16993061, 14878794, 21619651, -6197576, 27584817, 3093888, -8843694, 3849921 }, + }, + { + { -9064912, 2103172, 25561640, -15125738, -5239824, 9582958, 32477045, -9017955, 5002294, -15550259 }, + { -12057553, -11177906, 21115585, -13365155, 8808712, -12030708, 16489530, 13378448, -25845716, 12741426 }, + { -5946367, 10645103, -30911586, 15390284, -3286982, -7118677, 24306472, 15852464, 28834118, -7646072 }, + }, + { + { -17335748, -9107057, -24531279, 9434953, -8472084, -583362, -13090771, 455841, 20461858, 5491305 }, + { 13669248, -16095482, -12481974, -10203039, -14569770, -11893198, -24995986, 11293807, -28588204, -9421832 }, + { 28497928, 6272777, -33022994, 14470570, 8906179, -1225630, 18504674, -14165166, 29867745, -8795943 }, + }, + { + { -16207023, 13517196, -27799630, -13697798, 24009064, -6373891, -6367600, -13175392, 22853429, -4012011 }, + { 24191378, 16712145, -13931797, 15217831, 14542237, 1646131, 18603514, -11037887, 12876623, -2112447 }, + { 17902668, 4518229, -411702, -2829247, 26878217, 5258055, -12860753, 608397, 16031844, 3723494 }, + }, + { + { -28632773, 12763728, -20446446, 7577504, 33001348, -13017745, 17558842, -7872890, 23896954, -4314245 }, + { -20005381, -12011952, 31520464, 605201, 2543521, 5991821, -2945064, 7229064, -9919646, -8826859 }, + { 28816045, 298879, -28165016, -15920938, 19000928, -1665890, -12680833, -2949325, -18051778, -2082915 }, + }, + { + { 16000882, -344896, 3493092, -11447198, -29504595, -13159789, 12577740, 16041268, -19715240, 7847707 }, + { 10151868, 10572098, 27312476, 7922682, 14825339, 4723128, -32855931, -6519018, -10020567, 3852848 }, + { -11430470, 15697596, -21121557, -4420647, 5386314, 15063598, 16514493, -15932110, 29330899, -15076224 }, + }, + }, + { + { + { -25499735, -4378794, -15222908, -6901211, 16615731, 2051784, 3303702, 15490, -27548796, 12314391 }, + { 15683520, -6003043, 18109120, -9980648, 15337968, -5997823, -16717435, 15921866, 16103996, -3731215 }, + { -23169824, -10781249, 13588192, -1628807, -3798557, -1074929, -19273607, 5402699, -29815713, -9841101 }, + }, + { + { 23190676, 2384583, -32714340, 3462154, -29903655, -1529132, -11266856, 8911517, -25205859, 2739713 }, + { 21374101, -3554250, -33524649, 9874411, 15377179, 11831242, -33529904, 6134907, 4931255, 11987849 }, + { -7732, -2978858, -16223486, 7277597, 105524, -322051, -31480539, 13861388, -30076310, 10117930 }, + }, + { + { -29501170, -10744872, -26163768, 13051539, -25625564, 5089643, -6325503, 6704079, 12890019, 15728940 }, + { -21972360, -11771379, -951059, -4418840, 14704840, 2695116, 903376, -10428139, 12885167, 8311031 }, + { -17516482, 5352194, 10384213, -13811658, 7506451, 13453191, 26423267, 4384730, 1888765, -5435404 }, + }, + { + { -25817338, -3107312, -13494599, -3182506, 30896459, -13921729, -32251644, -12707869, -19464434, -3340243 }, + { -23607977, -2665774, -526091, 4651136, 5765089, 4618330, 6092245, 14845197, 17151279, -9854116 }, + { -24830458, -12733720, -15165978, 10367250, -29530908, -265356, 22825805, -7087279, -16866484, 16176525 }, + }, + { + { -23583256, 6564961, 20063689, 3798228, -4740178, 7359225, 2006182, -10363426, -28746253, -10197509 }, + { -10626600, -4486402, -13320562, -5125317, 3432136, -6393229, 23632037, -1940610, 32808310, 1099883 }, + { 15030977, 5768825, -27451236, -2887299, -6427378, -15361371, -15277896, -6809350, 2051441, -15225865 }, + }, + { + { -3362323, -7239372, 7517890, 9824992, 23555850, 295369, 5148398, -14154188, -22686354, 16633660 }, + { 4577086, -16752288, 13249841, -15304328, 19958763, -14537274, 18559670, -10759549, 8402478, -9864273 }, + { -28406330, -1051581, -26790155, -907698, -17212414, -11030789, 9453451, -14980072, 17983010, 9967138 }, + }, + { + { -25762494, 6524722, 26585488, 9969270, 24709298, 1220360, -1677990, 7806337, 17507396, 3651560 }, + { -10420457, -4118111, 14584639, 15971087, -15768321, 8861010, 26556809, -5574557, -18553322, -11357135 }, + { 2839101, 14284142, 4029895, 3472686, 14402957, 12689363, -26642121, 8459447, -5605463, -7621941 }, + }, + { + { -4839289, -3535444, 9744961, 2871048, 25113978, 3187018, -25110813, -849066, 17258084, -7977739 }, + { 18164541, -10595176, -17154882, -1542417, 19237078, -9745295, 23357533, -15217008, 26908270, 12150756 }, + { -30264870, -7647865, 5112249, -7036672, -1499807, -6974257, 43168, -5537701, -32302074, 16215819 }, + }, + }, + { + { + { -6898905, 9824394, -12304779, -4401089, -31397141, -6276835, 32574489, 12532905, -7503072, -8675347 }, + { -27343522, -16515468, -27151524, -10722951, 946346, 16291093, 254968, 7168080, 21676107, -1943028 }, + { 21260961, -8424752, -16831886, -11920822, -23677961, 3968121, -3651949, -6215466, -3556191, -7913075 }, + }, + { + { 16544754, 13250366, -16804428, 15546242, -4583003, 12757258, -2462308, -8680336, -18907032, -9662799 }, + { -2415239, -15577728, 18312303, 4964443, -15272530, -12653564, 26820651, 16690659, 25459437, -4564609 }, + { -25144690, 11425020, 28423002, -11020557, -6144921, -15826224, 9142795, -2391602, -6432418, -1644817 }, + }, + { + { -23104652, 6253476, 16964147, -3768872, -25113972, -12296437, -27457225, -16344658, 6335692, 7249989 }, + { -30333227, 13979675, 7503222, -12368314, -11956721, -4621693, -30272269, 2682242, 25993170, -12478523 }, + { 4364628, 5930691, 32304656, -10044554, -8054781, 15091131, 22857016, -10598955, 31820368, 15075278 }, + }, + { + { 31879134, -8918693, 17258761, 90626, -8041836, -4917709, 24162788, -9650886, -17970238, 12833045 }, + { 19073683, 14851414, -24403169, -11860168, 7625278, 11091125, -19619190, 2074449, -9413939, 14905377 }, + { 24483667, -11935567, -2518866, -11547418, -1553130, 15355506, -25282080, 9253129, 27628530, -7555480 }, + }, + { + { 17597607, 8340603, 19355617, 552187, 26198470, -3176583, 4593324, -9157582, -14110875, 15297016 }, + { 510886, 14337390, -31785257, 16638632, 6328095, 2713355, -20217417, -11864220, 8683221, 2921426 }, + { 18606791, 11874196, 27155355, -5281482, -24031742, 6265446, -25178240, -1278924, 4674690, 13890525 }, + }, + { + { 13609624, 13069022, -27372361, -13055908, 24360586, 9592974, 14977157, 9835105, 4389687, 288396 }, + { 9922506, -519394, 13613107, 5883594, -18758345, -434263, -12304062, 8317628, 23388070, 16052080 }, + { 12720016, 11937594, -31970060, -5028689, 26900120, 8561328, -20155687, -11632979, -14754271, -10812892 }, + }, + { + { 15961858, 14150409, 26716931, -665832, -22794328, 13603569, 11829573, 7467844, -28822128, 929275 }, + { 11038231, -11582396, -27310482, -7316562, -10498527, -16307831, -23479533, -9371869, -21393143, 2465074 }, + { 20017163, -4323226, 27915242, 1529148, 12396362, 15675764, 13817261, -9658066, 2463391, -4622140 }, + }, + { + { -16358878, -12663911, -12065183, 4996454, -1256422, 1073572, 9583558, 12851107, 4003896, 12673717 }, + { -1731589, -15155870, -3262930, 16143082, 19294135, 13385325, 14741514, -9103726, 7903886, 2348101 }, + { 24536016, -16515207, 12715592, -3862155, 1511293, 10047386, -3842346, -7129159, -28377538, 10048127 }, + }, + }, + { + { + { -12622226, -6204820, 30718825, 2591312, -10617028, 12192840, 18873298, -7297090, -32297756, 15221632 }, + { -26478122, -11103864, 11546244, -1852483, 9180880, 7656409, -21343950, 2095755, 29769758, 6593415 }, + { -31994208, -2907461, 4176912, 3264766, 12538965, -868111, 26312345, -6118678, 30958054, 8292160 }, + }, + { + { 31429822, -13959116, 29173532, 15632448, 12174511, -2760094, 32808831, 3977186, 26143136, -3148876 }, + { 22648901, 1402143, -22799984, 13746059, 7936347, 365344, -8668633, -1674433, -3758243, -2304625 }, + { -15491917, 8012313, -2514730, -12702462, -23965846, -10254029, -1612713, -1535569, -16664475, 8194478 }, + }, + { + { 27338066, -7507420, -7414224, 10140405, -19026427, -6589889, 27277191, 8855376, 28572286, 3005164 }, + { 26287124, 4821776, 25476601, -4145903, -3764513, -15788984, -18008582, 1182479, -26094821, -13079595 }, + { -7171154, 3178080, 23970071, 6201893, -17195577, -4489192, -21876275, -13982627, 32208683, -1198248 }, + }, + { + { -16657702, 2817643, -10286362, 14811298, 6024667, 13349505, -27315504, -10497842, -27672585, -11539858 }, + { 15941029, -9405932, -21367050, 8062055, 31876073, -238629, -15278393, -1444429, 15397331, -4130193 }, + { 8934485, -13485467, -23286397, -13423241, -32446090, 14047986, 31170398, -1441021, -27505566, 15087184 }, + }, + { + { -18357243, -2156491, 24524913, -16677868, 15520427, -6360776, -15502406, 11461896, 16788528, -5868942 }, + { -1947386, 16013773, 21750665, 3714552, -17401782, -16055433, -3770287, -10323320, 31322514, -11615635 }, + { 21426655, -5650218, -13648287, -5347537, -28812189, -4920970, -18275391, -14621414, 13040862, -12112948 }, + }, + { + { 11293895, 12478086, -27136401, 15083750, -29307421, 14748872, 14555558, -13417103, 1613711, 4896935 }, + { -25894883, 15323294, -8489791, -8057900, 25967126, -13425460, 2825960, -4897045, -23971776, -11267415 }, + { -15924766, -5229880, -17443532, 6410664, 3622847, 10243618, 20615400, 12405433, -23753030, -8436416 }, + }, + { + { -7091295, 12556208, -20191352, 9025187, -17072479, 4333801, 4378436, 2432030, 23097949, -566018 }, + { 4565804, -16025654, 20084412, -7842817, 1724999, 189254, 24767264, 10103221, -18512313, 2424778 }, + { 366633, -11976806, 8173090, -6890119, 30788634, 5745705, -7168678, 1344109, -3642553, 12412659 }, + }, + { + { -24001791, 7690286, 14929416, -168257, -32210835, -13412986, 24162697, -15326504, -3141501, 11179385 }, + { 18289522, -14724954, 8056945, 16430056, -21729724, 7842514, -6001441, -1486897, -18684645, -11443503 }, + { 476239, 6601091, -6152790, -9723375, 17503545, -4863900, 27672959, 13403813, 11052904, 5219329 }, + }, + }, + { + { + { 20678546, -8375738, -32671898, 8849123, -5009758, 14574752, 31186971, -3973730, 9014762, -8579056 }, + { -13644050, -10350239, -15962508, 5075808, -1514661, -11534600, -33102500, 9160280, 8473550, -3256838 }, + { 24900749, 14435722, 17209120, -15292541, -22592275, 9878983, -7689309, -16335821, -24568481, 11788948 }, + }, + { + { -3118155, -11395194, -13802089, 14797441, 9652448, -6845904, -20037437, 10410733, -24568470, -1458691 }, + { -15659161, 16736706, -22467150, 10215878, -9097177, 7563911, 11871841, -12505194, -18513325, 8464118 }, + { -23400612, 8348507, -14585951, -861714, -3950205, -6373419, 14325289, 8628612, 33313881, -8370517 }, + }, + { + { -20186973, -4967935, 22367356, 5271547, -1097117, -4788838, -24805667, -10236854, -8940735, -5818269 }, + { -6948785, -1795212, -32625683, -16021179, 32635414, -7374245, 15989197, -12838188, 28358192, -4253904 }, + { -23561781, -2799059, -32351682, -1661963, -9147719, 10429267, -16637684, 4072016, -5351664, 5596589 }, + }, + { + { -28236598, -3390048, 12312896, 6213178, 3117142, 16078565, 29266239, 2557221, 1768301, 15373193 }, + { -7243358, -3246960, -4593467, -7553353, -127927, -912245, -1090902, -4504991, -24660491, 3442910 }, + { -30210571, 5124043, 14181784, 8197961, 18964734, -11939093, 22597931, 7176455, -18585478, 13365930 }, + }, + { + { -7877390, -1499958, 8324673, 4690079, 6261860, 890446, 24538107, -8570186, -9689599, -3031667 }, + { 25008904, -10771599, -4305031, -9638010, 16265036, 15721635, 683793, -11823784, 15723479, -15163481 }, + { -9660625, 12374379, -27006999, -7026148, -7724114, -12314514, 11879682, 5400171, 519526, -1235876 }, + }, + { + { 22258397, -16332233, -7869817, 14613016, -22520255, -2950923, -20353881, 7315967, 16648397, 7605640 }, + { -8081308, -8464597, -8223311, 9719710, 19259459, -15348212, 23994942, -5281555, -9468848, 4763278 }, + { -21699244, 9220969, -15730624, 1084137, -25476107, -2852390, 31088447, -7764523, -11356529, 728112 }, + }, + { + { 26047220, -11751471, -6900323, -16521798, 24092068, 9158119, -4273545, -12555558, -29365436, -5498272 }, + { 17510331, -322857, 5854289, 8403524, 17133918, -3112612, -28111007, 12327945, 10750447, 10014012 }, + { -10312768, 3936952, 9156313, -8897683, 16498692, -994647, -27481051, -666732, 3424691, 7540221 }, + }, + { + { 30322361, -6964110, 11361005, -4143317, 7433304, 4989748, -7071422, -16317219, -9244265, 15258046 }, + { 13054562, -2779497, 19155474, 469045, -12482797, 4566042, 5631406, 2711395, 1062915, -5136345 }, + { -19240248, -11254599, -29509029, -7499965, -5835763, 13005411, -6066489, 12194497, 32960380, 1459310 }, + }, + }, + { + { + { 19852034, 7027924, 23669353, 10020366, 8586503, -6657907, 394197, -6101885, 18638003, -11174937 }, + { 31395534, 15098109, 26581030, 8030562, -16527914, -5007134, 9012486, -7584354, -6643087, -5442636 }, + { -9192165, -2347377, -1997099, 4529534, 25766844, 607986, -13222, 9677543, -32294889, -6456008 }, + }, + { + { -2444496, -149937, 29348902, 8186665, 1873760, 12489863, -30934579, -7839692, -7852844, -8138429 }, + { -15236356, -15433509, 7766470, 746860, 26346930, -10221762, -27333451, 10754588, -9431476, 5203576 }, + { 31834314, 14135496, -770007, 5159118, 20917671, -16768096, -7467973, -7337524, 31809243, 7347066 }, + }, + { + { -9606723, -11874240, 20414459, 13033986, 13716524, -11691881, 19797970, -12211255, 15192876, -2087490 }, + { -12663563, -2181719, 1168162, -3804809, 26747877, -14138091, 10609330, 12694420, 33473243, -13382104 }, + { 33184999, 11180355, 15832085, -11385430, -1633671, 225884, 15089336, -11023903, -6135662, 14480053 }, + }, + { + { 31308717, -5619998, 31030840, -1897099, 15674547, -6582883, 5496208, 13685227, 27595050, 8737275 }, + { -20318852, -15150239, 10933843, -16178022, 8335352, -7546022, -31008351, -12610604, 26498114, 66511 }, + { 22644454, -8761729, -16671776, 4884562, -3105614, -13559366, 30540766, -4286747, -13327787, -7515095 }, + }, + { + { -28017847, 9834845, 18617207, -2681312, -3401956, -13307506, 8205540, 13585437, -17127465, 15115439 }, + { 23711543, -672915, 31206561, -8362711, 6164647, -9709987, -33535882, -1426096, 8236921, 16492939 }, + { -23910559, -13515526, -26299483, -4503841, 25005590, -7687270, 19574902, 10071562, 6708380, -6222424 }, + }, + { + { 2101391, -4930054, 19702731, 2367575, -15427167, 1047675, 5301017, 9328700, 29955601, -11678310 }, + { 3096359, 9271816, -21620864, -15521844, -14847996, -7592937, -25892142, -12635595, -9917575, 6216608 }, + { -32615849, 338663, -25195611, 2510422, -29213566, -13820213, 24822830, -6146567, -26767480, 7525079 }, + }, + { + { -23066649, -13985623, 16133487, -7896178, -3389565, 778788, -910336, -2782495, -19386633, 11994101 }, + { 21691500, -13624626, -641331, -14367021, 3285881, -3483596, -25064666, 9718258, -7477437, 13381418 }, + { 18445390, -4202236, 14979846, 11622458, -1727110, -3582980, 23111648, -6375247, 28535282, 15779576 }, + }, + { + { 30098053, 3089662, -9234387, 16662135, -21306940, 11308411, -14068454, 12021730, 9955285, -16303356 }, + { 9734894, -14576830, -7473633, -9138735, 2060392, 11313496, -18426029, 9924399, 20194861, 13380996 }, + { -26378102, -7965207, -22167821, 15789297, -18055342, -6168792, -1984914, 15707771, 26342023, 10146099 }, + }, + }, + { + { + { -26016874, -219943, 21339191, -41388, 19745256, -2878700, -29637280, 2227040, 21612326, -545728 }, + { -13077387, 1184228, 23562814, -5970442, -20351244, -6348714, 25764461, 12243797, -20856566, 11649658 }, + { -10031494, 11262626, 27384172, 2271902, 26947504, -15997771, 39944, 6114064, 33514190, 2333242 }, + }, + { + { -21433588, -12421821, 8119782, 7219913, -21830522, -9016134, -6679750, -12670638, 24350578, -13450001 }, + { -4116307, -11271533, -23886186, 4843615, -30088339, 690623, -31536088, -10406836, 8317860, 12352766 }, + { 18200138, -14475911, -33087759, -2696619, -23702521, -9102511, -23552096, -2287550, 20712163, 6719373 }, + }, + { + { 26656208, 6075253, -7858556, 1886072, -28344043, 4262326, 11117530, -3763210, 26224235, -3297458 }, + { -17168938, -14854097, -3395676, -16369877, -19954045, 14050420, 21728352, 9493610, 18620611, -16428628 }, + { -13323321, 13325349, 11432106, 5964811, 18609221, 6062965, -5269471, -9725556, -30701573, -16479657 }, + }, + { + { -23860538, -11233159, 26961357, 1640861, -32413112, -16737940, 12248509, -5240639, 13735342, 1934062 }, + { 25089769, 6742589, 17081145, -13406266, 21909293, -16067981, -15136294, -3765346, -21277997, 5473616 }, + { 31883677, -7961101, 1083432, -11572403, 22828471, 13290673, -7125085, 12469656, 29111212, -5451014 }, + }, + { + { 24244947, -15050407, -26262976, 2791540, -14997599, 16666678, 24367466, 6388839, -10295587, 452383 }, + { -25640782, -3417841, 5217916, 16224624, 19987036, -4082269, -24236251, -5915248, 15766062, 8407814 }, + { -20406999, 13990231, 15495425, 16395525, 5377168, 15166495, -8917023, -4388953, -8067909, 2276718 }, + }, + { + { 30157918, 12924066, -17712050, 9245753, 19895028, 3368142, -23827587, 5096219, 22740376, -7303417 }, + { 2041139, -14256350, 7783687, 13876377, -25946985, -13352459, 24051124, 13742383, -15637599, 13295222 }, + { 33338237, -8505733, 12532113, 7977527, 9106186, -1715251, -17720195, -4612972, -4451357, -14669444 }, + }, + { + { -20045281, 5454097, -14346548, 6447146, 28862071, 1883651, -2469266, -4141880, 7770569, 9620597 }, + { 23208068, 7979712, 33071466, 8149229, 1758231, -10834995, 30945528, -1694323, -33502340, -14767970 }, + { 1439958, -16270480, -1079989, -793782, 4625402, 10647766, -5043801, 1220118, 30494170, -11440799 }, + }, + { + { -5037580, -13028295, -2970559, -3061767, 15640974, -6701666, -26739026, 926050, -1684339, -13333647 }, + { 13908495, -3549272, 30919928, -6273825, -21521863, 7989039, 9021034, 9078865, 3353509, 4033511 }, + { -29663431, -15113610, 32259991, -344482, 24295849, -12912123, 23161163, 8839127, 27485041, 7356032 }, + }, + }, + { + { + { 9661027, 705443, 11980065, -5370154, -1628543, 14661173, -6346142, 2625015, 28431036, -16771834 }, + { -23839233, -8311415, -25945511, 7480958, -17681669, -8354183, -22545972, 14150565, 15970762, 4099461 }, + { 29262576, 16756590, 26350592, -8793563, 8529671, -11208050, 13617293, -9937143, 11465739, 8317062 }, + }, + { + { -25493081, -6962928, 32500200, -9419051, -23038724, -2302222, 14898637, 3848455, 20969334, -5157516 }, + { -20384450, -14347713, -18336405, 13884722, -33039454, 2842114, -21610826, -3649888, 11177095, 14989547 }, + { -24496721, -11716016, 16959896, 2278463, 12066309, 10137771, 13515641, 2581286, -28487508, 9930240 }, + }, + { + { -17751622, -2097826, 16544300, -13009300, -15914807, -14949081, 18345767, -13403753, 16291481, -5314038 }, + { -33229194, 2553288, 32678213, 9875984, 8534129, 6889387, -9676774, 6957617, 4368891, 9788741 }, + { 16660756, 7281060, -10830758, 12911820, 20108584, -8101676, -21722536, -8613148, 16250552, -11111103 }, + }, + { + { -19765507, 2390526, -16551031, 14161980, 1905286, 6414907, 4689584, 10604807, -30190403, 4782747 }, + { -1354539, 14736941, -7367442, -13292886, 7710542, -14155590, -9981571, 4383045, 22546403, 437323 }, + { 31665577, -12180464, -16186830, 1491339, -18368625, 3294682, 27343084, 2786261, -30633590, -14097016 }, + }, + { + { -14467279, -683715, -33374107, 7448552, 19294360, 14334329, -19690631, 2355319, -19284671, -6114373 }, + { 15121312, -15796162, 6377020, -6031361, -10798111, -12957845, 18952177, 15496498, -29380133, 11754228 }, + { -2637277, -13483075, 8488727, -14303896, 12728761, -1622493, 7141596, 11724556, 22761615, -10134141 }, + }, + { + { 16918416, 11729663, -18083579, 3022987, -31015732, -13339659, -28741185, -12227393, 32851222, 11717399 }, + { 11166634, 7338049, -6722523, 4531520, -29468672, -7302055, 31474879, 3483633, -1193175, -4030831 }, + { -185635, 9921305, 31456609, -13536438, -12013818, 13348923, 33142652, 6546660, -19985279, -3948376 }, + }, + { + { -32460596, 11266712, -11197107, -7899103, 31703694, 3855903, -8537131, -12833048, -30772034, -15486313 }, + { -18006477, 12709068, 3991746, -6479188, -21491523, -10550425, -31135347, -16049879, 10928917, 3011958 }, + { -6957757, -15594337, 31696059, 334240, 29576716, 14796075, -30831056, -12805180, 18008031, 10258577 }, + }, + { + { -22448644, 15655569, 7018479, -4410003, -30314266, -1201591, -1853465, 1367120, 25127874, 6671743 }, + { 29701166, -14373934, -10878120, 9279288, -17568, 13127210, 21382910, 11042292, 25838796, 4642684 }, + { -20430234, 14955537, -24126347, 8124619, -5369288, -5990470, 30468147, -13900640, 18423289, 4177476 }, + }, + }, +}; + diff --git a/ed25519/src/sc.cpp b/ed25519/src/sc.cpp new file mode 100644 index 0000000..a6656c4 --- /dev/null +++ b/ed25519/src/sc.cpp @@ -0,0 +1,812 @@ +// 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; +} + +/* +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; + s10 += s22 * 666643; + s11 += s22 * 470296; + s12 += s22 * 654183; + s13 -= s22 * 997805; + s14 += s22 * 136657; + s15 -= s22 * 683901; + s22 = 0; + s9 += s21 * 666643; + s10 += s21 * 470296; + s11 += s21 * 654183; + s12 -= s21 * 997805; + s13 += s21 * 136657; + s14 -= s21 * 683901; + s21 = 0; + s8 += s20 * 666643; + s9 += s20 * 470296; + s10 += s20 * 654183; + s11 -= s20 * 997805; + s12 += s20 * 136657; + s13 -= s20 * 683901; + s20 = 0; + s7 += s19 * 666643; + s8 += s19 * 470296; + s9 += s19 * 654183; + s10 -= s19 * 997805; + s11 += s19 * 136657; + s12 -= s19 * 683901; + s19 = 0; + s6 += s18 * 666643; + s7 += s18 * 470296; + s8 += s18 * 654183; + s9 -= s18 * 997805; + s10 += s18 * 136657; + s11 -= s18 * 683901; + s18 = 0; + carry6 = (s6 + (1 << 20)) >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry8 = (s8 + (1 << 20)) >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry10 = (s10 + (1 << 20)) >> 21; + s11 += carry10; + s10 -= carry10 << 21; + carry12 = (s12 + (1 << 20)) >> 21; + s13 += carry12; + s12 -= carry12 << 21; + carry14 = (s14 + (1 << 20)) >> 21; + s15 += carry14; + s14 -= carry14 << 21; + carry16 = (s16 + (1 << 20)) >> 21; + s17 += carry16; + s16 -= carry16 << 21; + carry7 = (s7 + (1 << 20)) >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry9 = (s9 + (1 << 20)) >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry11 = (s11 + (1 << 20)) >> 21; + s12 += carry11; + s11 -= carry11 << 21; + carry13 = (s13 + (1 << 20)) >> 21; + s14 += carry13; + s13 -= carry13 << 21; + carry15 = (s15 + (1 << 20)) >> 21; + s16 += carry15; + s15 -= carry15 << 21; + s5 += s17 * 666643; + s6 += s17 * 470296; + s7 += s17 * 654183; + s8 -= s17 * 997805; + s9 += s17 * 136657; + s10 -= s17 * 683901; + s17 = 0; + s4 += s16 * 666643; + s5 += s16 * 470296; + s6 += s16 * 654183; + s7 -= s16 * 997805; + s8 += s16 * 136657; + s9 -= s16 * 683901; + s16 = 0; + s3 += s15 * 666643; + s4 += s15 * 470296; + s5 += s15 * 654183; + s6 -= s15 * 997805; + s7 += s15 * 136657; + s8 -= s15 * 683901; + s15 = 0; + s2 += s14 * 666643; + s3 += s14 * 470296; + s4 += s14 * 654183; + s5 -= s14 * 997805; + s6 += s14 * 136657; + s7 -= s14 * 683901; + s14 = 0; + s1 += s13 * 666643; + s2 += s13 * 470296; + s3 += s13 * 654183; + s4 -= s13 * 997805; + s5 += s13 * 136657; + s6 -= s13 * 683901; + s13 = 0; + 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 -= carry0 << 21; + carry2 = (s2 + (1 << 20)) >> 21; + s3 += carry2; + s2 -= carry2 << 21; + carry4 = (s4 + (1 << 20)) >> 21; + s5 += carry4; + s4 -= carry4 << 21; + carry6 = (s6 + (1 << 20)) >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry8 = (s8 + (1 << 20)) >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry10 = (s10 + (1 << 20)) >> 21; + s11 += carry10; + s10 -= carry10 << 21; + carry1 = (s1 + (1 << 20)) >> 21; + s2 += carry1; + s1 -= carry1 << 21; + carry3 = (s3 + (1 << 20)) >> 21; + s4 += carry3; + s3 -= carry3 << 21; + carry5 = (s5 + (1 << 20)) >> 21; + s6 += carry5; + s5 -= carry5 << 21; + carry7 = (s7 + (1 << 20)) >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry9 = (s9 + (1 << 20)) >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry11 = (s11 + (1 << 20)) >> 21; + s12 += carry11; + s11 -= 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 -= carry0 << 21; + carry1 = s1 >> 21; + s2 += carry1; + s1 -= carry1 << 21; + carry2 = s2 >> 21; + s3 += carry2; + s2 -= carry2 << 21; + carry3 = s3 >> 21; + s4 += carry3; + s3 -= carry3 << 21; + carry4 = s4 >> 21; + s5 += carry4; + s4 -= carry4 << 21; + carry5 = s5 >> 21; + s6 += carry5; + s5 -= carry5 << 21; + carry6 = s6 >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry7 = s7 >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry8 = s8 >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry9 = s9 >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry10 = s10 >> 21; + s11 += carry10; + s10 -= carry10 << 21; + carry11 = s11 >> 21; + s12 += carry11; + s11 -= 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 -= carry0 << 21; + carry1 = s1 >> 21; + s2 += carry1; + s1 -= carry1 << 21; + carry2 = s2 >> 21; + s3 += carry2; + s2 -= carry2 << 21; + carry3 = s3 >> 21; + s4 += carry3; + s3 -= carry3 << 21; + carry4 = s4 >> 21; + s5 += carry4; + s4 -= carry4 << 21; + carry5 = s5 >> 21; + s6 += carry5; + s5 -= carry5 << 21; + carry6 = s6 >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry7 = s7 >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry8 = s8 >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry9 = s9 >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry10 = s10 >> 21; + s11 += carry10; + s10 -= 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 -= carry0 << 21; + carry2 = (s2 + (1 << 20)) >> 21; + s3 += carry2; + s2 -= carry2 << 21; + carry4 = (s4 + (1 << 20)) >> 21; + s5 += carry4; + s4 -= carry4 << 21; + carry6 = (s6 + (1 << 20)) >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry8 = (s8 + (1 << 20)) >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry10 = (s10 + (1 << 20)) >> 21; + s11 += carry10; + s10 -= carry10 << 21; + carry12 = (s12 + (1 << 20)) >> 21; + s13 += carry12; + s12 -= carry12 << 21; + carry14 = (s14 + (1 << 20)) >> 21; + s15 += carry14; + s14 -= carry14 << 21; + carry16 = (s16 + (1 << 20)) >> 21; + s17 += carry16; + s16 -= carry16 << 21; + carry18 = (s18 + (1 << 20)) >> 21; + s19 += carry18; + s18 -= carry18 << 21; + carry20 = (s20 + (1 << 20)) >> 21; + s21 += carry20; + s20 -= carry20 << 21; + carry22 = (s22 + (1 << 20)) >> 21; + s23 += carry22; + s22 -= carry22 << 21; + carry1 = (s1 + (1 << 20)) >> 21; + s2 += carry1; + s1 -= carry1 << 21; + carry3 = (s3 + (1 << 20)) >> 21; + s4 += carry3; + s3 -= carry3 << 21; + carry5 = (s5 + (1 << 20)) >> 21; + s6 += carry5; + s5 -= carry5 << 21; + carry7 = (s7 + (1 << 20)) >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry9 = (s9 + (1 << 20)) >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry11 = (s11 + (1 << 20)) >> 21; + s12 += carry11; + s11 -= carry11 << 21; + carry13 = (s13 + (1 << 20)) >> 21; + s14 += carry13; + s13 -= carry13 << 21; + carry15 = (s15 + (1 << 20)) >> 21; + s16 += carry15; + s15 -= carry15 << 21; + carry17 = (s17 + (1 << 20)) >> 21; + s18 += carry17; + s17 -= carry17 << 21; + carry19 = (s19 + (1 << 20)) >> 21; + s20 += carry19; + s19 -= carry19 << 21; + carry21 = (s21 + (1 << 20)) >> 21; + s22 += carry21; + s21 -= carry21 << 21; + s11 += s23 * 666643; + s12 += s23 * 470296; + s13 += s23 * 654183; + s14 -= s23 * 997805; + s15 += s23 * 136657; + s16 -= s23 * 683901; + s23 = 0; + s10 += s22 * 666643; + s11 += s22 * 470296; + s12 += s22 * 654183; + s13 -= s22 * 997805; + s14 += s22 * 136657; + s15 -= s22 * 683901; + s22 = 0; + s9 += s21 * 666643; + s10 += s21 * 470296; + s11 += s21 * 654183; + s12 -= s21 * 997805; + s13 += s21 * 136657; + s14 -= s21 * 683901; + s21 = 0; + s8 += s20 * 666643; + s9 += s20 * 470296; + s10 += s20 * 654183; + s11 -= s20 * 997805; + s12 += s20 * 136657; + s13 -= s20 * 683901; + s20 = 0; + s7 += s19 * 666643; + s8 += s19 * 470296; + s9 += s19 * 654183; + s10 -= s19 * 997805; + s11 += s19 * 136657; + s12 -= s19 * 683901; + s19 = 0; + s6 += s18 * 666643; + s7 += s18 * 470296; + s8 += s18 * 654183; + s9 -= s18 * 997805; + s10 += s18 * 136657; + s11 -= s18 * 683901; + s18 = 0; + carry6 = (s6 + (1 << 20)) >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry8 = (s8 + (1 << 20)) >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry10 = (s10 + (1 << 20)) >> 21; + s11 += carry10; + s10 -= carry10 << 21; + carry12 = (s12 + (1 << 20)) >> 21; + s13 += carry12; + s12 -= carry12 << 21; + carry14 = (s14 + (1 << 20)) >> 21; + s15 += carry14; + s14 -= carry14 << 21; + carry16 = (s16 + (1 << 20)) >> 21; + s17 += carry16; + s16 -= carry16 << 21; + carry7 = (s7 + (1 << 20)) >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry9 = (s9 + (1 << 20)) >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry11 = (s11 + (1 << 20)) >> 21; + s12 += carry11; + s11 -= carry11 << 21; + carry13 = (s13 + (1 << 20)) >> 21; + s14 += carry13; + s13 -= carry13 << 21; + carry15 = (s15 + (1 << 20)) >> 21; + s16 += carry15; + s15 -= carry15 << 21; + s5 += s17 * 666643; + s6 += s17 * 470296; + s7 += s17 * 654183; + s8 -= s17 * 997805; + s9 += s17 * 136657; + s10 -= s17 * 683901; + s17 = 0; + s4 += s16 * 666643; + s5 += s16 * 470296; + s6 += s16 * 654183; + s7 -= s16 * 997805; + s8 += s16 * 136657; + s9 -= s16 * 683901; + s16 = 0; + s3 += s15 * 666643; + s4 += s15 * 470296; + s5 += s15 * 654183; + s6 -= s15 * 997805; + s7 += s15 * 136657; + s8 -= s15 * 683901; + s15 = 0; + s2 += s14 * 666643; + s3 += s14 * 470296; + s4 += s14 * 654183; + s5 -= s14 * 997805; + s6 += s14 * 136657; + s7 -= s14 * 683901; + s14 = 0; + s1 += s13 * 666643; + s2 += s13 * 470296; + s3 += s13 * 654183; + s4 -= s13 * 997805; + s5 += s13 * 136657; + s6 -= s13 * 683901; + s13 = 0; + 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 -= carry0 << 21; + carry2 = (s2 + (1 << 20)) >> 21; + s3 += carry2; + s2 -= carry2 << 21; + carry4 = (s4 + (1 << 20)) >> 21; + s5 += carry4; + s4 -= carry4 << 21; + carry6 = (s6 + (1 << 20)) >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry8 = (s8 + (1 << 20)) >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry10 = (s10 + (1 << 20)) >> 21; + s11 += carry10; + s10 -= carry10 << 21; + carry1 = (s1 + (1 << 20)) >> 21; + s2 += carry1; + s1 -= carry1 << 21; + carry3 = (s3 + (1 << 20)) >> 21; + s4 += carry3; + s3 -= carry3 << 21; + carry5 = (s5 + (1 << 20)) >> 21; + s6 += carry5; + s5 -= carry5 << 21; + carry7 = (s7 + (1 << 20)) >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry9 = (s9 + (1 << 20)) >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry11 = (s11 + (1 << 20)) >> 21; + s12 += carry11; + s11 -= 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 -= carry0 << 21; + carry1 = s1 >> 21; + s2 += carry1; + s1 -= carry1 << 21; + carry2 = s2 >> 21; + s3 += carry2; + s2 -= carry2 << 21; + carry3 = s3 >> 21; + s4 += carry3; + s3 -= carry3 << 21; + carry4 = s4 >> 21; + s5 += carry4; + s4 -= carry4 << 21; + carry5 = s5 >> 21; + s6 += carry5; + s5 -= carry5 << 21; + carry6 = s6 >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry7 = s7 >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry8 = s8 >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry9 = s9 >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry10 = s10 >> 21; + s11 += carry10; + s10 -= carry10 << 21; + carry11 = s11 >> 21; + s12 += carry11; + s11 -= 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 -= carry0 << 21; + carry1 = s1 >> 21; + s2 += carry1; + s1 -= carry1 << 21; + carry2 = s2 >> 21; + s3 += carry2; + s2 -= carry2 << 21; + carry3 = s3 >> 21; + s4 += carry3; + s3 -= carry3 << 21; + carry4 = s4 >> 21; + s5 += carry4; + s4 -= carry4 << 21; + carry5 = s5 >> 21; + s6 += carry5; + s5 -= carry5 << 21; + carry6 = s6 >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry7 = s7 >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry8 = s8 >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry9 = s9 >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry10 = s10 >> 21; + s11 += carry10; + s10 -= carry10 << 21; + + s[0] = (unsigned char) ((s0 >> 0) & 0xff); + s[1] = (unsigned char) ((s0 >> 8) & 0xff); + s[2] = (unsigned char) (((s0 >> 16) | (s1 << 5)) & 0xff); + s[3] = (unsigned char) ((s1 >> 3) & 0xff); + s[4] = (unsigned char) ((s1 >> 11) & 0xff); + s[5] = (unsigned char) (((s1 >> 19) | (s2 << 2)) & 0xff); + s[6] = (unsigned char) ((s2 >> 6) & 0xff); + s[7] = (unsigned char) (((s2 >> 14) | (s3 << 7)) & 0xff); + s[8] = (unsigned char) ((s3 >> 1) & 0xff); + s[9] = (unsigned char) ((s3 >> 9) & 0xff); + s[10] = (unsigned char) (((s3 >> 17) | (s4 << 4)) & 0xff); + s[11] = (unsigned char) ((s4 >> 4) & 0xff); + s[12] = (unsigned char) ((s4 >> 12) & 0xff); + s[13] = (unsigned char) (((s4 >> 20) | (s5 << 1)) & 0xff); + s[14] = (unsigned char) ((s5 >> 7) & 0xff); + s[15] = (unsigned char) (((s5 >> 15) | (s6 << 6)) & 0xff); + s[16] = (unsigned char) ((s6 >> 2) & 0xff); + s[17] = (unsigned char) ((s6 >> 10) & 0xff); + s[18] = (unsigned char) (((s6 >> 18) | (s7 << 3)) & 0xff); + s[19] = (unsigned char) ((s7 >> 5) & 0xff); + s[20] = (unsigned char) ((s7 >> 13) & 0xff); + s[21] = (unsigned char) ((s8 >> 0) & 0xff); + s[22] = (unsigned char) ((s8 >> 8) & 0xff); + s[23] = (unsigned char) (((s8 >> 16) | (s9 << 5)) & 0xff); + s[24] = (unsigned char) ((s9 >> 3) & 0xff); + s[25] = (unsigned char) ((s9 >> 11) & 0xff); + s[26] = (unsigned char) (((s9 >> 19) | (s10 << 2)) & 0xff); + s[27] = (unsigned char) ((s10 >> 6) & 0xff); + s[28] = (unsigned char) (((s10 >> 14) | (s11 << 7)) & 0xff); + s[29] = (unsigned char) ((s11 >> 1) & 0xff); + s[30] = (unsigned char) ((s11 >> 9) & 0xff); + s[31] = (unsigned char) ((s11 >> 17) & 0xff); +} diff --git a/ed25519/src/sc.h b/ed25519/src/sc.h new file mode 100644 index 0000000..0c058e5 --- /dev/null +++ b/ed25519/src/sc.h @@ -0,0 +1,13 @@ +#ifndef SC_H +#define SC_H + +/* +The set of scalars is \Z/l +where l = 2^252 + 27742317777372353535851937790883648493. +*/ + +void sc_reduce(unsigned char *s); +void sc_muladd(unsigned char *s, const unsigned char *a, const unsigned char *b, const unsigned char *c); + +#endif + diff --git a/ed25519/src/seed.cpp b/ed25519/src/seed.cpp new file mode 100644 index 0000000..7d03a37 --- /dev/null +++ b/ed25519/src/seed.cpp @@ -0,0 +1,63 @@ +// ignore warnings in this file +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include "libtorrent/config.hpp" +#include "libtorrent/ed25519.hpp" + +#ifndef ED25519_NO_SEED + +#if TORRENT_USE_CRYPTOGRAPHIC_BUFFER +#include +#include +using namespace Windows::Security::Cryptography; +using namespace Windows::Storage::Streams; +using namespace Microsoft::WRL; +#elif defined _WIN32 +#include +#include +#else +#include +#endif + +void ed25519_create_seed(unsigned char *seed) { +#if TORRENT_USE_CRYPTOGRAPHIC_BUFFER + IBuffer^ seedBuffer = CryptographicBuffer::GenerateRandom(32); + ComPtr bufferByteAccess; + reinterpret_cast(seedBuffer)->QueryInterface(IID_PPV_ARGS(&bufferByteAccess)); + bufferByteAccess->Buffer(&seed); +#elif defined _WIN32 + HCRYPTPROV prov; + + if (!CryptAcquireContext(&prov, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { + throw boost::system::system_error(boost::system::error_code(GetLastError() + , boost::system::system_category())); + } + + if (!CryptGenRandom(prov, 32, seed)) { + CryptReleaseContext(prov, 0); + throw boost::system::system_error(boost::system::error_code(GetLastError() + , boost::system::system_category())); + } + + CryptReleaseContext(prov, 0); +#else + FILE *f = fopen("/dev/urandom", "rb"); + + if (f == NULL) { + throw boost::system::system_error(boost::system::error_code(errno, boost::system::system_category())); + } + + int read = fread(seed, 1, 32, f); + if (read != 32) { + fclose(f); + throw boost::system::system_error(boost::system::error_code(errno, boost::system::system_category())); + } + + fclose(f); +#endif +} + +#endif + diff --git a/ed25519/src/sha512.cpp b/ed25519/src/sha512.cpp new file mode 100644 index 0000000..1b4c23f --- /dev/null +++ b/ed25519/src/sha512.cpp @@ -0,0 +1,282 @@ +// 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 + */ + +#include "fixedint.h" +#include "sha512.h" + +#ifndef UINT64_C +#define UINT64_C(x) x ## LL +#endif + +/* 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_context *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_context * md) { + if (md == NULL) 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_context * md, const unsigned char *in, size_t inlen) +{ + size_t n; + size_t i; + int err; + if (md == NULL) return 1; + if (in == NULL) 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(sha512_context * md, unsigned char *out) + { + int i; + + if (md == NULL) return 1; + if (out == NULL) 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; +} + +int sha512(const unsigned char *message, size_t message_len, unsigned char *out) +{ + sha512_context ctx; + int ret; + if ((ret = sha512_init(&ctx))) return ret; + if ((ret = sha512_update(&ctx, message, message_len))) return ret; + if ((ret = sha512_final(&ctx, out))) return ret; + return 0; +} diff --git a/ed25519/src/sha512.h b/ed25519/src/sha512.h new file mode 100644 index 0000000..7f93a32 --- /dev/null +++ b/ed25519/src/sha512.h @@ -0,0 +1,22 @@ +#ifndef SHA512_H +#define SHA512_H + +#include + +#include "fixedint.h" + +/* state */ +typedef struct sha512_context_ { + u64 length, state[8]; + size_t curlen; + unsigned char buf[128]; +} sha512_context; + + +int sha512_init(sha512_context * md); +int sha512_final(sha512_context * md, unsigned char *out); +int sha512_update(sha512_context * md, const unsigned char *in, size_t inlen); +int sha512(const unsigned char *message, size_t message_len, unsigned char *out); + +#endif + diff --git a/ed25519/src/sign.cpp b/ed25519/src/sign.cpp new file mode 100644 index 0000000..cf4b20d --- /dev/null +++ b/ed25519/src/sign.cpp @@ -0,0 +1,34 @@ +// ignore warnings in this file +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include "libtorrent/ed25519.hpp" +#include "sha512.h" +#include "ge.h" +#include "sc.h" + + +void ed25519_sign(unsigned char *signature, const unsigned char *message, size_t message_len, const unsigned char *public_key, const unsigned char *private_key) { + sha512_context hash; + unsigned char hram[64]; + unsigned char r[64]; + ge_p3 R; + + + sha512_init(&hash); + sha512_update(&hash, private_key + 32, 32); + sha512_update(&hash, message, message_len); + sha512_final(&hash, r); + + sc_reduce(r); + ge_scalarmult_base(&R, r); + ge_p3_tobytes(signature, &R); + + sha512_init(&hash); + sha512_update(&hash, signature, 32); + sha512_update(&hash, public_key, 32); + sha512_update(&hash, message, message_len); + sha512_final(&hash, hram); + + sc_reduce(hram); + sc_muladd(signature + 32, hram, private_key, r); +} diff --git a/ed25519/src/verify.cpp b/ed25519/src/verify.cpp new file mode 100644 index 0000000..b5d81b8 --- /dev/null +++ b/ed25519/src/verify.cpp @@ -0,0 +1,80 @@ +// ignore warnings in this file +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include "libtorrent/ed25519.hpp" +#include "sha512.h" +#include "ge.h" +#include "sc.h" + +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, size_t message_len, const unsigned char *public_key) { + unsigned char h[64]; + unsigned char checker[32]; + sha512_context hash; + ge_p3 A; + ge_p2 R; + + if (signature[63] & 224) { + return 0; + } + + if (ge_frombytes_negate_vartime(&A, public_key) != 0) { + return 0; + } + + sha512_init(&hash); + sha512_update(&hash, signature, 32); + sha512_update(&hash, public_key, 32); + sha512_update(&hash, message, message_len); + sha512_final(&hash, h); + + sc_reduce(h); + ge_double_scalarmult_vartime(&R, h, &A, signature + 32); + ge_tobytes(checker, &R); + + if (!consttime_equal(checker, signature)) { + return 0; + } + + return 1; +} diff --git a/ed25519/test.c b/ed25519/test.c new file mode 100644 index 0000000..a56d202 --- /dev/null +++ b/ed25519/test.c @@ -0,0 +1,149 @@ +#include +#include +#include +#include + +//#define ED25519_DLL +#include "src/ed25519.h" + +#include "src/ge.h" +#include "src/sc.h" + +const char message[] = "Hello, world!"; + + +int main(int argc, char *argv[]) { + unsigned char public_key[32], private_key[64], seed[32], scalar[32]; + unsigned char other_public_key[32], other_private_key[64]; + unsigned char shared_secret[32], other_shared_secret[32]; + unsigned char signature[64]; + + clock_t start; + clock_t end; + int i; + + /* create a random seed, and a keypair out of that seed */ + ed25519_create_seed(seed); + ed25519_create_keypair(public_key, private_key, seed); + + /* create signature on the message with the keypair */ + ed25519_sign(signature, message, strlen(message), public_key, private_key); + + /* verify the signature */ + if (ed25519_verify(signature, message, strlen(message), public_key)) { + printf("valid signature\n"); + } else { + printf("invalid signature\n"); + } + + /* create scalar and add it to the keypair */ + ed25519_create_seed(scalar); + ed25519_add_scalar(public_key, private_key, scalar); + + /* create signature with the new keypair */ + ed25519_sign(signature, message, strlen(message), public_key, private_key); + + /* verify the signature with the new keypair */ + if (ed25519_verify(signature, message, strlen(message), public_key)) { + printf("valid signature\n"); + } else { + printf("invalid signature\n"); + } + + /* make a slight adjustment and verify again */ + signature[44] ^= 0x10; + if (ed25519_verify(signature, message, strlen(message), public_key)) { + printf("did not detect signature change\n"); + } else { + printf("correctly detected signature change\n"); + } + + /* generate two keypairs for testing key exchange */ + ed25519_create_seed(seed); + ed25519_create_keypair(public_key, private_key, seed); + ed25519_create_seed(seed); + ed25519_create_keypair(other_public_key, other_private_key, seed); + + /* create two shared secrets - from both perspectives - and check if they're equal */ + ed25519_key_exchange(shared_secret, other_public_key, private_key); + ed25519_key_exchange(other_shared_secret, public_key, other_private_key); + + for (i = 0; i < 32; ++i) { + if (shared_secret[i] != other_shared_secret[i]) { + printf("key exchange was incorrect\n"); + break; + } + } + + if (i == 32) { + printf("key exchange was correct\n"); + } + + /* test performance */ + printf("testing seed generation performance: "); + start = clock(); + for (i = 0; i < 10000; ++i) { + ed25519_create_seed(seed); + } + end = clock(); + + printf("%fus per seed\n", ((double) ((end - start) * 1000)) / CLOCKS_PER_SEC / i * 1000); + + + printf("testing key generation performance: "); + start = clock(); + for (i = 0; i < 10000; ++i) { + ed25519_create_keypair(public_key, private_key, seed); + } + end = clock(); + + printf("%fus per keypair\n", ((double) ((end - start) * 1000)) / CLOCKS_PER_SEC / i * 1000); + + printf("testing sign performance: "); + start = clock(); + for (i = 0; i < 10000; ++i) { + ed25519_sign(signature, message, strlen(message), public_key, private_key); + } + end = clock(); + + printf("%fus per signature\n", ((double) ((end - start) * 1000)) / CLOCKS_PER_SEC / i * 1000); + + printf("testing verify performance: "); + start = clock(); + for (i = 0; i < 10000; ++i) { + ed25519_verify(signature, message, strlen(message), public_key); + } + end = clock(); + + printf("%fus per signature\n", ((double) ((end - start) * 1000)) / CLOCKS_PER_SEC / i * 1000); + + + printf("testing keypair scalar addition performance: "); + start = clock(); + for (i = 0; i < 10000; ++i) { + ed25519_add_scalar(public_key, private_key, scalar); + } + end = clock(); + + printf("%fus per keypair\n", ((double) ((end - start) * 1000)) / CLOCKS_PER_SEC / i * 1000); + + printf("testing public key scalar addition performance: "); + start = clock(); + for (i = 0; i < 10000; ++i) { + ed25519_add_scalar(public_key, NULL, scalar); + } + end = clock(); + + printf("%fus per key\n", ((double) ((end - start) * 1000)) / CLOCKS_PER_SEC / i * 1000); + + printf("testing key exchange performance: "); + start = clock(); + for (i = 0; i < 10000; ++i) { + ed25519_key_exchange(shared_secret, other_public_key, private_key); + } + end = clock(); + + printf("%fus per shared secret\n", ((double) ((end - start) * 1000)) / CLOCKS_PER_SEC / i * 1000); + + return 0; +} diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..ba7595f --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,42 @@ +project(libtorrent-examples) +cmake_minimum_required(VERSION 2.6) +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/") + +# Add extra include and library search directories so examples can optionally +# be built without a prior "make install" of libtorrent. +list(INSERT CMAKE_INCLUDE_PATH 0 "${CMAKE_CURRENT_SOURCE_DIR}/../include") +list(INSERT CMAKE_LIBRARY_PATH 0 "${CMAKE_CURRENT_BINARY_DIR}/..") + +# Also use the generated pkg-config file prior to "make install". +# In an independent project, these lines would simply not exist. +set(PKG_CONFIG_CHANGED_PATH "${CMAKE_CURRENT_BINARY_DIR}/..;$ENV{PKG_CONFIG_PATH}") +if(UNIX) + string(REPLACE ";" ":" PKG_CONFIG_CHANGED_PATH "${PKG_CONFIG_CHANGED_PATH}") +endif () +set(ENV{PKG_CONFIG_PATH} "${PKG_CONFIG_CHANGED_PATH}") + +find_package(LibtorrentRasterbar REQUIRED) +find_package(Boost REQUIRED COMPONENTS system) + +include_directories(${LibtorrentRasterbar_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS}) +add_definitions(${LibtorrentRasterbar_DEFINITIONS}) + +set(single_file_examples + simple_client + stats_counters + dump_torrent + make_torrent + connection_tester + upnp_test) + +foreach(example ${single_file_examples}) + add_executable(${example} "${example}.cpp") + target_link_libraries(${example} ${LibtorrentRasterbar_LIBRARIES} ${Boost_LIBRARIES}) +endforeach(example) + +add_executable(client_test + client_test.cpp + print.cpp + torrent_view.cpp + session_view.cpp) +target_link_libraries(client_test ${LibtorrentRasterbar_LIBRARIES} ${Boost_LIBRARIES}) diff --git a/examples/Jamfile b/examples/Jamfile new file mode 100644 index 0000000..6c885ac --- /dev/null +++ b/examples/Jamfile @@ -0,0 +1,38 @@ +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 + : default-build + static + ; + +exe client_test : client_test.cpp print.cpp torrent_view.cpp session_view.cpp ; + +exe simple_client : simple_client.cpp ; +exe bt-get : bt-get.cpp ; +exe bt-get2 : bt-get2.cpp ; +exe stats_counters : stats_counters.cpp ; +exe dump_torrent : dump_torrent.cpp ; +exe make_torrent : make_torrent.cpp ; +exe connection_tester : connection_tester.cpp ; +exe upnp_test : upnp_test.cpp ; + +explicit stage_client_test ; +explicit stage_connection_tester ; +explicit bt-get ; +explicit bt-get2 ; + +install stage_client_test : client_test : . ; +install stage_connection_tester : connection_tester : . ; + diff --git a/examples/Makefile.am b/examples/Makefile.am new file mode 100644 index 0000000..f786fc8 --- /dev/null +++ b/examples/Makefile.am @@ -0,0 +1,34 @@ +example_programs = \ + client_test \ + stats_counters \ + dump_torrent \ + make_torrent \ + simple_client \ + 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 run_cmake.sh.in 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 +connection_tester_SOURCES = connection_tester.cpp +upnp_test_SOURCES = upnp_test.cpp + +LDADD = $(top_builddir)/src/libtorrent-rasterbar.la + +AM_CPPFLAGS = -ftemplate-depth-50 -I$(top_srcdir)/include @DEBUGFLAGS@ + +AM_LDFLAGS = @BOOST_SYSTEM_LIB@ @BOOST_CHRONO_LIB@ @BOOST_RANDOM_LIB@ @OPENSSL_LDFLAGS@ @OPENSSL_LIBS@ + diff --git a/examples/Makefile.in b/examples/Makefile.in new file mode 100644 index 0000000..24ed7be --- /dev/null +++ b/examples/Makefile.in @@ -0,0 +1,796 @@ +# Makefile.in generated by automake 1.15 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2014 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +target_triplet = @target@ +@ENABLE_EXAMPLES_TRUE@bin_PROGRAMS = $(am__EXEEXT_1) +EXTRA_PROGRAMS = $(am__EXEEXT_1) +subdir = examples +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_boost_base.m4 \ + $(top_srcdir)/m4/ax_boost_chrono.m4 \ + $(top_srcdir)/m4/ax_boost_python.m4 \ + $(top_srcdir)/m4/ax_boost_random.m4 \ + $(top_srcdir)/m4/ax_boost_system.m4 \ + $(top_srcdir)/m4/ax_check_openssl.m4 \ + $(top_srcdir)/m4/ax_pthread.m4 \ + $(top_srcdir)/m4/ax_python_devel.m4 \ + $(top_srcdir)/m4/gettext-lib.m4 $(top_srcdir)/m4/iconv.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/pkgconfig.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__EXEEXT_1 = client_test$(EXEEXT) stats_counters$(EXEEXT) \ + dump_torrent$(EXEEXT) make_torrent$(EXEEXT) \ + simple_client$(EXEEXT) upnp_test$(EXEEXT) bt_get$(EXEEXT) \ + bt_get2$(EXEEXT) connection_tester$(EXEEXT) +am__installdirs = "$(DESTDIR)$(bindir)" +PROGRAMS = $(bin_PROGRAMS) +am_bt_get_OBJECTS = bt-get.$(OBJEXT) +bt_get_OBJECTS = $(am_bt_get_OBJECTS) +bt_get_LDADD = $(LDADD) +bt_get_DEPENDENCIES = $(top_builddir)/src/libtorrent-rasterbar.la +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +am_bt_get2_OBJECTS = bt-get2.$(OBJEXT) +bt_get2_OBJECTS = $(am_bt_get2_OBJECTS) +bt_get2_LDADD = $(LDADD) +bt_get2_DEPENDENCIES = $(top_builddir)/src/libtorrent-rasterbar.la +am_client_test_OBJECTS = client_test.$(OBJEXT) print.$(OBJEXT) \ + session_view.$(OBJEXT) torrent_view.$(OBJEXT) +client_test_OBJECTS = $(am_client_test_OBJECTS) +client_test_LDADD = $(LDADD) +client_test_DEPENDENCIES = \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_connection_tester_OBJECTS = connection_tester.$(OBJEXT) +connection_tester_OBJECTS = $(am_connection_tester_OBJECTS) +connection_tester_LDADD = $(LDADD) +connection_tester_DEPENDENCIES = \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_dump_torrent_OBJECTS = dump_torrent.$(OBJEXT) +dump_torrent_OBJECTS = $(am_dump_torrent_OBJECTS) +dump_torrent_LDADD = $(LDADD) +dump_torrent_DEPENDENCIES = \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_make_torrent_OBJECTS = make_torrent.$(OBJEXT) +make_torrent_OBJECTS = $(am_make_torrent_OBJECTS) +make_torrent_LDADD = $(LDADD) +make_torrent_DEPENDENCIES = \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_simple_client_OBJECTS = simple_client.$(OBJEXT) +simple_client_OBJECTS = $(am_simple_client_OBJECTS) +simple_client_LDADD = $(LDADD) +simple_client_DEPENDENCIES = \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_stats_counters_OBJECTS = stats_counters.$(OBJEXT) +stats_counters_OBJECTS = $(am_stats_counters_OBJECTS) +stats_counters_LDADD = $(LDADD) +stats_counters_DEPENDENCIES = \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_upnp_test_OBJECTS = upnp_test.$(OBJEXT) +upnp_test_OBJECTS = $(am_upnp_test_OBJECTS) +upnp_test_LDADD = $(LDADD) +upnp_test_DEPENDENCIES = $(top_builddir)/src/libtorrent-rasterbar.la +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ +depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) +LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CXXFLAGS) $(CXXFLAGS) +AM_V_CXX = $(am__v_CXX_@AM_V@) +am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@) +am__v_CXX_0 = @echo " CXX " $@; +am__v_CXX_1 = +CXXLD = $(CXX) +CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \ + $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CXXLD = $(am__v_CXXLD_@AM_V@) +am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@) +am__v_CXXLD_0 = @echo " CXXLD " $@; +am__v_CXXLD_1 = +SOURCES = $(bt_get_SOURCES) $(bt_get2_SOURCES) $(client_test_SOURCES) \ + $(connection_tester_SOURCES) $(dump_torrent_SOURCES) \ + $(make_torrent_SOURCES) $(simple_client_SOURCES) \ + $(stats_counters_SOURCES) $(upnp_test_SOURCES) +DIST_SOURCES = $(bt_get_SOURCES) $(bt_get2_SOURCES) \ + $(client_test_SOURCES) $(connection_tester_SOURCES) \ + $(dump_torrent_SOURCES) $(make_torrent_SOURCES) \ + $(simple_client_SOURCES) $(stats_counters_SOURCES) \ + $(upnp_test_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in \ + $(top_srcdir)/build-aux/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BOOST_CHRONO_LIB = @BOOST_CHRONO_LIB@ +BOOST_CPPFLAGS = @BOOST_CPPFLAGS@ +BOOST_LDFLAGS = @BOOST_LDFLAGS@ +BOOST_PYTHON_LIB = @BOOST_PYTHON_LIB@ +BOOST_RANDOM_LIB = @BOOST_RANDOM_LIB@ +BOOST_SYSTEM_LIB = @BOOST_SYSTEM_LIB@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +COMPILETIME_OPTIONS = @COMPILETIME_OPTIONS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEBUGFLAGS = @DEBUGFLAGS@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GREP = @GREP@ +ICONV_LIBS = @ICONV_LIBS@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INTERFACE_VERSION_INFO = @INTERFACE_VERSION_INFO@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBICONV = @LIBICONV@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OPENSSL_INCLUDES = @OPENSSL_INCLUDES@ +OPENSSL_LDFLAGS = @OPENSSL_LDFLAGS@ +OPENSSL_LIBS = @OPENSSL_LIBS@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PTHREAD_CC = @PTHREAD_CC@ +PTHREAD_CFLAGS = @PTHREAD_CFLAGS@ +PTHREAD_LIBS = @PTHREAD_LIBS@ +PYTHON = @PYTHON@ +PYTHON_CPPFLAGS = @PYTHON_CPPFLAGS@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_EXTRA_LDFLAGS = @PYTHON_EXTRA_LDFLAGS@ +PYTHON_EXTRA_LIBS = @PYTHON_EXTRA_LIBS@ +PYTHON_INSTALL_PARAMS = @PYTHON_INSTALL_PARAMS@ +PYTHON_LIBS = @PYTHON_LIBS@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_SITE_PKG = @PYTHON_SITE_PKG@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +ax_pthread_config = @ax_pthread_config@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target = @target@ +target_alias = @target_alias@ +target_cpu = @target_cpu@ +target_os = @target_os@ +target_vendor = @target_vendor@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +example_programs = \ + client_test \ + stats_counters \ + dump_torrent \ + make_torrent \ + simple_client \ + upnp_test \ + bt_get \ + bt_get2 \ + connection_tester + +EXTRA_DIST = Jamfile CMakeLists.txt run_cmake.sh.in 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 +connection_tester_SOURCES = connection_tester.cpp +upnp_test_SOURCES = upnp_test.cpp +LDADD = $(top_builddir)/src/libtorrent-rasterbar.la +AM_CPPFLAGS = -ftemplate-depth-50 -I$(top_srcdir)/include @DEBUGFLAGS@ +AM_LDFLAGS = @BOOST_SYSTEM_LIB@ @BOOST_CHRONO_LIB@ @BOOST_RANDOM_LIB@ @OPENSSL_LDFLAGS@ @OPENSSL_LIBS@ +all: all-am + +.SUFFIXES: +.SUFFIXES: .cpp .lo .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign examples/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign examples/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +install-binPROGRAMS: $(bin_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-binPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(bindir)" && rm -f $$files + +clean-binPROGRAMS: + @list='$(bin_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list + +bt_get$(EXEEXT): $(bt_get_OBJECTS) $(bt_get_DEPENDENCIES) $(EXTRA_bt_get_DEPENDENCIES) + @rm -f bt_get$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(bt_get_OBJECTS) $(bt_get_LDADD) $(LIBS) + +bt_get2$(EXEEXT): $(bt_get2_OBJECTS) $(bt_get2_DEPENDENCIES) $(EXTRA_bt_get2_DEPENDENCIES) + @rm -f bt_get2$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(bt_get2_OBJECTS) $(bt_get2_LDADD) $(LIBS) + +client_test$(EXEEXT): $(client_test_OBJECTS) $(client_test_DEPENDENCIES) $(EXTRA_client_test_DEPENDENCIES) + @rm -f client_test$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(client_test_OBJECTS) $(client_test_LDADD) $(LIBS) + +connection_tester$(EXEEXT): $(connection_tester_OBJECTS) $(connection_tester_DEPENDENCIES) $(EXTRA_connection_tester_DEPENDENCIES) + @rm -f connection_tester$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(connection_tester_OBJECTS) $(connection_tester_LDADD) $(LIBS) + +dump_torrent$(EXEEXT): $(dump_torrent_OBJECTS) $(dump_torrent_DEPENDENCIES) $(EXTRA_dump_torrent_DEPENDENCIES) + @rm -f dump_torrent$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(dump_torrent_OBJECTS) $(dump_torrent_LDADD) $(LIBS) + +make_torrent$(EXEEXT): $(make_torrent_OBJECTS) $(make_torrent_DEPENDENCIES) $(EXTRA_make_torrent_DEPENDENCIES) + @rm -f make_torrent$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(make_torrent_OBJECTS) $(make_torrent_LDADD) $(LIBS) + +simple_client$(EXEEXT): $(simple_client_OBJECTS) $(simple_client_DEPENDENCIES) $(EXTRA_simple_client_DEPENDENCIES) + @rm -f simple_client$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(simple_client_OBJECTS) $(simple_client_LDADD) $(LIBS) + +stats_counters$(EXEEXT): $(stats_counters_OBJECTS) $(stats_counters_DEPENDENCIES) $(EXTRA_stats_counters_DEPENDENCIES) + @rm -f stats_counters$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(stats_counters_OBJECTS) $(stats_counters_LDADD) $(LIBS) + +upnp_test$(EXEEXT): $(upnp_test_OBJECTS) $(upnp_test_DEPENDENCIES) $(EXTRA_upnp_test_DEPENDENCIES) + @rm -f upnp_test$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(upnp_test_OBJECTS) $(upnp_test_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bt-get.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bt-get2.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/client_test.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/connection_tester.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dump_torrent.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/make_torrent.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/print.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/session_view.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/simple_client.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stats_counters.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/torrent_view.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/upnp_test.Po@am__quote@ + +.cpp.o: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $< + +.cpp.obj: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.cpp.lo: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(PROGRAMS) +installdirs: + for dir in "$(DESTDIR)$(bindir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-binPROGRAMS clean-generic clean-libtool mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-binPROGRAMS + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-binPROGRAMS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am check check-am clean \ + clean-binPROGRAMS clean-generic clean-libtool cscopelist-am \ + ctags ctags-am distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-binPROGRAMS \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man install-pdf \ + install-pdf-am install-ps install-ps-am install-strip \ + installcheck installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am uninstall-binPROGRAMS + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/examples/bt-get.cpp b/examples/bt-get.cpp new file mode 100644 index 0000000..0029a81 --- /dev/null +++ b/examples/bt-get.cpp @@ -0,0 +1,75 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include + +#include +#include +#include +#include + +namespace lt = libtorrent; +int main(int argc, char const* argv[]) +{ + if (argc != 2) { + std::cerr << "usage: " << argv[0] << " " << std::endl; + return 1; + } + lt::session ses; + + lt::add_torrent_params atp; + atp.url = argv[1]; + atp.save_path = "."; // save in current dir + lt::torrent_handle h = ses.add_torrent(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; +} + diff --git a/examples/bt-get2.cpp b/examples/bt-get2.cpp new file mode 100644 index 0000000..9338978 --- /dev/null +++ b/examples/bt-get2.cpp @@ -0,0 +1,149 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace lt = libtorrent; +using clk = std::chrono::steady_clock; + +// return the name of a torrent status enum +char const* state(lt::torrent_status::state_t s) +{ + switch(s) { + case lt::torrent_status::checking_files: return "checking"; + case lt::torrent_status::downloading_metadata: return "dl metadata"; + case lt::torrent_status::downloading: return "downloading"; + case lt::torrent_status::finished: return "finished"; + case lt::torrent_status::seeding: return "seeding"; + case lt::torrent_status::allocating: return "allocating"; + case lt::torrent_status::checking_resume_data: return "checking resume"; + default: return "<>"; + } +} + +int main(int argc, char const* argv[]) +{ + 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::error_notification + | lt::alert::storage_notification + | lt::alert::status_notification); + + lt::session ses(pack); + lt::add_torrent_params atp; + 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); + atp.resume_data.assign(std::istream_iterator(ifs) + , std::istream_iterator()); + atp.url = argv[1]; + atp.save_path = "."; // save in current dir + ses.async_add_torrent(atp); + + // this is the handle we'll set once we get the notification of it being + // added + lt::torrent_handle h; + for (;;) { + std::vector alerts; + ses.pop_alerts(&alerts); + + for (lt::alert const* a : alerts) { + if (auto at = lt::alert_cast(a)) { + h = at->handle; + } + // if we receive the finished alert or an error, we're done + if (lt::alert_cast(a)) { + h.save_resume_data(); + goto done; + } + if (lt::alert_cast(a)) { + std::cout << a->message() << std::endl; + goto done; + } + + // when resume data is ready, save it + if (auto rd = lt::alert_cast(a)) { + std::ofstream of(".resume_file", std::ios_base::binary); + of.unsetf(std::ios_base::skipws); + lt::bencode(std::ostream_iterator(of) + , *rd->resume_data); + } + + if (auto st = lt::alert_cast(a)) { + if (st->status.empty()) continue; + + // we only have a single torrent, so we know which one + // the status is for + lt::torrent_status const& s = st->status[0]; + std::cout << "\r" << state(s.state) << " " + << (s.download_payload_rate / 1000) << " kB/s " + << (s.total_done / 1000) << " kB (" + << (s.progress_ppm / 10000) << "%) downloaded\x1b[K"; + std::cout.flush(); + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + + // ask the session to post a state_update_alert, to update our + // state output for the torrent + ses.post_torrent_updates(); + + // save resume data once every 30 seconds + if (clk::now() - last_save_resume > std::chrono::seconds(30)) { + h.save_resume_data(); + last_save_resume = clk::now(); + } + } + + // TODO: ideally we should save resume data here + +done: + std::cout << "\ndone, shutting down" << std::endl; +} + diff --git a/examples/client_test.cpp b/examples/client_test.cpp new file mode 100644 index 0000000..28b3d7e --- /dev/null +++ b/examples/client_test.cpp @@ -0,0 +1,2399 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include + +#include "libtorrent/config.hpp" + +#ifdef TORRENT_WINDOWS +#include // for _mkdir and _getcwd +#include // for _stat +#include +#endif + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include "libtorrent/extensions/ut_metadata.hpp" +#include "libtorrent/extensions/ut_pex.hpp" +#include "libtorrent/extensions/smart_ban.hpp" + +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/announce_entry.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/identify_client.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/magnet_uri.hpp" +#include "libtorrent/bitfield.hpp" +#include "libtorrent/peer_info.hpp" +#include "libtorrent/bdecode.hpp" +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/create_torrent.hpp" + +#include "torrent_view.hpp" +#include "session_view.hpp" +#include "print.hpp" + +using boost::bind; +using libtorrent::total_milliseconds; + +void sleep_ms(int milliseconds) +{ +#if defined TORRENT_WINDOWS || defined TORRENT_CYGWIN + Sleep(milliseconds); +#elif defined TORRENT_BEOS + snooze_until(system_time() + boost::int64_t(milliseconds) * 1000, B_SYSTEM_TIMEBASE); +#else + usleep(milliseconds * 1000); +#endif +} + +#ifdef _WIN32 + +#include +#include + +bool sleep_and_input(int* c, int sleep) +{ + for (int i = 0; i < 2; ++i) + { + if (_kbhit()) + { + *c = _getch(); + return true; + } + Sleep(sleep / 2); + } + return false; +}; + +#else + +#include // for snprintf +#include // for atoi + +#include +#include +#include +#include + +struct set_keypress +{ + set_keypress() + { + termios new_settings; + tcgetattr(0,&stored_settings); + new_settings = stored_settings; + // Disable canonical mode, and set buffer size to 1 byte + // and disable echo + new_settings.c_lflag &= ~(ICANON | ECHO); + new_settings.c_cc[VTIME] = 0; + new_settings.c_cc[VMIN] = 1; + tcsetattr(0,TCSANOW,&new_settings); + } + ~set_keypress() { tcsetattr(0,TCSANOW,&stored_settings); } + termios stored_settings; +}; + +bool sleep_and_input(int* c, int sleep) +{ + libtorrent::time_point start = libtorrent::clock_type::now(); + int ret = 0; +retry: + fd_set set; + FD_ZERO(&set); + FD_SET(0, &set); + timeval tv = {sleep/ 1000, (sleep % 1000) * 1000 }; + ret = select(1, &set, 0, 0, &tv); + if (ret > 0) + { + *c = getc(stdin); + return true; + } + if (errno == EINTR) + { + if (total_milliseconds(libtorrent::clock_type::now() - start) < sleep) + goto retry; + return false; + } + + if (ret < 0 && errno != 0 && errno != ETIMEDOUT) + { + fprintf(stderr, "select failed: %s\n", strerror(errno)); + sleep_ms(500); + } + + return false; +} + +#endif + +bool print_trackers = false; +bool print_peers = false; +bool print_log = false; +bool print_downloads = false; +bool print_matrix = false; +bool print_file_progress = false; +bool show_pad_files = false; +bool show_dht_status = false; +bool sequential_download = false; + +bool print_ip = true; +bool print_timers = false; +bool print_block = false; +bool print_peer_rate = 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 + +torrent_view view; +session_view ses_view; + +int load_file(std::string const& filename, std::vector& v + , libtorrent::error_code& ec, int limit = 8000000) +{ + ec.clear(); + FILE* f = fopen(filename.c_str(), "rb"); + if (f == NULL) + { + 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(s); + if (s == 0) + { + fclose(f); + return 0; + } + + r = 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 is_absolute_path(std::string const& f) +{ + if (f.empty()) return false; +#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) + int i = 0; + // match the xx:\ or xx:/ form + while (f[i] && strchr("abcdefghijklmnopqrstuvxyz", f[i])) ++i; + if (i < int(f.size()-1) && f[i] == ':' && (f[i+1] == '\\' || f[i+1] == '/')) + return true; + + // match the \\ form + if (int(f.size()) >= 2 && f[0] == '\\' && f[1] == '\\') + return true; + return false; +#else + if (f[0] == '/') return true; + return false; +#endif +} + +std::string 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 == 0) return f; + + if (sep - first == int(f.size()) - 1) + { + // if the last character is a / (or \) + // ignore it + int len = 0; + while (sep > first) + { + --sep; + if (*sep == '/' +#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) + || *sep == '\\' +#endif + ) + return std::string(sep + 1, len); + ++len; + } + return std::string(first, len); + } + return std::string(sep + 1); +} + +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; +} + +bool is_hex(char const *in, int len) +{ + for (char const* end = in + len; in < end; ++in) + { + if (*in >= '0' && *in <= '9') continue; + if (*in >= 'A' && *in <= 'F') continue; + if (*in >= 'a' && *in <= 'f') continue; + return false; + } + return true; +} + +std::string print_endpoint(libtorrent::tcp::endpoint const& ep) +{ + using namespace libtorrent; + error_code ec; + char buf[200]; + address const& addr = ep.address(); +#if TORRENT_USE_IPV6 + if (addr.is_v6()) + snprintf(buf, sizeof(buf), "[%s]:%d", addr.to_string(ec).c_str(), ep.port()); + else +#endif + snprintf(buf, sizeof(buf), "%s:%d", addr.to_string(ec).c_str(), ep.port()); + return buf; +} + +struct torrent_entry +{ + torrent_entry(libtorrent::torrent_handle h) : handle(h) {} + libtorrent::torrent_handle handle; + libtorrent::torrent_status status; +}; + +// maps filenames to torrent_handles +typedef std::map handles_t; +typedef std::map files_t; + +files_t hash_to_filename; + +using libtorrent::torrent_status; + +bool yes(libtorrent::torrent_status const&) +{ return true; } + +FILE* g_log_file = 0; + +std::string const& piece_bar(libtorrent::bitfield const& p, int width) +{ +#ifdef _WIN32 + int const table_size = 5; +#else + int const table_size = 18; + width *= 2; // we only print one character for every two "slots" +#endif + + double const piece_per_char = p.size() / double(width); + static std::string bar; + bar.clear(); + bar.reserve(width * 6); + bar += "["; + if (p.size() == 0) + { + for (int i = 0; i < width; ++i) bar += ' '; + bar += "]"; + return bar; + } + + // the [piece, piece + pieces_per_char) range is the pieces that are represented by each character + double piece = 0; + + // we print two blocks at a time, so calculate the color in pair + int color[2]; + int last_color[2] = { -1, -1}; + 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))); + char buf[40]; + color[i & 1] = c; + +#ifndef _WIN32 + 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 i = 0; i < 2; ++i) + { + if (color[i] != last_color[i]) + { + snprintf(buf, sizeof(buf), "\x1b[%d;5;%dm", bg[i & 1], 232 + color[i]); + last_color[i] = color[i]; + bar += buf; + } + } + bar += "\u258C"; + } +#else + static char const table[] = {' ', '\xb0', '\xb1', '\xb2', '\xdb'}; + bar += table[c]; +#endif + } + bar += esc("0"); + bar += "]"; + return bar; +} + +int peer_index(libtorrent::tcp::endpoint addr, std::vector const& peers) +{ + using namespace libtorrent; + std::vector::const_iterator i = std::find_if(peers.begin() + , peers.end(), boost::bind(&peer_info::ip, _1) == addr); + if (i == peers.end()) return -1; + + return 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 libtorrent; + int pos = 0; + if (print_ip) out += "IP "; + out += "progress down (total | peak ) up (total | peak ) sent-req tmo bsy rcv flags dn up source "; + if (print_fails) out += "fail hshf "; + if (print_send_bufs) out += "rq sndb rcvb q-bytes "; + if (print_timers) out += "inactive wait timeout q-time "; + out += " v disk ^ rtt "; + if (print_block) out += "block-progress "; + if (print_peer_rate) out += "peer-rate est.rec.rate "; + 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)) + continue; + + if (print_ip) + { + snprintf(str, sizeof(str), "%-30s ", (::print_endpoint(i->ip) + + (i->flags & peer_info::utp_socket ? " [uTP]" : "") + + (i->flags & peer_info::i2p_socket ? " [i2p]" : "") + ).c_str()); + out += str; + } + + char temp[10]; + snprintf(temp, sizeof(temp), "%d/%d" + , i->download_queue_length + , i->target_dl_queue_length); + temp[7] = 0; + + char peer_progress[10]; + snprintf(peer_progress, sizeof(peer_progress), "%.1f%%", i->progress_ppm / 10000.f); + snprintf(str, sizeof(str) + , "%s %s%s (%s|%s) %s%s (%s|%s) %s%7s %4d%4d%4d %s%s%s%s%s%s%s%s%s%s%s%s%s %s%s%s %s%s%s %s%s%s%s%s%s " + , progress_bar(i->progress_ppm / 1000, 15, col_green, '#', '-', peer_progress).c_str() + , esc("32"), add_suffix(i->down_speed, "/s").c_str() + , add_suffix(i->total_download).c_str(), add_suffix(i->download_rate_peak, "/s").c_str() + , esc("31"), add_suffix(i->up_speed, "/s").c_str(), add_suffix(i->total_upload).c_str() + , add_suffix(i->upload_rate_peak, "/s").c_str(), esc("0") + + , temp // sent requests and target number of outstanding reqs. + , i->timed_out_requests + , i->busy_requests + , i->upload_queue_length + + , color("I", (i->flags & peer_info::interesting)?col_white:col_blue).c_str() + , color("C", (i->flags & peer_info::choked)?col_white:col_blue).c_str() + , color("i", (i->flags & peer_info::remote_interested)?col_white:col_blue).c_str() + , color("c", (i->flags & peer_info::remote_choked)?col_white:col_blue).c_str() + , color("x", (i->flags & peer_info::supports_extensions)?col_white:col_blue).c_str() + , color("o", (i->flags & peer_info::local_connection)?col_white:col_blue).c_str() + , color("p", (i->flags & peer_info::on_parole)?col_white:col_blue).c_str() + , color("O", (i->flags & peer_info::optimistic_unchoke)?col_white:col_blue).c_str() + , color("S", (i->flags & peer_info::snubbed)?col_white:col_blue).c_str() + , color("U", (i->flags & peer_info::upload_only)?col_white:col_blue).c_str() + , color("e", (i->flags & peer_info::endgame_mode)?col_white:col_blue).c_str() + , color("E", (i->flags & peer_info::rc4_encrypted)?col_white:(i->flags & peer_info::plaintext_encrypted)?col_cyan:col_blue).c_str() + , color("h", (i->flags & peer_info::holepunched)?col_white:col_blue).c_str() + + , color("d", (i->read_state & peer_info::bw_disk)?col_white:col_blue).c_str() + , color("l", (i->read_state & peer_info::bw_limit)?col_white:col_blue).c_str() + , color("n", (i->read_state & peer_info::bw_network)?col_white:col_blue).c_str() + , color("d", (i->write_state & peer_info::bw_disk)?col_white:col_blue).c_str() + , color("l", (i->write_state & peer_info::bw_limit)?col_white:col_blue).c_str() + , color("n", (i->write_state & peer_info::bw_network)?col_white:col_blue).c_str() + + , color("t", (i->source & peer_info::tracker)?col_white:col_blue).c_str() + , color("p", (i->source & peer_info::pex)?col_white:col_blue).c_str() + , color("d", (i->source & peer_info::dht)?col_white:col_blue).c_str() + , color("l", (i->source & peer_info::lsd)?col_white:col_blue).c_str() + , color("r", (i->source & peer_info::resume_data)?col_white:col_blue).c_str() + , color("i", (i->source & peer_info::incoming)?col_white:col_blue).c_str()); + out += str; + + if (print_fails) + { + snprintf(str, sizeof(str), "%3d %3d " + , i->failcount, i->num_hashfails); + out += str; + } + if (print_send_bufs) + { + snprintf(str, sizeof(str), "%2d %6d %6d%5dkB " + , i->requests_in_buffer, i->used_send_buffer + , i->used_receive_buffer + , i->queue_bytes / 1000); + out += str; + } + if (print_timers) + { + char req_timeout[20] = "-"; + // timeout is only meaningful if there is at least one outstanding + // request to the peer + if (i->download_queue_length > 0) + snprintf(req_timeout, sizeof(req_timeout), "%d", i->request_timeout); + + 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; + } + 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 >= 0) + { + char buf[50]; + snprintf(buf, sizeof(buf), "%d:%d", 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); + } + } + + if (print_peer_rate) + { + bool unchoked = (i->flags & peer_info::choked) == 0; + + snprintf(str, sizeof(str), " %s %s" + , add_suffix(i->remote_dl_rate, "/s").c_str() + , unchoked ? add_suffix(i->estimated_reciprocation_rate, "/s").c_str() : " "); + out += str; + } + out += " "; + + if (i->flags & peer_info::handshake) + { + out += esc("31"); + out += " waiting for handshake"; + out += esc("0"); + } + else if (i->flags & 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; +} + +int listen_port = 6881; +int allocation_mode = libtorrent::storage_mode_sparse; +std::string save_path("."); +int torrent_upload_limit = 0; +int torrent_download_limit = 0; +std::string monitor_dir; +std::string bind_to_interface = ""; +int poll_interval = 5; +int max_connections_per_torrent = 50; +bool seed_mode = false; +int cache_size = 1024; + +bool share_mode = false; +bool disable_storage = false; + +bool quit = false; + +void signal_handler(int signo) +{ + // make the main loop terminate + quit = true; +} + +void load_torrent(libtorrent::sha1_hash const& ih, std::vector& buf, libtorrent::error_code& ec) +{ + files_t::iterator i = hash_to_filename.find(ih); + if (i == hash_to_filename.end()) + { + // for magnet links and torrents downloaded via + // URL, the metadata is saved in the resume file + // TODO: pick up metadata from the resume file + ec.assign(boost::system::errc::no_such_file_or_directory, boost::system::generic_category()); + return; + } + load_file(i->second, buf, ec); +} + +// if non-empty, a peer that will be added to all torrents +std::string peer; + +using boost::bind; + +std::string path_to_url(std::string f) +{ + std::string ret = "file://" +#ifdef TORRENT_WINDOWS + "/" +#endif + ; + static char const hex_chars[] = "0123456789abcdef"; + static const char unreserved[] = + "/-_!.~*()ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789"; + + // make sure the path is an absolute path + if (!is_absolute_path(f)) + { + char cwd[TORRENT_MAX_PATH]; +#if defined TORRENT_WINDOWS && !defined TORRENT_MINGW + _getcwd(cwd, sizeof(cwd)); +#else + getcwd(cwd, sizeof(cwd)); +#endif + f = path_append(cwd, f); + } + + for (int i = 0; i < int(f.size()); ++i) + { +#ifdef TORRENT_WINDOWS + if (f[i] == '\\') ret.push_back('/'); + else +#endif + if (std::strchr(unreserved, f[i]) != NULL) ret.push_back(f[i]); + else + { + ret.push_back('%'); + ret.push_back(hex_chars[f[i] >> 4]); + ret.push_back(hex_chars[f[i] & 0xf]); + } + } + return ret; +} + +// monitored_dir is true if this torrent is added because +// it was found in the directory that is monitored. If it +// is, it should be remembered so that it can be removed +// if it's no longer in that directory. +void add_torrent(libtorrent::session& ses + , handles_t& files + , std::set& non_files + , std::string torrent + , int allocation_mode + , std::string const& save_path + , bool monitored_dir + , int torrent_upload_limit + , int torrent_download_limit) +{ + using namespace libtorrent; + static int counter = 0; + + printf("[%d] %s\n", counter++, torrent.c_str()); + + add_torrent_params p; + if (seed_mode) p.flags |= add_torrent_params::flag_seed_mode; + if (disable_storage) p.storage = disabled_storage_constructor; + if (share_mode) p.flags |= add_torrent_params::flag_share_mode; + + std::string filename = path_append(save_path, path_append(".resume" + , leaf_path(torrent) + ".resume")); + + error_code ec; + load_file(filename, p.resume_data, ec); + + p.url = path_to_url(torrent); + p.save_path = save_path; + p.storage_mode = (storage_mode_t)allocation_mode; + p.flags |= add_torrent_params::flag_paused; + p.flags &= ~add_torrent_params::flag_duplicate_is_error; + p.flags |= add_torrent_params::flag_auto_managed; + p.userdata = (void*)strdup(torrent.c_str()); + ses.async_add_torrent(p); + files.insert(std::pair(torrent, torrent_handle())); +} + +std::vector list_dir(std::string path + , bool (*filter_fun)(std::string const&) + , libtorrent::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 + { + std::string p = fd.cFileName; + if (filter_fun(p)) + ret.push_back(p); + + } 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 == 0) + { + ec.assign(errno, boost::system::system_category()); + return ret; + } + + struct dirent de; + dirent* dummy; + while (readdir_r(handle, &de, &dummy) == 0) + { + if (dummy == 0) break; + + std::string p = de.d_name; + if (filter_fun(p)) + ret.push_back(p); + } + closedir(handle); +#endif + return ret; +} + +bool filter_fun(std::string const& p) +{ + for (int i = p.size() - 1; i >= 0; --i) + { + if (p[i] == '/') break; +#ifdef TORRENT_WINDOWS + if (p[i] == '\\') break; +#endif + if (p[i] != '.') continue; + return p.compare(i, 8, ".torrent") == 0; + } + return false; +} + +void scan_dir(std::string const& dir_path + , libtorrent::session& ses + , handles_t& files + , std::set& non_files + , int allocation_mode + , std::string const& save_path + , int torrent_upload_limit + , int torrent_download_limit) +{ + std::set valid; + + using namespace libtorrent; + + error_code ec; + std::vector ents = list_dir(dir_path, filter_fun, ec); + if (ec) + { + fprintf(stderr, "failed to list directory: (%s : %d) %s\n" + , ec.category().name(), ec.value(), ec.message().c_str()); + return; + } + + for (std::vector::iterator i = ents.begin() + , end(ents.end()); i != end; ++i) + { + std::string file = path_append(dir_path, *i); + + handles_t::iterator k = files.find(file); + if (k != files.end()) + { + valid.insert(file); + continue; + } + + // the file has been added to the dir, start + // downloading it. + add_torrent(ses, files, non_files, file, allocation_mode + , save_path, true, torrent_upload_limit, torrent_download_limit); + valid.insert(file); + } + + // remove the torrents that are no longer in the directory + + for (handles_t::iterator i = files.begin(); !files.empty() && i != files.end();) + { + if (i->first.empty() || valid.find(i->first) != valid.end()) + { + ++i; + continue; + } + + torrent_handle& h = i->second; + if (!h.is_valid()) + { + files.erase(i++); + continue; + } + + h.auto_managed(false); + h.pause(); + // the alert handler for save_resume_data_alert + // will save it to disk + h.save_resume_data(); + ++num_outstanding_resume_data; + + files.erase(i++); + } +} + +char const* timestamp() +{ + time_t t = std::time(0); + tm* timeinfo = std::localtime(&t); + static char str[200]; + std::strftime(str, 200, "%b %d %X", timeinfo); + return str; +} + +void print_alert(libtorrent::alert const* a, std::string& str) +{ + using namespace libtorrent; + + if (a->category() & alert::error_notification) + { + str += esc("31"); + } + else if (a->category() & (alert::peer_notification | alert::storage_notification)) + { + str += esc("33"); + } + str += "["; + str += timestamp(); + str += "] "; + str += a->message(); + str += esc("0"); + + if (g_log_file) + fprintf(g_log_file, "[%s] %s\n", timestamp(), a->message().c_str()); +} + +int save_file(std::string const& filename, std::vector& v) +{ + FILE* f = fopen(filename.c_str(), "wb"); + if (f == NULL) + return -1; + + int w = fwrite(&v[0], 1, v.size(), f); + fclose(f); + + if (w < 0) return -1; + if (w != int(v.size())) return -3; + return 0; +} + +// 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(libtorrent::session& ses, libtorrent::alert* a + , handles_t& files, std::set& non_files) +{ + using namespace libtorrent; + + if (session_stats_alert* s = alert_cast(a)) + { + ses_view.update_counters(s->values, sizeof(s->values)/sizeof(s->values[0]) + , duration_cast(s->timestamp().time_since_epoch()).count()); + return true; + } + +#ifndef TORRENT_DISABLE_DHT + if (dht_stats_alert* p = alert_cast(a)) + { + dht_active_requests = p->active_requests; + dht_routing_table = p->routing_table; + return true; + } +#endif + +#ifdef TORRENT_USE_OPENSSL + if (torrent_need_cert_alert* p = alert_cast(a)) + { + torrent_handle h = p->handle; + std::string base_name = path_append("certificates", to_hex(h.info_hash().to_string())); + 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]; + snprintf(msg, sizeof(msg), "ERROR. could not load certificate %s: %s\n", cert.c_str(), strerror(errno)); + if (g_log_file) 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]; + snprintf(msg, sizeof(msg), "ERROR. could not load private key %s: %s\n", priv.c_str(), strerror(errno)); + if (g_log_file) fprintf(g_log_file, "[%s] %s\n", timestamp(), msg); + return true; + } + + char msg[256]; + snprintf(msg, sizeof(msg), "loaded certificate %s and key %s\n", cert.c_str(), priv.c_str()); + if (g_log_file) 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->operation == op_connect + || pd->error == error_code(errors::timed_out_no_handshake + , get_libtorrent_category())) + return true; + } + + if (metadata_received_alert* p = alert_cast(a)) + { + // if we have a monitor dir, save the .torrent file we just received in it + // also, add it to the files map, and remove it from the non_files list + // to keep the scan dir logic in sync so it's not removed, or added twice + torrent_handle h = p->handle; + if (h.is_valid()) + { + boost::shared_ptr ti = h.torrent_file(); + create_torrent ct(*ti); + entry te = ct.generate(); + std::vector buffer; + bencode(std::back_inserter(buffer), te); + sha1_hash hash = ti->info_hash(); + std::string filename = ti->name() + "." + to_hex(hash.to_string()) + ".torrent"; + filename = path_append(monitor_dir, filename); + save_file(filename, buffer); + + files.insert(std::pair(filename, h)); + hash_to_filename.insert(std::make_pair(hash, filename)); + non_files.erase(h); + } + } + else if (add_torrent_alert* p = alert_cast(a)) + { + std::string filename; + if (p->params.userdata) + { + filename = (char*)p->params.userdata; + free(p->params.userdata); + } + + if (p->error) + { + fprintf(stderr, "failed to add torrent: %s %s\n", filename.c_str() + , p->error.message().c_str()); + } + else + { + torrent_handle h = p->handle; + + if (!filename.empty()) + files[filename] = h; + else + non_files.insert(h); + + h.set_max_connections(max_connections_per_torrent); + h.set_max_uploads(-1); + h.set_upload_limit(torrent_upload_limit); + h.set_download_limit(torrent_download_limit); + + // if we have a peer specified, connect to it + if (!peer.empty()) + { + char* port = (char*) strrchr((char*)peer.c_str(), ':'); + if (port != NULL) + { + *port++ = 0; + char const* ip = peer.c_str(); + int peer_port = atoi(port); + error_code ec; + if (peer_port > 0) + h.connect_peer(tcp::endpoint(address::from_string(ip, ec), peer_port)); + } + } + + sha1_hash info_hash; + if (p->params.ti) + { + info_hash = p->params.ti->info_hash(); + } + else if (!p->params.info_hash.is_all_zeros()) + { + info_hash = p->params.info_hash; + } + else + { + info_hash = h.info_hash(); + } + hash_to_filename.insert(std::make_pair(info_hash, filename)); + } + } + else if (torrent_finished_alert* p = alert_cast(a)) + { + p->handle.set_max_connections(max_connections_per_torrent / 2); + + // write resume data for the finished torrent + // the alert handler for save_resume_data_alert + // will save it to disk + torrent_handle h = p->handle; + h.save_resume_data(); + ++num_outstanding_resume_data; + } + else if (save_resume_data_alert* p = alert_cast(a)) + { + --num_outstanding_resume_data; + torrent_handle h = p->handle; + TORRENT_ASSERT(p->resume_data); + if (p->resume_data) + { + std::vector out; + bencode(std::back_inserter(out), *p->resume_data); + torrent_status st = h.status(torrent_handle::query_save_path); + save_file(path_append(st.save_path, path_append(".resume", leaf_path( + hash_to_filename[st.info_hash]) + ".resume")), out); + if (h.is_valid() + && non_files.find(h) == non_files.end() + && std::find_if(files.begin(), files.end() + , boost::bind(&handles_t::value_type::second, _1) == h) == files.end()) + ses.remove_torrent(h); + } + } + else if (save_resume_data_failed_alert* p = alert_cast(a)) + { + --num_outstanding_resume_data; + torrent_handle h = p->handle; + if (h.is_valid()) + { + fprintf(stderr, "FAILED TO SAVE RESUME DATA: %s\n" + , h.status().name.c_str()); + } + if (h.is_valid() + && non_files.find(h) == non_files.end() + && std::find_if(files.begin(), files.end() + , boost::bind(&handles_t::value_type::second, _1) == h) == files.end()) + ses.remove_torrent(h); + } + else if (torrent_paused_alert* p = alert_cast(a)) + { + // write resume data for the finished torrent + // the alert handler for save_resume_data_alert + // will save it to disk + torrent_handle h = p->handle; + h.save_resume_data(); + ++num_outstanding_resume_data; + } + else if (state_update_alert* p = alert_cast(a)) + { + view.update_torrents(p->status); + return true; + } + return false; +} + +void print_piece(libtorrent::partial_piece_info* pp + , libtorrent::cached_piece_info* cs + , std::vector const& peers + , torrent_status const* ts + , std::string& out) +{ + using namespace libtorrent; + + char str[1024]; + assert(pp == 0 || cs == 0 || cs->piece == pp->piece_index); + int piece = pp ? pp->piece_index : cs->piece; + int num_blocks = pp ? pp->blocks_in_piece : cs->blocks.size(); + + snprintf(str, sizeof(str), "%5d:[", piece); + out += str; + char const* last_color = 0; + 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; + bool snubbed = index >= 0 ? peers[index].flags & peer_info::snubbed : false; + + char const* color = ""; + + if (pp == 0) + { + color = cs->blocks[j] ? esc("34;7") : esc("0"); + chr = ' '; + } + else + { + if (cs && cs->blocks[j] && pp->blocks[j].state != block_info::finished) + color = esc("36;7"); + else if (pp->blocks[j].bytes_progress > 0 + && pp->blocks[j].state == block_info::requested) + { + if (pp->blocks[j].num_peers > 1) color = esc("1;7"); + else color = snubbed ? esc("35;7") : 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 = snubbed ? esc("35;7") : esc("0"); + else { color = esc("0"); chr = ' '; } + } + if (last_color == 0 || strcmp(last_color, color) != 0) + { + snprintf(str, sizeof(str), "%s%c", color, chr); + out += str; + } + else + out += chr; + + last_color = color; + } + out += esc("0"); + out += "]"; +/* + char const* cache_kind_str[] = {"read", "write", "read-volatile"}; + snprintf(str, sizeof(str), " %3d cache age: %-5.1f state: %s%s\n" + , cs ? cs->next_to_hash : 0 + , cs ? (total_milliseconds(clock_type::now() - cs->last_use) / 1000.f) : 0.f + , cs ? cache_kind_str[cs->kind] : "N/A" + , ts && ts->pieces.size() ? (ts->pieces[piece] ? " have" : " dont-have") : ""); + out += str; +*/ +} + +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) + { + fprintf(stderr, "usage: client_test [OPTIONS] [TORRENT|MAGNETURL|URL]\n\n" + "OPTIONS:\n" + "\n CLIENT OPTIONS\n" + " -f logs all events to the given file\n" + " -s sets the save path for downloads\n" + " -m sets the .torrent monitor directory\n" + " -t sets the scan interval of the monitor dir\n" + " -F sets the UI refresh rate. This is the number of\n" + " milliseconds between screen refreshes.\n" + " -k enable high performance settings. This overwrites any other\n" + " previous command line options, so be sure to specify this first\n" + " -G Add torrents in seed-mode (i.e. assume all pieces\n" + " are present and check hashes on-demand)\n" + " -E specify how many disk I/O threads to use\n" + "\n BITTORRENT OPTIONS\n" + " -c sets the max number of connections\n" + " -T sets the max number of connections per torrent\n" + " -U sets per-torrent upload rate\n" + " -D sets per-torrent download rate\n" + " -d limits the download rate\n" + " -u limits the upload rate\n" + " -S limits the upload slots\n" + " -A allowed pieces set size\n" + " -H Don't start DHT\n" + " -X Don't start local peer discovery\n" + " -n announce to trackers in all tiers\n" + " -W Set the max number of peers to keep in the peer list\n" + " -B sets the peer timeout\n" + " -Q enables share mode. Share mode attempts to maximize\n" + " share ratio rather than downloading\n" + " -K enable piece suggestions of read cache\n" + " -r connect to specified peer\n" +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + " -e force encrypted bittorrent connections\n" +#endif + "\n QUEING OPTIONS\n" + " -v Set the max number of active downloads\n" + " -^ Set the max number of active seeds\n" + "\n NETWORK OPTIONS\n" + " -p sets the listen port\n" +#ifndef TORRENT_NO_DEPRECATE + " -o limits the number of simultaneous\n" + " half-open TCP connections to the\n" + " given number.\n" +#endif + " -w sets the retry time for failed web seeds\n" + " -x loads an emule IP-filter file\n" + " -P Use the specified SOCKS5 proxy\n" + " -L Use the specified username and password for the\n" + " proxy specified by -P\n" + " -h allow multiple connections from the same IP\n" + " -M Disable TCP/uTP bandwidth balancing\n" + " -N Do not attempt to use UPnP and NAT-PMP to forward ports\n" + " -Y Rate limit local peers\n" + " -y Disable TCP connections (disable outgoing TCP and reject\n" + " incoming TCP connections)\n" + " -J Disable uTP connections (disable outgoing uTP and reject\n" + " incoming uTP connections)\n" + " -b sets IP of the interface to bind the\n" + " listen socket to\n" + " -I sets the IP of the interface to bind\n" + " outgoing peer connections to\n" +#if TORRENT_USE_I2P + " -i the hostname to an I2P SAM bridge to use\n" +#endif + " -l sets the listen socket queue size\n" + "\n DISK OPTIONS\n" + " -a sets the allocation mode. [sparse|allocate]\n" + " -R number of blocks per read cache line\n" + " -C sets the max cache size. Specified in 16kB blocks\n" + " -j disable disk read-ahead\n" + " -z disable piece hash checks (used for benchmarking)\n" + " -Z mmap the disk cache to the specified file, should be an SSD\n" + " -0 disable disk I/O, read garbage and don't flush to disk\n" + "\n\n" + "TORRENT is a path to a .torrent file\n" + "MAGNETURL is a magnet link\n" + "URL is a url to a torrent file\n" + "\n" + "Example for running benchmark:\n\n" + " client_test -k -z -N -h -H -M -l 2000 -S 1000 -T 1000 -c 1000 test.torrent\n"); + ; + return 0; + } + + using namespace libtorrent; + namespace lt = libtorrent; + + settings_pack settings; + settings.set_int(settings_pack::cache_size, cache_size); + settings.set_int(settings_pack::active_loaded_limit, 20); + settings.set_int(settings_pack::choking_algorithm, settings_pack::rate_based_choker); + + int refresh_delay = 500; +#ifndef TORRENT_DISABLE_DHT + bool start_dht = true; +#endif + bool rate_limit_locals = false; + + std::deque events; + + time_point next_dir_scan = clock_type::now(); + + // the string is the filename of the .torrent file, but only if + // it was added through the directory monitor. It is used to + // be able to remove torrents that were added via the directory + // monitor when they're not in the directory anymore. + handles_t files; + + // torrents that were not added via the monitor dir + std::set non_files; + + // load the torrents given on the commandline + + std::vector magnet_links; + std::vector torrents; + ip_filter loaded_ip_filter; + + for (int i = 1; i < argc; ++i) + { + if (argv[i][0] != '-') + { + // match it against the @ format + if (strlen(argv[i]) > 45 + && ::is_hex(argv[i], 40) + && (strncmp(argv[i] + 40, "@http://", 8) == 0 + || strncmp(argv[i] + 40, "@udp://", 7) == 0)) + { + sha1_hash info_hash; + from_hex(argv[i], 40, (char*)&info_hash[0]); + + add_torrent_params p; + if (seed_mode) p.flags |= add_torrent_params::flag_seed_mode; + if (disable_storage) p.storage = disabled_storage_constructor; + if (share_mode) p.flags |= add_torrent_params::flag_share_mode; + p.trackers.push_back(argv[i] + 41); + p.info_hash = info_hash; + p.save_path = save_path; + p.storage_mode = (storage_mode_t)allocation_mode; + p.flags |= add_torrent_params::flag_paused; + p.flags &= ~add_torrent_params::flag_duplicate_is_error; + p.flags |= add_torrent_params::flag_auto_managed; + p.flags |= add_torrent_params::flag_pinned; + magnet_links.push_back(p); + continue; + } + + torrents.push_back(argv[i]); + continue; + } + + // if there's a flag but no argument following, ignore it + if (argc == i) continue; + char const* arg = argv[i+1]; + if (arg == NULL) arg = ""; + + switch (argv[i][1]) + { + case 'f': g_log_file = fopen(arg, "w+"); break; +#ifndef TORRENT_NO_DEPRECATE + case 'o': settings.set_int(settings_pack::half_open_limit, atoi(arg)); break; +#endif + case 'h': settings.set_bool(settings_pack::allow_multiple_connections_per_ip, true); --i; break; + case 'p': listen_port = atoi(arg); break; + case 'k': high_performance_seed(settings); --i; break; + case 'j': settings.set_bool(settings_pack::use_disk_read_ahead, false); --i; break; + case 'z': settings.set_bool(settings_pack::disable_hash_checks, true); --i; break; + case 'K': settings.set_int(settings_pack::suggest_mode, settings_pack::suggest_read_cache); --i; break; + case 'B': settings.set_int(settings_pack::peer_timeout, atoi(arg)); break; + case 'n': settings.set_bool(settings_pack::announce_to_all_tiers, true); --i; break; + case 'G': seed_mode = true; --i; break; + case 'E': settings.set_int(settings_pack::aio_threads, atoi(arg)); break; + case 'd': settings.set_int(settings_pack::download_rate_limit, atoi(arg) * 1000); break; + case 'u': settings.set_int(settings_pack::upload_rate_limit, atoi(arg) * 1000); break; + case 'S': + settings.set_int(settings_pack::unchoke_slots_limit, atoi(arg)); + settings.set_int(settings_pack::choking_algorithm, settings_pack::fixed_slots_choker); + break; + case 'a': + if (strcmp(arg, "allocate") == 0) allocation_mode = storage_mode_allocate; + else if (strcmp(arg, "sparse") == 0) allocation_mode = storage_mode_sparse; + break; + case 's': save_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 = arg; break; + case 'Q': share_mode = true; --i; break; + case 'b': bind_to_interface = arg; break; + case 'w': settings.set_int(settings_pack::urlseed_wait_retry, atoi(arg)); break; + case 't': poll_interval = atoi(arg); break; + case 'F': refresh_delay = atoi(arg); break; + case 'H': +#ifndef TORRENT_DISABLE_DHT + start_dht = false; +#endif + settings.set_bool(settings_pack::enable_dht, false); + --i; + break; + case 'l': settings.set_int(settings_pack::listen_queue_size, atoi(arg)); break; +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + case 'e': + { + settings.set_int(settings_pack::out_enc_policy, settings_pack::pe_forced); + settings.set_int(settings_pack::in_enc_policy, settings_pack::pe_forced); + settings.set_int(settings_pack::allowed_enc_level, settings_pack::pe_rc4); + settings.set_bool(settings_pack::prefer_rc4, true); + --i; + break; + } +#endif + case 'W': + settings.set_int(settings_pack::max_peerlist_size, atoi(arg)); + settings.set_int(settings_pack::max_paused_peerlist_size, atoi(arg) / 2); + break; + case 'x': + { + FILE* filter = fopen(arg, "r"); + if (filter) + { + unsigned int a,b,c,d,e,f,g,h, flags; + while (fscanf(filter, "%u.%u.%u.%u - %u.%u.%u.%u %u\n", &a, &b, &c, &d, &e, &f, &g, &h, &flags) == 9) + { + address_v4 start((a << 24) + (b << 16) + (c << 8) + d); + address_v4 last((e << 24) + (f << 16) + (g << 8) + h); + if (flags <= 127) flags = ip_filter::blocked; + else flags = 0; + loaded_ip_filter.add_rule(start, last, flags); + } + fclose(filter); + } + } + break; + case 'c': settings.set_int(settings_pack::connections_limit, atoi(arg)); break; + case 'T': max_connections_per_torrent = atoi(arg); break; +#if TORRENT_USE_I2P + case 'i': + { + settings.set_str(settings_pack::i2p_hostname, arg); + settings.set_int(settings_pack::i2p_port, 7656); + settings.set_int(settings_pack::proxy_type, settings_pack::i2p_proxy); + break; + } +#endif // TORRENT_USE_I2P + case 'C': + cache_size = atoi(arg); + settings.set_int(settings_pack::cache_size, cache_size); + settings.set_int(settings_pack::cache_buffer_chunk_size, 0); + break; + case 'A': settings.set_int(settings_pack::allowed_fast_set_size, atoi(arg)); break; + case 'R': settings.set_int(settings_pack::read_cache_line_size, atoi(arg)); break; + case 'M': settings.set_int(settings_pack::mixed_mode_algorithm, settings_pack::prefer_tcp); --i; break; + case 'y': + settings.set_bool(settings_pack::enable_outgoing_tcp, false); + settings.set_bool(settings_pack::enable_incoming_tcp, false); + --i; + break; + case 'J': + settings.set_bool(settings_pack::enable_outgoing_utp, false); + settings.set_bool(settings_pack::enable_incoming_utp, false); + --i; + break; + case 'r': peer = arg; break; + case 'P': + { + char* port = (char*) strrchr(arg, ':'); + if (port == 0) + { + fprintf(stderr, "invalid proxy hostname, no port found\n"); + break; + } + *port++ = 0; + settings.set_str(settings_pack::proxy_hostname, arg); + settings.set_int(settings_pack::proxy_port, atoi(port)); + if (atoi(port) == 0) { + fprintf(stderr, "invalid proxy port\n"); + break; + } + if (settings.get_int(settings_pack::proxy_type) == settings_pack::none) + settings.set_int(settings_pack::proxy_type, settings_pack::socks5); + } + break; + case 'L': + { + char* pw = (char*) strchr(arg, ':'); + if (pw == 0) + { + fprintf(stderr, "invalid proxy username and password specified\n"); + break; + } + *pw++ = 0; + settings.set_str(settings_pack::proxy_username, arg); + settings.set_str(settings_pack::proxy_password, pw); + settings.set_int(settings_pack::proxy_type, settings_pack::socks5_pw); + } + break; + case 'I': settings.set_str(settings_pack::outgoing_interfaces, arg); break; + case 'N': + settings.set_bool(settings_pack::enable_upnp, false); + settings.set_bool(settings_pack::enable_natpmp, false); + --i; + break; + case 'Y': + { + --i; + rate_limit_locals = true; + break; + } + case 'X': settings.set_bool(settings_pack::enable_lsd, false); --i; break; + case 'Z': + settings.set_str(settings_pack::mmap_cache, arg); + settings.set_bool(settings_pack::contiguous_recv_buffer, false); + break; + case 'v': settings.set_int(settings_pack::active_downloads, atoi(arg)); + settings.set_int(settings_pack::active_limit, atoi(arg) * 2); + break; + case '^': + settings.set_int(settings_pack::active_seeds, atoi(arg)); + settings.set_int(settings_pack::active_limit, atoi(arg) * 2); + break; + case '0': disable_storage = true; --i; + } + ++i; // skip the argument + } + + // create directory for resume files +#ifdef TORRENT_WINDOWS + int ret = _mkdir(path_append(save_path, ".resume").c_str()); +#else + int ret = mkdir(path_append(save_path, ".resume").c_str(), 0777); +#endif + if (ret < 0) + fprintf(stderr, "failed to create resume file directory: (%d) %s\n" + , errno, strerror(errno)); + + if (bind_to_interface.empty()) bind_to_interface = "0.0.0.0"; + char iface_str[100]; + snprintf(iface_str, sizeof(iface_str), "%s:%d", bind_to_interface.c_str() + , listen_port); + settings.set_str(settings_pack::listen_interfaces, iface_str); + + settings.set_str(settings_pack::user_agent, "client_test/" LIBTORRENT_VERSION); + settings.set_int(settings_pack::alert_mask, alert::all_categories + & ~(alert::dht_notification + + alert::progress_notification + + alert::stats_notification + + alert::session_log_notification + + alert::torrent_log_notification + + alert::peer_log_notification + + alert::dht_log_notification + + alert::picker_log_notification + )); + + libtorrent::session ses(settings); + + if (rate_limit_locals) + { + ip_filter pcf; + // 1 is the global peer class. This should be done properly in the future + pcf.add_rule(address_v4::from_string("0.0.0.0") + , address_v4::from_string("255.255.255.255"), 1); +#if TORRENT_USE_IPV6 + pcf.add_rule(address_v6::from_string("::") + , address_v6::from_string("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), 1); +#endif + ses.set_peer_class_filter(pcf); + } + + ses.set_ip_filter(loaded_ip_filter); + ses.set_load_function(&load_torrent); + + error_code ec; + +#ifndef TORRENT_DISABLE_DHT + dht_settings dht; + dht.privacy_lookups = true; + ses.set_dht_settings(dht); + + if (start_dht) + { + settings.set_bool(settings_pack::use_dht_as_fallback, false); + + ses.add_dht_router(std::make_pair( + std::string("router.bittorrent.com"), 6881)); + ses.add_dht_router(std::make_pair( + std::string("router.utorrent.com"), 6881)); + ses.add_dht_router(std::make_pair( + std::string("router.bitcomet.com"), 6881)); + } + + std::vector in; + if (load_file(".ses_state", in, ec) == 0) + { + bdecode_node e; + if (bdecode(&in[0], &in[0] + in.size(), e, ec) == 0) + ses.load_state(e, session::save_dht_state); + } +#endif + + for (std::vector::iterator i = magnet_links.begin() + , end(magnet_links.end()); i != end; ++i) + { + ses.async_add_torrent(*i); + } + + for (std::vector::iterator i = torrents.begin() + , end(torrents.end()); i != end; ++i) + { + if (std::strstr(i->c_str(), "http://") == i->c_str() + || std::strstr(i->c_str(), "https://") == i->c_str() + || std::strstr(i->c_str(), "magnet:") == i->c_str()) + { + add_torrent_params p; + if (seed_mode) p.flags |= add_torrent_params::flag_seed_mode; + if (disable_storage) p.storage = disabled_storage_constructor; + if (share_mode) p.flags |= add_torrent_params::flag_share_mode; + p.save_path = save_path; + p.storage_mode = (storage_mode_t)allocation_mode; + p.url = *i; + + std::vector buf; + if (std::strstr(i->c_str(), "magnet:") == i->c_str()) + { + add_torrent_params tmp; + ec.clear(); + parse_magnet_uri(*i, tmp, ec); + + if (ec) continue; + + std::string filename = path_append(save_path, path_append(".resume" + , to_hex(tmp.info_hash.to_string()) + ".resume")); + + load_file(filename, p.resume_data, ec); + } + + printf("adding URL: %s\n", i->c_str()); + ses.async_add_torrent(p); + continue; + } + + // if it's a torrent file, open it as usual + add_torrent(ses, files, non_files, i->c_str() + , allocation_mode, save_path, false + , torrent_upload_limit, torrent_download_limit); + } + + // main loop + std::vector peers; + std::vector queue; + + int tick = 0; + +#ifndef _WIN32 + signal(SIGTERM, signal_handler); + signal(SIGINT, signal_handler); +#endif + + while (!quit) + { + ++tick; + ses.post_torrent_updates(); + ses.post_session_stats(); + ses.post_dht_stats(); + + int terminal_width = 80; + int terminal_height = 50; + terminal_size(&terminal_width, &terminal_height); + view.set_size(terminal_width, terminal_height / 3); + ses_view.set_pos(terminal_height / 3); + + int c = 0; + if (sleep_and_input(&c, refresh_delay)) + { + +#ifdef _WIN32 +#define ESCAPE_SEQ 224 +#define LEFT_ARROW 75 +#define RIGHT_ARROW 77 +#define UP_ARROW 72 +#define DOWN_ARROW 80 +#else +#define ESCAPE_SEQ 27 +#define LEFT_ARROW 68 +#define RIGHT_ARROW 67 +#define UP_ARROW 65 +#define DOWN_ARROW 66 +#endif + + torrent_handle h = view.get_active_handle(); + + if (c == EOF) { break; } + do + { + if (c == ESCAPE_SEQ) + { + // escape code, read another character +#ifdef _WIN32 + int c = _getch(); +#else + int c = getc(stdin); + if (c == EOF) { break; } + if (c != '[') continue; + c = getc(stdin); +#endif + if (c == EOF) break; + if (c == LEFT_ARROW) + { + // arrow left + int filter = view.filter(); + if (filter > 0) + { + --filter; + view.set_filter(filter); + h = view.get_active_handle(); + } + } + else if (c == RIGHT_ARROW) + { + // arrow right + int filter = view.filter(); + if (filter < torrent_view::torrents_max - 1) + { + ++filter; + view.set_filter(filter); + h = view.get_active_handle(); + } + } + else if (c == UP_ARROW) + { + // arrow up + view.arrow_up(); + h = view.get_active_handle(); + } + else if (c == DOWN_ARROW) + { + // arrow down + view.arrow_down(); + h = view.get_active_handle(); + } + } + + if (c == ' ') + { + if (ses.is_paused()) ses.resume(); + else ses.pause(); + } + + // add magnet link + if (c == 'm') + { + char url[4096]; + puts("Enter magnet link:\n"); + scanf("%4095s", url); + + add_torrent_params p; + if (seed_mode) p.flags |= add_torrent_params::flag_seed_mode; + if (disable_storage) p.storage = disabled_storage_constructor; + if (share_mode) p.flags |= add_torrent_params::flag_share_mode; + p.save_path = save_path; + p.storage_mode = (storage_mode_t)allocation_mode; + p.url = url; + + std::vector buf; + if (std::strstr(url, "magnet:") == url) + { + add_torrent_params tmp; + parse_magnet_uri(url, tmp, ec); + + if (ec) continue; + + std::string filename = path_append(save_path, path_append(".resume" + , to_hex(tmp.info_hash.to_string()) + ".resume")); + + load_file(filename, p.resume_data, ec); + } + + printf("adding URL: %s\n", url); + ses.async_add_torrent(p); + } + + if (c == 'q') break; + + if (c == 'W' && h.is_valid()) + { + std::set seeds = h.url_seeds(); + for (std::set::iterator i = seeds.begin() + , end(seeds.end()); i != end; ++i) + { + h.remove_url_seed(*i); + } + + seeds = h.http_seeds(); + for (std::set::iterator i = seeds.begin() + , end(seeds.end()); i != end; ++i) + { + h.remove_http_seed(*i); + } + } + + if (c == 'D' && h.is_valid()) + { + torrent_status const& st = view.get_active_torrent(); + printf("\n\nARE YOU SURE YOU WANT TO DELETE THE FILES FOR '%s'. THIS OPERATION CANNOT BE UNDONE. (y/N)" + , st.name.c_str()); + char response = 'n'; + scanf("%c", &response); + if (response == 'y') + { + // also delete the .torrent file from the torrent directory + handles_t::iterator i = std::find_if(files.begin(), files.end() + , boost::bind(&handles_t::value_type::second, _1) == st.handle); + if (i != files.end()) + { + error_code ec; + std::string path; + if (is_absolute_path(i->first)) path = i->first; + else path = path_append(monitor_dir, i->first); + if (::remove(path.c_str()) < 0) + printf("failed to delete .torrent file: %s\n", ec.message().c_str()); + files.erase(i); + } + if (st.handle.is_valid()) + ses.remove_torrent(st.handle, lt::session::delete_files); + } + } + + 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_sequential_download(!ts.sequential_download); + } + + if (c == 'R') + { + // save resume data for all torrents + std::vector torrents; + ses.get_torrent_status(&torrents, &yes, 0); + for (std::vector::iterator i = torrents.begin() + , end(torrents.end()); i != end; ++i) + { + if (i->need_save_resume) + { + i->handle.save_resume_data(); + ++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 (int i = 0; i < num_pieces; ++i) + { + h.set_piece_deadline(i, (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.auto_managed && ts.paused) + { + h.auto_managed(true); + } + else + { + h.auto_managed(false); + h.pause(torrent_handle::graceful_pause); + } + } + + // toggle force-start + if (c == 'k' && h.is_valid()) + { + torrent_status const& ts = view.get_active_torrent(); + h.auto_managed(!ts.auto_managed); + if (ts.auto_managed && ts.paused) h.resume(); + } + + if (c == 'c' && h.is_valid()) + { + h.clear_error(); + } + + // toggle displays + if (c == 't') print_trackers = !print_trackers; + if (c == 'i') print_peers = !print_peers; + if (c == 'l') print_log = !print_log; + if (c == 'd') print_downloads = !print_downloads; + if (c == 'y') print_matrix = !print_matrix; + if (c == 'f') print_file_progress = !print_file_progress; + if (c == 'P') show_pad_files = !show_pad_files; + if (c == 'g') show_dht_status = !show_dht_status; + if (c == 'u') ses_view.print_utp_stats(!ses_view.print_utp_stats()); + if (c == 'x') print_disk_stats = !print_disk_stats; + // toggle columns + if (c == '1') print_ip = !print_ip; + if (c == '3') print_timers = !print_timers; + if (c == '4') print_block = !print_block; + if (c == '5') print_peer_rate = !print_peer_rate; + if (c == '6') print_fails = !print_fails; + if (c == '7') print_send_bufs = !print_send_bufs; + if (c == 'C') + { + cache_size = (cache_size == 0) ? -1 : 0; + settings_pack p; + p.set_int(settings_pack::cache_size, cache_size); + ses.apply_settings(p); + } + if (c == 'h') + { + clear_screen(); + set_cursor_pos(0,0); + print( + "HELP SCREEN (press any key to dismiss)\n\n" + "CLIENT OPTIONS\n" + "[q] quit client [m] add magnet link\n" + "\n" + "TORRENT ACTIONS\n" + "[p] pause/unpause selected torrent [C] toggle disk cache\n" + "[s] toggle sequential download [j] force recheck\n" + "[space] toggle session pause [c] clear error\n" + "[v] scrape [D] delete torrent and data\n" + "[r] force reannounce [R] save resume data for all torrents\n" + "[o] set piece deadlines (sequential dl) [P] toggle auto-managed\n" + "[k] toggle force-started [W] remove all web seeds\n" + "\n" + "DISPLAY OPTIONS\n" + "left/right arrow keys: select torrent filter\n" + "up/down arrow keys: select torrent\n" + "[i] toggle show peers [d] toggle show downloading pieces\n" + "[u] show uTP stats [f] toggle show files\n" + "[g] show DHT [x] toggle disk cache stats\n" + "[t] show trackers [l] toggle show log\n" + "[P] show pad files (in file list) [y] toggle show piece matrix\n" + "\n" + "COLUMN OPTIONS\n" + "[1] toggle IP column [2]\n" + "[3] toggle timers column [4] toggle block progress column\n" + "[5] toggle peer rate column [6] toggle failures column\n" + "[7] toggle send buffers column\n" + ); + int tmp; + while (sleep_and_input(&tmp, 500) == false); + } + + } while (sleep_and_input(&c, 0)); + if (c == 'q') break; + } + + // loop through the alert queue to see if anything has happened. + std::vector alerts; + ses.pop_alerts(&alerts); + std::string now = timestamp(); + for (std::vector::iterator i = alerts.begin() + , end(alerts.end()); i != end; ++i) + { + TORRENT_TRY + { + if (!::handle_alert(ses, *i, files, non_files)) + { + // if we didn't handle the alert, print it to the log + std::string event_string; + print_alert(*i, event_string); + events.push_back(event_string); + if (events.size() >= 20) events.pop_front(); + } + } TORRENT_CATCH(std::exception& e) {} + } + alerts.clear(); + + std::string out; + + char str[500]; + + int pos = view.height() + ses_view.height(); + set_cursor_pos(0, pos); + + int cache_flags = print_downloads ? 0 : lt::session::disk_cache_no_pieces; + torrent_handle h = view.get_active_handle(); + + cache_status cs; + ses.get_cache_info(&cs, h, cache_flags); + +#ifndef TORRENT_DISABLE_DHT + if (show_dht_status) + { + // TODO: 3 expose these counters as performance counters +/* + 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 (std::vector::iterator i = dht_routing_table.begin() + , end(dht_routing_table.end()); i != end; ++i, ++bucket) + { + char const* progress_bar = + "################################" + "################################" + "################################" + "################################"; + char const* short_progress_bar = "--------"; + snprintf(str, sizeof(str) + , "%3d [%3d, %d] %s%s\x1b[K\n" + , bucket, i->num_nodes, i->num_replacements + , progress_bar + (128 - i->num_nodes) + , short_progress_bar + (8 - (std::min)(8, i->num_replacements))); + out += str; + pos += 1; + } + + for (std::vector::iterator i = dht_active_requests.begin() + , end(dht_active_requests.end()); i != end; ++i) + { + snprintf(str, sizeof(str) + , " %10s [limit: %2d] " + "in-flight: %-2d " + "left: %-3d " + "1st-timeout: %-2d " + "timeouts: %-2d " + "responses: %-2d " + "last_sent: %-2d\x1b[K\n" + , i->type + , i->branch_factor + , i->outstanding_requests + , i->nodes_left + , i->first_timeout + , i->timeouts + , i->responses + , i->last_sent); + out += str; + pos += 1; + } + } +#endif + if (h.is_valid()) + { + torrent_status const& s = view.get_active_torrent(); + + print((piece_bar(s.pieces, 126) + "\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()) + pos += print_peer_info(out, peers, terminal_height - pos - 2); + + if (print_trackers) + { + std::vector tr = h.trackers(); + time_point now = clock_type::now(); + for (std::vector::iterator i = tr.begin() + , end(tr.end()); i != end; ++i) + { + if (pos + 1 >= terminal_height) break; + snprintf(str, sizeof(str), "%2d %-55s fails: %-3d (%-3d) %s %s %5d \"%s\" %s\x1b[K\n" + , i->tier, i->url.c_str(), i->fails, i->fail_limit, i->verified?"OK ":"- " + , i->updating?"updating" + :to_string(int(total_seconds(i->next_announce - now)), 8).c_str() + , int(i->min_announce > now ? total_seconds(i->min_announce - now) : 0) + , i->last_error ? i->last_error.message().c_str() : "" + , i->message.c_str()); + out += str; + pos += 1; + } + } + + if (print_matrix) + { + int height = 0; + print(piece_matrix(s.pieces, terminal_width, &height).c_str()); + pos += height; + } + + if (print_downloads) + { + h.get_download_queue(queue); + + std::sort(queue.begin(), queue.end(), boost::bind(&partial_piece_info::piece_index, _1) + < boost::bind(&partial_piece_info::piece_index, _2)); + + std::sort(cs.pieces.begin(), cs.pieces.end(), boost::bind(&cached_piece_info::piece, _1) + > boost::bind(&cached_piece_info::piece, _2)); + + int p = 0; // this is horizontal position + for (std::vector::iterator i = cs.pieces.begin(); + i != cs.pieces.end(); ++i) + { + if (pos + 3 >= terminal_height) break; + + partial_piece_info* pp = 0; + partial_piece_info tmp; + tmp.piece_index = i->piece; + std::vector::iterator ppi + = std::lower_bound(queue.begin(), queue.end(), tmp + , boost::bind(&partial_piece_info::piece_index, _1) + < boost::bind(&partial_piece_info::piece_index, _2)); + if (ppi != queue.end() && ppi->piece_index == i->piece) pp = &*ppi; + + print_piece(pp, &*i, peers, &s, out); + + int num_blocks = pp ? pp->blocks_in_piece : i->blocks.size(); + p += num_blocks + 8; + bool continuous_mode = 8 + num_blocks > terminal_width; + if (continuous_mode) + { + while (p > terminal_width) + { + p -= terminal_width; + ++pos; + } + } + else if (p + num_blocks + 8 > terminal_width) + { + out += "\x1b[K\n"; + pos += 1; + p = 0; + } + + if (pp) queue.erase(ppi); + } + + for (std::vector::iterator i = queue.begin() + , end(queue.end()); i != end; ++i) + { + if (pos + 3 >= terminal_height) break; + + print_piece(&*i, 0, peers, &s, out); + + int num_blocks = i->blocks_in_piece; + p += num_blocks + 8; + bool continuous_mode = 8 + num_blocks > terminal_width; + if (continuous_mode) + { + while (p > terminal_width) + { + p -= terminal_width; + ++pos; + } + } + else if (p + num_blocks + 8 > terminal_width) + { + out += "\x1b[K\n"; + pos += 1; + p = 0; + } + } + if (p != 0) + { + out += "\x1b[K\n"; + pos += 1; + } + + snprintf(str, sizeof(str), "%s %s read cache | %s %s downloading | %s %s cached | %s %s flushed | %s %s snubbed\x1b[K\n" + , esc("34;7"), esc("0") // read cache + , esc("33;7"), esc("0") // downloading + , esc("36;7"), esc("0") // cached + , esc("32;7"), esc("0") // flushed + , esc("35;7"), esc("0") // snubbed + ); + out += str; + pos += 1; + } + + if (print_file_progress && s.has_metadata) + { + std::vector file_progress; + h.file_progress(file_progress); + std::vector file_status; + h.file_status(file_status); + std::vector file_prio = h.file_priorities(); + std::vector::iterator f = file_status.begin(); + boost::shared_ptr ti = h.torrent_file(); + + int p = 0; // this is horizontal position + for (int i = 0; i < ti->num_files(); ++i) + { + if (pos + 1 >= terminal_height) break; + + bool pad_file = ti->files().pad_file_at(i); + if (pad_file) + { + if (show_pad_files) + { + snprintf(str, sizeof(str), "\x1b[34m%-70s %s\x1b[0m\x1b[K\n" + , ti->files().file_name(i).c_str() + , add_suffix(ti->files().file_size(i)).c_str()); + out += str; + pos += 1; + } + continue; + } + + int progress = ti->files().file_size(i) > 0 + ?file_progress[i] * 1000 / ti->files().file_size(i):1000; + + bool complete = file_progress[i] == ti->files().file_size(i); + + std::string title = ti->files().file_name(i); + if (!complete) + { + snprintf(str, sizeof(str), " (%.1f%%)", progress / 10.f); + title += str; + } + + if (f != file_status.end() && f->file_index == i) + { + title += " [ "; + if ((f->open_mode & file::rw_mask) == file::read_write) title += "read/write "; + else if ((f->open_mode & file::rw_mask) == file::read_only) title += "read "; + else if ((f->open_mode & file::rw_mask) == file::write_only) title += "write "; + if (f->open_mode & file::random_access) title += "random_access "; + if (f->open_mode & file::lock_file) title += "locked "; + if (f->open_mode & file::sparse) title += "sparse "; + title += "]"; + ++f; + } + + const int file_progress_width = 65; + + // do we need to line-break? + if (p + file_progress_width + 13 > terminal_width) + { + out += "\x1b[K\n"; + pos += 1; + p = 0; + } + + snprintf(str, sizeof(str), "%s %7s p: %d ", + progress_bar(progress, file_progress_width, complete ? col_green : col_yellow, '-', '#' + , title.c_str()).c_str() + , add_suffix(file_progress[i]).c_str() + , file_prio[i]); + + p += file_progress_width + 13; + out += str; + } + + if (p != 0) + { + out += "\x1b[K\n"; + pos += 1; + } + } + } + + if (print_log) + { + for (std::deque::iterator i = events.begin(); + i != events.end(); ++i) + { + if (pos + 1 >= terminal_height) break; + out += *i; + out += "\x1b[K\n"; + pos += 1; + } + } + + // clear rest of screen + out += "\x1b[J"; + print(out.c_str()); + + fflush(stdout); + + if (!monitor_dir.empty() + && next_dir_scan < clock_type::now()) + { + scan_dir(monitor_dir, ses, files, non_files + , allocation_mode, save_path, torrent_upload_limit + , torrent_download_limit); + next_dir_scan = clock_type::now() + seconds(poll_interval); + } + } + + ses.pause(); + printf("saving resume data\n"); + std::vector temp; + ses.get_torrent_status(&temp, &yes, 0); + for (std::vector::iterator i = temp.begin(); + i != temp.end(); ++i) + { + torrent_status& st = *i; + if (!st.handle.is_valid()) + { + printf(" skipping, invalid handle\n"); + continue; + } + if (!st.has_metadata) + { + printf(" skipping %s, no metadata\n", st.name.c_str()); + continue; + } + if (!st.need_save_resume) + { + printf(" skipping %s, resume file up-to-date\n", st.name.c_str()); + continue; + } + + // save_resume_data will generate an alert when it's done + st.handle.save_resume_data(); + ++num_outstanding_resume_data; + printf("\r%d ", num_outstanding_resume_data); + } + 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 == 0) continue; + + std::vector alerts; + ses.pop_alerts(&alerts); + std::string now = timestamp(); + for (std::vector::iterator i = alerts.begin() + , end(alerts.end()); i != end; ++i) + { + if (!::handle_alert(ses, *i, files, non_files)) + { + // if we didn't handle the alert, print it to the log + std::string event_string; + print_alert(*i, event_string); + } + } + } + + if (g_log_file) fclose(g_log_file); + + // we're just saving the DHT state +#ifndef TORRENT_DISABLE_DHT + printf("\nsaving session state\n"); + { + entry session_state; + ses.save_state(session_state, session::save_dht_state); + + std::vector out; + bencode(std::back_inserter(out), session_state); + save_file(".ses_state", out); + } +#endif + + printf("closing session"); + + return 0; +} + diff --git a/examples/cmake/FindLibtorrentRasterbar.cmake b/examples/cmake/FindLibtorrentRasterbar.cmake new file mode 100644 index 0000000..1edeab9 --- /dev/null +++ b/examples/cmake/FindLibtorrentRasterbar.cmake @@ -0,0 +1,93 @@ +# - Try to find libtorrent-rasterbar +# +# If not using pkg-config, 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_INCLUDE_DIRS - The libtorrent-rasterbar include directories +# LibtorrentRasterbar_LIBRARIES - The libraries needed to use libtorrent-rasterbar +# LibtorrentRasterbar_DEFINITIONS - Compiler switches required for using libtorrent-rasterbar +# LibtorrentRasterbar_OPENSSL_ENABLED - libtorrent-rasterbar uses and links against OpenSSL + +find_package(Threads REQUIRED) +find_package(PkgConfig QUIET) + +if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_LIBTORRENT_RASTERBAR QUIET libtorrent-rasterbar) +endif() + +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(PC_LIBTORRENT_RASTERBAR_FOUND) + set(LibtorrentRasterbar_DEFINITIONS ${PC_LIBTORRENT_RASTERBAR_CFLAGS}) +else() + 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 + -DUNICODE -D_UNICODE -D_FILE_OFFSET_BITS=64) + endif() + + if(NOT LibtorrentRasterbar_USE_STATIC_LIBS) + list(APPEND LibtorrentRasterbar_DEFINITIONS + -DTORRENT_LINKING_SHARED + -DBOOST_SYSTEM_DYN_LINK -DBOOST_CHRONO_DYN_LINK) + endif() +endif() + +message(STATUS "libtorrent definitions: ${LibtorrentRasterbar_DEFINITIONS}") + +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() + +set(LibtorrentRasterbar_LIBRARIES ${LibtorrentRasterbar_LIBRARY} ${CMAKE_THREAD_LIBS_INIT}) +set(LibtorrentRasterbar_INCLUDE_DIRS ${LibtorrentRasterbar_INCLUDE_DIR}) + +if(NOT Boost_SYSTEM_FOUND OR NOT Boost_CHRONO_FOUND OR NOT Boost_RANDOM_FOUND) + find_package(Boost REQUIRED COMPONENTS system chrono random) + set(LibtorrentRasterbar_LIBRARIES + ${LibtorrentRasterbar_LIBRARIES} ${Boost_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) + set(LibtorrentRasterbar_INCLUDE_DIRS + ${LibtorrentRasterbar_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS}) +endif() + +list(FIND LibtorrentRasterbar_DEFINITIONS -DTORRENT_USE_OPENSSL LibtorrentRasterbar_ENCRYPTION_INDEX) +if(LibtorrentRasterbar_ENCRYPTION_INDEX GREATER -1) + find_package(OpenSSL REQUIRED) + set(LibtorrentRasterbar_LIBRARIES ${LibtorrentRasterbar_LIBRARIES} ${OPENSSL_LIBRARIES}) + set(LibtorrentRasterbar_INCLUDE_DIRS ${LibtorrentRasterbar_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIRS}) + set(LibtorrentRasterbar_OPENSSL_ENABLED ON) +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_LIBRARY + LibtorrentRasterbar_INCLUDE_DIR + Boost_SYSTEM_FOUND + Boost_CHRONO_FOUND + Boost_RANDOM_FOUND) + +mark_as_advanced(LibtorrentRasterbar_INCLUDE_DIR LibtorrentRasterbar_LIBRARY + LibtorrentRasterbar_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES + LibtorrentRasterbar_ENCRYPTION_INDEX) diff --git a/examples/connection_tester.cpp b/examples/connection_tester.cpp new file mode 100644 index 0000000..b35a422 --- /dev/null +++ b/examples/connection_tester.cpp @@ -0,0 +1,1100 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/peer_id.hpp" +#include "libtorrent/io_service.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/thread.hpp" +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/socket_io.hpp" +#include "libtorrent/file_pool.hpp" +#include +#include +#include +#include +#include + +#if BOOST_ASIO_DYN_LINK +#if BOOST_VERSION >= 104500 +#include +#elif BOOST_VERSION >= 104400 +#include +#endif +#endif + +using namespace libtorrent; +using namespace libtorrent::detail; // for write_* and read_* + +void generate_block(boost::uint32_t* buffer, int piece, int start, int length) +{ + boost::uint32_t fill = (piece << 8) | ((start / 0x4000) & 0xff); + for (int i = 0; i < length / 4; ++i) + { + buffer[i] = 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 +static boost::detail::atomic_count 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) +boost::detail::atomic_count num_suggest(0); + +// the number of requests made from suggested pieces +boost::detail::atomic_count num_suggested_requests(0); + +void sleep_ms(int milliseconds) +{ +#if defined TORRENT_WINDOWS || defined TORRENT_CYGWIN + Sleep(milliseconds); +#elif defined TORRENT_BEOS + snooze_until(system_time() + boost::int64_t(milliseconds) * 1000, B_SYSTEM_TIMEBASE); +#else + usleep(milliseconds * 1000); +#endif +} + +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 == 0) return f; + + if (sep - first == int(f.size()) - 1) + { + // if the last character is a / (or \) + // ignore it + int len = 0; + while (sep > first) + { + --sep; + if (*sep == '/' +#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) + || *sep == '\\' +#endif + ) + return std::string(sep + 1, len); + ++len; + } + return std::string(first, len); + } + return std::string(sep + 1); +} + +struct peer_conn +{ + peer_conn(io_service& ios, int num_pieces, int blocks_pp, tcp::endpoint const& ep + , char const* ih, bool seed_, int churn_, bool corrupt_) + : s(ios) + , read_pos(0) + , state(handshaking) + , choked(true) + , current_piece(-1) + , current_piece_is_allowed(false) + , block(0) + , blocks_per_piece(blocks_pp) + , info_hash(ih) + , outstanding_requests(0) + , seed(seed_) + , fast_extension(false) + , blocks_received(0) + , blocks_sent(0) + , num_pieces(num_pieces) + , start_time(clock_type::now()) + , churn(churn_) + , corrupt(corrupt_) + , endpoint(ep) + , restarting(false) + { + corruption_counter = rand() % 1000; + if (seed) ++num_seeds; + pieces.reserve(num_pieces); + start_conn(); + } + + void start_conn() + { + if (local_bind) + { + error_code ec; + s.open(endpoint.protocol(), ec); + if (ec) + { + close("ERROR OPEN: %s", ec); + return; + } + tcp::endpoint bind_if(address_v4( + (127 << 24) + + ((local_if_counter / 255) << 16) + + ((local_if_counter % 255) + 1)), 0); + ++local_if_counter; + s.bind(bind_if, ec); + if (ec) + { + close("ERROR BIND: %s", ec); + return; + } + } + restarting = false; + s.async_connect(endpoint, boost::bind(&peer_conn::on_connect, this, _1)); + } + + tcp::socket s; + char write_buf_proto[100]; + boost::uint32_t write_buffer[17*1024/4]; + boost::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; + int current_piece; // the piece we're currently requesting blocks from + bool current_piece_is_allowed; + int block; + int blocks_per_piece; + char const* info_hash; + int outstanding_requests; + // if this is true, this connection is a seed + bool seed; + bool fast_extension; + int blocks_received; + int blocks_sent; + int num_pieces; + time_point start_time; + time_point end_time; + int churn; + bool corrupt; + tcp::endpoint endpoint; + bool restarting; + + void on_connect(error_code const& ec) + { + if (ec) + { + close("ERROR CONNECT: %s", ec); + return; + } + + char handshake[] = "\x13" "BitTorrent protocol\0\0\0\0\0\0\0\x04" + " " // space for info-hash + "aaaaaaaaaaaaaaaaaaaa" // peer-id + "\0\0\0\x01\x02"; // interested + char* h = (char*)malloc(sizeof(handshake)); + memcpy(h, handshake, sizeof(handshake)); + std::memcpy(h + 28, info_hash, 20); + std::generate(h + 48, h + 68, &rand); + // for seeds, don't send the interested message + boost::asio::async_write(s, boost::asio::buffer(h, (sizeof(handshake) - 1) - (seed ? 5 : 0)) + , boost::bind(&peer_conn::on_handshake, this, h, _1, _2)); + } + + void on_handshake(char* h, error_code const& ec, size_t bytes_transferred) + { + free(h); + if (ec) + { + close("ERROR SEND HANDSHAKE: %s", ec); + return; + } + + // read handshake + boost::asio::async_read(s, boost::asio::buffer((char*)buffer, 68) + , boost::bind(&peer_conn::on_handshake2, this, _1, _2)); + } + + void on_handshake2(error_code const& ec, size_t bytes_transferred) + { + if (ec) + { + close("ERROR READ HANDSHAKE: %s", ec); + return; + } + + // buffer is the full 68 byte handshake + // look at the extension bits + + fast_extension = ((char*)buffer)[27] & 4; + + 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); + error_code ec; + boost::asio::async_write(s, boost::asio::buffer(write_buf_proto, ptr - write_buf_proto) + , boost::bind(&peer_conn::on_have_all_sent, this, _1, _2)); + } + else + { + // bitfield + int len = (num_pieces + 7) / 8; + char* ptr = (char*)buffer; + write_uint32(len + 1, ptr); + write_uint8(5, ptr); + memset(ptr, 255, len); + ptr += len; + // unchoke + write_uint32(1, ptr); + write_uint8(1, ptr); + error_code ec; + boost::asio::async_write(s, boost::asio::buffer((char*)buffer, len + 10) + , boost::bind(&peer_conn::on_have_all_sent, this, _1, _2)); + } + } + + void on_have_all_sent(error_code const& ec, size_t bytes_transferred) + { + if (ec) + { + close("ERROR SEND HAVE ALL: %s", ec); + return; + } + + // read message + boost::asio::async_read(s, boost::asio::buffer((char*)buffer, 4) + , boost::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 == -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()); + 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(false); + } + } + char msg[] = "\0\0\0\xd\x06" + " " // piece + " " // offset + " "; // length + char* m = (char*)malloc(sizeof(msg)); + memcpy(m, msg, sizeof(msg)); + char* ptr = m + 5; + write_uint32(current_piece, ptr); + write_uint32(block * 16 * 1024, ptr); + write_uint32(16 * 1024, ptr); + error_code ec; + boost::asio::async_write(s, boost::asio::buffer(m, sizeof(msg) - 1) + , boost::bind(&peer_conn::on_req_sent, this, m, _1, _2)); + + ++outstanding_requests; + ++block; + if (block == blocks_per_piece) + { + block = 0; + current_piece = -1; + current_piece_is_allowed = false; + } + return true; + } + + void on_req_sent(char* m, error_code const& ec, size_t bytes_transferred) + { + free(m); + if (ec) + { + close("ERROR SEND REQUEST: %s", ec); + return; + } + + work_download(); + } + + void close(char const* fmt, error_code const& ec) + { + end_time = clock_type::now(); + char tmp[1024]; + snprintf(tmp, sizeof(tmp), fmt, ec.message().c_str()); + int time = total_milliseconds(end_time - start_time); + if (time == 0) time = 1; + float up = (boost::int64_t(blocks_sent) * 0x4000) / time / 1000.f; + float down = (boost::int64_t(blocks_received) * 0x4000) / time / 1000.f; + error_code e; + + char ep_str[200]; + address const& addr = s.local_endpoint(e).address(); +#if TORRENT_USE_IPV6 + if (addr.is_v6()) + snprintf(ep_str, sizeof(ep_str), "[%s]:%d", addr.to_string(e).c_str() + , s.local_endpoint(e).port()); + else +#endif + snprintf(ep_str, sizeof(ep_str), "%s:%d", addr.to_string(e).c_str() + , s.local_endpoint(e).port()); + 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 == -1 + && outstanding_requests == 0 + && blocks_received >= num_pieces * blocks_per_piece) + { + close("COMPLETED DOWNLOAD", error_code()); + return; + } + + // send requests + if (outstanding_requests < 40) + { + if (write_request()) return; + } + + // read message + boost::asio::async_read(s, boost::asio::buffer((char*)buffer, 4) + , boost::bind(&peer_conn::on_msg_length, this, _1, _2)); + } + + void on_msg_length(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 PREFIX: %s", ec); + return; + } + char* ptr = (char*)buffer; + unsigned int length = read_uint32(ptr); + if (length > sizeof(buffer)) + { + fprintf(stderr, "len: %d\n", length); + close("ERROR RECEIVE MESSAGE PREFIX: packet too big", error_code()); + return; + } + boost::asio::async_read(s, boost::asio::buffer((char*)buffer, length) + , boost::bind(&peer_conn::on_message, this, _1, _2)); + } + + void on_message(error_code const& ec, size_t bytes_transferred) + { + if ((ec == boost::asio::error::operation_aborted || ec == boost::asio::error::bad_descriptor) + && restarting) + { + start_conn(); + return; + } + + if (ec) + { + close("ERROR RECEIVE MESSAGE: %s", ec); + return; + } + char* ptr = (char*)buffer; + int msg = read_uint8(ptr); + + if (test_mode == dual_test && num_seeds == 0) + { + TORRENT_ASSERT(!seed); + close("NO MORE SEEDS, test done", error_code()); + return; + } + + //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; + } + int piece = detail::read_int32(ptr); + int start = detail::read_int32(ptr); + int length = detail::read_int32(ptr); + write_piece(piece, start, length); + } + else if (msg == 3) // not-interested + { + close("DONE", error_code()); + return; + } + else + { + // read another message + boost::asio::async_read(s, boost::asio::buffer(buffer, 4) + , boost::bind(&peer_conn::on_msg_length, this, _1, _2)); + } + } + else + { + if (msg == 0xe) // have_all + { + // build a list of all pieces and request them all! + pieces.resize(num_pieces); + for (int i = 0; i < int(pieces.size()); ++i) + pieces[i] = i; + std::random_shuffle(pieces.begin(), pieces.end()); + } + else if (msg == 4) // have + { + int piece = detail::read_int32(ptr); + if (pieces.empty()) pieces.push_back(piece); + else pieces.insert(pieces.begin() + (rand() % pieces.size()), piece); + } + else if (msg == 5) // bitfield + { + pieces.reserve(num_pieces); + int piece = 0; + for (int i = 0; i < int(bytes_transferred); ++i) + { + int mask = 0x80; + for (int k = 0; k < 8; ++k) + { + if (piece > num_pieces) break; + if (*ptr & mask) pieces.push_back(piece); + mask >>= 1; + ++piece; + } + ++ptr; + } + std::random_shuffle(pieces.begin(), pieces.end()); + } + 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 piece = detail::read_int32(ptr); + int start = detail::read_int32(ptr); + + if (churn && (blocks_received % churn) == 0) { + outstanding_requests = 0; + restarting = true; + s.close(); + return; + } + if (int((start + bytes_transferred) / 0x4000) == blocks_per_piece) + { + write_have(piece); + return; + } + } + else if (msg == 13) // suggest + { + int piece = detail::read_int32(ptr); + std::vector::iterator i = std::find(pieces.begin(), pieces.end(), piece); + if (i != pieces.end()) + { + pieces.erase(i); + suggested_pieces.push_back(piece); + ++num_suggest; + } + } + else if (msg == 16) // reject request + { + int piece = detail::read_int32(ptr); + int start = detail::read_int32(ptr); + int length = detail::read_int32(ptr); + + // put it back! + if (current_piece != piece) + { + if (pieces.empty() || pieces.back() != piece) + pieces.push_back(piece); + } + else + { + block = (std::min)(start / 0x4000, block); + if (block == 0) + { + pieces.push_back(current_piece); + current_piece = -1; + current_piece_is_allowed = false; + } + } + --outstanding_requests; + fprintf(stderr, "REJECT: [ piece: %d start: %d length: %d ]\n", piece, start, length); + } + else if (msg == 0) // choke + { + choked = true; + } + else if (msg == 1) // unchoke + { + choked = false; + } + else if (msg == 17) // allowed_fast + { + int piece = detail::read_int32(ptr); + std::vector::iterator i = std::find(pieces.begin(), pieces.end(), piece); + if (i != pieces.end()) + { + pieces.erase(i); + allowed_fast.push_back(piece); + } + } + work_download(); + } + } + + bool verify_piece(int piece, int start, char const* ptr, int size) + { + boost::uint32_t* buf = (boost::uint32_t*)ptr; + boost::uint32_t fill = (piece << 8) | ((start / 0x4000) & 0xff); + for (int i = 0; i < size / 4; ++i) + { + if (buf[i] != fill) + { + fprintf(stderr, "received invalid block. piece %d block %d\n", piece, start / 0x4000); + exit(1); + return false; + } + } + return true; + } + + void write_piece(int piece, int start, int length) + { + generate_block(write_buffer, piece, start, length); + + if (corrupt) + { + --corruption_counter; + if (corruption_counter == 0) + { + corruption_counter = 1000; + memset(write_buffer, 0, 10); + } + } + char* ptr = write_buf_proto; + write_uint32(9 + length, ptr); + assert(length == 0x4000); + write_uint8(7, ptr); + write_uint32(piece, ptr); + write_uint32(start, ptr); + boost::array vec; + vec[0] = boost::asio::buffer(write_buf_proto, ptr - write_buf_proto); + vec[1] = boost::asio::buffer(write_buffer, length); + boost::asio::async_write(s, vec, boost::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(int piece) + { + char* ptr = write_buf_proto; + write_uint32(5, ptr); + write_uint8(4, ptr); + write_uint32(piece, ptr); + boost::asio::async_write(s, boost::asio::buffer(write_buf_proto, 9), boost::bind(&peer_conn::on_have_all_sent, this, _1, _2)); + } +}; + +void print_usage() +{ + 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" + " -T the name of the torrent (and directory\n" + " its files are saved in)\n\n" + " gen-data generate the data file(s) for the test torrent\n" + " options for this command:\n" + " -t the torrent file that was previously generated\n" + " -P the path to where the data should be stored\n\n" + " gen-test-torrents generate many test torrents (cannot be used for up/down tests)\n" + " options for this command:\n" + " -N number of torrents to generate\n" + " -n number of files in each torrent\n" + " -t base name of torrent files (index is appended)\n\n" + " 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(libtorrent::create_torrent* t, int start_piece, int end_piece, int piece_size, bool print) +{ + if (print) fprintf(stderr, "\n"); + boost::uint32_t piece[0x4000 / 4]; + for (int i = start_piece; i < end_piece; ++i) + { + hasher ph; + for (int j = 0; j < piece_size; j += 0x4000) + { + generate_block(piece, i, j, 0x4000); + ph.update((char*)piece, 0x4000); + } + t->set_hash(i, ph.final()); + if (print && (i & 1)) fprintf(stderr, "\r%.1f %% ", float((i-start_piece) * 100) / float(end_piece-start_piece)); + } + if (print) fprintf(stderr, "\n"); +} + +// size is in megabytes +void generate_torrent(std::vector& buf, int size, int num_files + , char const* torrent_name) +{ + file_storage fs; + // 1 MiB piece size + const int piece_size = 1024 * 1024; + const int num_pieces = size; + const boost::int64_t total_size = boost::int64_t(piece_size) * num_pieces; + + boost::int64_t s = total_size; + int i = 0; + boost::int64_t file_size = total_size / num_files; + while (s > 0) + { + char b[100]; + snprintf(b, sizeof(b), "%s/stress_test%d", torrent_name, i); + ++i; + fs.add_file(b, (std::min)(s, boost::int64_t(file_size))); + s -= file_size; + file_size += 200; + } + +// fs.add_file("stress_test_file", total_size); + libtorrent::create_torrent t(fs, piece_size); + + // generate the hashes in 4 threads + thread t1(boost::bind(&hasher_thread, &t, 0, 1 * num_pieces / 4, piece_size, false)); + thread t2(boost::bind(&hasher_thread, &t, 1 * num_pieces / 4, 2 * num_pieces / 4, piece_size, false)); + thread t3(boost::bind(&hasher_thread, &t, 2 * num_pieces / 4, 3 * num_pieces / 4, piece_size, false)); + thread t4(boost::bind(&hasher_thread, &t, 3 * num_pieces / 4, 4 * num_pieces / 4, piece_size, true)); + + t1.join(); + t2.join(); + t3.join(); + t4.join(); + + std::back_insert_iterator > out(buf); + + bencode(out, t.generate()); +} + +void generate_data(char const* path, torrent_info const& ti) +{ + file_storage const& fs = ti.files(); + + file_pool fp; + + storage_params params; + params.files = &const_cast(fs); + params.mapped_files = NULL; + params.path = path; + params.pool = &fp; + params.mode = storage_mode_sparse; + + boost::scoped_ptr st(default_storage_constructor(params)); + + storage_error error; + st->initialize(error); + + boost::uint32_t piece[0x4000 / 4]; + for (int i = 0; i < ti.num_pieces(); ++i) + { + for (int j = 0; j < ti.piece_size(i); j += 0x4000) + { + generate_block(piece, i, j, 0x4000); + file::iovec_t b = { piece, 0x4000}; + storage_error error; + st->writev(&b, 1, i, j, 0, error); + if (error) + fprintf(stderr, "storage error: %s\n", error.ec.message().c_str()); + } + if (i & 1) fprintf(stderr, "\r%.1f %% ", float(i * 100) / float(ti.num_pieces())); + } +} + +void io_thread(io_service* ios) +{ + error_code ec; + ios->run(ec); + if (ec) fprintf(stderr, "ERROR: %s\n", ec.message().c_str()); +} + +int main(int argc, char* argv[]) +{ + if (argc <= 1) print_usage(); + + char const* command = argv[1]; + int size = 1000; + int num_files = 10; + int num_torrents = 1; + char const* torrent_file = "benchmark.torrent"; + char const* data_path = "."; + int num_connections = 50; + char const* destination_ip = "127.0.0.1"; + int destination_port = 6881; + int churn = 0; + + argv += 2; + argc -= 2; + + while (argc > 0) + { + char const* optname = argv[0]; + ++argv; + --argc; + + if (optname[0] != '-' || strlen(optname) != 2) + { + fprintf(stderr, "unknown option: %s\n", optname); + continue; + } + + // options with no arguments + switch (optname[1]) + { + case 'C': test_corruption = true; continue; + } + + if (argc == 0) + { + fprintf(stderr, "missing argument for option: %s\n", optname); + break; + } + + char const* optarg = argv[0]; + ++argv; + --argc; + + switch (optname[1]) + { + case 's': size = atoi(optarg); break; + case 'n': num_files = atoi(optarg); break; + case 'N': num_torrents = atoi(optarg); break; + case 't': torrent_file = optarg; break; + case 'P': data_path = optarg; break; + case 'c': num_connections = atoi(optarg); break; + case 'p': destination_port = atoi(optarg); break; + case 'd': destination_ip = optarg; break; + case 'r': churn = atoi(optarg); break; + default: fprintf(stderr, "unknown option: %s\n", optname); + } + } + + if (strcmp(command, "gen-torrent") == 0) + { + std::vector tmp; + std::string name = leaf_path(torrent_file); + name = name.substr(0, name.find_last_of('.')); + 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 (strcmp("-", torrent_file) != 0) + { + if( (output = fopen(torrent_file, "wb+")) == 0) + { + fprintf(stderr, "Could not open file '%s' for writing: %s\n", torrent_file, strerror(errno)); + exit(2); + } + } + fprintf(stderr, "writing file to: %s\n", torrent_file); + fwrite(&tmp[0], 1, tmp.size(), output); + if (output != stdout) + fclose(output); + + return 0; + } + else if (strcmp(command, "gen-data") == 0) + { + error_code ec; + torrent_info ti(torrent_file, ec); + if (ec) + { + fprintf(stderr, "ERROR LOADING .TORRENT: %s\n", ec.message().c_str()); + return 1; + } + generate_data(data_path, ti); + return 0; + } + else if (strcmp(command, "gen-test-torrents") == 0) + { + std::vector buf; + for (int i = 0; i < num_torrents; ++i) + { + char torrent_name[100]; + 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]; + snprintf(file_name, sizeof(file_name), "%s-%d/file-%d", torrent_file, i, j); + fs.add_file(file_name, boost::int64_t(j + i + 1) * 251); + } + // 1 MiB piece size + const int piece_size = 1024 * 1024; + libtorrent::create_torrent t(fs, piece_size); + sha1_hash zero(0); + for (int i = 0; i < fs.num_pieces(); ++i) + t.set_hash(i, zero); + + + buf.clear(); + std::back_insert_iterator > out(buf); + bencode(out, t.generate()); + FILE* f = fopen(torrent_name, "w+"); + if (f == 0) + { + fprintf(stderr, "Could not open file '%s' for writing: %s\n", torrent_name, strerror(errno)); + return 1; + } + size_t ret = fwrite(&buf[0], 1, buf.size(), f); + if (ret != buf.size()) + { + fprintf(stderr, "write returned: %d (expected %d)\n", int(ret), int(buf.size())); + fclose(f); + return 1; + } + printf("wrote %s\n", torrent_name); + fclose(f); + } + return 0; + } + else if (strcmp(command, "upload") == 0) + { + test_mode = upload_test; + } + else if (strcmp(command, "download") == 0) + { + test_mode = download_test; + } + else if (strcmp(command, "dual") == 0) + { + test_mode = dual_test; + } + else + { + fprintf(stderr, "unknown command: %s\n\n", command); + print_usage(); + } + + error_code ec; + address_v4 addr = address_v4::from_string(destination_ip, ec); + if (ec) + { + fprintf(stderr, "ERROR RESOLVING %s: %s\n", destination_ip, ec.message().c_str()); + return 1; + } + tcp::endpoint ep(addr, 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 + unsigned long ip = addr.to_ulong(); + if ((ip & 0xff000000) == 0x7f000000) + { + local_bind = true; + } +#endif + + torrent_info ti(torrent_file, ec); + if (ec) + { + fprintf(stderr, "ERROR LOADING .TORRENT: %s\n", ec.message().c_str()); + return 1; + } + + std::vector conns; + conns.reserve(num_connections); + const int num_threads = 2; + io_service ios[num_threads]; + for (int i = 0; i < num_connections; ++i) + { + bool corrupt = test_corruption && (i & 1) == 0; + bool seed = false; + if (test_mode == upload_test) seed = true; + else if (test_mode == dual_test) seed = (i & 1); + conns.push_back(new peer_conn(ios[i % num_threads], ti.num_pieces(), ti.piece_length() / 16 / 1024 + , ep, (char const*)&ti.info_hash()[0], seed, churn, corrupt)); + sleep_ms(1); + ios[i % num_threads].poll_one(ec); + if (ec) + { + fprintf(stderr, "ERROR: %s\n", ec.message().c_str()); + break; + } + } + + thread t1(boost::bind(&io_thread, &ios[0])); + thread t2(boost::bind(&io_thread, &ios[1])); + + t1.join(); + t2.join(); + + float up = 0.f; + float down = 0.f; + boost::uint64_t total_sent = 0; + boost::uint64_t total_received = 0; + + for (std::vector::iterator i = conns.begin() + , end(conns.end()); i != end; ++i) + { + peer_conn* p = *i; + int time = total_milliseconds(p->end_time - p->start_time); + if (time == 0) time = 1; + total_sent += p->blocks_sent; + up += (boost::int64_t(p->blocks_sent) * 0x4000) / time / 1000.f; + down += (boost::int64_t(p->blocks_received) * 0x4000) / time / 1000.f; + delete p; + } + + printf("=========================\n" + "suggests: %d suggested-requests: %d\n" + "total sent: %.1f %% received: %.1f %%\n" + "rate sent: %.1f MB/s received: %.1f MB/s\n" + , int(num_suggest), int(num_suggested_requests) + , total_sent * 0x4000 * 100.f / float(ti.total_size()) + , total_received * 0x4000 * 100.f / float(ti.total_size()) + , up, down); + + return 0; +} diff --git a/examples/dump_torrent.cpp b/examples/dump_torrent.cpp new file mode 100644 index 0000000..fa1b155 --- /dev/null +++ b/examples/dump_torrent.cpp @@ -0,0 +1,216 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/entry.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/announce_entry.hpp" +#include "libtorrent/bdecode.hpp" +#include "libtorrent/magnet_uri.hpp" + +int load_file(std::string const& filename, std::vector& v + , libtorrent::error_code& ec, int limit = 8000000) +{ + ec.clear(); + FILE* f = fopen(filename.c_str(), "rb"); + if (f == NULL) + { + 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(s); + if (s == 0) + { + fclose(f); + return 0; + } + + r = 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; +} + +int main(int argc, char* argv[]) +{ + using namespace libtorrent; + + if (argc < 2 || argc > 4) + { + fputs("usage: dump_torrent torrent-file [total-items-limit] [recursion-limit]\n", stderr); + return 1; + } + + int item_limit = 1000000; + int depth_limit = 1000; + + if (argc > 2) item_limit = atoi(argv[2]); + if (argc > 3) depth_limit = atoi(argv[3]); + + std::vector buf; + error_code ec; + int ret = load_file(argv[1], buf, ec, 40 * 1000000); + if (ret == -1) + { + fprintf(stderr, "file too big, aborting\n"); + return 1; + } + + if (ret != 0) + { + fprintf(stderr, "failed to load file: %s\n", ec.message().c_str()); + return 1; + } + bdecode_node e; + int pos = -1; + printf("decoding. recursion limit: %d total item count limit: %d\n" + , depth_limit, item_limit); + ret = bdecode(&buf[0], &buf[0] + buf.size(), e, ec, &pos + , depth_limit, item_limit); + + printf("\n\n----- raw info -----\n\n%s\n", print_entry(e).c_str()); + + if (ret != 0) + { + fprintf(stderr, "failed to decode: '%s' at character: %d\n", ec.message().c_str(), pos); + return 1; + } + + torrent_info t(e, ec); + if (ec) + { + fprintf(stderr, "%s\n", ec.message().c_str()); + return 1; + } + e.clear(); + std::vector().swap(buf); + + // print info about torrent + printf("\n\n----- torrent file info -----\n\n" + "nodes:\n"); + + typedef std::vector > node_vec; + node_vec const& nodes = t.nodes(); + for (node_vec::const_iterator i = nodes.begin(), end(nodes.end()); + i != end; ++i) + { + printf("%s: %d\n", i->first.c_str(), i->second); + } + puts("trackers:\n"); + for (std::vector::const_iterator i = t.trackers().begin(); + i != t.trackers().end(); ++i) + { + printf("%2d: %s\n", i->tier, i->url.c_str()); + } + + char ih[41]; + to_hex((char const*)&t.info_hash()[0], 20, ih); + 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 + , t.comment().c_str() + , t.creator().c_str() + , make_magnet_uri(t).c_str() + , t.name().c_str() + , t.num_files()); + file_storage const& st = t.files(); + for (int i = 0; i < st.num_files(); ++i) + { + int first = st.map_file(i, 0, 0).piece; + int last = st.map_file(i, (std::max)(boost::int64_t(st.file_size(i))-1, boost::int64_t(0)), 0).piece; + int flags = st.file_flags(i); + 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 & file_storage::flag_pad_file)?'p':'-') + , ((flags & file_storage::flag_executable)?'x':'-') + , ((flags & file_storage::flag_hidden)?'h':'-') + , ((flags & file_storage::flag_symlink)?'l':'-') + , first, last + , boost::uint32_t(st.mtime(i)) + , st.hash(i) != sha1_hash(0) ? to_hex(st.hash(i).to_string()).c_str() : "" + , st.file_path(i).c_str() + , (flags & file_storage::flag_symlink) ? "-> " : "" + , (flags & file_storage::flag_symlink) ? st.symlink(i).c_str() : ""); + } + + return 0; +} + diff --git a/examples/make_torrent.cpp b/examples/make_torrent.cpp new file mode 100644 index 0000000..2814513 --- /dev/null +++ b/examples/make_torrent.cpp @@ -0,0 +1,443 @@ +/* + +Copyright (c) 2006, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/entry.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/file.hpp" +#include "libtorrent/storage.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/file.hpp" +#include "libtorrent/file_pool.hpp" +#include "libtorrent/hex.hpp" // for from_hex + +#include + +#ifdef TORRENT_WINDOWS +#include // for _getcwd +#endif + +using namespace libtorrent; + +int load_file(std::string const& filename, std::vector& v, libtorrent::error_code& ec, int limit = 8000000) +{ + ec.clear(); + FILE* f = fopen(filename.c_str(), "rb"); + if (f == NULL) + { + 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(s); + if (s == 0) + { + fclose(f); + return 0; + } + + r = 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; +} + +std::string branch_path(std::string const& f) +{ + if (f.empty()) return f; + +#ifdef TORRENT_WINDOWS + if (f == "\\\\") return ""; +#endif + if (f == "/") return ""; + + int len = 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 == NULL || 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 == NULL) sep = first; + else ++sep; + + // return false if the first character of the filename is a . + if (sep[0] == '.') return false; + + fprintf(stderr, "%s\n", f.c_str()); + return true; +} + +void print_progress(int i, int num) +{ + fprintf(stderr, "\r%d/%d", i+1, num); +} + +void print_usage() +{ + fputs("usage: make_torrent FILE [OPTIONS]\n" + "\n" + "Generates a torrent file from the specified file\n" + "or directory and writes it to standard out\n\n" + "OPTIONS:\n" + "-m file generate a merkle hash tree torrent.\n" + " merkle torrents require client support\n" + " the resulting full merkle tree is written to\n" + " the specified file\n" + "-w url adds a web seed to the torrent with\n" + " the specified url\n" + "-t url adds the specified tracker to the\n" + " torrent. For multiple trackers, specify more\n" + " -t options\n" + "-c comment sets the comment to the specified string\n" + "-C creator sets the created-by field to the specified string\n" + "-p bytes enables padding files. Files larger\n" + " than bytes will be piece-aligned\n" + "-s bytes specifies a piece size for the torrent\n" + " This has to be a multiple of 16 kiB\n" + "-l Don't follow symlinks, instead encode them as\n" + " links in the torrent file\n" + "-o file specifies the output filename of the torrent file\n" + " If this is not specified, the torrent file is\n" + " printed to the standard out, except on windows\n" + " where the filename defaults to a.torrent\n" + "-r file add root certificate to the torrent, to verify\n" + " the HTTPS tracker\n" + "-S info-hash add a similar torrent by info-hash. The similar\n" + " torrent is expected to share some files with this one\n" + "-L collection add a collection name to this torrent. Other torrents\n" + " in the same collection is expected to share files\n" + " with this one.\n" + "-M make the torrent compatible with mutable torrents\n" + " this means aligning large files and pad them in order\n" + " for piece hashes to uniquely indentify a file without\n" + " overlap\n" + , stderr); +} + +int main(int argc, char* argv[]) +{ + using namespace libtorrent; + + std::string creator_str = "libtorrent"; + std::string comment_str; + + if (argc < 2) + { + print_usage(); + return 1; + } + +#ifndef BOOST_NO_EXCEPTIONS + try + { +#endif + std::vector web_seeds; + std::vector trackers; + std::vector collections; + std::vector similar; + int pad_file_limit = -1; + int piece_size = 0; + int flags = 0; + std::string root_cert; + + std::string outfile; + std::string merklefile; +#ifdef TORRENT_WINDOWS + // don't ever write binary data to the console on windows + // it will just be interpreted as text and corrupted + outfile = "a.torrent"; +#endif + + for (int i = 2; i < argc; ++i) + { + if (argv[i][0] != '-') + { + print_usage(); + return 1; + } + + switch (argv[i][1]) + { + case 'w': + ++i; + web_seeds.push_back(argv[i]); + break; + case 't': + ++i; + trackers.push_back(argv[i]); + break; + case 'M': + flags |= create_torrent::mutable_torrent_support; + pad_file_limit = 0x4000; + break; + case 'p': + ++i; + pad_file_limit = atoi(argv[i]); + flags |= create_torrent::optimize_alignment; + break; + case 's': + ++i; + piece_size = atoi(argv[i]); + break; + case 'm': + ++i; + merklefile = argv[i]; + flags |= create_torrent::merkle; + break; + case 'o': + ++i; + outfile = argv[i]; + break; + case 'l': + flags |= create_torrent::symlinks; + break; + case 'C': + ++i; + creator_str = argv[i]; + break; + case 'c': + ++i; + comment_str = argv[i]; + break; + case 'r': + ++i; + root_cert = argv[i]; + break; + case 'S': + { + ++i; + if (strlen(argv[i]) != 40) + { + fprintf(stderr, "invalid info-hash for -S. " + "Expected 40 hex characters\n"); + print_usage(); + return 1; + } + sha1_hash ih; + if (!from_hex(argv[i], 40, (char*)&ih[0])) + { + fprintf(stderr, "invalid info-hash for -S\n"); + print_usage(); + return 1; + } + similar.push_back(ih); + } + break; + case 'L': + ++i; + collections.push_back(argv[i]); + break; + default: + print_usage(); + return 1; + } + } + + file_storage fs; + std::string full_path = argv[1]; +#ifdef TORRENT_WINDOWS + if (full_path[1] != ':') +#else + if (full_path[0] != '/') +#endif + { + char cwd[TORRENT_MAX_PATH]; +#ifdef TORRENT_WINDOWS + _getcwd(cwd, sizeof(cwd)); + full_path = cwd + ("\\" + full_path); +#else + getcwd(cwd, sizeof(cwd)); + full_path = cwd + ("/" + full_path); +#endif + } + + add_files(fs, full_path, file_filter, flags); + if (fs.num_files() == 0) + { + fputs("no files specified.\n", stderr); + return 1; + } + + create_torrent t(fs, piece_size, pad_file_limit, flags); + int tier = 0; + for (std::vector::iterator i = trackers.begin() + , end(trackers.end()); i != end; ++i, ++tier) + t.add_tracker(*i, tier); + + for (std::vector::iterator i = web_seeds.begin() + , end(web_seeds.end()); i != end; ++i) + t.add_url_seed(*i); + + for (std::vector::iterator i = collections.begin() + , end(collections.end()); i != end; ++i) + t.add_collection(*i); + + for (std::vector::iterator i = similar.begin() + , end(similar.end()); i != end; ++i) + t.add_similar_torrent(*i); + + error_code ec; + set_piece_hashes(t, branch_path(full_path) + , boost::bind(&print_progress, _1, t.num_pieces()), ec); + if (ec) + { + fprintf(stderr, "%s\n", ec.message().c_str()); + return 1; + } + + fprintf(stderr, "\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 pem; + load_file(root_cert, pem, ec, 10000); + if (ec) + { + fprintf(stderr, "failed to load root certificate for tracker: %s\n", ec.message().c_str()); + } + else + { + t.set_root_cert(std::string(&pem[0], pem.size())); + } + } + + // create the torrent and print it to stdout + std::vector torrent; + bencode(back_inserter(torrent), t.generate()); + FILE* output = stdout; + if (!outfile.empty()) + output = fopen(outfile.c_str(), "wb+"); + if (output == NULL) + { + fprintf(stderr, "failed to open file \"%s\": (%d) %s\n" + , outfile.c_str(), errno, strerror(errno)); + return 1; + } + fwrite(&torrent[0], 1, torrent.size(), output); + + if (output != stdout) + fclose(output); + + if (!merklefile.empty()) + { + output = fopen(merklefile.c_str(), "wb+"); + if (output == NULL) + { + fprintf(stderr, "failed to open file \"%s\": (%d) %s\n" + , merklefile.c_str(), errno, strerror(errno)); + return 1; + } + int ret = fwrite(&t.merkle_tree()[0], 20, t.merkle_tree().size(), output); + if (ret != int(t.merkle_tree().size())) + { + fprintf(stderr, "failed to write %s: (%d) %s\n" + , merklefile.c_str(), errno, strerror(errno)); + } + fclose(output); + } + +#ifndef BOOST_NO_EXCEPTIONS + } + catch (std::exception& e) + { + fprintf(stderr, "%s\n", e.what()); + } +#endif + + return 0; +} + diff --git a/examples/print.cpp b/examples/print.cpp new file mode 100644 index 0000000..c02ab00 --- /dev/null +++ b/examples/print.cpp @@ -0,0 +1,475 @@ +#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]; + snprintf(buf, sizeof(buf), "%*d", width, v); + return buf; +} + +std::string add_suffix(float val, char const* suffix) +{ + if (val < 0.001f) + { + 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.f; + if (std::fabs(val) < 1000.f) break; + } + char ret[100]; + 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]; + 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)); + + int const progress_chars = (progress * width + 500) / 1000; + + if (caption.empty()) + { + char code[10]; + snprintf(code, sizeof(code), "\x1b[3%dm", c); + bar = code; + std::fill_n(std::back_inserter(bar), progress_chars, fill); + std::fill_n(std::back_inserter(bar), width - progress_chars, bg); + bar += esc("39"); + } + else + { + // foreground color (depends a bit on background color) + color_code tc = col_black; + if (c == col_black || c == col_blue) + tc = col_white; + + caption.resize(size_t(width), ' '); + +#ifdef _WIN32 + char const* background = "40"; +#else + char const* background = "48;5;238"; +#endif + + char str[256]; + if (flags & progress_invert) + 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 + 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; +} + +bool get_piece(libtorrent::bitfield const& p, int index) +{ + if (index < 0 || index >= p.size()) return false; + return p.get_bit(index); +} + +#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(libtorrent::bitfield const& p, int width, int* height) +{ + // print two rows of pieces at a time + int piece = 0; + ++*height; + std::string ret; + ret.reserve((p.size() + width * 2 - 1) / width / 2 * 4); + while (piece < p.size()) + { + for (int i = 0; i < width; ++i) + { + // each character has 4 pieces. store them in a byte to use for lookups + int const c = get_piece(p, piece) + | (get_piece(p, piece+1) << 1) + | (get_piece(p, width*2+piece) << 2) + | (get_piece(p, width*2+piece+1) << 3); + + // we have 4 bits, 16 different combinations + static char const* const chars[] = + { + " ", // no bit is set 0000 + "\u2598", // upper left 0001 + "\u259d", // upper right 0010 + "\u2580", // both top bits 0011 + "\u2596", // lower left 0100 + "\u258c", // both left bits 0101 + "\u259e", // upper right, lower left 0110 + "\u259b", // left and upper sides 0111 + "\u2597", // lower right 1000 + "\u259a", // lower right, upper left 1001 + "\u2590", // right side 1010 + "\u259c", // lower right, top side 1011 + "\u2584", // both lower bits 1100 + "\u2599", // both lower, top left 1101 + "\u259f", // both lower, top right 1110 + "\x1b[7m \x1b[27m" // all bits are set (full block) + }; + + ret += chars[c]; + piece += 2; + } + ret += '\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(libtorrent::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 = {x, y}; + SetConsoleCursorPosition(out, c); +#else + 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 + 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, 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) + printf("\033[%d;1H\033[2K", i + 1); +#endif +} + +void terminal_size(int* terminal_width, int* terminal_height) +{ +#ifdef _WIN32 + HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); + CONSOLE_SCREEN_BUFFER_INFO coninfo; + if (GetConsoleScreenBufferInfo(out, &coninfo)) + { + *terminal_width = coninfo.dwSize.X; + *terminal_height = coninfo.srWindow.Bottom - coninfo.srWindow.Top; +#else + int tty = open("/dev/tty", O_RDONLY); + if (tty < 0) + { + *terminal_width = 190; + *terminal_height = 100; + return; + } + winsize size; + int ret = ioctl(tty, TIOCGWINSZ, reinterpret_cast(&size)); + close(tty); + if (ret == 0) + { + *terminal_width = size.ws_col; + *terminal_height = size.ws_row; +#endif + + if (*terminal_width < 64) + *terminal_width = 64; + if (*terminal_height < 25) + *terminal_height = 25; + } + else + { + *terminal_width = 190; + *terminal_height = 100; + } +} + +#ifdef _WIN32 +void apply_ansi_code(int* attributes, bool* reverse, bool* support_chaining, int code) +{ + static const int 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); + + int 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, buf - start, &written, NULL); + 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, buf - start, &written, NULL); + +#else + fputs(buf, stdout); +#endif +} + diff --git a/examples/print.hpp b/examples/print.hpp new file mode 100644 index 0000000..4970731 --- /dev/null +++ b/examples/print.hpp @@ -0,0 +1,45 @@ +#ifndef PRINT_HPP_ +#define PRINT_HPP_ + +#include +#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 val, char const* suffix = 0); + +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); + +void set_cursor_pos(int x, int y); + +void clear_screen(); + +void clear_rows(int y1, int y2); + +void terminal_size(int* terminal_width, int* terminal_height); +std::string piece_matrix(libtorrent::bitfield const& p, int width, int* height); + +void print(char const* str); + +#endif // PRINT_HPP_ + diff --git a/examples/run_cmake.sh.in b/examples/run_cmake.sh.in new file mode 100644 index 0000000..53269e7 --- /dev/null +++ b/examples/run_cmake.sh.in @@ -0,0 +1,8 @@ +#!/bin/sh + +cd ${libtorrent_BINARY_DIR}/examples +cmake \ + -D libtorrent_includes_asio_source=${asio_source} \ + -G "${CMAKE_GENERATOR}" \ + $@ \ + ${libtorrent_SOURCE_DIR}/examples diff --git a/examples/session_view.cpp b/examples/session_view.cpp new file mode 100644 index 0000000..a2011d2 --- /dev/null +++ b/examples/session_view.cpp @@ -0,0 +1,207 @@ +#include "session_view.hpp" +#include "print.hpp" + +#include // for std::max + +session_view::session_view() + : m_position(0) + , m_print_utp_stats(false) +{ + using libtorrent::find_metric_idx; + + m_width = 128; + + std::vector metrics = lt::session_stats_metrics(); + m_cnt[0].resize(metrics.size(), 0); + m_cnt[1].resize(metrics.size(), 0); + + m_queued_bytes_idx = find_metric_idx("disk.queued_write_bytes"); + m_wasted_bytes_idx = find_metric_idx("net.recv_redundant_bytes"); + m_failed_bytes_idx = find_metric_idx("net.recv_failed_bytes"); + m_num_peers_idx = find_metric_idx("peer.num_peers_connected"); + m_recv_payload_idx = find_metric_idx("net.recv_payload_bytes"); + m_sent_payload_idx = find_metric_idx("net.sent_payload_bytes"); + m_unchoked_idx = find_metric_idx("peer.num_peers_up_unchoked"); + m_unchoke_slots_idx = find_metric_idx("ses.num_unchoke_slots"); + m_limiter_up_queue_idx = find_metric_idx("net.limiter_up_queue"); + m_limiter_down_queue_idx = find_metric_idx("net.limiter_down_queue"); + m_queued_writes_idx = find_metric_idx("disk.num_write_jobs"); + m_queued_reads_idx = find_metric_idx("disk.num_read_jobs"); + + m_writes_cache_idx = find_metric_idx("disk.write_cache_blocks"); + m_reads_cache_idx = find_metric_idx("disk.read_cache_blocks"); + m_pinned_idx = find_metric_idx("disk.pinned_blocks"); + m_num_blocks_read_idx = find_metric_idx("disk.num_blocks_read"); + m_cache_hit_idx = find_metric_idx("disk.num_blocks_cache_hits"); + m_blocks_in_use_idx = find_metric_idx("disk.disk_blocks_in_use"); + m_blocks_written_idx = find_metric_idx("disk.num_blocks_written"); + m_write_ops_idx = find_metric_idx("disk.num_write_ops"); + + m_mfu_size_idx = find_metric_idx("disk.arc_mfu_size"); + m_mfu_ghost_idx = find_metric_idx("disk.arc_mfu_ghost_size"); + m_mru_size_idx = find_metric_idx("disk.arc_mru_size"); + m_mru_ghost_idx = find_metric_idx("disk.arc_mru_ghost_size"); + + m_utp_idle = find_metric_idx("utp.num_utp_idle"); + m_utp_syn_sent = find_metric_idx("utp.num_utp_syn_sent"); + m_utp_connected = find_metric_idx("utp.num_utp_connected"); + m_utp_fin_sent = find_metric_idx("utp.num_utp_fin_sent"); + m_utp_close_wait = find_metric_idx("utp.num_utp_close_wait"); +} + +void session_view::set_pos(int pos) +{ + m_position = pos; +} + +int session_view::pos() const { return m_position; } + +int session_view::height() const +{ + return 3 + m_print_utp_stats; +} + +void session_view::render() +{ + char str[1024]; + int pos = 0; + + int y = m_position; + + float seconds = (m_timestamp[0] - m_timestamp[1]) / 1000000.f; + + int download_rate = (m_cnt[0][m_recv_payload_idx] - m_cnt[1][m_recv_payload_idx]) + / seconds; + int upload_rate = (m_cnt[0][m_sent_payload_idx] - m_cnt[1][m_sent_payload_idx]) + / seconds; + + pos += snprintf(str, sizeof(str), "%s%s fail: %s down: %s (%s) " + " bw queue: %s | %s conns: %3d unchoked: %2d / %2d " + " %s\x1b[K" + , esc("48;5;238") + , esc("1") + , add_suffix(m_cnt[0][m_failed_bytes_idx]).c_str() + , color(add_suffix(download_rate, "/s"), col_green).c_str() + , color(add_suffix(m_cnt[0][m_recv_payload_idx]), col_green).c_str() + , color(to_string(m_cnt[0][m_limiter_up_queue_idx], 3), col_red).c_str() + , color(to_string(m_cnt[0][m_limiter_down_queue_idx], 3), col_green).c_str() + , int(m_cnt[0][m_num_peers_idx]) + , int(m_cnt[0][m_unchoked_idx]) + , int(m_cnt[0][m_unchoke_slots_idx]) + , esc("0")); + + set_cursor_pos(0, y++); + print(str); + + snprintf(str, sizeof(str), "%s%swaste: %s up: %s (%s) " + "disk queue: %s | %s cache w: %3d%% r: %3d%% " + "size: w: %s r: %s total: %s %s\x1b[K" +#ifdef _WIN32 + , esc("40") +#else + , esc("48;5;238") +#endif + , esc("1") + , add_suffix(m_cnt[0][m_wasted_bytes_idx]).c_str() + , color(add_suffix(upload_rate, "/s"), col_red).c_str() + , color(add_suffix(m_cnt[0][m_sent_payload_idx]), col_red).c_str() + , color(to_string(m_cnt[0][m_queued_reads_idx], 3), col_red).c_str() + , color(to_string(m_cnt[0][m_queued_writes_idx], 3), col_green).c_str() + , int((m_cnt[0][m_blocks_written_idx] - m_cnt[0][m_write_ops_idx]) * 100 + / (std::max)(boost::uint64_t(1), m_cnt[0][m_blocks_written_idx])) + , int(m_cnt[0][m_cache_hit_idx] * 100 + / (std::max)(boost::uint64_t(1), m_cnt[0][m_num_blocks_read_idx])) + , add_suffix(m_cnt[0][m_writes_cache_idx] * 16 * 1024).c_str() + , add_suffix(m_cnt[0][m_reads_cache_idx] * 16 * 1024).c_str() + , add_suffix(m_cnt[0][m_blocks_in_use_idx] * 16 * 1024).c_str() + , esc("0")); + set_cursor_pos(0, y++); + print(str); + +/* + snprintf(str, sizeof(str), "| timing - " + " read: %6d ms | write: %6d ms | hash: %6d" + , cs.average_read_time / 1000, cs.average_write_time / 1000 + , cs.average_hash_time / 1000); + + set_cursor_pos(0, y++); + print(str); + + snprintf(str, sizeof(str), "| jobs - queued: %4d (%4d) pending: %4d blocked: %4d " + "queued-bytes: %5" PRId64 " kB" + , cs.queued_jobs, cs.peak_queued, cs.pending_jobs, cs.blocked_jobs + , m_cnt[0][m_queued_bytes_idx] / 1000); + + set_cursor_pos(0, y++); + print(str); + + snprintf(str, sizeof(str), "| cache - total: %4d read: %4d write: %4d pinned: %4d write-queue: %4d" + , cs.read_cache_size + cs.write_cache_size, cs.read_cache_size + , cs.write_cache_size, cs.pinned_blocks + , int(m_cnt[0][m_queued_bytes_idx] / 0x4000)); + set_cursor_pos(0, y++); + print(str); +*/ + int mru_size = int(m_cnt[0][m_mru_size_idx] + m_cnt[0][m_mru_ghost_idx]); + int mfu_size = int(m_cnt[0][m_mfu_size_idx] + m_cnt[0][m_mfu_ghost_idx]); + int arc_size = mru_size + mfu_size; + + char mru_caption[100]; + snprintf(mru_caption, sizeof(mru_caption), "MRU: %d (%d)" + , int(m_cnt[0][m_mru_size_idx]), int(m_cnt[0][m_mru_ghost_idx])); + char mfu_caption[100]; + snprintf(mfu_caption, sizeof(mfu_caption), "MFU: %d (%d)" + , int(m_cnt[0][m_mfu_size_idx]), int(m_cnt[0][m_mfu_ghost_idx])); + + pos = snprintf(str, sizeof(str), "cache: "); + if (arc_size > 0) + { + if (mru_size > 0) + { + pos += snprintf(str + pos, sizeof(str) - pos, "%s" + , progress_bar(m_cnt[0][m_mru_ghost_idx] * 1000 / mru_size + , mru_size * (m_width-8) / arc_size, col_yellow, '-', '#' + , mru_caption, progress_invert).c_str()); + } + pos += snprintf(str + pos, sizeof(str) - pos, "|"); + if (mfu_size) + { + pos += snprintf(str + pos, sizeof(str) - pos, "%s" + , progress_bar(m_cnt[0][m_mfu_size_idx] * 1000 / mfu_size + , mfu_size * (m_width-8) / arc_size, col_green, '#', '-' + , mfu_caption).c_str()); + } + } + pos += snprintf(str + pos, sizeof(str) - pos, "\x1b[K"); + set_cursor_pos(0, y++); + print(str); + + if (m_print_utp_stats) + { + snprintf(str, sizeof(str), "uTP idle: %d syn: %d est: %d fin: %d wait: %d\x1b[K" + , int(m_cnt[0][m_utp_idle]) + , int(m_cnt[0][m_utp_syn_sent]) + , int(m_cnt[0][m_utp_connected]) + , int(m_cnt[0][m_utp_fin_sent]) + , int(m_cnt[0][m_utp_close_wait])); + set_cursor_pos(0, y++); + print(str); + } +} + +void session_view::update_counters(boost::uint64_t* stats_counters + , int num_cnt, boost::uint64_t t) +{ + // only update the previous counters if there's been enough + // time since it was last updated + if (t - m_timestamp[1] > 2000000) + { + m_cnt[1].swap(m_cnt[0]); + m_timestamp[1] = m_timestamp[0]; + } + + m_cnt[0].assign(stats_counters, stats_counters + num_cnt); + m_timestamp[0] = t; + render(); +} + diff --git a/examples/session_view.hpp b/examples/session_view.hpp new file mode 100644 index 0000000..12fa298 --- /dev/null +++ b/examples/session_view.hpp @@ -0,0 +1,76 @@ +#ifndef SESSION_VIEW_HPP_ +#define SESSION_VIEW_HPP_ + +#include "libtorrent/session_stats.hpp" +#include + +namespace lt = libtorrent; + +struct session_view +{ + session_view(); + + void set_pos(int pos); + + int pos() const; + + int height() const; + + void render(); + + void print_utp_stats(bool p) { m_print_utp_stats = p; } + bool print_utp_stats() const { return m_print_utp_stats; } + + void update_counters(boost::uint64_t* stats_counters, int num_cnt + , boost::uint64_t t); + +private: + + int m_position; + int m_width; + + // there are two sets of counters. the current one and the last one. This + // is used to calculate rates + std::vector m_cnt[2]; + + // the timestamps of the counters in m_cnt[0] and m_cnt[1] + // respectively. The timestamps are microseconds since session start + boost::uint64_t m_timestamp[2]; + + bool m_print_utp_stats; + + int m_queued_bytes_idx; + int m_wasted_bytes_idx; + int m_failed_bytes_idx; + int m_num_peers_idx; + int m_recv_payload_idx; + int m_sent_payload_idx; + int m_unchoked_idx; + int m_unchoke_slots_idx; + int m_limiter_up_queue_idx; + int m_limiter_down_queue_idx; + int m_queued_writes_idx; + int m_queued_reads_idx; + int m_writes_cache_idx; + int m_reads_cache_idx; + int m_pinned_idx; + int m_num_blocks_read_idx; + int m_cache_hit_idx; + int m_blocks_in_use_idx; + int m_blocks_written_idx; + int m_write_ops_idx; + + int m_mfu_size_idx; + int m_mfu_ghost_idx; + int m_mru_size_idx; + int m_mru_ghost_idx; + + int m_utp_idle; + int m_utp_syn_sent; + int m_utp_connected; + int m_utp_fin_sent; + int m_utp_close_wait; +}; + +#endif + diff --git a/examples/simple_client.cpp b/examples/simple_client.cpp new file mode 100644 index 0000000..ef43ffc --- /dev/null +++ b/examples/simple_client.cpp @@ -0,0 +1,81 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include "libtorrent/entry.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/torrent_info.hpp" + +int main(int argc, char* argv[]) +{ + using namespace libtorrent; + namespace lt = libtorrent; + + if (argc != 2) + { + fputs("usage: ./simple_client torrent-file\n" + "to stop the client, press return.\n", stderr); + return 1; + } + + settings_pack sett; + sett.set_str(settings_pack::listen_interfaces, "0.0.0.0:6881"); + lt::session s(sett); + error_code ec; + if (ec) + { + fprintf(stderr, "failed to open listen socket: %s\n", ec.message().c_str()); + return 1; + } + add_torrent_params p; + p.save_path = "./"; + p.ti = boost::make_shared(std::string(argv[1]), boost::ref(ec), 0); + if (ec) + { + fprintf(stderr, "%s\n", ec.message().c_str()); + return 1; + } + s.add_torrent(p, ec); + if (ec) + { + fprintf(stderr, "%s\n", ec.message().c_str()); + return 1; + } + + // wait for the user to end + char a; + scanf("%c\n", &a); + return 0; +} + diff --git a/examples/stats_counters.cpp b/examples/stats_counters.cpp new file mode 100644 index 0000000..b39b41e --- /dev/null +++ b/examples/stats_counters.cpp @@ -0,0 +1,49 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include "libtorrent/session_stats.hpp" + +using namespace libtorrent; + +int main() +{ + std::vector m = session_stats_metrics(); + for (int i = 0; i < int(m.size()); ++i) + { + printf("%s: %s (%d)\n" + , m[i].type == stats_metric::type_counter ? "CNTR" : "GAUG" + , m[i].name, m[i].value_index); + } + return 0; +} + diff --git a/examples/torrent_view.cpp b/examples/torrent_view.cpp new file mode 100644 index 0000000..75c8a5d --- /dev/null +++ b/examples/torrent_view.cpp @@ -0,0 +1,384 @@ +#include "torrent_view.hpp" +#include "print.hpp" +#include "libtorrent/torrent_status.hpp" + +const int header_size = 2; + +std::string torrent_state(lt::torrent_status const& s) +{ + static char const* state_str[] = + {"checking (q)", "checking", "dl metadata" + , "downloading", "finished", "seeding", "allocating", "checking (r)"}; + + if (s.errc) return s.errc.message(); + std::string ret; + if (s.paused && !s.auto_managed) ret += "paused"; + else if (s.paused && s.auto_managed) ret += "queued"; + else if (s.upload_mode) ret += "upload mode"; + else ret += state_str[s.state]; + if (!s.paused && !s.auto_managed) ret += " [F]"; + char buf[10]; + snprintf(buf, sizeof(buf), " (%.1f%%)", s.progress_ppm / 10000.f); + ret += buf; + return ret; +} + +bool compare_torrent(lt::torrent_status const* lhs, lt::torrent_status const* rhs) +{ + if (lhs->queue_position != -1 && rhs->queue_position != -1) + { + // both are downloading, sort by queue pos + return lhs->queue_position < rhs->queue_position; + } + else if (lhs->queue_position == -1 && rhs->queue_position == -1) + { + // both are seeding, sort by seed-rank + if (lhs->seed_rank != rhs->seed_rank) + return lhs->seed_rank > rhs->seed_rank; + + return lhs->info_hash < rhs->info_hash; + } + + return (lhs->queue_position == -1) < (rhs->queue_position == -1); +} + +torrent_view::torrent_view() + : m_active_torrent(0) + , m_scroll_position(0) + , m_torrent_filter(0) + , m_width(80) + , m_height(30) +{} + +void torrent_view::set_size(int width, int height) +{ + if (m_width == width && m_height == height) return; + + m_width = width; + m_height = height; + render(); +} + +int torrent_view::filter() const +{ + return m_torrent_filter; +} + +void torrent_view::set_filter(int filter) +{ + if (filter == m_torrent_filter) return; + m_torrent_filter = filter; + + update_filtered_torrents(); + render(); +} + +// returns the lt::torrent_status of the currently selected torrent. +lt::torrent_status const& torrent_view::get_active_torrent() const +{ + if (m_active_torrent >= int(m_filtered_handles.size())) + m_active_torrent = int(m_filtered_handles.size()) - 1; + if (m_active_torrent < 0) m_active_torrent = 0; + TORRENT_ASSERT(m_active_torrent >= 0); + + return *m_filtered_handles[m_active_torrent]; +} + +lt::torrent_handle torrent_view::get_active_handle() const +{ + if (m_active_torrent >= int(m_filtered_handles.size())) + m_active_torrent = int(m_filtered_handles.size()) - 1; + if (m_active_torrent < 0) m_active_torrent = 0; + TORRENT_ASSERT(m_active_torrent >= 0); + + if (m_filtered_handles.empty()) return lt::torrent_handle(); + + return m_filtered_handles[m_active_torrent]->handle; +} + +void torrent_view::update_torrents(std::vector const& st) +{ + std::set updates; + bool need_filter_update = false; + for (std::vector::const_iterator i = st.begin(); + i != st.end(); ++i) + { + boost::unordered_set::iterator j = m_all_handles.find(*i); + // add new entries here + if (j == m_all_handles.end()) + { + j = m_all_handles.insert(*i).first; + if (show_torrent(*j)) + { + m_filtered_handles.push_back(&*j); + need_filter_update = true; + } + } + else + { + bool prev_show = show_torrent(*j); + ((lt::torrent_status&)*j) = *i; + if (prev_show != show_torrent(*j)) + need_filter_update = true; + else + updates.insert(i->handle); + } + } + if (need_filter_update) + { + update_filtered_torrents(); + render(); + } + else + { + int torrent_index = 0; + for (std::vector::iterator i + = m_filtered_handles.begin(); + i != m_filtered_handles.end(); ++torrent_index) + { + if (torrent_index < m_scroll_position + || torrent_index >= m_scroll_position + m_height - header_size) + { + ++i; + continue; + } + + lt::torrent_status const& s = **i; + ++i; + + if (!s.handle.is_valid()) + continue; + + if (updates.count(s.handle) == 0) + continue; + + set_cursor_pos(0, header_size + torrent_index - m_scroll_position); + print_torrent(s, torrent_index == m_active_torrent); + } + } +} + +int torrent_view::height() const +{ + return m_height; +} + +void torrent_view::arrow_up() +{ + if (m_filtered_handles.empty()) return; + if (m_active_torrent <= 0) return; + + if (m_active_torrent - 1 < m_scroll_position) + { + --m_active_torrent; + m_scroll_position = m_active_torrent; + TORRENT_ASSERT(m_scroll_position >= 0); + render(); + return; + } + + set_cursor_pos(0, header_size + m_active_torrent - m_scroll_position); + print_torrent(*m_filtered_handles[m_active_torrent], false); + --m_active_torrent; + TORRENT_ASSERT(m_active_torrent >= 0); + + set_cursor_pos(0, header_size + m_active_torrent - m_scroll_position); + print_torrent(*m_filtered_handles[m_active_torrent], true); +} + +void torrent_view::arrow_down() +{ + if (m_filtered_handles.empty()) return; + if (m_active_torrent >= int(m_filtered_handles.size()) - 1) return; + + int bottom_pos = m_height - header_size - 1; + if (m_active_torrent - m_scroll_position + 1 > bottom_pos) + { + ++m_active_torrent; + m_scroll_position = m_active_torrent - bottom_pos; + TORRENT_ASSERT(m_scroll_position >= 0); + render(); + return; + } + + set_cursor_pos(0, header_size + m_active_torrent - m_scroll_position); + print_torrent(*m_filtered_handles[m_active_torrent], false); + + TORRENT_ASSERT(m_active_torrent >= 0); + ++m_active_torrent; + + set_cursor_pos(0, header_size + m_active_torrent - m_scroll_position); + print_torrent(*m_filtered_handles[m_active_torrent], true); +} + +void torrent_view::render() +{ + print_tabs(); + print_headers(); + + int lines_printed = header_size; + + int torrent_index = 0; + + for (std::vector::iterator i = m_filtered_handles.begin(); + i != m_filtered_handles.end(); ++torrent_index) + { + if (torrent_index < m_scroll_position) + { + ++i; + 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; + } + + clear_rows(torrent_index + header_size, m_height); +} + +void torrent_view::print_tabs() +{ + set_cursor_pos(0, 0); + + char str[400]; + int pos = 0; + char const* filter_names[] = { "all", "downloading", "non-paused" + , "seeding", "queued", "stopped", "checking", "loaded"}; + for (int i = 0; i < int(sizeof(filter_names)/sizeof(filter_names[0])); ++i) + { + pos += snprintf(str+ pos, sizeof(str) - pos, "%s[%s]%s" + , m_torrent_filter == i?esc("7"):"" + , filter_names[i], m_torrent_filter == i?esc("0"):""); + } + pos += snprintf(str + pos, sizeof(str) - pos, "\x1b[K"); + + if (m_width + 1 < int(sizeof(str))) + str[m_width + 1] = '\0'; + print(str); +} + +void torrent_view::print_headers() +{ + set_cursor_pos(0, 1); + + char str[400]; + + // print title bar for torrent list + snprintf(str, sizeof(str) + , " %-3s %-50s %-35s %-17s %-17s %-11s %-6s %-6s %-4s\x1b[K" + , "#", "Name", "Progress", "Download", "Upload", "Peers (D:S)" + , "Down", "Up", "Flags"); + + if (m_width + 1 < int(sizeof(str))) + str[m_width + 1] = '\0'; + + print(str); +} + +void torrent_view::print_torrent(lt::torrent_status const& s, bool selected) +{ + int pos = 0; + char str[512]; + + // 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 == -1) + snprintf(queue_pos, sizeof(queue_pos), "-"); + else + snprintf(queue_pos, sizeof(queue_pos), "%d", 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.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; + + pos += snprintf(str + pos, sizeof(str) - pos, "%s%c%-3s %-50s %s%s %s (%s) " + "%s (%s) %5d:%-5d %s %s %c" + , selection + , s.is_loaded ? 'L' : ' ' + , queue_pos + , name.c_str() + , progress_bar(s.progress_ppm / 1000, 35, progress_bar_color, '-', '#', torrent_state(s)).c_str() + , selection + , 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 this is the selected torrent, restore the background color + if (selected) + pos += snprintf(str + pos, sizeof(str) - pos, "%s", esc("0")); + + pos += snprintf(str + pos, sizeof(str) - pos, "\x1b[K"); + + print(str); +} + +bool torrent_view::show_torrent(lt::torrent_status const& st) +{ + switch (m_torrent_filter) + { + case torrents_all: return true; + case torrents_downloading: + return !st.paused + && st.state != lt::torrent_status::seeding + && st.state != lt::torrent_status::finished; + case torrents_not_paused: return !st.paused; + case torrents_seeding: + return !st.paused + && (st.state == lt::torrent_status::seeding + || st.state == lt::torrent_status::finished); + case torrents_queued: return st.paused && st.auto_managed; + case torrents_stopped: return st.paused && !st.auto_managed; + case torrents_checking: return st.state == lt::torrent_status::checking_files; + case torrents_loaded: return st.is_loaded; + } + 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_scroll_position = 0; + m_filtered_handles.clear(); + for (boost::unordered_set::iterator i = m_all_handles.begin() + , end(m_all_handles.end()); i != end; ++i) + { + if (!show_torrent(*i)) continue; + m_filtered_handles.push_back(&*i); + } + if (m_active_torrent >= int(m_filtered_handles.size())) m_active_torrent = 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); +} + diff --git a/examples/torrent_view.hpp b/examples/torrent_view.hpp new file mode 100644 index 0000000..0060b8c --- /dev/null +++ b/examples/torrent_view.hpp @@ -0,0 +1,78 @@ +#ifndef TORRENT_VIEW_HPP_ +#define TORRENT_VIEW_HPP_ + +#include +#include +#include + +#include "libtorrent/torrent_handle.hpp" + +namespace lt = libtorrent; + +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_loaded, + + 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 update_torrents(std::vector const& st); + + 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 + boost::unordered_set m_all_handles; + + // pointers into m_all_handles of the remaining torrents after filtering + std::vector m_filtered_handles; + + mutable int m_active_torrent; // index into m_filtered_handles + int m_scroll_position; + int m_torrent_filter; + int m_width; + int m_height; +}; + +#endif // TORRENT_VIEW_HPP_ + diff --git a/examples/upnp_test.cpp b/examples/upnp_test.cpp new file mode 100644 index 0000000..d7bfa90 --- /dev/null +++ b/examples/upnp_test.cpp @@ -0,0 +1,116 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include "libtorrent/session.hpp" +#include "libtorrent/alert_types.hpp" + +char const* timestamp() +{ + time_t t = std::time(0); + tm* timeinfo = std::localtime(&t); + static char str[200]; + std::strftime(str, 200, "%b %d %X", timeinfo); + return str; +} + +void print_alert(libtorrent::alert const* a) +{ + using namespace libtorrent; + + if (alert_cast(a)) + { + printf("%s","\x1b[32m"); + } + else if (alert_cast(a)) + { + printf("%s","\x1b[33m"); + } + + printf("[%s] %s\n", timestamp(), a->message().c_str()); + printf("%s", "\x1b[0m"); +} + +int main(int argc, char* argv[]) +{ + using namespace libtorrent; + namespace lt = libtorrent; + + if (argc != 1) + { + fputs("usage: ./upnp_test\n", stderr); + return 1; + } + + settings_pack p; + p.set_int(settings_pack::alert_mask, alert::port_mapping_notification); + lt::session s(p); + + for (;;) + { + alert const* a = s.wait_for_alert(seconds(5)); + if (a == 0) + { + settings_pack p; + 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); + } + } + + 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 == 0) break; + std::vector alerts; + s.pop_alerts(&alerts); + for (std::vector::iterator i = alerts.begin() + , end(alerts.end()); i != end; ++i) + { + print_alert(*i); + } + } + + + return 0; +} + diff --git a/include/libtorrent/ConvertUTF.h b/include/libtorrent/ConvertUTF.h new file mode 100644 index 0000000..f8b14dd --- /dev/null +++ b/include/libtorrent/ConvertUTF.h @@ -0,0 +1,165 @@ +/* + * Copyright 2001-2004 Unicode, Inc. + * + * Disclaimer + * + * This source code is provided as is by Unicode, Inc. No claims are + * made as to fitness for any particular purpose. No warranties of any + * kind are expressed or implied. The recipient agrees to determine + * applicability of information provided. If this file has been + * purchased on magnetic or optical media from Unicode, Inc., the + * sole remedy for any claim will be exchange of defective media + * within 90 days of receipt. + * + * Limitations on Rights to Redistribute This Code + * + * Unicode, Inc. hereby grants the right to freely use the information + * supplied in this file in the creation of products supporting the + * Unicode Standard, and to make copies of this file in any form + * for internal or external distribution as long as this notice + * remains attached. + */ + +/* --------------------------------------------------------------------- + + Conversions between UTF32, UTF-16, and UTF-8. Header file. + + Several funtions are included here, forming a complete set of + conversions between the three formats. UTF-7 is not included + here, but is handled in a separate source file. + + Each of these routines takes pointers to input buffers and output + buffers. The input buffers are const. + + Each routine converts the text between *sourceStart and sourceEnd, + putting the result into the buffer between *targetStart and + targetEnd. Note: the end pointers are *after* the last item: e.g. + *(sourceEnd - 1) is the last item. + + The return result indicates whether the conversion was successful, + and if not, whether the problem was in the source or target buffers. + (Only the first encountered problem is indicated.) + + After the conversion, *sourceStart and *targetStart are both + updated to point to the end of last text successfully converted in + the respective buffers. + + Input parameters: + sourceStart - pointer to a pointer to the source buffer. + The contents of this are modified on return so that + it points at the next thing to be converted. + targetStart - similarly, pointer to pointer to the target buffer. + sourceEnd, targetEnd - respectively pointers to the ends of the + two buffers, for overflow checking only. + + These conversion functions take a ConversionFlags argument. When this + flag is set to strict, both irregular sequences and isolated surrogates + will cause an error. When the flag is set to lenient, both irregular + sequences and isolated surrogates are converted. + + Whether the flag is strict or lenient, all illegal sequences will cause + an error return. This includes sequences such as: , , + or in UTF-8, and values above 0x10FFFF in UTF-32. Conformant code + must check for illegal sequences. + + When the flag is set to lenient, characters over 0x10FFFF are converted + to the replacement character; otherwise (when the flag is set to strict) + they constitute an error. + + Output parameters: + The value "sourceIllegal" is returned from some routines if the input + sequence is malformed. When "sourceIllegal" is returned, the source + value will point to the illegal value that caused the problem. E.g., + in UTF-8 when a sequence is malformed, it points to the start of the + malformed sequence. + + Author: Mark E. Davis, 1994. + Rev History: Rick McGowan, fixes & updates May 2001. + Fixes & updates, Sept 2001. + +------------------------------------------------------------------------ */ + +/* --------------------------------------------------------------------- + The following 4 definitions are compiler-specific. + The C standard does not guarantee that wchar_t has at least + 16 bits, so wchar_t is no less portable than unsigned short! + All should be unsigned values to avoid sign extension during + bit mask & shift operations. +------------------------------------------------------------------------ */ + +#ifdef __cplusplus +#include "libtorrent/config.hpp" +// these are standard C types, but they might +// not be available in c++ +#include +typedef boost::uint32_t UTF32; +typedef boost::uint16_t UTF16; +typedef boost::uint8_t UTF8; +extern "C" { +#else +#define TORRENT_EXTRA_EXPORT +#ifdef _MSC_VER +// msvc doesn't seem to have stdint.h +typedef unsigned __int32 UTF32; +typedef unsigned __int16 UTF16; +typedef unsigned __int8 UTF8; +#else +#include +typedef uint32_t UTF32; +typedef uint16_t UTF16; +typedef uint8_t UTF8; +#endif +#endif + +typedef unsigned char Boolean; /* 0 or 1 */ + +/* Some fundamental constants */ +#define UNI_REPLACEMENT_CHAR UTF32(0x0000FFFD) +#define UNI_MAX_BMP UTF32(0x0000FFFF) +#define UNI_MAX_UTF16 UTF32(0x0010FFFF) +#define UNI_MAX_UTF32 UTF32(0x7FFFFFFF) +#define UNI_MAX_LEGAL_UTF32 UTF32(0x0010FFFF) + +typedef enum { + conversionOK, /* conversion successful */ + sourceExhausted, /* partial character in source, but hit end */ + targetExhausted, /* insuff. room in target for conversion */ + sourceIllegal /* source sequence is illegal/malformed */ +} ConversionResult; + +typedef enum { + strictConversion = 0, + lenientConversion +} ConversionFlags; + +TORRENT_EXTRA_EXPORT ConversionResult ConvertUTF8toUTF16 ( + const UTF8** sourceStart, const UTF8* sourceEnd, + UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags); + +TORRENT_EXTRA_EXPORT ConversionResult ConvertUTF16toUTF8 ( + const UTF16** sourceStart, const UTF16* sourceEnd, + UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags); + +TORRENT_EXTRA_EXPORT ConversionResult ConvertUTF8toUTF32 ( + const UTF8** sourceStart, const UTF8* sourceEnd, + UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags); + +TORRENT_EXTRA_EXPORT ConversionResult ConvertUTF32toUTF8 ( + const UTF32** sourceStart, const UTF32* sourceEnd, + UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags); + +TORRENT_EXTRA_EXPORT ConversionResult ConvertUTF16toUTF32 ( + const UTF16** sourceStart, const UTF16* sourceEnd, + UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags); + +TORRENT_EXTRA_EXPORT ConversionResult ConvertUTF32toUTF16 ( + const UTF32** sourceStart, const UTF32* sourceEnd, + UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags); + +TORRENT_EXTRA_EXPORT Boolean isLegalUTF8Sequence(const UTF8 *source, + const UTF8 *sourceEnd); + +#ifdef __cplusplus +} +#endif +/* --------------------------------------------------------------------- */ diff --git a/include/libtorrent/Makefile.am b/include/libtorrent/Makefile.am new file mode 100644 index 0000000..5085418 --- /dev/null +++ b/include/libtorrent/Makefile.am @@ -0,0 +1,202 @@ +includedir = @includedir@/libtorrent + +nobase_include_HEADERS = \ + address.hpp \ + add_torrent_params.hpp \ + alert.hpp \ + alert_manager.hpp \ + alert_types.hpp \ + alloca.hpp \ + allocator.hpp \ + announce_entry.hpp \ + assert.hpp \ + bandwidth_limit.hpp \ + bandwidth_manager.hpp \ + bandwidth_socket.hpp \ + bandwidth_queue_entry.hpp \ + bencode.hpp \ + bdecode.hpp \ + bitfield.hpp \ + block_cache.hpp \ + bloom_filter.hpp \ + broadcast_socket.hpp \ + bt_peer_connection.hpp \ + buffer.hpp \ + build_config.hpp \ + chained_buffer.hpp \ + choker.hpp \ + close_reason.hpp \ + config.hpp \ + ConvertUTF.h \ + copy_ptr.hpp \ + crc32c.hpp \ + create_torrent.hpp \ + deadline_timer.hpp \ + debug.hpp \ + disk_buffer_holder.hpp \ + disk_buffer_pool.hpp \ + disk_interface.hpp \ + disk_io_job.hpp \ + disk_io_thread.hpp \ + disk_observer.hpp \ + disk_job_pool.hpp \ + ed25519.hpp \ + entry.hpp \ + enum_net.hpp \ + error.hpp \ + error_code.hpp \ + export.hpp \ + extensions.hpp \ + file.hpp \ + file_pool.hpp \ + file_storage.hpp \ + fingerprint.hpp \ + gzip.hpp \ + hasher.hpp \ + hex.hpp \ + heterogeneous_queue.hpp \ + http_connection.hpp \ + http_parser.hpp \ + http_seed_connection.hpp \ + http_stream.hpp \ + http_tracker_connection.hpp \ + i2p_stream.hpp \ + identify_client.hpp \ + instantiate_connection.hpp \ + invariant_check.hpp \ + io.hpp \ + io_service.hpp \ + io_service_fwd.hpp \ + ip_filter.hpp \ + ip_voter.hpp \ + lazy_entry.hpp \ + link.hpp \ + linked_list.hpp \ + lsd.hpp \ + magnet_uri.hpp \ + max.hpp \ + natpmp.hpp \ + network_thread_pool.hpp \ + operations.hpp \ + packet_buffer.hpp \ + parse_url.hpp \ + part_file.hpp \ + pe_crypto.hpp \ + performance_counters.hpp \ + peer_connection.hpp \ + peer_connection_handle.hpp \ + peer_connection_interface.hpp \ + peer.hpp \ + peer_class.hpp \ + peer_class_set.hpp \ + peer_class_type_filter.hpp \ + peer_id.hpp \ + peer_info.hpp \ + peer_request.hpp \ + piece_block_progress.hpp \ + piece_picker.hpp \ + platform_util.hpp \ + peer_list.hpp \ + proxy_base.hpp \ + puff.hpp \ + random.hpp \ + receive_buffer.hpp \ + resolve_links.hpp \ + resolver.hpp \ + resolver_interface.hpp \ + request_blocks.hpp \ + rss.hpp \ + session.hpp \ + session_handle.hpp \ + session_settings.hpp \ + session_stats.hpp \ + session_status.hpp \ + settings_pack.hpp \ + sha1.hpp \ + sha1_hash.hpp \ + sliding_average.hpp \ + socket.hpp \ + socket_io.hpp \ + socket_type.hpp \ + socket_type_fwd.hpp \ + socks5_stream.hpp \ + ssl_stream.hpp \ + stack_allocator.hpp \ + stat.hpp \ + stat_cache.hpp \ + storage.hpp \ + storage_defs.hpp \ + tailqueue.hpp \ + string_util.hpp \ + thread.hpp \ + thread_pool.hpp \ + time.hpp \ + timestamp_history.hpp \ + torrent_handle.hpp \ + torrent.hpp \ + torrent_info.hpp \ + torrent_peer.hpp \ + torrent_peer_allocator.hpp \ + tracker_manager.hpp \ + torrent_status.hpp \ + udp_socket.hpp \ + udp_tracker_connection.hpp \ + uncork_interface.hpp \ + union_endpoint.hpp \ + upnp.hpp \ + utp_socket_manager.hpp \ + utp_stream.hpp \ + utf8.hpp \ + vector_utils.hpp \ + version.hpp \ + web_connection_base.hpp \ + web_peer_connection.hpp \ + xml_parse.hpp \ + \ + tommath.h \ + tommath_class.h \ + tommath_superclass.h \ + tommath_private.h \ + \ + aux_/alert_manager_variadic_emplace.hpp \ + aux_/allocating_handler.hpp \ + aux_/cpuid.hpp \ + aux_/disable_warnings_push.hpp \ + aux_/disable_warnings_pop.hpp \ + aux_/escape_string.hpp \ + aux_/merkle.hpp \ + aux_/session_call.hpp \ + aux_/session_impl.hpp \ + aux_/session_settings.hpp \ + aux_/proxy_settings.hpp \ + aux_/session_interface.hpp \ + aux_/time.hpp \ + aux_/file_progress.hpp \ + aux_/openssl.hpp \ + aux_/byteswap.hpp \ + \ + extensions/lt_trackers.hpp \ + extensions/metadata_transfer.hpp \ + extensions/smart_ban.hpp \ + extensions/ut_metadata.hpp \ + extensions/ut_pex.hpp \ + \ + kademlia/dht_storage.hpp \ + kademlia/dht_tracker.hpp \ + kademlia/dht_observer.hpp \ + kademlia/direct_request.hpp \ + kademlia/dos_blocker.hpp \ + kademlia/find_data.hpp \ + kademlia/put_data.hpp \ + kademlia/msg.hpp \ + kademlia/node.hpp \ + kademlia/node_entry.hpp \ + kademlia/node_id.hpp \ + kademlia/observer.hpp \ + kademlia/refresh.hpp \ + kademlia/routing_table.hpp \ + kademlia/rpc_manager.hpp \ + kademlia/traversal_algorithm.hpp \ + kademlia/item.hpp \ + kademlia/get_item.hpp \ + kademlia/get_peers.hpp diff --git a/include/libtorrent/Makefile.in b/include/libtorrent/Makefile.in new file mode 100644 index 0000000..15af03a --- /dev/null +++ b/include/libtorrent/Makefile.in @@ -0,0 +1,812 @@ +# Makefile.in generated by automake 1.15 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2014 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +target_triplet = @target@ +subdir = include/libtorrent +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_boost_base.m4 \ + $(top_srcdir)/m4/ax_boost_chrono.m4 \ + $(top_srcdir)/m4/ax_boost_python.m4 \ + $(top_srcdir)/m4/ax_boost_random.m4 \ + $(top_srcdir)/m4/ax_boost_system.m4 \ + $(top_srcdir)/m4/ax_check_openssl.m4 \ + $(top_srcdir)/m4/ax_pthread.m4 \ + $(top_srcdir)/m4/ax_python_devel.m4 \ + $(top_srcdir)/m4/gettext-lib.m4 $(top_srcdir)/m4/iconv.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/pkgconfig.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(nobase_include_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +SOURCES = +DIST_SOURCES = +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(includedir)" +HEADERS = $(nobase_include_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BOOST_CHRONO_LIB = @BOOST_CHRONO_LIB@ +BOOST_CPPFLAGS = @BOOST_CPPFLAGS@ +BOOST_LDFLAGS = @BOOST_LDFLAGS@ +BOOST_PYTHON_LIB = @BOOST_PYTHON_LIB@ +BOOST_RANDOM_LIB = @BOOST_RANDOM_LIB@ +BOOST_SYSTEM_LIB = @BOOST_SYSTEM_LIB@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +COMPILETIME_OPTIONS = @COMPILETIME_OPTIONS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEBUGFLAGS = @DEBUGFLAGS@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GREP = @GREP@ +ICONV_LIBS = @ICONV_LIBS@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INTERFACE_VERSION_INFO = @INTERFACE_VERSION_INFO@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBICONV = @LIBICONV@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OPENSSL_INCLUDES = @OPENSSL_INCLUDES@ +OPENSSL_LDFLAGS = @OPENSSL_LDFLAGS@ +OPENSSL_LIBS = @OPENSSL_LIBS@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PTHREAD_CC = @PTHREAD_CC@ +PTHREAD_CFLAGS = @PTHREAD_CFLAGS@ +PTHREAD_LIBS = @PTHREAD_LIBS@ +PYTHON = @PYTHON@ +PYTHON_CPPFLAGS = @PYTHON_CPPFLAGS@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_EXTRA_LDFLAGS = @PYTHON_EXTRA_LDFLAGS@ +PYTHON_EXTRA_LIBS = @PYTHON_EXTRA_LIBS@ +PYTHON_INSTALL_PARAMS = @PYTHON_INSTALL_PARAMS@ +PYTHON_LIBS = @PYTHON_LIBS@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_SITE_PKG = @PYTHON_SITE_PKG@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +ax_pthread_config = @ax_pthread_config@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@/libtorrent +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target = @target@ +target_alias = @target_alias@ +target_cpu = @target_cpu@ +target_os = @target_os@ +target_vendor = @target_vendor@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +nobase_include_HEADERS = \ + address.hpp \ + add_torrent_params.hpp \ + alert.hpp \ + alert_manager.hpp \ + alert_types.hpp \ + alloca.hpp \ + allocator.hpp \ + announce_entry.hpp \ + assert.hpp \ + bandwidth_limit.hpp \ + bandwidth_manager.hpp \ + bandwidth_socket.hpp \ + bandwidth_queue_entry.hpp \ + bencode.hpp \ + bdecode.hpp \ + bitfield.hpp \ + block_cache.hpp \ + bloom_filter.hpp \ + broadcast_socket.hpp \ + bt_peer_connection.hpp \ + buffer.hpp \ + build_config.hpp \ + chained_buffer.hpp \ + choker.hpp \ + close_reason.hpp \ + config.hpp \ + ConvertUTF.h \ + copy_ptr.hpp \ + crc32c.hpp \ + create_torrent.hpp \ + deadline_timer.hpp \ + debug.hpp \ + disk_buffer_holder.hpp \ + disk_buffer_pool.hpp \ + disk_interface.hpp \ + disk_io_job.hpp \ + disk_io_thread.hpp \ + disk_observer.hpp \ + disk_job_pool.hpp \ + ed25519.hpp \ + entry.hpp \ + enum_net.hpp \ + error.hpp \ + error_code.hpp \ + export.hpp \ + extensions.hpp \ + file.hpp \ + file_pool.hpp \ + file_storage.hpp \ + fingerprint.hpp \ + gzip.hpp \ + hasher.hpp \ + hex.hpp \ + heterogeneous_queue.hpp \ + http_connection.hpp \ + http_parser.hpp \ + http_seed_connection.hpp \ + http_stream.hpp \ + http_tracker_connection.hpp \ + i2p_stream.hpp \ + identify_client.hpp \ + instantiate_connection.hpp \ + invariant_check.hpp \ + io.hpp \ + io_service.hpp \ + io_service_fwd.hpp \ + ip_filter.hpp \ + ip_voter.hpp \ + lazy_entry.hpp \ + link.hpp \ + linked_list.hpp \ + lsd.hpp \ + magnet_uri.hpp \ + max.hpp \ + natpmp.hpp \ + network_thread_pool.hpp \ + operations.hpp \ + packet_buffer.hpp \ + parse_url.hpp \ + part_file.hpp \ + pe_crypto.hpp \ + performance_counters.hpp \ + peer_connection.hpp \ + peer_connection_handle.hpp \ + peer_connection_interface.hpp \ + peer.hpp \ + peer_class.hpp \ + peer_class_set.hpp \ + peer_class_type_filter.hpp \ + peer_id.hpp \ + peer_info.hpp \ + peer_request.hpp \ + piece_block_progress.hpp \ + piece_picker.hpp \ + platform_util.hpp \ + peer_list.hpp \ + proxy_base.hpp \ + puff.hpp \ + random.hpp \ + receive_buffer.hpp \ + resolve_links.hpp \ + resolver.hpp \ + resolver_interface.hpp \ + request_blocks.hpp \ + rss.hpp \ + session.hpp \ + session_handle.hpp \ + session_settings.hpp \ + session_stats.hpp \ + session_status.hpp \ + settings_pack.hpp \ + sha1.hpp \ + sha1_hash.hpp \ + sliding_average.hpp \ + socket.hpp \ + socket_io.hpp \ + socket_type.hpp \ + socket_type_fwd.hpp \ + socks5_stream.hpp \ + ssl_stream.hpp \ + stack_allocator.hpp \ + stat.hpp \ + stat_cache.hpp \ + storage.hpp \ + storage_defs.hpp \ + tailqueue.hpp \ + string_util.hpp \ + thread.hpp \ + thread_pool.hpp \ + time.hpp \ + timestamp_history.hpp \ + torrent_handle.hpp \ + torrent.hpp \ + torrent_info.hpp \ + torrent_peer.hpp \ + torrent_peer_allocator.hpp \ + tracker_manager.hpp \ + torrent_status.hpp \ + udp_socket.hpp \ + udp_tracker_connection.hpp \ + uncork_interface.hpp \ + union_endpoint.hpp \ + upnp.hpp \ + utp_socket_manager.hpp \ + utp_stream.hpp \ + utf8.hpp \ + vector_utils.hpp \ + version.hpp \ + web_connection_base.hpp \ + web_peer_connection.hpp \ + xml_parse.hpp \ + \ + tommath.h \ + tommath_class.h \ + tommath_superclass.h \ + tommath_private.h \ + \ + aux_/alert_manager_variadic_emplace.hpp \ + aux_/allocating_handler.hpp \ + aux_/cpuid.hpp \ + aux_/disable_warnings_push.hpp \ + aux_/disable_warnings_pop.hpp \ + aux_/escape_string.hpp \ + aux_/merkle.hpp \ + aux_/session_call.hpp \ + aux_/session_impl.hpp \ + aux_/session_settings.hpp \ + aux_/proxy_settings.hpp \ + aux_/session_interface.hpp \ + aux_/time.hpp \ + aux_/file_progress.hpp \ + aux_/openssl.hpp \ + aux_/byteswap.hpp \ + \ + extensions/lt_trackers.hpp \ + extensions/metadata_transfer.hpp \ + extensions/smart_ban.hpp \ + extensions/ut_metadata.hpp \ + extensions/ut_pex.hpp \ + \ + kademlia/dht_storage.hpp \ + kademlia/dht_tracker.hpp \ + kademlia/dht_observer.hpp \ + kademlia/direct_request.hpp \ + kademlia/dos_blocker.hpp \ + kademlia/find_data.hpp \ + kademlia/put_data.hpp \ + kademlia/msg.hpp \ + kademlia/node.hpp \ + kademlia/node_entry.hpp \ + kademlia/node_id.hpp \ + kademlia/observer.hpp \ + kademlia/refresh.hpp \ + kademlia/routing_table.hpp \ + kademlia/rpc_manager.hpp \ + kademlia/traversal_algorithm.hpp \ + kademlia/item.hpp \ + kademlia/get_item.hpp \ + kademlia/get_peers.hpp + +all: all-am + +.SUFFIXES: +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign include/libtorrent/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign include/libtorrent/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +install-nobase_includeHEADERS: $(nobase_include_HEADERS) + @$(NORMAL_INSTALL) + @list='$(nobase_include_HEADERS)'; test -n "$(includedir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(includedir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(includedir)" || exit 1; \ + fi; \ + $(am__nobase_list) | while read dir files; do \ + xfiles=; for file in $$files; do \ + if test -f "$$file"; then xfiles="$$xfiles $$file"; \ + else xfiles="$$xfiles $(srcdir)/$$file"; fi; done; \ + test -z "$$xfiles" || { \ + test "x$$dir" = x. || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(includedir)/$$dir'"; \ + $(MKDIR_P) "$(DESTDIR)$(includedir)/$$dir"; }; \ + echo " $(INSTALL_HEADER) $$xfiles '$(DESTDIR)$(includedir)/$$dir'"; \ + $(INSTALL_HEADER) $$xfiles "$(DESTDIR)$(includedir)/$$dir" || exit $$?; }; \ + done + +uninstall-nobase_includeHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(nobase_include_HEADERS)'; test -n "$(includedir)" || list=; \ + $(am__nobase_strip_setup); files=`$(am__nobase_strip)`; \ + dir='$(DESTDIR)$(includedir)'; $(am__uninstall_files_from_dir) + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(includedir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool mostlyclean-am + +distclean: distclean-am + -rm -f Makefile +distclean-am: clean-am distclean-generic distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-nobase_includeHEADERS + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-generic mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-nobase_includeHEADERS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am check check-am clean clean-generic \ + clean-libtool cscopelist-am ctags ctags-am distclean \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man \ + install-nobase_includeHEADERS install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-generic \ + mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \ + uninstall-am uninstall-nobase_includeHEADERS + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/include/libtorrent/add_torrent_params.hpp b/include/libtorrent/add_torrent_params.hpp new file mode 100644 index 0000000..a52ad0e --- /dev/null +++ b/include/libtorrent/add_torrent_params.hpp @@ -0,0 +1,423 @@ +/* + +Copyright (c) 2009-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_ADD_TORRENT_PARAMS_HPP_INCLUDED +#define TORRENT_ADD_TORRENT_PARAMS_HPP_INCLUDED + +#include +#include +#include + +#include "libtorrent/storage_defs.hpp" +#include "libtorrent/peer_id.hpp" // sha1_hash +#include "libtorrent/version.hpp" + +namespace libtorrent +{ + class torrent_info; + struct torrent_plugin; + struct torrent_handle; + + // 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 a .torrent file + // * url - when you have a magnet link + // * info_hash - when all you have is an info-hash (this is similar to a + // magnet link) + // + // one of those fields need to 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``. + // + struct TORRENT_EXPORT add_torrent_params + { + // The constructor can be used to initialize the storage constructor, + // which determines the storage mechanism for the downloaded or seeding + // data for the torrent. For more information, see the ``storage`` field. + add_torrent_params(storage_constructor_type sc = default_storage_constructor) + : version(LIBTORRENT_VERSION_NUM) +#ifndef TORRENT_NO_DEPRECATE + , tracker_url(0) +#endif + , storage_mode(storage_mode_sparse) + , storage(sc) + , userdata(0) +#ifndef TORRENT_NO_DEPRECATE + , flags(flag_ignore_flags | default_flags) +#else + , flags(default_flags) +#endif + , max_uploads(-1) + , max_connections(-1) + , upload_limit(-1) + , download_limit(-1) +#ifndef TORRENT_NO_DEPRECATE + , seed_mode(false) + , override_resume_data(false) + , upload_mode(false) + , share_mode(false) + , apply_ip_filter(true) + , paused(true) + , auto_managed(true) + , duplicate_is_error(false) + , merge_resume_trackers(false) +#endif + { + } + +#ifndef TORRENT_NO_DEPRECATE + void update_flags() const + { + if (flags != (flag_ignore_flags | default_flags)) return; + + boost::uint64_t& f = const_cast(flags); + f = flag_update_subscribe; + if (seed_mode) f |= flag_seed_mode; + if (override_resume_data) f |= flag_override_resume_data; + if (upload_mode) f |= flag_upload_mode; + if (share_mode) f |= flag_share_mode; + if (apply_ip_filter) f |= flag_apply_ip_filter; + if (paused) f |= flag_paused; + if (auto_managed) f |= flag_auto_managed; + if (duplicate_is_error) f |= flag_duplicate_is_error; + if (merge_resume_trackers) f |= flag_merge_resume_trackers; + } +#endif + + // values for the ``flags`` field + enum flags_t + { + // If ``flag_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 ``flag_seed_mode`` on a torrent without metadata (a + // .torrent file) is a no-op and will be ignored. + // + // If resume data is passed in with this torrent, the seed mode saved + // in there will override the seed mode you set here. + flag_seed_mode = 0x001, + + // If ``flag_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). + // If this flag is set, but file_priorities is empty, file priorities + // are still loaded from the resume data, if present. + flag_override_resume_data = 0x002, + + // If ``flag_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. 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 (``flag_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. + flag_upload_mode = 0x004, + + // 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. + flag_share_mode = 0x008, + + // 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. + flag_apply_ip_filter = 0x010, + + // specifies whether or not the torrent is to be started in a paused + // state. I.e. it won't connect to the tracker or any of the peers + // until it's resumed. This is typically a good way of avoiding race + // conditions when setting configuration options on torrents before + // starting them. + flag_paused = 0x020, + + // If the torrent is auto-managed (``flag_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 ``flag_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_. + // + // If you pass in resume data, the auto_managed state of the torrent + // when the resume data was saved will override the auto_managed state + // you pass in here. You can override this by setting + // ``override_resume_data``. + flag_auto_managed = 0x040, + flag_duplicate_is_error = 0x080, + + // defaults to off 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. + flag_merge_resume_trackers = 0x100, + + // on by default and means that this torrent will be part of state + // updates when calling post_torrent_updates(). + flag_update_subscribe = 0x200, + + // sets the torrent into super seeding mode. If the torrent is not a + // seed, this flag has no effect. It has the same effect as calling + // ``torrent_handle::super_seeding(true)`` on the torrent handle + // immediately after adding it. + flag_super_seeding = 0x400, + + // sets the sequential download state for the torrent. It has the same + // effect as calling ``torrent_handle::sequential_download(true)`` on + // the torrent handle immediately after adding it. + flag_sequential_download = 0x800, + + // 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. + flag_use_resume_save_path = 0x1000, + + // 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. + flag_pinned = 0x2000, + + // defaults to off 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. + flag_merge_resume_http_seeds = 0x8000, + + // the stop when ready flag. Setting this flag is equivalent to calling + // torrent_handle::stop_when_ready() immediately after the torrent is + // added. + flag_stop_when_ready = 0x4000, + + // internal + default_flags = flag_pinned | flag_update_subscribe | flag_auto_managed | flag_paused | flag_apply_ip_filter + +#ifndef TORRENT_NO_DEPRECATE + , flag_ignore_flags = 0x80000000 +#endif + }; + + // filled in by the constructor and should be left untouched. It is used + // for forward binary compatibility. + int version; + // torrent_info object with the torrent to add. Unless the url or + // info_hash is set, this is required to be initialized. + boost::shared_ptr ti; + +#ifndef TORRENT_NO_DEPRECATE + char const* tracker_url; +#endif + // 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. + std::vector trackers; + + // url seeds to be added to the torrent (`BEP 17`_). + std::vector url_seeds; + + // 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. + std::vector > dht_nodes; + std::string name; + + // the path where the torrent is or will be stored. Note that this may + // also be stored in resume data. If you want the save path saved in + // the resume data to be used, you need to set the + // flag_use_resume_save_path flag. + // + // .. 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 "..". + std::string save_path; + + // 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()``. + std::vector resume_data; + + // One of the values from storage_mode_t. For more information, see + // storage-allocation_. + storage_mode_t storage_mode; + + // can be used to customize how the data is stored. The default storage + // will simply write the data to the files it belongs to, but it could be + // overridden to save everything to a single file at a specific location + // or encrypt the content on disk for instance. For more information + // about the storage_interface that needs to be implemented for a custom + // storage, see storage_interface. + storage_constructor_type storage; + + // The ``userdata`` parameter is optional and will be passed on to the + // extension constructor functions, if any + // (see torrent_handle::add_extension()). + void* userdata; + + // can be set to control the initial file priorities when adding a + // torrent. The semantics are the same as for + // ``torrent_handle::prioritize_files()``. + std::vector 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. + std::vector(torrent_handle const&, void*)> > + extensions; + + // the default tracker id to be used when announcing to trackers. By + // default this is empty, and no tracker ID is used, since this is an + // optional argument. If a tracker returns a tracker ID, that ID is used + // instead of this. + std::string trackerid; + + // If you specify a ``url``, the torrent will be set in + // ``downloading_metadata`` state until the .torrent file has been + // downloaded. If there's any error while downloading, the torrent will + // be stopped and the torrent error state (``torrent_status::error``) + // will indicate what went wrong. The ``url`` may be set to a magnet link. + std::string url; + + // if ``uuid`` is specified, it is used to find duplicates. If another + // torrent is already running with the same UUID as the one being added, + // it will be considered a duplicate. This is mainly useful for RSS feed + // items which has UUIDs specified. + std::string uuid; + + // should point to the URL of the RSS feed this torrent comes from, if it + // comes from an RSS feed. + std::string source_feed_url; + + // flags controlling aspects of this torrent and how it's added. See + // 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. + boost::uint64_t flags; + + // set this to the info hash of the torrent to add in case the info-hash + // is the only known property of the torrent. i.e. you don't have a + // .torrent file nor a magnet link. + sha1_hash info_hash; + + // ``max_uploads``, ``max_connections``, ``upload_limit``, + // ``download_limit`` correspond to the ``set_max_uploads()``, + // ``set_max_connections()``, ``set_upload_limit()`` and + // ``set_download_limit()`` functions on torrent_handle. These values let + // you initialize these settings when the torrent is added, instead of + // calling these functions immediately following adding it. + // + // -1 means unlimited on these settings just like their counterpart + // functions on torrent_handle + int max_uploads; + int max_connections; + int upload_limit; + int download_limit; + +#ifndef TORRENT_NO_DEPRECATE + bool seed_mode; + bool override_resume_data; + bool upload_mode; + bool share_mode; + bool apply_ip_filter; + bool paused; + bool auto_managed; + bool duplicate_is_error; + bool merge_resume_trackers; +#endif + + }; +} + +#endif + diff --git a/include/libtorrent/address.hpp b/include/libtorrent/address.hpp new file mode 100644 index 0000000..0682991 --- /dev/null +++ b/include/libtorrent/address.hpp @@ -0,0 +1,80 @@ +/* + +Copyright (c) 2009-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_ADDRESS_HPP_INCLUDED +#define TORRENT_ADDRESS_HPP_INCLUDED + +#include +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#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 + +#if defined TORRENT_BUILD_SIMULATOR +#include "simulator/simulator.hpp" +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#ifdef __OBJC__ +#undef Protocol +#endif + +namespace libtorrent +{ +#if defined TORRENT_BUILD_SIMULATOR + typedef sim::asio::ip::address address; + typedef sim::asio::ip::address_v4 address_v4; +#if TORRENT_USE_IPV6 + typedef sim::asio::ip::address_v6 address_v6; +#endif +#else + typedef boost::asio::ip::address address; + typedef boost::asio::ip::address_v4 address_v4; +#if TORRENT_USE_IPV6 + typedef boost::asio::ip::address_v6 address_v6; +#endif +#endif // SIMULATOR +} + +#endif + diff --git a/include/libtorrent/alert.hpp b/include/libtorrent/alert.hpp new file mode 100644 index 0000000..06b48ee --- /dev/null +++ b/include/libtorrent/alert.hpp @@ -0,0 +1,324 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg, Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ALERT_HPP_INCLUDED +#define TORRENT_ALERT_HPP_INCLUDED + +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +// 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 +// comprised by bits from the category_t enum. +// +// 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" + +namespace libtorrent { + + // The ``alert`` class is the base class that specific messages are derived from. + // alert types are not copyable, and cannot be constructed by the client. The + // pointers returned by libtorrent are short lived (the details are described + // under session_handle::pop_alerts()) + class TORRENT_EXPORT alert + { + public: + +#ifndef TORRENT_NO_DEPRECATE + // only here for backwards compatibility + enum severity_t { debug, info, warning, critical, fatal, none }; +#endif + + // these are bits for the alert_mask used by the session. See + // settings_pack::alert_mask. + enum category_t + { + // 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 + error_notification = 0x1, + + // Enables alerts when peers send invalid requests, get banned or + // snubbed. + peer_notification = 0x2, + + // Enables alerts for port mapping events. For NAT-PMP and UPnP. + port_mapping_notification = 0x4, + + // Enables alerts for events related to the storage. File errors and + // synchronization events for moving the storage, renaming files etc. + storage_notification = 0x8, + + // Enables all tracker events. Includes announcing to trackers, + // receiving responses, warnings and errors. + tracker_notification = 0x10, + + // Low level alerts for when peers are connected and disconnected. + debug_notification = 0x20, + + // Enables alerts for when a torrent or the session changes state. + status_notification = 0x40, + + // Alerts for when blocks are requested and completed. Also when + // pieces are completed. + progress_notification = 0x80, + + // Alerts when a peer is blocked by the ip blocker or port blocker. + ip_block_notification = 0x100, + + // Alerts when some limit is reached that might limit the download + // or upload rate. + performance_warning = 0x200, + + // Alerts on events in the DHT node. For incoming searches or + // bootstrapping being done etc. + dht_notification = 0x400, + + // 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. + stats_notification = 0x800, + +#ifndef TORRENT_NO_DEPRECATE + // Alerts on RSS related events, like feeds being updated, feed error + // conditions and successful RSS feed updates. Enabling this categoty + // will make you receive rss_alert alerts. + rss_notification = 0x1000, +#endif + + // 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. + session_log_notification = 0x2000, + + // 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. + torrent_log_notification = 0x4000, + + // 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. + peer_log_notification = 0x8000, + + // enables the incoming_request_alert. + incoming_request_notification = 0x10000, + + // enables dht_log_alert, debug logging for the DHT + dht_log_notification = 0x20000, + + // enable events from pure dht operations not related to torrents + dht_operation_notification = 0x40000, + + // enables port mapping log events. This log is useful + // for debugging the UPnP or NAT-PMP implementation + port_mapping_log_notification = 0x80000, + + // enables verbose logging from the piece picker. + picker_log_notification = 0x100000, + + // 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. + all_categories = 0x7fffffff + }; + + // hidden + alert(); + // hidden + virtual ~alert(); + + // a timestamp is automatically created in the constructor + time_point timestamp() const; + + // returns an integer that is unique to this alert type. It can be + // compared against a specific alert by querying a static constant called ``alert_type`` + // in the alert. It can be used to determine the run-time type of an alert* in + // order to cast to that alert type and access specific members. + // + // e.g: + // + // .. code:: c++ + // + // std::vector alerts; + // ses.pop_alerts(&alerts); + // for (alert* i : alerts) { + // switch (a->type()) { + // + // case read_piece_alert::alert_type: + // { + // read_piece_alert* p = (read_piece_alert*)a; + // if (p->ec) { + // // read_piece failed + // break; + // } + // // use p + // break; + // } + // case file_renamed_alert::alert_type: + // { + // // etc... + // } + // } + // } + virtual int type() const = 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 = 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 int category() const = 0; + +#ifndef TORRENT_NO_DEPRECATE + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + + // determines whether or not an alert is allowed to be discarded + // when the alert queue is full. There are a few alerts which may not be discared, + // since they would break the user contract, such as save_resume_data_alert. + TORRENT_DEPRECATED + bool discardable() const { return discardable_impl(); } + + TORRENT_DEPRECATED + severity_t severity() const { return warning; } + + // returns a pointer to a copy of the alert. + TORRENT_DEPRECATED + std::auto_ptr clone() const { return clone_impl(); } + + protected: + + virtual bool discardable_impl() const { return true; } + + virtual std::auto_ptr clone_impl() const = 0; + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +#endif // TORRENT_NO_DEPRECATE + + protected: + // the alert is not copyable (but for backwards compatibility reasons it + // retains the ability to clone itself, for now). +#if __cplusplus >= 201103L + alert(alert const& rhs) = default; +#endif + + private: + // explicitly disallow assignment and copyconstruction + alert& operator=(alert const&); + + time_point 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) +{ + if (a == 0) return 0; + if (a->type() == T::alert_type) return static_cast(a); + return 0; +} +template T const* alert_cast(alert const* a) +{ + if (a == 0) return 0; + if (a->type() == T::alert_type) return static_cast(a); + return 0; +} + +} // namespace libtorrent + +#endif // TORRENT_ALERT_HPP_INCLUDED + diff --git a/include/libtorrent/alert_manager.hpp b/include/libtorrent/alert_manager.hpp new file mode 100644 index 0000000..41e883a --- /dev/null +++ b/include/libtorrent/alert_manager.hpp @@ -0,0 +1,214 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg, Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ALERT_MANAGER_HPP_INCLUDED +#define TORRENT_ALERT_MANAGER_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/thread.hpp" +#include "libtorrent/heterogeneous_queue.hpp" +#include "libtorrent/stack_allocator.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#ifndef TORRENT_NO_DEPRECATE +#include +#endif +#include +#include +#include +#include +#include // for std::forward + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#ifdef __GNUC__ +// this is to suppress the warnings for using std::auto_ptr +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +// used for emplace_alert() variadic template emulation for c++98 +#define TORRENT_ALERT_MANAGER_MAX_ARITY 7 + +namespace libtorrent { + +#ifndef TORRENT_DISABLE_EXTENSIONS + struct plugin; +#endif + + class TORRENT_EXTRA_EXPORT alert_manager + { + public: + alert_manager(int queue_limit + , boost::uint32_t alert_mask = alert::error_notification); + ~alert_manager(); + +#ifndef BOOST_NO_CXX11_VARIADIC_TEMPLATES + + template + void emplace_alert(Args&&... args) + { + mutex::scoped_lock lock(m_mutex); +#ifndef TORRENT_NO_DEPRECATE + if (m_dispatch) + { + m_dispatch(std::auto_ptr(new T(m_allocations[m_generation] + , std::forward(args)...))); + return; + } +#endif + // don't add more than this number of alerts, unless it's a + // high priority alert, in which case we try harder to deliver it + // for high priority alerts, double the upper limit + if (m_alerts[m_generation].size() >= m_queue_size_limit + * (1 + T::priority)) + return; + + T alert(m_allocations[m_generation], std::forward(args)...); + m_alerts[m_generation].push_back(alert); + + maybe_notify(&alert, lock); + } + +#else + +// emulate variadic templates for c++98 + +#include "libtorrent/aux_/alert_manager_variadic_emplace.hpp" + +#endif + + bool pending() const; + void get_all(std::vector& alerts, int& num_resume); + + template + bool should_post() const + { + mutex::scoped_lock lock(m_mutex); + if (m_alerts[m_generation].size() >= m_queue_size_limit + * (1 + T::priority)) + { + return false; + } + return (m_alert_mask & T::static_category) != 0; + } + + alert* wait_for_alert(time_duration max_wait); + + void set_alert_mask(boost::uint32_t m) + { + mutex::scoped_lock lock(m_mutex); + m_alert_mask = m; + } + + boost::uint32_t alert_mask() const + { + mutex::scoped_lock lock(m_mutex); + return m_alert_mask; + } + + int alert_queue_size_limit() const { return m_queue_size_limit; } + int set_alert_queue_size_limit(int queue_size_limit_); + + void set_notify_function(boost::function const& fun); + +#ifndef TORRENT_NO_DEPRECATE + void set_dispatch_function(boost::function)> const&); +#endif + + int num_queued_resume() const; + +#ifndef TORRENT_DISABLE_EXTENSIONS + void add_extension(boost::shared_ptr ext); +#endif + + private: + + // non-copyable + alert_manager(alert_manager const&); + alert_manager& operator=(alert_manager const&); + + void maybe_notify(alert* a, mutex::scoped_lock& lock); + + mutable mutex m_mutex; + condition_variable m_condition; + boost::uint32_t m_alert_mask; + int m_queue_size_limit; + +#ifndef TORRENT_NO_DEPRECATE + bool maybe_dispatch(alert const& a); + boost::function)> m_dispatch; +#endif + + // 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 + boost::function m_notify; + + // the number of resume data alerts in the alert queue + int m_num_queued_resume; + + // 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; + + // this is where all alerts are queued up. There are two heterogenous + // queues to double buffer the thread access. The 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. + heterogeneous_queue m_alerts[2]; + + // this is a stack where alerts can allocate variable length content, + // such as strings, to go with the alerts. + aux::stack_allocator m_allocations[2]; + +#ifndef TORRENT_DISABLE_EXTENSIONS + typedef std::list > ses_extension_list_t; + ses_extension_list_t m_ses_extensions; +#endif + }; +} + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +#endif + diff --git a/include/libtorrent/alert_types.hpp b/include/libtorrent/alert_types.hpp new file mode 100644 index 0000000..06e571b --- /dev/null +++ b/include/libtorrent/alert_types.hpp @@ -0,0 +1,2515 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_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" + +#ifndef TORRENT_NO_DEPRECATE +#include "libtorrent/rss.hpp" // for feed_handle +#endif +#include "libtorrent/operations.hpp" // for operation_t enum +#include "libtorrent/close_reason.hpp" +#include "libtorrent/aux_/escape_string.hpp" // for convert_from_native + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#ifdef __GNUC__ +#pragma GCC diagnostic push +// this is to suppress the warnings for using std::auto_ptr +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +namespace libtorrent +{ + + namespace aux { + struct stack_allocator; + } + struct piece_block; + + // maps an operation id (from peer_error_alert and peer_disconnected_alert) + // to its name. See peer_connection for the constants + TORRENT_EXPORT char const* operation_name(int op); + + // user defined alerts should use IDs greater than this + static const int user_alert_id = 10000; + + // This is a base class for alerts that are associated with a + // specific torrent. It contains a handle to the torrent. + struct TORRENT_EXPORT torrent_alert : alert + { + // internal + torrent_alert(aux::stack_allocator& alloc, torrent_handle const& h); + + // internal + static const int alert_type = 0; + + // returns the message associated with this alert + virtual std::string message() const TORRENT_OVERRIDE; + + // The torrent_handle pointing to the torrent this + // alert is associated with. + torrent_handle handle; + + char const* torrent_name() const; + +#ifndef TORRENT_NO_DEPRECATE + std::string name; +#endif + + protected: + aux::stack_allocator const& m_alloc; + private: + int m_name_idx; + }; + + // The peer alert is a base class for alerts that refer to a specific peer. It includes all + // the information to identify the peer. i.e. ``ip`` and ``peer-id``. + struct TORRENT_EXPORT peer_alert : torrent_alert + { + // internal + peer_alert(aux::stack_allocator& alloc, torrent_handle const& h, + tcp::endpoint const& i, peer_id const& pi); + + static const int alert_type = 1; + static const int static_category = alert::peer_notification; + virtual int category() const TORRENT_OVERRIDE { return static_category; } + virtual std::string message() const TORRENT_OVERRIDE; + + // The peer's IP address and port. + tcp::endpoint ip; + + // the peer ID, if known. + peer_id pid; + }; + + // This is a base class used for alerts that are associated with a + // specific tracker. It derives from torrent_alert since a tracker + // is also associated with a specific torrent. + struct TORRENT_EXPORT tracker_alert : torrent_alert + { + // internal + tracker_alert(aux::stack_allocator& alloc, torrent_handle const& h + , std::string const& u); + + static const int alert_type = 2; + static const int static_category = alert::tracker_notification; + virtual int category() const TORRENT_OVERRIDE { return static_category; } + virtual std::string message() const TORRENT_OVERRIDE; + + // returns a null-terminated string of the tracker's URL + char const* tracker_url() const; + +#ifndef TORRENT_NO_DEPRECATE + // The tracker URL + std::string url; +#endif + private: + int m_url_idx; + }; + +#ifndef TORRENT_NO_DEPRECATE + #define TORRENT_CLONE(name) \ + virtual std::auto_ptr clone_impl() const TORRENT_OVERRIDE \ + { return std::auto_ptr(new name(*this)); } +#else + #define TORRENT_CLONE(name) +#endif + + // we can only use = default in C++11 + // the purpose of this is just to make all alert types non-copyable from user + // code. The heterogeneous queue does not yet have an emplace_back(), so it + // still needs to copy alerts, but the important part is that it's not + // copyable for clients. + // TODO: Once the backwards compatibility of clone() is removed, and once + // C++11 is required, this can be simplified to just say = delete +#if __cplusplus >= 201103L + #define TORRENT_PROTECTED_CCTOR(name) \ + protected: \ + template friend struct heterogeneous_queue; \ + name(name const&) = default; \ + public: +#else + #define TORRENT_PROTECTED_CCTOR(name) +#endif + +#define TORRENT_DEFINE_ALERT_IMPL(name, seq, prio) \ + TORRENT_PROTECTED_CCTOR(name) \ + static const int priority = prio; \ + static const int alert_type = seq; \ + virtual int type() const TORRENT_OVERRIDE { return alert_type; } \ + TORRENT_CLONE(name) \ + virtual int category() const TORRENT_OVERRIDE { return static_category; } \ + virtual char const* what() const TORRENT_OVERRIDE { return #name; } + +#define TORRENT_DEFINE_ALERT(name, seq) \ + TORRENT_DEFINE_ALERT_IMPL(name, seq, 0) + +#define TORRENT_DEFINE_ALERT_PRIO(name, seq) \ + TORRENT_DEFINE_ALERT_IMPL(name, seq, 1) + + // The ``torrent_added_alert`` is posted once every time a torrent is successfully + // added. It doesn't contain any members of its own, but inherits the torrent handle + // from its base class. + // It's posted when the ``status_notification`` bit is set in the alert_mask. + struct TORRENT_EXPORT torrent_added_alert TORRENT_FINAL : torrent_alert + { + // internal + torrent_added_alert(aux::stack_allocator& alloc, torrent_handle const& h); + + TORRENT_DEFINE_ALERT(torrent_added_alert, 3) + static const int static_category = alert::status_notification; + virtual std::string message() const TORRENT_OVERRIDE; + }; + + // The ``torrent_removed_alert`` is posted whenever a torrent is removed. Since + // the torrent handle in its base class will always be invalid (since the torrent + // is already removed) it has the info hash as a member, to identify it. + // It's posted when the ``status_notification`` bit is set in the alert_mask. + // + // Even though the ``handle`` member doesn't point to an existing torrent anymore, + // it is still useful for comparing to other handles, which may also no + // longer point to existing torrents, but to the same non-existing torrents. + // + // The ``torrent_handle`` acts as a ``weak_ptr``, even though its object no + // longer exists, it can still compare equal to another weak pointer which + // points to the same non-existent object. + struct TORRENT_EXPORT torrent_removed_alert TORRENT_FINAL : torrent_alert + { + // internal + torrent_removed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, sha1_hash const& ih); + + TORRENT_DEFINE_ALERT_PRIO(torrent_removed_alert, 4) + static const int static_category = alert::status_notification; + virtual std::string message() const TORRENT_OVERRIDE; + sha1_hash info_hash; + }; + + // This alert is posted when the asynchronous read operation initiated by + // a call to torrent_handle::read_piece() is completed. If the read failed, the torrent + // is paused and an error state is set and the buffer member of the alert + // is 0. If successful, ``buffer`` points to a buffer containing all the data + // of the piece. ``piece`` is the piece index that was read. ``size`` is the + // number of bytes that was read. + // + // If the operation fails, ec will indicate what went wrong. + struct TORRENT_EXPORT read_piece_alert TORRENT_FINAL : torrent_alert + { + // internal + read_piece_alert(aux::stack_allocator& alloc, torrent_handle const& h + , int p, boost::shared_array d, int s); + read_piece_alert(aux::stack_allocator& alloc, torrent_handle h, int p, error_code e); + + TORRENT_DEFINE_ALERT_PRIO(read_piece_alert, 5) + + static const int static_category = alert::storage_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + error_code ec; + boost::shared_array buffer; + int piece; + int size; + }; + + // 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 TORRENT_FINAL : torrent_alert + { + // internal + file_completed_alert(aux::stack_allocator& alloc, torrent_handle const& h + , int idx); + + TORRENT_DEFINE_ALERT(file_completed_alert, 6) + + static const int static_category = alert::progress_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + // refers to the index of the file that completed. + int 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 TORRENT_FINAL : torrent_alert + { + // internal + file_renamed_alert(aux::stack_allocator& alloc, torrent_handle const& h + , std::string const& n + , int idx); + + TORRENT_DEFINE_ALERT_PRIO(file_renamed_alert, 7) + + static const int static_category = alert::storage_notification; + virtual std::string message() const TORRENT_OVERRIDE; +#ifndef TORRENT_NO_DEPRECATE + std::string name; +#endif + + char const* new_name() const; + + // refers to the index of the file that was renamed, + int index; + private: + int m_name_idx; + }; + + // 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 TORRENT_FINAL : torrent_alert + { + // internal + file_rename_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, int idx + , error_code ec); + + TORRENT_DEFINE_ALERT_PRIO(file_rename_failed_alert, 8) + + static const int static_category = alert::storage_notification; + + virtual std::string message() const TORRENT_OVERRIDE; + + // refers to the index of the file that was supposed to be renamed, + // ``error`` is the error code returned from the filesystem. + int index; + error_code 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 TORRENT_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:: + // + // 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, + bittyrant_with_no_uplimit, + + // This is generated if outgoing peer connections are failing because of *address in use* + // errors, indicating that ``settings_pack::outgoing_ports`` is set and is too small of + // a range. Consider not using the ``outgoing_ports`` setting at all, or widen the range to + // include more ports. + too_few_outgoing_ports, + + too_few_file_descriptors, + + num_warnings + }; + + // internal + performance_alert(aux::stack_allocator& alloc, torrent_handle const& h + , performance_warning_t w); + + TORRENT_DEFINE_ALERT(performance_alert, 9) + + static const int static_category = alert::performance_warning; + + virtual std::string message() const TORRENT_OVERRIDE; + + performance_warning_t warning_code; + }; + + // Generated whenever a torrent changes its state. + struct TORRENT_EXPORT state_changed_alert TORRENT_FINAL : torrent_alert + { + // internal + state_changed_alert(aux::stack_allocator& alloc, torrent_handle const& h + , torrent_status::state_t st + , torrent_status::state_t prev_st); + + TORRENT_DEFINE_ALERT(state_changed_alert, 10) + + static const int static_category = alert::status_notification; + + virtual std::string message() const TORRENT_OVERRIDE; + + // the new state of the torrent. + torrent_status::state_t state; + + // the previous state. + torrent_status::state_t prev_state; + }; + + // This alert is generated on tracker time outs, premature disconnects, + // invalid response or a HTTP response other than "200 OK". From the alert + // you can get the handle to the torrent the tracker belongs to. + // + // The ``times_in_row`` member says how many times in a row this tracker has + // failed. ``status_code`` is the code returned from the HTTP server. 401 + // means the tracker needs authentication, 404 means not found etc. If the + // tracker timed out, the code will be set to 0. + struct TORRENT_EXPORT tracker_error_alert TORRENT_FINAL : tracker_alert + { + // internal + tracker_error_alert(aux::stack_allocator& alloc + , torrent_handle const& h + , int times + , int status + , std::string const& u + , error_code const& e + , std::string const& m); + + TORRENT_DEFINE_ALERT(tracker_error_alert, 11) + + static const int static_category = alert::tracker_notification | alert::error_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + int times_in_row; + int status_code; + error_code error; +#ifndef TORRENT_NO_DEPRECATE + std::string msg; +#endif + + // the message associated with this error + char const* error_message() const; + + private: + int m_msg_idx; + }; + + // 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 TORRENT_FINAL : tracker_alert + { + // internal + tracker_warning_alert(aux::stack_allocator& alloc + , torrent_handle const& h + , std::string const& u + , std::string const& m); + + TORRENT_DEFINE_ALERT(tracker_warning_alert, 12) + + static const int static_category = alert::tracker_notification | alert::error_notification; + virtual std::string message() const TORRENT_OVERRIDE; + +#ifndef TORRENT_NO_DEPRECATE + // contains the warning message from the tracker. + std::string msg; +#endif + + // the message associated with this warning + char const* warning_message() const; + + private: + int m_msg_idx; + }; + + // This alert is generated when a scrape request succeeds. + struct TORRENT_EXPORT scrape_reply_alert TORRENT_FINAL : tracker_alert + { + // internal + scrape_reply_alert(aux::stack_allocator& alloc + , torrent_handle const& h + , int incomp + , int comp + , std::string const& u); + + TORRENT_DEFINE_ALERT(scrape_reply_alert, 13) + + virtual std::string message() const TORRENT_OVERRIDE; + + // the data returned in the scrape response. These numbers + // may be -1 if the response was malformed. + int incomplete; + int 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 TORRENT_FINAL : tracker_alert + { + // internal + scrape_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h + , std::string const& u + , error_code const& e); + scrape_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h + , std::string const& u + , std::string const& m); + + TORRENT_DEFINE_ALERT(scrape_failed_alert, 14) + + static const int static_category = alert::tracker_notification | alert::error_notification; + virtual std::string message() const TORRENT_OVERRIDE; + +#ifndef TORRENT_NO_DEPRECATE + // contains a message describing the error. + std::string msg; +#endif + + // 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 error; + + // if the error indicates there is an associated message, this returns + // that message. Otherwise and empty string. + char const* error_message() const; + + private: + int m_msg_idx; + }; + + // 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 TORRENT_FINAL : tracker_alert + { + // internal + tracker_reply_alert(aux::stack_allocator& alloc + , torrent_handle const& h + , int np + , std::string const& u); + + TORRENT_DEFINE_ALERT(tracker_reply_alert, 15) + + virtual std::string message() const TORRENT_OVERRIDE; + + // tells how many peers the tracker returned in this response. This is + // not expected to be more thant the ``num_want`` settings. These are not necessarily + // all new peers, some of them may already be connected. + int 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 TORRENT_FINAL : tracker_alert + { + // internal + dht_reply_alert(aux::stack_allocator& alloc + , torrent_handle const& h + , int np); + + TORRENT_DEFINE_ALERT(dht_reply_alert, 16) + + virtual std::string message() const TORRENT_OVERRIDE; + + int 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 TORRENT_FINAL : tracker_alert + { + // internal + tracker_announce_alert(aux::stack_allocator& alloc + , torrent_handle const& h + , std::string const& u, int e); + + TORRENT_DEFINE_ALERT(tracker_announce_alert, 17) + + virtual std::string message() const TORRENT_OVERRIDE; + + // specifies what event was sent to the tracker. It is defined as: + // + // 0. None + // 1. Completed + // 2. Started + // 3. Stopped + int 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 TORRENT_FINAL : torrent_alert + { + // internal + hash_failed_alert(aux::stack_allocator& alloc, torrent_handle const& h + , int index); + + TORRENT_DEFINE_ALERT(hash_failed_alert, 18) + + static const int static_category = alert::status_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + int 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 TORRENT_FINAL : peer_alert + { + // internal + peer_ban_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id); + + TORRENT_DEFINE_ALERT(peer_ban_alert, 19) + + virtual std::string message() const TORRENT_OVERRIDE; + }; + + // This alert is generated when a peer is unsnubbed. Essentially when it was snubbed for stalling + // sending data, and now it started sending data again. + struct TORRENT_EXPORT peer_unsnubbed_alert TORRENT_FINAL : peer_alert + { + // internal + peer_unsnubbed_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id); + + TORRENT_DEFINE_ALERT(peer_unsnubbed_alert, 20) + + virtual std::string message() const TORRENT_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 TORRENT_FINAL : peer_alert + { + // internal + peer_snubbed_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id); + + TORRENT_DEFINE_ALERT(peer_snubbed_alert, 21) + + virtual std::string message() const TORRENT_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 TORRENT_FINAL : peer_alert + { + // internal + peer_error_alert(aux::stack_allocator& alloc, torrent_handle const& h + , tcp::endpoint const& ep, peer_id const& peer_id, int op + , error_code const& e); + + TORRENT_DEFINE_ALERT(peer_error_alert, 22) + + static const int static_category = alert::peer_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + // a NULL-terminated string of the low-level operation that failed, or NULL if + // there was no low level disk operation. + int operation; + + // tells you what error caused this alert. + error_code error; + +#ifndef TORRENT_NO_DEPRECATE + std::string msg; +#endif + }; + + // This alert is posted every time an outgoing peer connect attempts succeeds. + struct TORRENT_EXPORT peer_connect_alert TORRENT_FINAL : peer_alert + { + // internal + peer_connect_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id, int type); + + TORRENT_DEFINE_ALERT(peer_connect_alert, 23) + + static const int static_category = alert::debug_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + int 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 TORRENT_FINAL : peer_alert + { + // internal + peer_disconnected_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , peer_id const& peer_id, operation_t op, int type, error_code const& e + , close_reason_t r); + + TORRENT_DEFINE_ALERT(peer_disconnected_alert, 24) + + static const int static_category = alert::debug_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + // the kind of socket this peer was connected over + int 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 operation; + + // tells you what error caused peer to disconnect. + error_code error; + + // the reason the peer disconnected (if specified) + close_reason_t reason; + +#ifndef TORRENT_NO_DEPRECATE + 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 TORRENT_FINAL : peer_alert + { + // internal + invalid_request_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , peer_id const& peer_id, peer_request const& r + , bool we_have, bool peer_interested, bool withheld); + + TORRENT_DEFINE_ALERT(invalid_request_alert, 25) + + virtual std::string message() const TORRENT_OVERRIDE; + + // the request we received from the peer + peer_request request; + + // true if we have this piece + bool we_have; + + // true if the peer indicated that it was interested to download before + // sending the request + bool peer_interested; + + // if this is true, the peer is not allowed to download this piece because + // of superseeding rules. + bool 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 TORRENT_FINAL : torrent_alert + { + // internal + torrent_finished_alert(aux::stack_allocator& alloc, + torrent_handle h); + + TORRENT_DEFINE_ALERT(torrent_finished_alert, 26) + + static const int static_category = alert::status_notification; + virtual std::string message() const TORRENT_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. + struct TORRENT_EXPORT piece_finished_alert TORRENT_FINAL : torrent_alert + { + // internal + piece_finished_alert(aux::stack_allocator& alloc, + torrent_handle const& h, int piece_num); + + TORRENT_DEFINE_ALERT(piece_finished_alert, 27) + + static const int static_category = alert::progress_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + // the index of the piece that finished + int piece_index; + }; + + // This alert is generated when a peer rejects or ignores a piece request. + struct TORRENT_EXPORT request_dropped_alert TORRENT_FINAL : peer_alert + { + // internal + request_dropped_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id, int block_num + , int piece_num); + + TORRENT_DEFINE_ALERT(request_dropped_alert, 28) + + static const int static_category = alert::progress_notification + | alert::peer_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + int block_index; + int piece_index; + }; + + // This alert is generated when a block request times out. + struct TORRENT_EXPORT block_timeout_alert TORRENT_FINAL : peer_alert + { + // internal + block_timeout_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id, int block_num + , int piece_num); + + TORRENT_DEFINE_ALERT(block_timeout_alert, 29) + + static const int static_category = alert::progress_notification + | alert::peer_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + int block_index; + int piece_index; + }; + + // This alert is generated when a block request receives a response. + struct TORRENT_EXPORT block_finished_alert TORRENT_FINAL : peer_alert + { + // internal + block_finished_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id, int block_num + , int piece_num); + + TORRENT_DEFINE_ALERT(block_finished_alert, 30) + + static const int static_category = alert::progress_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + int block_index; + int piece_index; + }; + + // This alert is generated when a block request is sent to a peer. + struct TORRENT_EXPORT block_downloading_alert TORRENT_FINAL : peer_alert + { + // internal + block_downloading_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep + , peer_id const& peer_id, int block_num, int piece_num); + + TORRENT_DEFINE_ALERT(block_downloading_alert, 31) + + static const int static_category = alert::progress_notification; + virtual std::string message() const TORRENT_OVERRIDE; + +#ifndef TORRENT_NO_DEPRECATE + char const* peer_speedmsg; +#endif + int block_index; + int piece_index; + }; + + // This alert is generated when a block is received that was not requested or + // whose request timed out. + struct TORRENT_EXPORT unwanted_block_alert TORRENT_FINAL : peer_alert + { + // internal + unwanted_block_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep + , peer_id const& peer_id, int block_num, int piece_num); + + TORRENT_DEFINE_ALERT(unwanted_block_alert, 32) + + virtual std::string message() const TORRENT_OVERRIDE; + + int block_index; + int 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 ``path`` member is the new path of + // the storage. + struct TORRENT_EXPORT storage_moved_alert TORRENT_FINAL : torrent_alert + { + // internal + storage_moved_alert(aux::stack_allocator& alloc + , torrent_handle const& h, std::string const& p); + + TORRENT_DEFINE_ALERT(storage_moved_alert, 33) + + static const int static_category = alert::storage_notification; + virtual std::string message() const TORRENT_OVERRIDE; + +#ifndef TORRENT_NO_DEPRECATE + std::string path; +#endif + + // the path the torrent was moved to + char const* storage_path() const; + + private: + int m_path_idx; + }; + + // 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 TORRENT_FINAL : torrent_alert + { + // internal + storage_moved_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h + , error_code const& e + , std::string const& file + , char const* op); + + TORRENT_DEFINE_ALERT(storage_moved_failed_alert, 34) + + static const int static_category = alert::storage_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + error_code error; + +#ifndef TORRENT_NO_DEPRECATE + // If the error happened for a specific file, ``file`` is its path. + std::string file; +#endif + + // If the error happened for a specific file, this returns its path. + char const* file_path() const; + + // If the error happened in a specific disk operation this is a NULL + // terminated string naming which one, otherwise it's NULL. + char const* operation; + private: + int m_file_idx; + }; + + // This alert is generated when a request to delete the files of a torrent complete. + // + // The ``info_hash`` is the info-hash of the torrent that was just deleted. Most of + // the time the torrent_handle in the ``torrent_alert`` will be invalid by the time + // this alert arrives, since the torrent is being deleted. The ``info_hash`` member + // is hence the main way of identifying which torrent just completed the delete. + // + // This alert is posted in the ``storage_notification`` category, and that bit + // needs to be set in the alert_mask. + struct TORRENT_EXPORT torrent_deleted_alert TORRENT_FINAL : torrent_alert + { + // internal + torrent_deleted_alert(aux::stack_allocator& alloc + , torrent_handle const& h, sha1_hash const& ih); + + TORRENT_DEFINE_ALERT_PRIO(torrent_deleted_alert, 35) + + static const int static_category = alert::storage_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + + sha1_hash info_hash; + }; + + // This alert is generated when a request to delete the files of a torrent fails. + // Just removing a torrent from the session cannot fail + struct TORRENT_EXPORT torrent_delete_failed_alert TORRENT_FINAL : torrent_alert + { + // internal + torrent_delete_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, error_code const& e, sha1_hash const& ih); + + TORRENT_DEFINE_ALERT_PRIO(torrent_delete_failed_alert, 36) + + static const int static_category = alert::storage_notification + | alert::error_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + // tells you why it failed. + error_code error; + + // the info hash of the torrent whose files failed to be deleted + sha1_hash info_hash; + +#ifndef TORRENT_NO_DEPRECATE + 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 TORRENT_FINAL : torrent_alert + { + // internal + save_resume_data_alert(aux::stack_allocator& alloc + , boost::shared_ptr const& rd + , torrent_handle const& h); + + TORRENT_DEFINE_ALERT_PRIO(save_resume_data_alert, 37) + + static const int static_category = alert::storage_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + // points to the resume data. + boost::shared_ptr resume_data; + }; + + // 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 TORRENT_FINAL : torrent_alert + { + // internal + save_resume_data_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, error_code const& e); + + TORRENT_DEFINE_ALERT_PRIO(save_resume_data_failed_alert, 38) + + static const int static_category = alert::storage_notification + | alert::error_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + // the error code from the resume_data failure + error_code error; + +#ifndef TORRENT_NO_DEPRECATE + 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 TORRENT_FINAL : torrent_alert + { + // internal + torrent_paused_alert(aux::stack_allocator& alloc, torrent_handle const& h); + + TORRENT_DEFINE_ALERT(torrent_paused_alert, 39) + + static const int static_category = alert::status_notification; + virtual std::string message() const TORRENT_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 TORRENT_FINAL : torrent_alert + { + // internal + torrent_resumed_alert(aux::stack_allocator& alloc, torrent_handle const& h); + + TORRENT_DEFINE_ALERT(torrent_resumed_alert, 40) + + static const int static_category = alert::status_notification; + virtual std::string message() const TORRENT_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 TORRENT_FINAL : torrent_alert + { + // internal + torrent_checked_alert(aux::stack_allocator& alloc, torrent_handle const& h); + + TORRENT_DEFINE_ALERT(torrent_checked_alert, 41) + + static const int static_category = alert::status_notification; + virtual std::string message() const TORRENT_OVERRIDE; + }; + + // This alert is generated when a HTTP seed name lookup fails. + struct TORRENT_EXPORT url_seed_alert TORRENT_FINAL : torrent_alert + { + // internal + url_seed_alert(aux::stack_allocator& alloc, torrent_handle const& h + , std::string const& u, error_code const& e); + url_seed_alert(aux::stack_allocator& alloc, torrent_handle const& h + , std::string const& u, std::string const& m); + + TORRENT_DEFINE_ALERT(url_seed_alert, 42) + + static const int static_category = alert::peer_notification | alert::error_notification; + virtual std::string message() const TORRENT_OVERRIDE; + +#ifndef TORRENT_NO_DEPRECATE + // the HTTP seed that failed + std::string url; + + // the error message, potentially from the server + std::string msg; +#endif + + // the error the web seed encountered. If this is not set, the server + // sent an error message, call ``error_message()``. + error_code 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: + int m_url_idx; + int m_msg_idx; + }; + + // 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 TORRENT_FINAL : torrent_alert + { + // internal + file_error_alert(aux::stack_allocator& alloc + , error_code const& ec + , std::string const& file + , char const* op + , torrent_handle const& h); + + TORRENT_DEFINE_ALERT(file_error_alert, 43) + + static const int static_category = alert::status_notification + | alert::error_notification + | alert::storage_notification; + virtual std::string message() const TORRENT_OVERRIDE; + +#ifndef TORRENT_NO_DEPRECATE + // the path to the file that was accessed when the error occurred. + std::string file; +#endif + + // the error code describing the error. + error_code error; + char const* operation; + + // the file that experienced the error + char const* filename() const; + +#ifndef TORRENT_NO_DEPRECATE + std::string msg; +#endif + private: + int m_file_idx; + }; + + // 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 TORRENT_FINAL : torrent_alert + { + // internal + metadata_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, error_code const& ec); + + TORRENT_DEFINE_ALERT(metadata_failed_alert, 44) + + static const int static_category = alert::error_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + // indicates what failed when parsing the metadata. This error is + // what's returned from lazy_bdecode(). + error_code 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:: + // + // torrent_handle h = alert->handle(); + // if (h.is_valid()) { + // boost::shared_ptr ti = h.torrent_file(); + // create_torrent ct(*ti); + // entry te = ct.generate(); + // std::vector buffer; + // bencode(std::back_inserter(buffer), te); + // FILE* f = fopen((to_hex(ti->info_hash().to_string()) + ".torrent").c_str(), "wb+"); + // if (f) { + // fwrite(&buffer[0], 1, buffer.size(), f); + // fclose(f); + // } + // } + // + struct TORRENT_EXPORT metadata_received_alert TORRENT_FINAL : torrent_alert + { + // internal + metadata_received_alert(aux::stack_allocator& alloc + , torrent_handle const& h); + + TORRENT_DEFINE_ALERT(metadata_received_alert, 45) + + static const int static_category = alert::status_notification; + virtual std::string message() const TORRENT_OVERRIDE; + }; + + // This alert is posted when there is an error on the UDP socket. The + // UDP socket is used for all uTP, DHT and UDP tracker traffic. It's + // global to the session. + struct TORRENT_EXPORT udp_error_alert TORRENT_FINAL : alert + { + // internal + udp_error_alert( + aux::stack_allocator& alloc + , udp::endpoint const& ep + , error_code const& ec); + + TORRENT_DEFINE_ALERT(udp_error_alert, 46) + + static const int static_category = alert::error_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + // the source address associated with the error (if any) + udp::endpoint endpoint; + + // the error code describing the error + error_code 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 TORRENT_FINAL : alert + { + // internal + external_ip_alert(aux::stack_allocator& alloc, address const& ip); + + TORRENT_DEFINE_ALERT(external_ip_alert, 47) + + static const int static_category = alert::status_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + // the IP address that is believed to be our external IP + address external_address; + }; + + // This alert is generated when none of the ports, given in the port range, to + // session can be opened for listening. The ``endpoint`` member is the + // interface and port that failed, ``error`` is the error code describing + // the failure. + // + // 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 TORRENT_FINAL : alert + { + enum socket_type_t { tcp, tcp_ssl, udp, i2p, socks5, utp_ssl }; + + // internal + listen_failed_alert( + aux::stack_allocator& alloc + , std::string const& iface + , int port + , int op + , error_code const& ec + , socket_type_t t); + + TORRENT_DEFINE_ALERT_PRIO(listen_failed_alert, 48) + + static const int static_category = alert::status_notification | alert::error_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + // the interface libtorrent attempted to listen on that failed. + char const* listen_interface() const; + + // the error the system returned + error_code error; + + enum op_t + { + parse_addr, open, bind, listen, get_peer_name, accept + }; + + // the specific low level operation that failed. See op_t. + int operation; + + // the type of listen socket this alert refers to. + socket_type_t sock_type; + + // the address and port libtorrent attempted to listen on + tcp::endpoint endpoint; + + private: + aux::stack_allocator const& m_alloc; + int m_interface_idx; + }; + + // This alert is posted when the listen port succeeds to be opened on a + // particular interface. ``endpoint`` is the endpoint that successfully + // was opened for listening. + struct TORRENT_EXPORT listen_succeeded_alert TORRENT_FINAL : alert + { + enum socket_type_t { tcp, tcp_ssl, udp, i2p, socks5, utp_ssl }; + + // internal + listen_succeeded_alert(aux::stack_allocator& alloc, tcp::endpoint const& ep + , socket_type_t t); + + TORRENT_DEFINE_ALERT_PRIO(listen_succeeded_alert, 49) + + static const int static_category = alert::status_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + // the endpoint libtorrent ended up listening on. The address + // refers to the local interface and the port is the listen port. + tcp::endpoint endpoint; + + // the type of listen socket this alert refers to. + socket_type_t sock_type; + }; + + // 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 TORRENT_FINAL : alert + { + // internal + portmap_error_alert(aux::stack_allocator& alloc, int i, int t + , error_code const& e); + + TORRENT_DEFINE_ALERT(portmap_error_alert, 50) + + static const int static_category = alert::port_mapping_notification + | alert::error_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + // refers to the mapping index of the port map that failed, i.e. + // the index returned from add_mapping(). + int mapping; + + // is 0 for NAT-PMP and 1 for UPnP. + int map_type; + + // tells you what failed. + error_code error; +#ifndef TORRENT_NO_DEPRECATE + 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 TORRENT_FINAL : alert + { + // internal + portmap_alert(aux::stack_allocator& alloc, int i, int port, int t, int protocol); + + TORRENT_DEFINE_ALERT(portmap_alert, 51) + + static const int static_category = alert::port_mapping_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + // refers to the mapping index of the port map that failed, i.e. + // the index returned from add_mapping(). + int mapping; + + // the external port allocated for the mapping. + int external_port; + + // 0 for NAT-PMP and 1 for UPnP. + int map_type; + + enum protocol_t + { + tcp, + udp + }; + + // the protocol this mapping was for. one of protocol_t enums + int protocol; + }; + +#ifndef TORRENT_DISABLE_LOGGING + + // 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::port_mapping_log_notification flag is enabled in + // the alert mask. + struct TORRENT_EXPORT portmap_log_alert TORRENT_FINAL : alert + { + // internal + portmap_log_alert(aux::stack_allocator& alloc, int t, const char* m); + + TORRENT_DEFINE_ALERT(portmap_log_alert, 52) + + static const int static_category = alert::port_mapping_log_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + int map_type; + +#ifndef TORRENT_NO_DEPRECATE + std::string msg; +#endif + + // the message associated with this log line + char const* log_message() const; + + private: + + // TODO: 2 should the alert baseclass have this object instead? + aux::stack_allocator const& m_alloc; + + int m_log_idx; + }; + +#endif + + // This alert is generated when a fastresume file has been passed to + // add_torrent() but the files on disk did not match the fastresume file. + // The error_code explains the reason why the resume file was rejected. + struct TORRENT_EXPORT fastresume_rejected_alert TORRENT_FINAL : torrent_alert + { + // internal + fastresume_rejected_alert(aux::stack_allocator& alloc + , torrent_handle const& h + , error_code const& ec + , std::string const& file + , char const* op); + + TORRENT_DEFINE_ALERT(fastresume_rejected_alert, 53) + + static const int static_category = alert::status_notification + | alert::error_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + error_code error; + +#ifndef TORRENT_NO_DEPRECATE + // If the error happened to a specific file, ``file`` is the path to it. + std::string file; +#endif + + // If the error happened to a specific file, this returns the path to it. + char const* file_path() const; + + // If the error happened in a disk operation. a NULL-terminated string of + // the name of that operation. ``operation`` is NULL otherwise. + char const* operation; + +#ifndef TORRENT_NO_DEPRECATE + std::string msg; +#endif + private: + int m_path_idx; + }; + + // 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 TORRENT_FINAL : torrent_alert + { + // internal + peer_blocked_alert(aux::stack_allocator& alloc, torrent_handle const& h + , address const& i, int r); + + TORRENT_DEFINE_ALERT(peer_blocked_alert, 54) + + static const int static_category = alert::ip_block_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + // the address that was blocked. + address ip; + + enum reason_t + { + ip_filter, + port_filter, + i2p_mixed, + privileged_ports, + utp_disabled, + tcp_disabled, + invalid_local_interface + }; + + int reason; + }; + + // This alert is generated when a DHT node announces to an info-hash on our + // DHT node. It belongs to the ``dht_notification`` category. + struct TORRENT_EXPORT dht_announce_alert TORRENT_FINAL : alert + { + // internal + dht_announce_alert(aux::stack_allocator& alloc, address const& i, int p + , sha1_hash const& ih); + + TORRENT_DEFINE_ALERT(dht_announce_alert, 55) + + static const int static_category = alert::dht_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + address ip; + int port; + sha1_hash info_hash; + }; + + // This alert is generated when a DHT node sends a ``get_peers`` message to + // our DHT node. It belongs to the ``dht_notification`` category. + struct TORRENT_EXPORT dht_get_peers_alert TORRENT_FINAL : alert + { + // internal + dht_get_peers_alert(aux::stack_allocator& alloc, sha1_hash const& ih); + + TORRENT_DEFINE_ALERT(dht_get_peers_alert, 56) + + static const int static_category = alert::dht_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + sha1_hash info_hash; + }; + + // This alert is posted approximately once every second, and it contains + // byte counters of most statistics that's tracked for torrents. Each active + // torrent posts these alerts regularly. + struct TORRENT_EXPORT stats_alert TORRENT_FINAL : torrent_alert + { + // internal + stats_alert(aux::stack_allocator& alloc, torrent_handle const& h, int interval + , stat const& s); + + TORRENT_DEFINE_ALERT(stats_alert, 57) + + static const int static_category = alert::stats_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + enum stats_channel + { + upload_payload, + upload_protocol, + download_payload, + download_protocol, + upload_ip_protocol, +#ifndef TORRENT_NO_DEPRECATE + upload_dht_protocol, + upload_tracker_protocol, +#else + deprecated1, + deprecated2, +#endif + download_ip_protocol, +#ifndef TORRENT_NO_DEPRECATE + download_dht_protocol, + download_tracker_protocol, +#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. + int transferred[num_channels]; + + // 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 interval; + }; + + // This alert is posted when the disk cache has been flushed for a specific + // torrent as a result of a call to torrent_handle::flush_cache(). This + // alert belongs to the ``storage_notification`` category, which must be + // enabled to let this alert through. The alert is also posted when removing + // a torrent from the session, once the outstanding cache flush is complete + // and the torrent does no longer have any files open. + struct TORRENT_EXPORT cache_flushed_alert TORRENT_FINAL : torrent_alert + { + // internal + cache_flushed_alert(aux::stack_allocator& alloc, torrent_handle const& h); + + TORRENT_DEFINE_ALERT(cache_flushed_alert, 58) + + static const int static_category = alert::storage_notification; + }; + + // 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_EXPORT anonymous_mode_alert TORRENT_FINAL : torrent_alert + { + // internal + anonymous_mode_alert(aux::stack_allocator& alloc, torrent_handle const& h + , int k, std::string const& s); + + TORRENT_DEFINE_ALERT(anonymous_mode_alert, 59) + + static const int static_category = alert::error_notification; + virtual std::string message() const TORRENT_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; + }; + + // 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 TORRENT_FINAL : peer_alert + { + // internal + lsd_peer_alert(aux::stack_allocator& alloc, torrent_handle const& h + , tcp::endpoint const& i); + + TORRENT_DEFINE_ALERT(lsd_peer_alert, 60) + + static const int static_category = alert::peer_notification; + virtual std::string message() const TORRENT_OVERRIDE; + }; + + // This alert is posted whenever a tracker responds with a ``trackerid``. + // The tracker ID is like a cookie. The libtorrent will store the tracker ID + // for this tracker and repeat it in subsequent announces. + struct TORRENT_EXPORT trackerid_alert TORRENT_FINAL : tracker_alert + { + // internal + trackerid_alert(aux::stack_allocator& alloc, torrent_handle const& h + , std::string const& u + , const std::string& id); + + TORRENT_DEFINE_ALERT(trackerid_alert, 61) + + static const int static_category = alert::status_notification; + virtual std::string message() const TORRENT_OVERRIDE; + +#ifndef TORRENT_NO_DEPRECATE + // The tracker ID returned by the tracker + std::string trackerid; +#endif + + // The tracker ID returned by the tracker + char const* tracker_id() const; + + private: + int m_tracker_idx; + }; + + // This alert is posted when the initial DHT bootstrap is done. + struct TORRENT_EXPORT dht_bootstrap_alert TORRENT_FINAL : alert + { + // internal + dht_bootstrap_alert(aux::stack_allocator& alloc); + + TORRENT_DEFINE_ALERT(dht_bootstrap_alert, 62) + + static const int static_category = alert::dht_notification; + virtual std::string message() const TORRENT_OVERRIDE; + }; + +#ifndef TORRENT_NO_DEPRECATE + // This alert is posted on RSS feed events such as start of RSS feed updates, + // successful completed updates and errors during updates. + // + // This alert is only posted if the ``rss_notifications`` category is enabled + // in the alert_mask. + struct TORRENT_DEPRECATED TORRENT_EXPORT rss_alert TORRENT_FINAL : alert + { + // internal + rss_alert(aux::stack_allocator& alloc, feed_handle h + , std::string const& u, int s, error_code const& ec); + + TORRENT_DEFINE_ALERT(rss_alert, 63) + + static const int static_category = alert::rss_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + enum state_t + { + // An update of this feed was just initiated, it will either succeed + // or fail soon. + state_updating, + + // The feed just completed a successful update, there may be new items + // in it. If you're adding torrents manually, you may want to request + // the feed status of the feed and look through the ``items`` vector. + state_updated, + + // An error just occurred. See the ``error`` field for information on + // what went wrong. + state_error + }; + + // the handle to the feed which generated this alert. + feed_handle handle; + + // a short cut to access the url of the feed, without + // having to call feed_handle::get_settings(). + std::string url; + + // one of the values from rss_alert::state_t. + int state; + + // an error code used for when an error occurs on the feed. + error_code error; + }; +#endif // TORRENT_NO_DEPRECATE + + // This is posted whenever a torrent is transitioned into the error state. + struct TORRENT_EXPORT torrent_error_alert TORRENT_FINAL : torrent_alert + { + // internal + torrent_error_alert(aux::stack_allocator& alloc, torrent_handle const& h + , error_code const& e, std::string const& f); + + TORRENT_DEFINE_ALERT(torrent_error_alert, 64) + + static const int static_category = alert::error_notification | alert::status_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + // specifies which error the torrent encountered. + error_code error; + +#ifndef TORRENT_NO_DEPRECATE + // the filename (or object) the error occurred on. + std::string error_file; +#endif + + // the filename (or object) the error occurred on. + char const* filename() const; + + private: + int m_file_idx; + }; + + // 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 TORRENT_FINAL : torrent_alert + { + // internal + torrent_need_cert_alert(aux::stack_allocator& alloc + , torrent_handle const& h); + + TORRENT_DEFINE_ALERT_PRIO(torrent_need_cert_alert, 65) + + static const int static_category = alert::status_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + error_code error; + }; + + // 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 TORRENT_FINAL : alert + { + // internal + incoming_connection_alert(aux::stack_allocator& alloc, int t + , tcp::endpoint const& i); + + TORRENT_DEFINE_ALERT(incoming_connection_alert, 66) + + static const int static_category = alert::peer_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + // tells you what kind of socket the connection was accepted + // as: + // + // 0. none (no socket instantiated) + // 1. TCP + // 2. Socks5 + // 3. HTTP + // 4. uTP + // 5. i2p + // 6. SSL/TCP + // 7. SSL/Socks5 + // 8. HTTPS (SSL/HTTP) + // 9. SSL/uTP + // + int socket_type; + + // is the IP address and port the connection came from. + tcp::endpoint ip; + }; + + // 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 in the base class' ``handle`` member. If adding + // the torrent failed, ``error`` contains the error code. + struct TORRENT_EXPORT add_torrent_alert TORRENT_FINAL : torrent_alert + { + // internal + add_torrent_alert(aux::stack_allocator& alloc, torrent_handle h + , add_torrent_params const& p, error_code ec); + + TORRENT_DEFINE_ALERT_PRIO(add_torrent_alert, 67) + + static const int static_category = alert::status_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + // a copy of the parameters used when adding the torrent, it can be used + // to identify which invocation to ``async_add_torrent()`` caused this alert. + add_torrent_params params; + + // set to the error, if one occurred while adding the torrent. + error_code error; + }; + + // This alert is only posted when requested by the user, by calling + // session::post_torrent_updates() on the session. It contains the torrent + // status of all torrents that changed since last time this message was + // posted. Its category is ``status_notification``, but it's not subject to + // filtering, since it's only manually posted anyway. + struct TORRENT_EXPORT state_update_alert TORRENT_FINAL : alert + { + state_update_alert(aux::stack_allocator& alloc + , std::vector st); + + TORRENT_DEFINE_ALERT_PRIO(state_update_alert, 68) + + static const int static_category = alert::status_notification; + virtual std::string message() const TORRENT_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; + }; + + struct TORRENT_EXPORT mmap_cache_alert TORRENT_FINAL : alert + { + mmap_cache_alert(aux::stack_allocator& alloc + , error_code const& ec); + TORRENT_DEFINE_ALERT(mmap_cache_alert, 69) + + static const int static_category = alert::error_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + error_code error; + }; + + // The session_stats_alert is posted when the user requests session statistics by + // calling post_session_stats() on the session object. Its category is + // ``status_notification``, but it is not subject to filtering, since it's only + // manually posted anyway. + struct TORRENT_EXPORT session_stats_alert TORRENT_FINAL : alert + { + session_stats_alert(aux::stack_allocator& alloc, counters const& cnt); + TORRENT_DEFINE_ALERT_PRIO(session_stats_alert, 70) + + static const int static_category = alert::stats_notification; + virtual std::string message() const TORRENT_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. + boost::uint64_t values[counters::num_counters]; + }; + + // hidden + // When a torrent changes its info-hash, this alert is posted. This only + // happens in very specific cases. For instance, when a torrent is + // downloaded from a URL, the true info hash is not known immediately. First + // the .torrent file must be downloaded and parsed. + // + // Once this download completes, the ``torrent_update_alert`` is posted to + // notify the client of the info-hash changing. + struct TORRENT_EXPORT torrent_update_alert TORRENT_FINAL : torrent_alert + { + // internal + torrent_update_alert(aux::stack_allocator& alloc, torrent_handle h + , sha1_hash const& old_hash, sha1_hash const& new_hash); + + TORRENT_DEFINE_ALERT_PRIO(torrent_update_alert, 71) + + static const int static_category = alert::status_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + // ``old_ih`` and ``new_ih`` are the previous and new info-hash for the torrent, respectively. + sha1_hash old_ih; + sha1_hash new_ih; + }; + +#ifndef TORRENT_NO_DEPRECATE + // This alert is posted every time a new RSS item (i.e. torrent) is received + // from an RSS feed. + // + // It is only posted if the ``rss_notifications`` category is enabled in the + // alert_mask. + struct TORRENT_EXPORT rss_item_alert TORRENT_FINAL : alert + { + // internal + rss_item_alert(aux::stack_allocator& alloc, feed_handle h + , feed_item const& item); + + TORRENT_DEFINE_ALERT(rss_item_alert, 72) + + static const int static_category = alert::rss_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + feed_handle handle; + feed_item item; + }; +#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 TORRENT_FINAL : alert + { + // internal + dht_error_alert(aux::stack_allocator& alloc, int op, error_code const& ec); + + TORRENT_DEFINE_ALERT(dht_error_alert, 73) + + static const int static_category = alert::error_notification | alert::dht_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + // the error code + error_code error; + + enum op_t + { + unknown, + hostname_lookup + }; + + // the operation that failed + op_t operation; + }; + + // 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 TORRENT_FINAL : alert + { + dht_immutable_item_alert(aux::stack_allocator& alloc, sha1_hash const& t + , entry const& i); + + TORRENT_DEFINE_ALERT_PRIO(dht_immutable_item_alert, 74) + + static const int static_category = alert::dht_notification; + + virtual std::string message() const TORRENT_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 TORRENT_FINAL : alert + { + dht_mutable_item_alert(aux::stack_allocator& alloc + , boost::array k + , boost::array sig + , boost::uint64_t sequence + , std::string const& s + , entry const& i + , bool a); + + TORRENT_DEFINE_ALERT_PRIO(dht_mutable_item_alert, 75) + + static const int static_category = alert::dht_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + // the public key that was looked up + boost::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. + boost::array signature; + + // the sequence number of this item + boost::uint64_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 TORRENT_FINAL : alert + { + // internal + dht_put_alert(aux::stack_allocator& alloc, sha1_hash const& t, int n); + dht_put_alert(aux::stack_allocator& alloc, boost::array key + , boost::array sig + , std::string s + , boost::uint64_t sequence_number + , int n); + + TORRENT_DEFINE_ALERT(dht_put_alert, 76) + + static const int static_category = alert::dht_notification; + virtual std::string message() const TORRENT_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. + boost::array public_key; + boost::array signature; + std::string salt; + boost::uint64_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 TORRENT_FINAL : alert + { + i2p_alert(aux::stack_allocator& alloc, error_code const& ec); + + TORRENT_DEFINE_ALERT(i2p_alert, 77) + + static const int static_category = alert::error_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + // the error that occurred in the i2p SAM connection + error_code error; + }; + + // This alert is generated when we send a get_peers request + // It belongs to the ``dht_notification`` category. + struct TORRENT_EXPORT dht_outgoing_get_peers_alert TORRENT_FINAL : alert + { + // internal + dht_outgoing_get_peers_alert(aux::stack_allocator& alloc + , sha1_hash const& ih, sha1_hash const& obfih + , udp::endpoint ep); + + TORRENT_DEFINE_ALERT(dht_outgoing_get_peers_alert, 78) + + static const int static_category = alert::dht_notification; + virtual std::string message() const TORRENT_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 + udp::endpoint ip; + }; + +#ifndef TORRENT_DISABLE_LOGGING + // 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::session_log_notification`` bit. + // Furthermore, it's by default disabled as a build configuration. + struct TORRENT_EXPORT log_alert TORRENT_FINAL : alert + { + // internal + log_alert(aux::stack_allocator& alloc, char const* log); + + TORRENT_DEFINE_ALERT(log_alert, 79) + + static const int static_category = alert::session_log_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + // returns the log message + char const* msg() const; + + private: + aux::stack_allocator const& m_alloc; + int 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::torrent_log_notification`` bit. By + // default it is disabled as a build configuration. + struct TORRENT_EXPORT torrent_log_alert TORRENT_FINAL : torrent_alert + { + // internal + torrent_log_alert(aux::stack_allocator& alloc, torrent_handle const& h + , char const* log); + + TORRENT_DEFINE_ALERT(torrent_log_alert, 80) + + static const int static_category = alert::torrent_log_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + // returns the log message + char const* msg() const; + + private: + int 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::peer_log_notification`` bit. By + // default it is disabled as a build configuration. + struct TORRENT_EXPORT peer_log_alert TORRENT_FINAL : peer_alert + { + // describes whether this log refers to in-flow or out-flow of the + // peer. The exception is ``info`` which is neither incoming or outgoing. + enum direction_t + { + incoming_message, + outgoing_message, + incoming, + outgoing, + info + }; + + // internal + peer_log_alert(aux::stack_allocator& alloc, torrent_handle const& h + , tcp::endpoint const& i, peer_id const& pi + , peer_log_alert::direction_t dir + , char const* event, char const* log); + + TORRENT_DEFINE_ALERT(peer_log_alert, 81) + + static const int static_category = alert::peer_log_notification; + virtual std::string message() const TORRENT_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* msg() const; + + private: + int m_str_idx; + }; + +#endif + + // posted if the local service discovery socket fails to start properly. + // it's categorized as ``error_notification``. + struct TORRENT_EXPORT lsd_error_alert TORRENT_FINAL : alert + { + // internal + lsd_error_alert(aux::stack_allocator& alloc, error_code const& ec); + + TORRENT_DEFINE_ALERT(lsd_error_alert, 82) + + static const int static_category = alert::error_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + // The error code + error_code error; + }; + + // holds statistics about a current dht_lookup operation. + // a DHT lookup is the traversal of nodes, looking up a + // set of target nodes in the DHT for retrieving and possibly + // storing information in the DHT + struct TORRENT_EXPORT dht_lookup + { + // string literal indicating which kind of lookup this is + char const* type; + + // the number of outstanding request to individual nodes + // this lookup has right now + int outstanding_requests; + + // the total number of requests that have timed out so far + // for this lookup + int timeouts; + + // the total number of responses we have received for this + // lookup so far for this lookup + int responses; + + // the branch factor for this lookup. This is the number of + // nodes we keep outstanding requests to in parallel by default. + // when nodes time out we may increase this. + int branch_factor; + + // the number of nodes left that could be queries for this + // lookup. Many of these are likely to be part of the trail + // while performing the lookup and would never end up actually + // being queried. + int nodes_left; + + // the number of seconds ago the + // last message was sent that's still + // outstanding + int last_sent; + + // the number of outstanding requests + // that have exceeded the short timeout + // and are considered timed out in the + // sense that they increased the branch + // factor + int first_timeout; + }; + + // 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; + }; + + // contains current DHT state. Posted in response to session::post_dht_stats(). + struct TORRENT_EXPORT dht_stats_alert TORRENT_FINAL : alert + { + // internal + dht_stats_alert(aux::stack_allocator& alloc + , std::vector const& table + , std::vector const& requests); + + TORRENT_DEFINE_ALERT(dht_stats_alert, 83) + + static const int static_category = alert::stats_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + // a vector of the currently running DHT lookups. + std::vector active_requests; + + // contains information about every bucket in the DHT routing + // table. + std::vector routing_table; + }; + + // posted every time an incoming request from a peer is accepted and queued + // up for being serviced. This alert is only posted if + // the alert::incoming_request_notification flag is enabled in the alert + // mask. + struct TORRENT_EXPORT incoming_request_alert TORRENT_FINAL : peer_alert + { + // internal + incoming_request_alert(aux::stack_allocator& alloc + , peer_request r, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id); + + static const int static_category = alert::incoming_request_notification; + TORRENT_DEFINE_ALERT(incoming_request_alert, 84) + + virtual std::string message() const TORRENT_OVERRIDE; + + // the request this peer sent to us + peer_request req; + }; + + struct TORRENT_EXPORT dht_log_alert TORRENT_FINAL : alert + { + enum dht_module_t + { + tracker, + node, + routing_table, + rpc_manager, + traversal + }; + + dht_log_alert(aux::stack_allocator& alloc + , dht_module_t m, char const* msg); + + static const int static_category = alert::dht_log_notification; + TORRENT_DEFINE_ALERT(dht_log_alert, 85) + + virtual std::string message() const TORRENT_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: + aux::stack_allocator& m_alloc; + int m_msg_idx; + }; + + // This alert is posted every time a DHT message is sent or received. It is + // only posted if the ``alert::dht_log_notification`` alert category is + // enabled. It contains a verbatim copy of the message. + struct TORRENT_EXPORT dht_pkt_alert TORRENT_FINAL : alert + { + enum direction_t + { incoming, outgoing }; + + dht_pkt_alert(aux::stack_allocator& alloc, char const* buf, int size + , dht_pkt_alert::direction_t d, udp::endpoint ep); + + static const int static_category = alert::dht_log_notification; + TORRENT_DEFINE_ALERT(dht_pkt_alert, 86) + + virtual std::string message() const TORRENT_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. + char const* pkt_buf() const; + int pkt_size() const; + + // whether this is an incoming or outgoing packet. + direction_t dir; + + // the DHT node we received this packet from, or sent this packet to + // (depending on ``dir``). + udp::endpoint node; + + private: + aux::stack_allocator& m_alloc; + int m_msg_idx; + int m_size; + }; + + struct TORRENT_EXPORT dht_get_peers_reply_alert TORRENT_FINAL : alert { + + dht_get_peers_reply_alert(aux::stack_allocator& alloc + , sha1_hash const& ih + , std::vector const& v); + + static const int static_category = alert::dht_operation_notification; + TORRENT_DEFINE_ALERT(dht_get_peers_reply_alert, 87) + + virtual std::string message() const TORRENT_OVERRIDE; + + sha1_hash info_hash; + + int num_peers() const; + +#ifndef TORRENT_NO_DEPRECATE + TORRENT_DEPRECATED + void peers(std::vector& v) const; +#endif + std::vector peers() const; + + private: + aux::stack_allocator& m_alloc; + int m_num_peers; + int m_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 TORRENT_FINAL : alert + { + dht_direct_response_alert(aux::stack_allocator& alloc, void* userdata + , udp::endpoint const& addr, bdecode_node const& response); + + // for when there was a timeout so we don't have a response + dht_direct_response_alert(aux::stack_allocator& alloc, void* userdata + , udp::endpoint const& addr); + + TORRENT_DEFINE_ALERT(dht_direct_response_alert, 88) + + static const int static_category = alert::dht_notification; + virtual std::string message() const TORRENT_OVERRIDE; + + void* userdata; + udp::endpoint addr; + + bdecode_node response() const; + + private: + aux::stack_allocator& m_alloc; + int m_response_idx; + int m_response_size; + }; + + // this is posted when one or more blocks are picked by the piece picker, + // assuming the verbose piece picker logging is enabled (see + // picker_log_notification). + struct TORRENT_EXPORT picker_log_alert : peer_alert + { +#ifndef TORRENT_DISABLE_LOGGING + + // internal + picker_log_alert(aux::stack_allocator& alloc, torrent_handle const& h + , tcp::endpoint const& ep, peer_id const& peer_id, boost::uint32_t flags + , piece_block const* blocks, int num_blocks); + + TORRENT_DEFINE_ALERT(picker_log_alert, 89) + + static const int static_category = alert::picker_log_notification; + virtual std::string message() const TORRENT_OVERRIDE; + +#endif // TORRENT_DISABLE_LOGGING + + enum picker_flags_t + { + // the ratio of partial pieces is too high. This forces a preference + // for picking blocks from partial pieces. + partial_ratio = 0x1, + prioritize_partials = 0x2, + rarest_first_partials = 0x4, + rarest_first = 0x8, + reverse_rarest_first = 0x10, + suggested_pieces = 0x20, + prio_sequential_pieces = 0x40, + sequential_pieces = 0x80, + reverse_pieces = 0x100, + time_critical = 0x200, + random_pieces = 0x400, + prefer_contiguous = 0x800, + reverse_sequential = 0x1000, + backup1 = 0x2000, + backup2 = 0x4000, + end_game = 0x8000 + }; + +#ifndef TORRENT_DISABLE_LOGGING + + // this is a bitmask of which features were enabled for this particular + // pick. The bits are defined in the picker_flags_t enum. + boost::uint32_t picker_flags; + + std::vector blocks() const; + + private: + int m_array_idx; + int m_num_blocks; +#endif // TORRENT_DISABLE_LOGGING + }; + +#undef TORRENT_DEFINE_ALERT_IMPL +#undef TORRENT_DEFINE_ALERT +#undef TORRENT_DEFINE_ALERT_PRIO +#undef TORRENT_CLONE + + enum { num_alert_types = 90 }; // this enum represents "max_alert_index" + 1 +} + + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +#endif + diff --git a/include/libtorrent/alloca.hpp b/include/libtorrent/alloca.hpp new file mode 100644 index 0000000..984c773 --- /dev/null +++ b/include/libtorrent/alloca.hpp @@ -0,0 +1,54 @@ +/* + +Copyright (c) 2008-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ALLOCA + +#include "libtorrent/config.hpp" + +#if defined TORRENT_WINDOWS || defined TORRENT_MINGW + +#include +#define TORRENT_ALLOCA(t, n) static_cast(_alloca(sizeof(t) * (n))) + +#elif defined TORRENT_BSD + +#include +#define TORRENT_ALLOCA(t, n) static_cast(alloca(sizeof(t) * (n))) + +#else + +#include +#define TORRENT_ALLOCA(t, n) static_cast(alloca(sizeof(t) * (n))) + +#endif + +#endif diff --git a/include/libtorrent/allocator.hpp b/include/libtorrent/allocator.hpp new file mode 100644 index 0000000..3c04aae --- /dev/null +++ b/include/libtorrent/allocator.hpp @@ -0,0 +1,59 @@ +/* + +Copyright (c) 2009-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_ALLOCATOR_HPP_INCLUDED +#define TORRENT_ALLOCATOR_HPP_INCLUDED + +#include +#include "libtorrent/config.hpp" + +namespace libtorrent +{ + + TORRENT_EXTRA_EXPORT int page_size(); + + struct TORRENT_EXTRA_EXPORT page_aligned_allocator + { + typedef int size_type; + typedef std::ptrdiff_t difference_type; + + static char* malloc(size_type bytes); + static void free(char* block); +#ifdef TORRENT_DEBUG_BUFFERS + static bool in_use(char const* block); +#endif + }; + +} + +#endif + diff --git a/include/libtorrent/announce_entry.hpp b/include/libtorrent/announce_entry.hpp new file mode 100644 index 0000000..506d46a --- /dev/null +++ b/include/libtorrent/announce_entry.hpp @@ -0,0 +1,202 @@ +/* + +Copyright (c) 2015-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ANNOUNCE_ENTRY_HPP_INCLUDED +#define TORRENT_ANNOUNCE_ENTRY_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/time.hpp" // for time_point +#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 { + struct session_settings; + } + + // 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. + announce_entry(std::string const& u); + announce_entry(); + ~announce_entry(); +#if __cplusplus >= 201103L + announce_entry(announce_entry const&) = default; + announce_entry& operator=(announce_entry const&) = default; +#endif + + // 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; + + // 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; + + // returns the number of seconds to the next announce on this tracker. + // ``min_announce_in()`` returns the number of seconds until we are + // allowed to force another tracker update with this tracker. + // + // If the last time this tracker was contacted failed, ``last_error`` is + // the error code describing what error occurred. + int next_announce_in() const; + int min_announce_in() const; + + // the time of next tracker announce + time_point next_announce; + + // no announces before this time + time_point min_announce; + + // 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. the number of + // current downloaders + int scrape_incomplete; + int scrape_complete; + int scrape_downloaded; + + // the tier this tracker belongs to + boost::uint8_t tier; + + // the max number of failures to announce to this tracker in + // a row, before this tracker is not used anymore. 0 means unlimited + boost::uint8_t fail_limit; + + // the number of times in a row we have failed to announce to this + // tracker. + boost::uint8_t fails:7; + + // true while we're waiting for a response from the tracker. + bool updating:1; + + // 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 programatically via the add_troacker()_ 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. + boost::uint8_t source:4; + + // set to true the first time we receive a valid response + // from this tracker. + bool verified: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; + + // this is false the stats sent to this tracker will be 0 + bool send_stats:1; + + // internal + bool triggered_manually: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(); + + // updates the failure counter and time-outs for re-trying. + // This is called when the tracker announce fails. + void failed(aux::session_settings const& sett, int retry_interval = 0); + +#ifndef TORRENT_NO_DEPRECATE + // deprecated in 1.0 + TORRENT_DEPRECATED + bool will_announce(time_point now) const + { + return now <= next_announce + && (fails < fail_limit || fail_limit == 0) + && !updating; + } +#endif + + // 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) 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; } + + // trims whitespace characters from the beginning of the URL. + void trim(); + }; + +} + +#endif + diff --git a/include/libtorrent/assert.hpp b/include/libtorrent/assert.hpp new file mode 100644 index 0000000..d8dee26 --- /dev/null +++ b/include/libtorrent/assert.hpp @@ -0,0 +1,115 @@ +/* + +Copyright (c) 2007-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_ASSERT + +#include "libtorrent/config.hpp" + +#if (defined TORRENT_DEBUG && TORRENT_USE_ASSERTS) \ + || defined TORRENT_ASIO_DEBUGGING \ + || defined TORRENT_PROFILE_CALLS \ + || defined TORRENT_RELEASE_ASSERTS \ + || defined TORRENT_DEBUG_BUFFERS + +#include +std::string demangle(char const* name); +TORRENT_EXPORT void print_backtrace(char* out, int len, int max_depth = 0 + , void* ctx = NULL); +#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 (0) \ + __pragma( warning(pop) ) +#else +#define TORRENT_WHILE_0 while (0) +#endif + + +// 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 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 assert_fail(#x, __LINE__, __FILE__, TORRENT_FUNCTION, 0, 1); } TORRENT_WHILE_0 + +#define TORRENT_ASSERT(x) \ + do { if (x) {} else assert_fail(#x, __LINE__, __FILE__, TORRENT_FUNCTION, 0, 0); } TORRENT_WHILE_0 + +#if TORRENT_USE_IOSTREAM +#define TORRENT_ASSERT_VAL(x, y) \ + do { if (x) {} else { std::stringstream __s__; __s__ << #y ": " << y; \ + assert_fail(#x, __LINE__, __FILE__, TORRENT_FUNCTION, __s__.str().c_str(), 0); } } TORRENT_WHILE_0 +#else +#define TORRENT_ASSERT_VAL(x, y) TORRENT_ASSERT(x) +#endif + +#else +#include +#define TORRENT_ASSERT_PRECOND(x) assert(x) +#define TORRENT_ASSERT(x) assert(x) +#define TORRENT_ASSERT_VAL(x, y) assert(x) +#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 + +#endif // TORRENT_USE_ASSERTS + +#endif + diff --git a/include/libtorrent/aux_/alert_manager_variadic_emplace.hpp b/include/libtorrent/aux_/alert_manager_variadic_emplace.hpp new file mode 100644 index 0000000..5c360eb --- /dev/null +++ b/include/libtorrent/aux_/alert_manager_variadic_emplace.hpp @@ -0,0 +1,55 @@ + +#if !defined BOOST_PP_IS_ITERATING || !BOOST_PP_IS_ITERATING +// set-up iteration + +#include +#include +#include +#include + +#define BOOST_PP_ITERATION_PARAMS_1 \ + (3, (0, TORRENT_ALERT_MANAGER_MAX_ARITY, \ + "libtorrent/aux_/alert_manager_variadic_emplace.hpp")) +#include BOOST_PP_ITERATE() + + +#else // BOOST_PP_IS_ITERATING + +// loop body + +#define I BOOST_PP_ITERATION() + + template + void emplace_alert(BOOST_PP_ENUM_BINARY_PARAMS(I, A, const& a) ) + { + mutex::scoped_lock lock(m_mutex); +#ifndef TORRENT_NO_DEPRECATE + if (m_dispatch) + { + m_dispatch(std::auto_ptr(new T(m_allocations[m_generation] + BOOST_PP_COMMA_IF(I) + BOOST_PP_ENUM_PARAMS(I, a)))); + return; + } +#endif + // don't add more than this number of alerts, unless it's a + // high priority alert, in which case we try harder to deliver it + // for high priority alerts, double the upper limit + if (m_alerts[m_generation].size() >= m_queue_size_limit + * (1 + T::priority)) + return; + + T alert(m_allocations[m_generation] + BOOST_PP_COMMA_IF(I) + BOOST_PP_ENUM_PARAMS(I, a)); + m_alerts[m_generation].push_back(alert); + + maybe_notify(&alert, lock); + } + +#undef I + +#endif + diff --git a/include/libtorrent/aux_/allocating_handler.hpp b/include/libtorrent/aux_/allocating_handler.hpp new file mode 100644 index 0000000..82a162d --- /dev/null +++ b/include/libtorrent/aux_/allocating_handler.hpp @@ -0,0 +1,153 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ALLOCATING_HANDLER_HPP_INCLUDED +#define TORRENT_ALLOCATING_HANDLER_HPP_INCLUDED + +#include +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { namespace aux +{ + // this is meant to provide the actual storage for the handler allocator. + // There's only a single slot, so the allocator is only supposed to be used + // for handlers where there's only a single outstanding operation at a time, + // per storage object. For instance, peers only ever have one outstanding + // read operation at a time, so it can reuse its storage for read handlers. + template + struct handler_storage + { +#ifdef TORRENT_DEBUG + handler_storage() + : used(false) + {} + + bool used; +#else + handler_storage() {} +#endif + boost::aligned_storage bytes; + private: + handler_storage(handler_storage const&); + }; + + // 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 + { + + // TODO: 3 make sure the handlers we pass in are potentially movable! +#if !defined BOOST_NO_CXX11_RVALUE_REFERENCES + allocating_handler( + Handler&& h, handler_storage& s) + : handler(std::move(h)) + , storage(s) + {} +#endif + + allocating_handler( + Handler const& h, handler_storage& s) + : handler(h) + , storage(s) + {} + +#if !defined BOOST_NO_CXX11_VARIADIC_TEMPLATES \ + && !defined BOOST_NO_CXX11_RVALUE_REFERENCES + template + void operator()(A&&... a) const + { + handler(std::forward(a)...); + } +#else + template + void operator()(A0 const& a0) const + { + handler(a0); + } + + template + void operator()(A0 const& a0, A1 const& a1) const + { + handler(a0, a1); + } + + template + void operator()(A0 const& a0, A1 const& a1, A2 const& a2) const + { + handler(a0, a1, a2); + } +#endif + + friend void* asio_handler_allocate( + std::size_t size, allocating_handler* ctx) + { + TORRENT_UNUSED(size); + TORRENT_ASSERT(size <= Size); +#ifdef TORRENT_DEBUG + TORRENT_ASSERT(!ctx->storage.used); + ctx->storage.used = true; +#endif + return &ctx->storage.bytes; + } + + friend void asio_handler_deallocate( + void* ptr, std::size_t size, allocating_handler* ctx) + { + TORRENT_UNUSED(ptr); + TORRENT_UNUSED(size); + TORRENT_UNUSED(ctx); + + TORRENT_ASSERT(size <= Size); + TORRENT_ASSERT(ptr == &ctx->storage.bytes); +#ifdef TORRENT_DEBUG + ctx->storage.used = false; +#endif + } + + Handler handler; + handler_storage& storage; + }; + +} +} + +#endif + diff --git a/include/libtorrent/aux_/byteswap.hpp b/include/libtorrent/aux_/byteswap.hpp new file mode 100644 index 0000000..32a0f56 --- /dev/null +++ b/include/libtorrent/aux_/byteswap.hpp @@ -0,0 +1,76 @@ +/* + +Copyright (c) 2013-2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BYTESWAP_HPP_INCLUDED +#define TORRENT_BYTESWAP_HPP_INCLUDED + +// this header makes sure htonl(), nothl(), htons() and ntohs() +// are available + +#include "libtorrent/config.hpp" + +#include "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 boost::uint32_t host_to_network(boost::uint32_t x) +{ return htonl(x); } + +inline boost::uint32_t network_to_host(boost::uint32_t x) +{ return ntohl(x); } + +inline boost::uint16_t host_to_network(boost::uint16_t x) +{ return htons(x); } + +inline boost::uint16_t network_to_host(boost::uint16_t x) +{ return ntohs(x); } + +} +} + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#endif // TORRENT_BYTESWAP_HPP_INCLUDED + diff --git a/include/libtorrent/aux_/cpuid.hpp b/include/libtorrent/aux_/cpuid.hpp new file mode 100644 index 0000000..0165e51 --- /dev/null +++ b/include/libtorrent/aux_/cpuid.hpp @@ -0,0 +1,46 @@ +/* + +Copyright (c) 2014, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_CPUID_HPP_INCLUDED +#define TORRENT_CPUID_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +namespace libtorrent { namespace aux +{ + // initialized by static initializers (in cpuid.cpp) + TORRENT_EXTRA_EXPORT extern bool sse42_support; + TORRENT_EXTRA_EXPORT extern bool mmx_support; +} } + +#endif // TORRENT_CPUID_HPP_INCLUDED + diff --git a/include/libtorrent/aux_/disable_warnings_pop.hpp b/include/libtorrent/aux_/disable_warnings_pop.hpp new file mode 100644 index 0000000..8ffbe4f --- /dev/null +++ b/include/libtorrent/aux_/disable_warnings_pop.hpp @@ -0,0 +1,44 @@ +/* + +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..e124fd3 --- /dev/null +++ b/include/libtorrent/aux_/disable_warnings_push.hpp @@ -0,0 +1,78 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wall" +#pragma GCC diagnostic ignored "-Wsign-conversion" +#pragma GCC diagnostic ignored "-Wconversion" +#pragma GCC diagnostic ignored "-Wswitch-enum" +#pragma GCC diagnostic ignored "-Wold-style-cast" +#pragma GCC diagnostic ignored "-Wundef" +#pragma GCC diagnostic ignored "-Wmissing-noreturn" +#pragma GCC diagnostic ignored "-Wdeprecated" +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#pragma GCC diagnostic ignored "-Wshadow" +#pragma GCC diagnostic ignored "-Wunused-variable" +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#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" +#endif + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + diff --git a/include/libtorrent/aux_/escape_string.hpp b/include/libtorrent/aux_/escape_string.hpp new file mode 100644 index 0000000..c5d5f24 --- /dev/null +++ b/include/libtorrent/aux_/escape_string.hpp @@ -0,0 +1,106 @@ +/* + +Copyright (c) 2003-2014, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ESCAPE_STRING_HPP_INCLUDED +#define TORRENT_ESCAPE_STRING_HPP_INCLUDED + +#include +#include "libtorrent/config.hpp" +#include "libtorrent/error_code.hpp" + +namespace libtorrent +{ + namespace string + { + enum flags_t + { + // use lower case alphabet used with i2p + lowercase = 0x1, + // don't insert padding + no_padding = 0x2, + // shortcut used for addresses as sha256 hashes + i2p = lowercase | no_padding + }; + + } + TORRENT_EXTRA_EXPORT std::string unescape_string(std::string const& s, error_code& ec); + // replaces all disallowed URL characters by their %-encoding + TORRENT_EXTRA_EXPORT std::string escape_string(const char* str, int len); + // same as escape_string but does not encode '/' + TORRENT_EXTRA_EXPORT std::string escape_path(const char* str, int len); + // 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); + + // convert a file://-URL to a proper path + TORRENT_EXTRA_EXPORT std::string resolve_file_url(std::string const& url); + + // returns true if the given string (not null 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); + // encodes a string using the base32 scheme + TORRENT_EXTRA_EXPORT std::string base32encode(std::string const& s, int flags=0); + TORRENT_EXTRA_EXPORT std::string base32decode(std::string const& s); + + TORRENT_EXTRA_EXPORT std::string url_has_argument( + std::string const& url, std::string argument, std::string::size_type* out_pos = 0); + + // replaces \ with / + TORRENT_EXTRA_EXPORT void convert_path_to_posix(std::string& path); +#ifdef TORRENT_WINDOWS + TORRENT_EXTRA_EXPORT void convert_path_to_windows(std::string& path); +#endif + + TORRENT_EXTRA_EXPORT std::string read_until(char const*& str, char delim + , char const* end); + +#if defined TORRENT_WINDOWS && TORRENT_USE_WSTRING + TORRENT_EXTRA_EXPORT std::wstring convert_to_wstring(std::string const& s); + TORRENT_EXTRA_EXPORT std::string convert_from_wstring(std::wstring const& s); +#endif + +#if TORRENT_USE_ICONV || TORRENT_USE_LOCALE || defined TORRENT_WINDOWS + TORRENT_EXTRA_EXPORT std::string convert_to_native(std::string const& s); + TORRENT_EXTRA_EXPORT std::string convert_from_native(std::string const& s); +#else + // internal + inline std::string const& convert_to_native(std::string const& s) { return s; } + // internal + inline std::string const& convert_from_native(std::string const& s) { return s; } +#endif +} + +#endif // TORRENT_ESCAPE_STRING_HPP_INCLUDED + diff --git a/include/libtorrent/aux_/file_progress.hpp b/include/libtorrent/aux_/file_progress.hpp new file mode 100644 index 0000000..c4ddf9b --- /dev/null +++ b/include/libtorrent/aux_/file_progress.hpp @@ -0,0 +1,81 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_FILE_PROGRESS_HPP_INCLUDE +#define TORRENT_FILE_PROGRESS_HPP_INCLUDE + +#include +#include + +#include "libtorrent/export.hpp" + +namespace libtorrent +{ +class piece_picker; +class file_storage; +class alert_manager; +struct torrent_handle; + +namespace aux +{ + struct TORRENT_EXTRA_EXPORT file_progress + { + file_progress(); + + void init(piece_picker const& picker + , file_storage const& fs); + + void export_progress(std::vector &fp); + + bool empty() const { return m_file_progress.empty(); } + void clear(); + + void update(file_storage const& fs, int index + , alert_manager* alerts, torrent_handle const& h); + +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant(file_storage const& fs) const; +#endif + + private: + + // this vector contains the number of bytes completely + // downloaded (as in passed-hash-check) in each file. + // this lets us trigger on individual files completing + // the vector is allocated lazily, when file progress + // is first queried by the client + std::vector m_file_progress; + }; +} } + +#endif + diff --git a/include/libtorrent/aux_/merkle.hpp b/include/libtorrent/aux_/merkle.hpp new file mode 100644 index 0000000..8e99c7e --- /dev/null +++ b/include/libtorrent/aux_/merkle.hpp @@ -0,0 +1,48 @@ +/* + +Copyright (c) 2003-2014, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_MERKLE_HPP_INCLUDED +#define TORRENT_MERKLE_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" + +namespace libtorrent +{ + TORRENT_EXTRA_EXPORT int merkle_num_leafs(int); + TORRENT_EXTRA_EXPORT int merkle_num_nodes(int); + TORRENT_EXTRA_EXPORT int merkle_get_parent(int); + TORRENT_EXTRA_EXPORT int merkle_get_sibling(int); +} + +#endif + diff --git a/include/libtorrent/aux_/openssl.hpp b/include/libtorrent/aux_/openssl.hpp new file mode 100644 index 0000000..6280d3c --- /dev/null +++ b/include/libtorrent/aux_/openssl.hpp @@ -0,0 +1,95 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_OPENSSL_HPP_INCLUDED +#define TORRENT_OPENSSL_HPP_INCLUDED + +#ifdef TORRENT_USE_OPENSSL + +// all of OpenSSL causes warnings, so we just have to disable them +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#ifdef TORRENT_WINDOWS +// because openssl includes winsock.h, we must include winsock2.h first +#include +#endif + +#include +#include // for sk_GENERAL_NAME_value +#include // for GENERAL_NAME + +namespace libtorrent { +namespace aux { + +inline void openssl_set_tlsext_hostname(SSL* s, char const* name) +{ +#if OPENSSL_VERSION_NUMBER >= 0x90812f + SSL_set_tlsext_host_name(s, name); +#endif +} + +#if BOOST_VERSION >= 104700 +#if OPENSSL_VERSION_NUMBER >= 0x90812f + +inline void openssl_set_tlsext_servername_callback(SSL_CTX* ctx + , int (*servername_callback)(SSL*, int*, void*)) +{ + SSL_CTX_set_tlsext_servername_callback(ctx, servername_callback); +} + +inline void openssl_set_tlsext_servername_arg(SSL_CTX* ctx, void* userdata) +{ + SSL_CTX_set_tlsext_servername_arg(ctx, userdata); +} + +inline int openssl_num_general_names(GENERAL_NAMES* gens) +{ + return sk_GENERAL_NAME_num(gens); +} + +inline GENERAL_NAME* openssl_general_name_value(GENERAL_NAMES* gens, int i) +{ + return sk_GENERAL_NAME_value(gens, i); +} + +#endif // OPENSSL_VERSION_NUMBER +#endif // BOOST_VERSION + +} +} + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#endif // TORRENT_USE_OPENSSL + +#endif // TORRENT_OPENSSL_HPP_INCLUDED + diff --git a/include/libtorrent/aux_/proxy_settings.hpp b/include/libtorrent/aux_/proxy_settings.hpp new file mode 100644 index 0000000..19e9de6 --- /dev/null +++ b/include/libtorrent/aux_/proxy_settings.hpp @@ -0,0 +1,145 @@ +/* + +Copyright (c) 2003-2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PROXY_SETTINGS_HPP_INCLUDED +#define TORRENT_PROXY_SETTINGS_HPP_INCLUDED + +#include "libtorrent/version.hpp" +#include "libtorrent/config.hpp" + +#include +#include + +namespace libtorrent { +struct settings_pack; +namespace aux { + + struct session_settings; + + // The ``proxy_settings`` structs contains the information needed to + // direct certain traffic to a proxy. + struct TORRENT_DEPRECATED_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 + proxy_settings(settings_pack const& sett); + 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; + +#ifndef TORRENT_NO_DEPRECATE + // the type of proxy to use. Assign one of these to the + // proxy_settings::type field. + enum proxy_type + { + // This is the default, no proxy server is used, 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 an i2p SAM proxy + i2p_proxy + }; +#endif + + // tells libtorrent what kind of proxy server it is. See proxy_type + // enum for options + boost::uint8_t type; + + // the port the proxy server is running on + boost::uint16_t port; + + // 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; + + // 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; + + // if true, tracker connections are subject to the proxy settings + bool proxy_tracker_connections; + }; + + +} +} + +#endif + diff --git a/include/libtorrent/aux_/session_call.hpp b/include/libtorrent/aux_/session_call.hpp new file mode 100644 index 0000000..957d777 --- /dev/null +++ b/include/libtorrent/aux_/session_call.hpp @@ -0,0 +1,108 @@ +/* + +Copyright (c) 2014, Arvid Norberg, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SESSION_CALL_HPP_INCLUDED +#define TORRENT_SESSION_CALL_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/thread.hpp" +#include "libtorrent/aux_/session_impl.hpp" + +#include + +namespace libtorrent { namespace aux { + +void blocking_call(); +void dump_call_profile(); + +void fun_wrap(bool& done, condition_variable& e, mutex& m, boost::function f); + +template +void fun_ret(R& ret, bool& done, condition_variable& e, mutex& m, boost::function f) +{ + ret = f(); + mutex::scoped_lock l(m); + done = true; + e.notify_all(); +} + +void torrent_wait(bool& done, aux::session_impl& ses); + +void sync_call(aux::session_impl& ses, boost::function f); + +template +void sync_call_handle(Handle& h, boost::function f) +{ + bool done = false; + session_impl& ses = static_cast(h->session()); + ses.get_io_service().dispatch(boost::bind(&aux::fun_wrap + , boost::ref(done) + , boost::ref(ses.cond) + , boost::ref(ses.mut), f)); + h.reset(); + aux::torrent_wait(done, ses); +} + +template +Ret sync_call_ret(aux::session_impl& ses, boost::function f) +{ + bool done = false; + Ret r; + ses.get_io_service().dispatch(boost::bind(&fun_ret + , boost::ref(r) + , boost::ref(done) + , boost::ref(ses.cond) + , boost::ref(ses.mut) + , f)); + torrent_wait(done, ses); + return r; +} + +template +void sync_call_ret_handle(Handle& h, Ret& r, boost::function f) +{ + bool done = false; + session_impl& ses = static_cast(h->session()); + ses.get_io_service().dispatch(boost::bind(&aux::fun_ret + , boost::ref(r) + , boost::ref(done) + , boost::ref(ses.cond) + , boost::ref(ses.mut) + , f)); + h.reset(); + aux::torrent_wait(done, 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..6a6424d --- /dev/null +++ b/include/libtorrent/aux_/session_impl.hpp @@ -0,0 +1,1278 @@ +/* + +Copyright (c) 2006, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SESSION_IMPL_HPP_INCLUDED +#define TORRENT_SESSION_IMPL_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/aux_/session_interface.hpp" +#include "libtorrent/uncork_interface.hpp" +#include "libtorrent/linked_list.hpp" +#include "libtorrent/torrent_peer.hpp" +#include "libtorrent/torrent_peer_allocator.hpp" +#include "libtorrent/performance_counters.hpp" // for counters + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include +#include // for va_start, va_end + +#if TORRENT_HAS_BOOST_UNORDERED +#include +#endif + +#ifdef TORRENT_USE_OPENSSL +#include "libtorrent/ssl_stream.hpp" +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#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/session_settings.hpp" +#include "libtorrent/session_status.hpp" +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/stat.hpp" +#include "libtorrent/file_pool.hpp" +#include "libtorrent/bandwidth_manager.hpp" +#include "libtorrent/socket_type.hpp" +#include "libtorrent/disk_io_thread.hpp" +#include "libtorrent/udp_socket.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/thread.hpp" +#include "libtorrent/alert_manager.hpp" // for alert_manager +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/socket_io.hpp" // for print_address +#include "libtorrent/address.hpp" +#include "libtorrent/utp_socket_manager.hpp" +#include "libtorrent/bloom_filter.hpp" +#include "libtorrent/rss.hpp" +#include "libtorrent/peer_class.hpp" +#include "libtorrent/disk_io_job.hpp" // block_cache_reference +#include "libtorrent/network_thread_pool.hpp" +#include "libtorrent/peer_class_type_filter.hpp" +#include "libtorrent/kademlia/dht_observer.hpp" +#include "libtorrent/resolver.hpp" +#include "libtorrent/invariant_check.hpp" + +#if TORRENT_COMPLETE_TYPES_REQUIRED +#include "libtorrent/peer_connection.hpp" +#endif + +namespace libtorrent +{ + + struct plugin; + class upnp; + class natpmp; + class lsd; + class torrent; + class alert; + struct cache_info; + struct torrent_handle; + + namespace dht + { + struct dht_tracker; + class item; + } + + struct bencode_map_entry; + + typedef boost::function dht_extension_handler_t; + + struct listen_socket_t + { + listen_socket_t(): external_port(0), ssl(false) {} + + // this is typically empty but can be set + // to the WAN IP address of NAT-PMP or UPnP router + address external_address; + + // this is typically set to the same as the local + // listen port. In case a NAT port forward was + // successfully opened, this will be set to the + // port that is open on the external (NAT) interface + // on the NAT box itself. This is the port that has + // to be published to peers, since this is the port + // the client is reachable through. + int external_port; + + // set to true if this is an SSL listen socket + bool ssl; + + // the actual socket + boost::shared_ptr sock; + }; + + namespace aux + { + struct session_impl; + struct session_settings; + +#ifndef TORRENT_DISABLE_LOGGING + struct tracker_logger; +#endif + + TORRENT_EXPORT std::pair settings_map(); + + // this is the link between the main thread and the + // thread started to run the main downloader loop + struct TORRENT_EXTRA_EXPORT session_impl TORRENT_FINAL + : session_interface + , dht::dht_observer + , boost::noncopyable + , udp_socket_observer + , uncork_interface + , single_threaded + { + // the size of each allocation that is chained in the send buffer + enum { send_buffer_size_impl = 128 }; + // maximum length of query names which can be registered by extensions + enum { max_dht_query_length = 15 }; + +#ifdef TORRENT_DEBUG +// friend class ::libtorrent::peer_connection; +#endif +#if TORRENT_USE_INVARIANT_CHECKS + friend class libtorrent::invariant_access; +#endif + typedef std::set > connection_map; +#if TORRENT_HAS_BOOST_UNORDERED + typedef boost::unordered_map > torrent_map; +#else + typedef std::map > torrent_map; +#endif + + session_impl(io_service& ios); + virtual ~session_impl(); + + void start_session(settings_pack const& pack); + + void set_load_function(user_load_function_t fun) + { m_user_load_torrent = fun; } + + void init_peer_class_filter(bool unlimited_local); + +#ifndef TORRENT_DISABLE_EXTENSIONS + void add_extension(boost::function( + torrent_handle const&, void*)> ext); + void add_ses_extension(boost::shared_ptr ext); +#endif +#if TORRENT_USE_ASSERTS + bool has_peer(peer_connection const* p) const TORRENT_OVERRIDE; + bool any_torrent_has_peer(peer_connection const* p) const TORRENT_OVERRIDE; + bool is_single_thread() const TORRENT_OVERRIDE { return single_threaded::is_single_thread(); } + bool is_posting_torrent_updates() const TORRENT_OVERRIDE { return m_posting_torrent_updates; } + // this is set while the session is building the + // torrent status update message + bool m_posting_torrent_updates; +#endif + + void open_listen_port(); + + torrent_peer_allocator_interface* get_peer_allocator() TORRENT_OVERRIDE + { return &m_peer_allocator; } + + io_service& get_io_service() TORRENT_OVERRIDE { return m_io_service; } + resolver_interface& get_resolver() TORRENT_OVERRIDE { return m_host_resolver; } + void async_resolve(std::string const& host, int flags + , callback_t const& h) TORRENT_OVERRIDE; + + std::vector& torrent_list(int i) TORRENT_OVERRIDE + { + TORRENT_ASSERT(i >= 0); + TORRENT_ASSERT(i < session_interface::num_torrent_lists); + 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(boost::weak_ptr t) TORRENT_OVERRIDE; + + // if we are listening on an IPv6 interface + // this will return one of the IPv6 addresses on this + // machine, otherwise just an empty endpoint + tcp::endpoint get_ipv6_interface() const TORRENT_OVERRIDE; + tcp::endpoint get_ipv4_interface() const TORRENT_OVERRIDE; + + void async_accept(boost::shared_ptr const& listener, bool ssl); + void on_accept_connection(boost::shared_ptr const& s + , boost::weak_ptr listener, error_code const& e, bool ssl); + void on_socks_listen(boost::shared_ptr const& s + , error_code const& e); + void on_socks_accept(boost::shared_ptr const& s + , error_code const& e); + + void incoming_connection(boost::shared_ptr const& s); + +#ifndef TORRENT_NO_DEPRECATE + feed_handle add_feed(feed_settings const& feed); + void remove_feed(feed_handle h); + void get_feeds(std::vector* f) const; +#endif + + boost::weak_ptr find_torrent(sha1_hash const& info_hash) const TORRENT_OVERRIDE; + boost::weak_ptr find_torrent(std::string const& uuid) const; +#ifndef TORRENT_DISABLE_MUTABLE_TORRENTS + std::vector > find_collection( + std::string const& collection) const TORRENT_OVERRIDE; +#endif + boost::weak_ptr find_disconnect_candidate_torrent() const TORRENT_OVERRIDE; + int num_torrents() const TORRENT_OVERRIDE { return int(m_torrents.size()); } + + void insert_torrent(sha1_hash const& ih, boost::shared_ptr const& t + , std::string uuid) TORRENT_OVERRIDE; + void insert_uuid_torrent(std::string uuid, boost::shared_ptr const& t) TORRENT_OVERRIDE + { m_uuids.insert(std::make_pair(uuid, t)); } + boost::shared_ptr delay_load_torrent(sha1_hash const& info_hash + , peer_connection* pc) TORRENT_OVERRIDE; + void set_queue_position(torrent* t, int p) TORRENT_OVERRIDE; + + peer_id const& get_peer_id() const TORRENT_OVERRIDE { return m_peer_id; } + + void close_connection(peer_connection* p, error_code const& ec) TORRENT_OVERRIDE; + +#ifndef TORRENT_NO_DEPRECATE + void set_settings(libtorrent::session_settings const& s); + libtorrent::session_settings deprecated_settings() const; +#endif + + void apply_settings_pack(boost::shared_ptr pack) TORRENT_OVERRIDE; + void apply_settings_pack_impl(settings_pack const& pack); + session_settings const& settings() const TORRENT_OVERRIDE { return m_settings; } + settings_pack get_settings() const; + +#ifndef TORRENT_DISABLE_DHT + dht::dht_tracker* dht() TORRENT_OVERRIDE { return m_dht.get(); } + bool announce_dht() const TORRENT_OVERRIDE { return !m_listen_sockets.empty(); } + + void add_dht_node_name(std::pair const& node); + void add_dht_node(udp::endpoint n) TORRENT_OVERRIDE; + void add_dht_router(std::pair const& node); + void set_dht_settings(dht_settings const& s); + dht_settings const& get_dht_settings() const { return m_dht_settings; } + void set_dht_storage(dht::dht_storage_constructor_type sc); + void start_dht(); + void stop_dht(); + void start_dht(entry const& startup_state); + bool has_dht() const TORRENT_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(boost::weak_ptr t) TORRENT_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(boost::array key + , std::string salt = std::string()); + + void dht_put_immutable_item(entry const& data, sha1_hash target); + + void dht_put_mutable_item(boost::array key + , boost::function& + , boost::uint64_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, int flags = 0); + + void dht_direct_request(udp::endpoint ep, entry& e, void* userdata = 0); + +#ifndef TORRENT_NO_DEPRECATE + entry dht_state() const; + 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 + + void maybe_update_udp_mapping(int nat, bool ssl, int local_port, int external_port); + +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + torrent const* find_encrypted_torrent( + sha1_hash const& info_hash, sha1_hash const& xor_mask) TORRENT_OVERRIDE; + + void add_obfuscated_hash(sha1_hash const& obfuscated, boost::weak_ptr const& t) TORRENT_OVERRIDE; +#endif + + void on_port_map_log(char const* msg, int map_transport); + + void on_lsd_announce(error_code const& e); +#ifndef TORRENT_DISABLE_LOGGING + void on_lsd_log(char const* log); +#endif + + // called when a port mapping is successful, or a router returns + // a failure to map a port + void on_port_mapping(int mapping, address const& ip, int port + , int protocol, error_code const& ec, int nat_transport); + + bool is_aborted() const TORRENT_OVERRIDE { return m_abort; } + bool is_paused() const TORRENT_OVERRIDE { return m_paused; } + + void pause(); + void resume(); + + void set_ip_filter(boost::shared_ptr const& f); + ip_filter const& get_ip_filter(); + + void set_port_filter(port_filter const& f); + port_filter const& get_port_filter() const TORRENT_OVERRIDE; + void ban_ip(address addr) TORRENT_OVERRIDE; + + void queue_tracker_request(tracker_request& req + , boost::weak_ptr c) TORRENT_OVERRIDE; + + // ==== peer class operations ==== + + // implements session_interface + void set_peer_classes(peer_class_set* s, address const& a, int st) TORRENT_OVERRIDE; + peer_class_pool const& peer_classes() const TORRENT_OVERRIDE { return m_classes; } + peer_class_pool& peer_classes() TORRENT_OVERRIDE { return m_classes; } + bool ignore_unchoke_slots_set(peer_class_set const& set) const TORRENT_OVERRIDE; + int copy_pertinent_channels(peer_class_set const& set + , int channel, bandwidth_channel** dst, int max) TORRENT_OVERRIDE; + int use_quota_overhead(peer_class_set& set, int amount_down, int amount_up) TORRENT_OVERRIDE; + bool use_quota_overhead(bandwidth_channel* ch, int amount); + + int create_peer_class(char const* name); + void delete_peer_class(int 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(int cid); + void set_peer_class(int cid, peer_class_info const& pci); + + bool is_listening() const; + +#ifndef TORRENT_DISABLE_EXTENSIONS + void add_extensions_to_torrent( + boost::shared_ptr const& torrent_ptr, void* userdata); +#endif + + torrent_handle add_torrent(add_torrent_params const&, error_code& ec); + // second return value is true if the torrent was added and false if an + // existing one was found. + std::pair, bool> + add_torrent_impl(add_torrent_params& p, error_code& ec); + void async_add_torrent(add_torrent_params* params); + void on_async_load_torrent(disk_io_job const* j); + + void remove_torrent(torrent_handle const& h, int options) TORRENT_OVERRIDE; + void remove_torrent_impl(boost::shared_ptr tptr, int options) TORRENT_OVERRIDE; + + void get_torrent_status(std::vector* ret + , boost::function const& pred + , boost::uint32_t flags) const; + void refresh_torrent_status(std::vector* ret + , boost::uint32_t flags) const; + void post_torrent_updates(boost::uint32_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); + +#ifndef TORRENT_NO_DEPRECATE + void pop_alerts(); + alert const* pop_alert(); + void pop_alerts(std::deque* alerts); + size_t set_alert_queue_size_limit(size_t queue_size_limit_); + int upload_rate_limit() const; + int download_rate_limit() const; + int local_upload_rate_limit() const; + int local_download_rate_limit() const; + + void set_local_download_rate_limit(int bytes_per_second); + void set_local_upload_rate_limit(int bytes_per_second); + void set_download_rate_limit(int bytes_per_second); + void set_upload_rate_limit(int bytes_per_second); + void set_max_connections(int limit); + void set_max_uploads(int limit); + + int max_connections() const; + int max_uploads() const; +#endif + + bandwidth_manager* get_bandwidth_manager(int channel) TORRENT_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 TORRENT_OVERRIDE; + int num_uploads() const TORRENT_OVERRIDE + { return int(m_stats_counters[counters::num_peers_up_unchoked]); } + int num_connections() const TORRENT_OVERRIDE { return int(m_connections.size()); } + + int peak_up_rate() const { return m_peak_up_rate; } + + void trigger_unchoke() TORRENT_OVERRIDE + { + TORRENT_ASSERT(is_single_thread()); + m_unchoke_time_scaler = 0; + } + void trigger_optimistic_unchoke() TORRENT_OVERRIDE + { + TORRENT_ASSERT(is_single_thread()); + m_optimistic_unchoke_time_scaler = 0; + } + +#ifndef TORRENT_NO_DEPRECATE + session_status status() const; +#endif + + void set_peer_id(peer_id const& id); + void set_key(int key); + address listen_address() const; + boost::uint16_t listen_port() const TORRENT_OVERRIDE; + boost::uint16_t ssl_listen_port() const TORRENT_OVERRIDE; + + alert_manager& alerts() TORRENT_OVERRIDE { return m_alerts; } + disk_interface& disk_thread() TORRENT_OVERRIDE { return m_disk_thread; } + + void abort(); + void abort_stage2(); + + torrent_handle find_torrent_handle(sha1_hash const& info_hash); + + void announce_lsd(sha1_hash const& ih, int port, bool broadcast = false) TORRENT_OVERRIDE; + + void save_state(entry* e, boost::uint32_t flags) const; + void load_state(bdecode_node const* e, boost::uint32_t flags); + + bool has_connection(peer_connection* p) const TORRENT_OVERRIDE; + void insert_peer(boost::shared_ptr const& c) TORRENT_OVERRIDE; + + proxy_settings proxy() const TORRENT_OVERRIDE; + +#ifndef TORRENT_DISABLE_DHT + bool is_dht_running() const { return (m_dht.get() != NULL); } + int external_udp_port() const TORRENT_OVERRIDE { return m_external_udp_port; } +#endif + +#if TORRENT_USE_I2P + char const* i2p_session() const TORRENT_OVERRIDE { return m_i2p_conn.session_id(); } + proxy_settings i2p_proxy() const TORRENT_OVERRIDE; + + void on_i2p_open(error_code const& ec); + void open_new_incoming_i2p_connection(); + void on_i2p_accept(boost::shared_ptr const& s + , error_code const& e); +#endif + + void start_lsd(); + natpmp* start_natpmp(); + upnp* start_upnp(); + + void stop_lsd(); + void stop_natpmp(); + void stop_upnp(); + + int add_port_mapping(int t, int external_port + , int local_port); + void delete_port_mapping(int handle); + + int next_port() const; + + // load the specified torrent, also + // pick the least recently used torrent and unload it, unless + // t is the least recently used, then the next least recently + // used is picked + // returns true if the torrent was loaded successfully + bool load_torrent(torrent* t) TORRENT_OVERRIDE; + + // bump t to the top of the list of least recently used. i.e. + // make it the most recently used. This is done every time + // an action is performed that required the torrent to be + // loaded, indicating activity + void bump_torrent(torrent* t, bool back = true) TORRENT_OVERRIDE; + + // evict torrents until there's space for one new torrent, + void evict_torrents_except(torrent* ignore); + void evict_torrent(torrent* t) TORRENT_OVERRIDE; + + void deferred_submit_jobs() TORRENT_OVERRIDE; + + char* allocate_buffer() TORRENT_OVERRIDE; + torrent_peer* allocate_peer_entry(int type); + void free_peer_entry(torrent_peer* p); + + void free_buffer(char* buf) TORRENT_OVERRIDE; + int send_buffer_size() const TORRENT_OVERRIDE { return send_buffer_size_impl; } + + // implements buffer_allocator_interface + void free_disk_buffer(char* buf) TORRENT_OVERRIDE; + char* allocate_disk_buffer(char const* category) TORRENT_OVERRIDE; + char* allocate_disk_buffer(bool& exceeded + , boost::shared_ptr o + , char const* category) TORRENT_OVERRIDE; + void reclaim_block(block_cache_reference ref) TORRENT_OVERRIDE; + + bool exceeded_cache_use() const + { return m_disk_thread.exceeded_cache_use(); } + + // implements dht_observer + virtual void set_external_address(address const& ip + , address const& source) TORRENT_OVERRIDE; + virtual address external_address() TORRENT_OVERRIDE; + virtual void get_peers(sha1_hash const& ih) TORRENT_OVERRIDE; + virtual void announce(sha1_hash const& ih, address const& addr, int port) TORRENT_OVERRIDE; + virtual void outgoing_get_peers(sha1_hash const& target + , sha1_hash const& sent_target, udp::endpoint const& ep) TORRENT_OVERRIDE; + virtual void log(libtorrent::dht::dht_logger::module_t m, char const* fmt, ...) + TORRENT_OVERRIDE TORRENT_FORMAT(3,4); + virtual void log_packet(message_direction_t dir, char const* pkt, int len + , udp::endpoint node) TORRENT_OVERRIDE; + + virtual bool on_dht_request(char const* query, int query_len + , dht::msg const& request, entry& response) TORRENT_OVERRIDE; + + void set_external_address(address const& ip + , int source_type, address const& source) TORRENT_OVERRIDE; + virtual external_ip const& external_address() const TORRENT_OVERRIDE; + + // used when posting synchronous function + // calls to session_impl and torrent objects + mutable libtorrent::mutex mut; + mutable libtorrent::condition_variable cond; + + // cork a peer and schedule a delayed uncork + // does nothing if the peer is already corked + void cork_burst(peer_connection* p) TORRENT_OVERRIDE; + + // uncork all peers added to the delayed uncork queue + // implements uncork_interface + virtual void do_delayed_uncork() TORRENT_OVERRIDE; + + void post_socket_job(socket_job& j) TORRENT_OVERRIDE; + + // implements session_interface + virtual tcp::endpoint bind_outgoing_socket(socket_type& s, address + const& remote_address, error_code& ec) const TORRENT_OVERRIDE; + virtual bool verify_bound_address(address const& addr, bool utp + , error_code& ec) TORRENT_OVERRIDE; + + bool has_lsd() const TORRENT_OVERRIDE { return m_lsd.get() != NULL; } + + std::vector& block_info_storage() TORRENT_OVERRIDE { return m_block_info_storage; } + + libtorrent::utp_socket_manager* utp_socket_manager() TORRENT_OVERRIDE + { return &m_utp_socket_manager; } + void inc_boost_connections() TORRENT_OVERRIDE { ++m_boost_connections; } + +#ifndef TORRENT_NO_DEPRECATE + // the time when the next rss feed needs updating + time_point m_next_rss_update; + + // update any rss feeds that need updating and + // recalculate m_next_rss_update + void update_rss_feeds(); +#endif + + void update_proxy(); + void update_i2p_bridge(); + void update_peer_tos(); + void update_user_agent(); + void update_unchoke_limit(); + void update_connection_speed(); + void update_queued_disk_bytes(); + void update_alert_queue_size(); + void update_dht_upload_rate_limit(); + void update_disk_threads(); + void update_network_threads(); + void update_cache_buffer_chunk_size(); + 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_upnp(); + void update_natpmp(); + void update_lsd(); + void update_dht(); + void update_count_slow(); + void update_peer_fingerprint(); + void update_dht_bootstrap_nodes(); + + void update_socket_buffer_size(); + void update_dht_announce_interval(); + void update_anonymous_mode(); + void update_force_proxy(); + void update_download_rate(); + void update_upload_rate(); + void update_connections_limit(); +#ifndef TORRENT_NO_DEPRECATE + 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_alert_mask(); + + void trigger_auto_manage() TORRENT_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; + + std::vector m_torrent_lists[num_torrent_lists]; + + peer_class_pool m_classes; + + void init(boost::shared_ptr pack); + + void submit_disk_jobs(); + + void on_trigger_auto_manage(); + + void on_lsd_peer(tcp::endpoint peer, sha1_hash const& ih); + void setup_socket_buffers(socket_type& s) TORRENT_OVERRIDE; + + // the settings for the client + aux::session_settings m_settings; + + counters m_stats_counters; + + // this is a pool allocator for torrent_peer objects + 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; + +#ifndef TORRENT_DISABLE_POOL_ALLOCATOR + // this pool is used to allocate and recycle send + // buffers from. + boost::pool<> m_send_buffers; +#endif + + io_service& m_io_service; + +#ifdef TORRENT_USE_OPENSSL + // this is a generic SSL context used when talking to + // unauthenticated HTTPS servers + ssl::context m_ssl_ctx; +#endif + + // handles delayed alerts + mutable alert_manager m_alerts; + +#ifndef TORRENT_NO_DEPRECATE + // the alert pointers stored in m_alerts + mutable std::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; +#endif + + // handles disk io requests asynchronously + // peers have pointers into the disk buffer + // pool, and must be destructed before this + // object. The disk thread relies on the file + // pool object, and must be destructed before + // m_files. The disk io thread posts completion + // events to the io service, and needs to be + // constructed after it. + disk_io_thread m_disk_thread; + + // a thread pool used for async_write_some calls, + // to distribute its cost to multiple threads + std::vector > m_net_thread_pool; + + // 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; + + // 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; + + // peer class for local peers + peer_class_t m_local_peer_class; + + tracker_manager m_tracker_manager; + torrent_map m_torrents; + +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + // this maps obfuscated hashes to torrents. It's only + // used when encryption is enabled + torrent_map m_obfuscated_torrents; +#endif + + // this is an LRU for torrents. It's used to determine + // which torrents should be loaded into RAM and which ones + // shouldn't. Each torrent that's loaded is part of this + // list. + linked_list m_torrent_lru; + + std::map > m_uuids; + + // when saving resume data for many torrents, torrents are + // queued up in this list in order to not have too many of them + // outstanding at any given time, since the resume data may use + // a lot of memory. + std::list > m_save_resume_queue; + + // the number of save resume data disk jobs that are currently + // outstanding + int m_num_save_resume; + + // peer connections are put here when disconnected to avoid + // race conditions with the disk thread. It's important that + // peer connections are destructed from the network thread, + // once a peer is disconnected, it's put in this list and + // every second their refcount is checked, and if it's 1, + // they are deleted (from the network thread) + std::vector > m_undead_peers; + + // keep the io_service alive until we have posted the job + // to clear the undead peers + boost::optional m_work; + + // this maps sockets to their peer_connection + // object. It is the complete list of all connected + // peers. + connection_map m_connections; + + // this list holds incoming connections while they + // are performing SSL handshake. When we shut down + // the session, all of these are disconnected, otherwise + // they would linger and stall or hang session shutdown + std::set > m_incoming_sockets; + + // maps IP ranges to bitfields representing peer class IDs + // to assign peers matching a specific IP range based on its + // remote endpoint + ip_filter m_peer_class_filter; + + // maps socket types to peer classes + peer_class_type_filter m_peer_class_type_filter; + + // filters incoming connections + boost::shared_ptr m_ip_filter; + + // filters outgoing connections + port_filter m_port_filter; + + // the peer id that is generated at the start of the session + peer_id m_peer_id; + + // this is the highest queue position of any torrent + // in this session. queue positions are packed (i.e. there + // are no gaps). If there are no torrents with queue positions + // this is -1. + int m_max_queue_pos; + + // the key is an id that is used to identify the + // client with the tracker only. It is randomized + // at startup + int m_key; + + // 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; + + // keep this around until everything uses the list of interfaces + // instead. + tcp::endpoint m_listen_interface; + + // 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. + // TODO: should this be renamed m_outgoing_interfaces? + std::vector m_net_interfaces; + + // if we're listening on an IPv6 interface + // this is one of the non local IPv6 interfaces + // on this machine + tcp::endpoint m_ipv6_interface; + tcp::endpoint m_ipv4_interface; + + // since we might be listening on multiple interfaces + // we might need more than one listen socket + std::list m_listen_sockets; + +#if TORRENT_USE_I2P + i2p_connection m_i2p_conn; + boost::shared_ptr m_i2p_listen_socket; +#endif + +#ifdef TORRENT_USE_OPENSSL + ssl::context* ssl_ctx() { return &m_ssl_ctx; } + void on_incoming_utp_ssl(boost::shared_ptr const& s); + void ssl_handshake(error_code const& ec, boost::shared_ptr s); +#endif + + // when as a socks proxy is used for peers, also + // listen for incoming connections on a socks connection + boost::shared_ptr m_socks_listen_socket; + boost::uint16_t m_socks_listen_port; + + // round-robin index into m_net_interfaces + mutable boost::uint8_t m_interface_index; + + void open_new_incoming_socks_connection(); + + enum listen_on_flags_t + { + open_ssl_socket = 0x10 + }; + + listen_socket_t setup_listener(std::string const& device + , boost::asio::ip::tcp const& protocol, int port, int flags + , error_code& ec); + +#ifndef TORRENT_DISABLE_DHT + entry 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; + + // 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; + + // 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; + + // 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; + + // when this scaler reaches zero, it will + // scrape one of the auto managed, paused, + // torrents. + int m_auto_scrape_time_scaler; + +#ifndef TORRENT_NO_DEPRECATE + // the index of the torrent that we'll + // refresh the next time + int m_next_explicit_cache_torrent; + + // this is a counter of the number of seconds until + // the next time the read cache is rotated, if we're + // using an explicit read read cache. + int m_cache_rotation_timer; +#endif + + // the index of the torrent that we'll + // refresh the next time + int m_next_suggest_torrent; + + // this is a counter of the number of seconds until + // the next time the suggest pieces are refreshed + int m_suggest_timer; + + // statistics gathered from all torrents. + stat m_stat; + + // implements session_interface + virtual void sent_bytes(int bytes_payload, int bytes_protocol) TORRENT_OVERRIDE; + virtual void received_bytes(int bytes_payload, int bytes_protocol) TORRENT_OVERRIDE; + virtual void trancieve_ip_packet(int bytes, bool ipv6) TORRENT_OVERRIDE; + virtual void sent_syn(bool ipv6) TORRENT_OVERRIDE; + virtual void received_synack(bool ipv6) TORRENT_OVERRIDE; + + int m_peak_up_rate; + int m_peak_down_rate; + + 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; + boost::uint16_t session_time() const TORRENT_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 + boost::int64_t const ret = total_seconds(aux::time_now() - m_created) + 1; + TORRENT_ASSERT(ret >= 0); + TORRENT_ASSERT(ret <= (std::numeric_limits::max)()); + return static_cast(ret); + } + + 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; + +#ifndef TORRENT_DISABLE_DHT + boost::shared_ptr m_dht; + dht_settings m_dht_settings; + dht::dht_storage_constructor_type m_dht_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; + + // 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; +#endif + + bool incoming_packet(error_code const& ec + , udp::endpoint const&, char const* buf, int size) TORRENT_OVERRIDE; + + // see m_external_listen_port. This is the same + // but for the udp port used by the DHT. + int m_external_udp_port; + + rate_limited_udp_socket m_udp_socket; + libtorrent::utp_socket_manager m_utp_socket_manager; + +#ifdef TORRENT_USE_OPENSSL + // used for uTP connections over SSL + udp_socket m_ssl_udp_socket; + libtorrent::utp_socket_manager m_ssl_utp_socket_manager; +#endif + + // the number of torrent connection boosts + // connections that have been made this second + // this is deducted from the connect speed + int m_boost_connections; + + boost::shared_ptr m_natpmp; + boost::shared_ptr m_upnp; + boost::shared_ptr m_lsd; + + // mask is a bitmask of which protocols to remap on: + // 1: NAT-PMP + // 2: UPnP + void remap_tcp_ports(boost::uint32_t mask, int tcp_port, int ssl_port); + + // 0 is natpmp 1 is upnp + int m_tcp_mapping[2]; + int m_udp_mapping[2]; +#ifdef TORRENT_USE_OPENSSL + int m_ssl_tcp_mapping[2]; + int m_ssl_udp_mapping[2]; +#endif + + // the timer used to fire the tick + deadline_timer m_timer; + aux::handler_storage m_tick_handler_storage; + + template + aux::allocating_handler + make_tick_handler(Handler const& handler) + { + return aux::allocating_handler( + handler, m_tick_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) + torrent_map::iterator m_next_lsd_torrent; + +#ifndef TORRENT_DISABLE_DHT + // torrents are announced on the DHT in a + // round-robin fashion. All torrents are cycled through + // within the DHT announce interval (which defaults to + // 15 minutes) + torrent_map::iterator m_next_dht_torrent; + + // torrents that don't have any peers + // when added should be announced to the DHT + // as soon as possible. Such torrents are put + // in this queue and get announced the next time + // the timer fires, instead of the next one in + // the round-robin sequence. + std::deque > m_dht_torrents; +#endif + + // torrents prioritized to get connection attempts + std::deque, int> > m_prio_torrents; + + // this announce timer is used + // by Local service discovery + deadline_timer m_lsd_announce_timer; + + resolver m_host_resolver; + + // 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; + int m_next_finished_connect_torrent; + + // 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; + + // index into m_torrent_lists[torrent_want_scrape] referring + // to the next torrent to auto-scrape + int m_next_scrape_torrent; + +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const; +#endif + + counters& stats_counters() TORRENT_OVERRIDE { return m_stats_counters; } + + void received_buffer(int size) TORRENT_OVERRIDE; + void sent_buffer(int size) TORRENT_OVERRIDE; + + // each second tick the timer takes a little + // bit longer than one second to trigger. The + // extra time it took is accumulated into this + // counter. Every time it exceeds 1000, torrents + // will tick their timers 2 seconds instead of one. + // this keeps the timers more accurate over time + // as a kind of "leap second" to adjust for the + // accumulated error + boost::uint16_t m_tick_residual; + +#ifndef TORRENT_DISABLE_LOGGING + virtual void session_log(char const* fmt, ...) const TORRENT_OVERRIDE TORRENT_FORMAT(2,3); + virtual void session_vlog(char const* fmt, va_list& va) const TORRENT_OVERRIDE TORRENT_FORMAT(2,0); + + // this list of tracker loggers serves as tracker_callbacks when + // shutting down. This list is just here to keep them alive during + // whe shutting down process + std::list > m_tracker_loggers; +#endif + + // TODO: 2 the throttling of saving resume data could probably be + // factored out into a separate class + virtual void queue_async_resume_data(boost::shared_ptr const& t) TORRENT_OVERRIDE; + virtual void done_async_resume() TORRENT_OVERRIDE; + void async_resume_dispatched(); + + // state for keeping track of external IPs + external_ip m_external_ip; + +#ifndef TORRENT_DISABLE_EXTENSIONS + // this is a list to allow extensions to potentially remove themselves. + typedef std::list > ses_extension_list_t; + ses_extension_list_t m_ses_extensions; + + // the union of all session extensions' implemented_features(). This is + // used to exclude callbacks to the session extensions. + boost::uint32_t m_session_extension_features; + + // std::string could be used for the query names if only all common + // implementations used SSO *glares at gcc* + struct extension_dht_query + { + boost::uint8_t query_len; + boost::array query; + dht_extension_handler_t handler; + }; + typedef std::vector m_extension_dht_queries_t; + m_extension_dht_queries_t m_extension_dht_queries; +#endif + + // if this function is set, it indicates that torrents are allowed + // to be unloaded. If it isn't, torrents will never be unloaded + user_load_function_t m_user_load_torrent; + + // 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; + + // 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; + + // this is also set to true when triggering an auto-manage + // of the torrents. However, if the normal auto-manage + // timer comes along and executes the auto-management, + // this is set to false, which means the triggered event + // no longer needs to execute the auto-management. + bool m_need_auto_manage; + + // set to true when the session object + // is being destructed and the thread + // should exit + bool m_abort; + + // is true if the session is paused + bool m_paused; + +#ifndef TORRENT_NO_DEPRECATE + std::vector > m_feeds; +#endif + + // this is a list of peer connections who have been + // corked (i.e. their network socket) and needs to be + // uncorked at the end of the burst of events. This is + // here to coalesce the effects of bursts of events + // into fewer network writes, saving CPU and possibly + // ending up sending larger network packets + std::vector m_delayed_uncorks; + }; + +#ifndef TORRENT_DISABLE_LOGGING + struct tracker_logger : request_callback + { + tracker_logger(session_interface& ses); + void tracker_warning(tracker_request const& req + , std::string const& str); + void tracker_response(tracker_request const& + , libtorrent::address const& tracker_ip + , std::list
    const& ip_list + , struct tracker_response const& resp); + void tracker_request_timed_out( + tracker_request const&); + void tracker_request_error(tracker_request const& r + , int response_code, error_code const& ec, const std::string& str + , int retry_interval); + void debug_log(const char* fmt, ...) const 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..fcd2c43 --- /dev/null +++ b/include/libtorrent/aux_/session_interface.hpp @@ -0,0 +1,351 @@ +/* + +Copyright (c) 2012, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SESSION_INTERFACE_HPP_INCLUDED +#define TORRENT_SESSION_INTERFACE_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/io_service.hpp" +#include "libtorrent/disk_buffer_holder.hpp" + +#ifndef TORRENT_DISABLE_DHT +#include "libtorrent/socket.hpp" +#endif + +#include "libtorrent/socket.hpp" // for tcp::endpoint + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include + +#ifndef TORRENT_DISABLE_LOGGING +#include +#endif + +#ifdef TORRENT_USE_OPENSSL +#include +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent +{ + class peer_connection; + class torrent; + struct socket_job; +#ifndef TORRENT_NO_DEPRECATE + struct pe_settings; +#endif + struct peer_class_set; + struct bandwidth_channel; + struct bandwidth_manager; + struct peer_class_pool; + struct disk_observer; + struct torrent_peer; + class alert_manager; + struct disk_interface; + struct tracker_request; + struct request_callback; + struct utp_socket_manager; + struct socket_type; + struct block_info; + struct external_ip; + struct torrent_handle; + struct ip_filter; + class port_filter; + struct settings_pack; + struct torrent_peer_allocator_interface; + struct counters; + struct resolver_interface; + +#ifndef TORRENT_DISABLE_DHT + namespace dht + { + struct dht_tracker; + } +#endif +} + +namespace libtorrent { namespace aux +{ + struct proxy_settings; + struct session_settings; + +#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 void session_log(char const* fmt, ...) const TORRENT_FORMAT(2,3) = 0; + virtual void session_vlog(char const* fmt, va_list& va) const = 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 + : buffer_allocator_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 + enum + { + source_dht = 1, + source_peer = 2, + source_tracker = 4, + source_router = 8 + }; + + virtual void set_external_address(address const& ip + , int source_type, address const& source) = 0; + virtual external_ip const& external_address() const = 0; + + virtual disk_interface& disk_thread() = 0; + + virtual alert_manager& alerts() = 0; + + virtual torrent_peer_allocator_interface* get_peer_allocator() = 0; + virtual io_service& get_io_service() = 0; + virtual resolver_interface& get_resolver() = 0; + + typedef boost::function const&)> + callback_t; + + // TODO: 2 remove this. There's already get_resolver() + virtual void async_resolve(std::string const& host, int flags + , callback_t const& h) = 0; + + virtual bool has_connection(peer_connection* p) const = 0; + virtual void insert_peer(boost::shared_ptr const& c) = 0; + + virtual void queue_async_resume_data(boost::shared_ptr const& t) = 0; + virtual void done_async_resume() = 0; + virtual void evict_torrent(torrent* t) = 0; + + virtual void remove_torrent(torrent_handle const& h, int options = 0) = 0; + virtual void remove_torrent_impl(boost::shared_ptr tptr, int options) = 0; + + // port filter + virtual port_filter const& get_port_filter() const = 0; + virtual void ban_ip(address addr) = 0; + + virtual boost::uint16_t session_time() const = 0; + + virtual bool is_paused() 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() = 0; + virtual void trigger_unchoke() = 0; + + virtual boost::weak_ptr find_torrent(sha1_hash const& info_hash) const = 0; + virtual boost::weak_ptr find_disconnect_candidate_torrent() const = 0; + virtual boost::shared_ptr delay_load_torrent(sha1_hash const& info_hash + , peer_connection* pc) = 0; + virtual void insert_torrent(sha1_hash const& ih, boost::shared_ptr const& t + , std::string uuid) = 0; + virtual void insert_uuid_torrent(std::string uuid, boost::shared_ptr const& t) = 0; + virtual void set_queue_position(torrent* t, int p) = 0; + virtual int num_torrents() const = 0; + + virtual peer_id const& get_peer_id() const = 0; + + // cork a peer and schedule a delayed uncork + // does nothing if the peer is already corked + virtual void cork_burst(peer_connection* p) = 0; + + virtual void close_connection(peer_connection* p, error_code const& ec) = 0; + virtual int num_connections() const = 0; + + virtual char* allocate_buffer() = 0; + virtual void free_buffer(char* buf) = 0; + virtual int send_buffer_size() const = 0; + + virtual void deferred_submit_jobs() = 0; + + virtual boost::uint16_t listen_port() const = 0; + virtual boost::uint16_t ssl_listen_port() const = 0; + + // TODO: 2 factor out the thread pool for socket jobs into a separate + // class + // used to (potentially) issue socket write calls onto multiple threads + virtual void post_socket_job(socket_job& j) = 0; + + // load the specified torrent. also evict one torrent, except + // for the one specified, if we are at the limit of loaded torrents + virtual bool load_torrent(torrent* t) = 0; + + // bump the specified torrent to make it the most recently used one + // in the torrent LRU (i.e. the least likely to get unloaded) + virtual void bump_torrent(torrent* t, bool back = true) = 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(boost::weak_ptr t) = 0; + + virtual tcp::endpoint get_ipv6_interface() const = 0; + virtual tcp::endpoint get_ipv4_interface() const = 0; + + virtual void trigger_auto_manage() = 0; + + virtual void apply_settings_pack(boost::shared_ptr pack) = 0; + virtual session_settings const& settings() const = 0; + + virtual void queue_tracker_request(tracker_request& req + , boost::weak_ptr c) = 0; + + // peer-classes + virtual void set_peer_classes(peer_class_set* s, address const& a, int st) = 0; + virtual peer_class_pool const& peer_classes() const = 0; + virtual peer_class_pool& peer_classes() = 0; + virtual bool ignore_unchoke_slots_set(peer_class_set const& set) const = 0; + virtual int copy_pertinent_channels(peer_class_set const& set + , int channel, bandwidth_channel** dst, int max) = 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; + + enum torrent_list_index + { + // this is the set of (subscribed) torrents that have changed + // their states since the last time the user requested updates. + torrent_state_updates, + + // all torrents that want to be ticked every second + torrent_want_tick, + + // all torrents that want more peers and are still downloading + // these typically have higher priority when connecting peers + torrent_want_peers_download, + + // all torrents that want more peers and are finished downloading + torrent_want_peers_finished, + + // torrents that want auto-scrape (only paused auto-managed ones) + torrent_want_scrape, + + // 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 + // unconditionallty) + torrent_downloading_auto_managed, + torrent_seeding_auto_managed, + torrent_checking_auto_managed, + + // all torrents that have resume data to save +// torrent_want_save_resume, + + num_torrent_lists + }; + + virtual std::vector& torrent_list(int i) = 0; + + virtual bool has_lsd() const = 0; + virtual void announce_lsd(sha1_hash const& ih, int port, bool broadcast = false) = 0; + virtual libtorrent::utp_socket_manager* utp_socket_manager() = 0; + virtual void inc_boost_connections() = 0; + virtual void setup_socket_buffers(socket_type& s) = 0; + virtual std::vector& block_info_storage() = 0; + +#ifdef TORRENT_USE_OPENSSL + virtual boost::asio::ssl::context* ssl_ctx() = 0 ; +#endif + +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + virtual torrent const* find_encrypted_torrent( + sha1_hash const& info_hash, sha1_hash const& xor_mask) = 0; + virtual void add_obfuscated_hash(sha1_hash const& obfuscated + , boost::weak_ptr const& t) = 0; +#endif + +#ifndef TORRENT_DISABLE_DHT + virtual bool announce_dht() const = 0; + virtual void add_dht_node(udp::endpoint n) = 0; + virtual bool has_dht() const = 0; + virtual int external_udp_port() const = 0; + virtual dht::dht_tracker* dht() = 0; + virtual void prioritize_dht(boost::weak_ptr t) = 0; +#endif + + virtual counters& stats_counters() = 0; + virtual void received_buffer(int size) = 0; + virtual void sent_buffer(int size) = 0; + protected: + ~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..ad981fb --- /dev/null +++ b/include/libtorrent/aux_/session_settings.hpp @@ -0,0 +1,88 @@ +/* + +Copyright (c) 2012, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_AUX_SESSION_SETTINGS_HPP_INCLUDED +#define TORRENT_AUX_SESSION_SETTINGS_HPP_INCLUDED + +#include "libtorrent/version.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/assert.hpp" + +#include + +namespace libtorrent +{ + TORRENT_EXTRA_EXPORT void initialize_default_settings(aux::session_settings& s); +} + +namespace libtorrent { namespace aux +{ + +#define SET(type) \ + TORRENT_ASSERT((name & settings_pack::type_mask) == settings_pack:: type ## _type_base); \ + if ((name & settings_pack::type_mask) != settings_pack:: type ## _type_base) return; \ + m_ ## type ## s[name - settings_pack:: type ## _type_base] = value + +#define GET(type, default_val) \ + TORRENT_ASSERT((name & settings_pack::type_mask) == settings_pack:: type ## _type_base); \ + if ((name & settings_pack::type_mask) != settings_pack:: type ## _type_base) return default_val; \ + return m_ ## type ## s[name - settings_pack:: type ## _type_base] + + struct TORRENT_EXTRA_EXPORT session_settings + { + friend void libtorrent::save_settings_to_dict( + aux::session_settings const& s, entry::dictionary_type& sett); + + void set_str(int name, std::string const& value) { SET(string); } + std::string const& get_str(int name) const { GET(string, m_strings[0]); } + void set_int(int name, int value) { SET(int); } + int get_int(int name) const { GET(int, 0); } + void set_bool(int name, bool value) { SET(bool); } + bool get_bool(int name) const { GET(bool, false); } + + session_settings(); + + private: + std::string m_strings[settings_pack::num_string_settings]; + int m_ints[settings_pack::num_int_settings]; + // TODO: make this a bitfield + bool m_bools[settings_pack::num_bool_settings]; + }; + +#undef GET +#undef SET + +} } + +#endif + diff --git a/include/libtorrent/aux_/time.hpp b/include/libtorrent/aux_/time.hpp new file mode 100644 index 0000000..a9afb89 --- /dev/null +++ b/include/libtorrent/aux_/time.hpp @@ -0,0 +1,52 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_AUX_TIME_HPP +#define TORRENT_AUX_TIME_HPP + +#include "libtorrent/config.hpp" +#include "libtorrent/time.hpp" +#include +#include + +namespace libtorrent { namespace aux +{ + // returns the current time, as represented by time_point. The + // resolution of this timer is about 100 ms. + time_point const& time_now(); + + TORRENT_EXTRA_EXPORT void update_time_now(); + +} } + +#endif + diff --git a/include/libtorrent/bandwidth_limit.hpp b/include/libtorrent/bandwidth_limit.hpp new file mode 100644 index 0000000..3c7a3f8 --- /dev/null +++ b/include/libtorrent/bandwidth_limit.hpp @@ -0,0 +1,105 @@ +/* + +Copyright (c) 2007-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_BANDWIDTH_CHANNEL_HPP_INCLUDED +#define TORRENT_BANDWIDTH_CHANNEL_HPP_INCLUDED + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/assert.hpp" + +namespace libtorrent { + +// member of peer_connection +struct TORRENT_EXTRA_EXPORT bandwidth_channel +{ + static const int inf = boost::integer_traits::const_max; + + bandwidth_channel(); + + // 0 means infinite + void throttle(int limit); + int throttle() const + { + TORRENT_ASSERT_VAL(m_limit < INT_MAX, m_limit); + return int(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. + boost::int64_t m_quota_left; + + // the limit is the number of bytes + // per second we are allowed to use. + boost::int64_t m_limit; +}; + +} + +#endif + diff --git a/include/libtorrent/bandwidth_manager.hpp b/include/libtorrent/bandwidth_manager.hpp new file mode 100644 index 0000000..26b99b1 --- /dev/null +++ b/include/libtorrent/bandwidth_manager.hpp @@ -0,0 +1,99 @@ +/* + +Copyright (c) 2007-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_BANDWIDTH_MANAGER_HPP_INCLUDED +#define TORRENT_BANDWIDTH_MANAGER_HPP_INCLUDED + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/socket.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/bandwidth_limit.hpp" +#include "libtorrent/bandwidth_queue_entry.hpp" +#include "libtorrent/thread.hpp" +#include "libtorrent/bandwidth_socket.hpp" +#include "libtorrent/time.hpp" + +namespace libtorrent { + +struct TORRENT_EXTRA_EXPORT bandwidth_manager +{ + bandwidth_manager(int channel); + + void close(); + +#if TORRENT_USE_ASSERTS + bool is_queued(bandwidth_socket const* peer) const; +#endif + + int queue_size() const; + boost::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(boost::shared_ptr const& 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 + typedef std::vector queue_t; + queue_t m_queue; + // the number of bytes all the requests in queue are for + boost::int64_t m_queued_bytes; + + // this is the channel within the consumers + // that bandwidth is assigned to (upload or download) + int m_channel; + + bool m_abort; +}; + +} + +#endif + diff --git a/include/libtorrent/bandwidth_queue_entry.hpp b/include/libtorrent/bandwidth_queue_entry.hpp new file mode 100644 index 0000000..61af5c2 --- /dev/null +++ b/include/libtorrent/bandwidth_queue_entry.hpp @@ -0,0 +1,78 @@ +/* + +Copyright (c) 2007-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_BANDWIDTH_QUEUE_ENTRY_HPP_INCLUDED +#define TORRENT_BANDWIDTH_QUEUE_ENTRY_HPP_INCLUDED + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/bandwidth_limit.hpp" +#include "libtorrent/bandwidth_socket.hpp" + +namespace libtorrent { + +struct TORRENT_EXTRA_EXPORT bw_request +{ + bw_request(boost::shared_ptr const& pe + , int blk, int prio); + + boost::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(); + + enum { max_bandwidth_channels = 10 }; + // we don't actually support more than 10 channels per peer + bandwidth_channel* channel[max_bandwidth_channels]; +}; + +} + +#endif + diff --git a/include/libtorrent/bandwidth_socket.hpp b/include/libtorrent/bandwidth_socket.hpp new file mode 100644 index 0000000..9bf7f32 --- /dev/null +++ b/include/libtorrent/bandwidth_socket.hpp @@ -0,0 +1,48 @@ +/* + +Copyright (c) 2009-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_BANDWIDTH_SOCKET_HPP_INCLUDED +#define TORRENT_BANDWIDTH_SOCKET_HPP_INCLUDED + +namespace libtorrent +{ + struct TORRENT_EXTRA_EXPORT bandwidth_socket + { + virtual void assign_bandwidth(int channel, int amount) = 0; + virtual bool is_disconnecting() const = 0; + virtual ~bandwidth_socket() {} + }; +} + +#endif // TORRENT_BANDWIDTH_SOCKET_HPP_INCLUDED + diff --git a/include/libtorrent/bdecode.hpp b/include/libtorrent/bdecode.hpp new file mode 100644 index 0000000..d95a59d --- /dev/null +++ b/include/libtorrent/bdecode.hpp @@ -0,0 +1,420 @@ +/* + +Copyright (c) 2015-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/assert.hpp" + +#ifndef TORRENT_BDECODE_HPP +#define TORRENT_BDECODE_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& get_bdecode_category(); + +namespace bdecode_errors +{ + // libtorrent uses boost.system's ``error_code`` class to represent + // errors. libtorrent has its own error category get_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; }; + + template<> struct is_error_condition_enum + { static const bool value = true; }; +} } + +namespace libtorrent { + + typedef boost::system::error_code error_code; + +TORRENT_EXTRA_EXPORT char const* parse_int(char const* start + , char const* end, char delimiter, boost::int64_t& val + , bdecode_errors::error_code_enum& ec); + +namespace detail +{ +// internal +struct bdecode_token +{ + // the node with type 'end' is a logical node, pointing to the end + // of the bencoded buffer. + enum type_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(boost::uint32_t off, bdecode_token::type_t t) + : offset(off) + , type(t) + , next_item(0) + , header(0) + { + TORRENT_ASSERT(off <= max_offset); + TORRENT_ASSERT(t >= 0 && t <= end); + } + + bdecode_token(boost::uint32_t off, boost::uint32_t next + , bdecode_token::type_t t, boost::uint8_t header_size = 0) + : offset(off) + , type(t) + , next_item(next) + , header(type == string ? header_size - 2 : 0) + { + TORRENT_ASSERT(type != string || header_size >= 2); + 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 >= 0 && t <= end); + } + + int start_offset() const { TORRENT_ASSERT(type == string); return header + 2; } + + // offset into the bdecoded buffer where this node is + boost::uint32_t offset:29; + + // one of type_t enums + boost::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 + boost::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. + boost::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 +{ + 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); + + // creates a default constructed node, it will have the type ``none_t``. + bdecode_node(); + + // 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&); + + // 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; + + // returns true if type() != none_t. + operator bool() const; + + // 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. + std::pair data_section() 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. + bdecode_node list_at(int i) const; + std::string list_string_value_at(int i + , char const* default_val = ""); + boost::int64_t list_int_value_at(int i + , boost::int64_t default_val = 0); + 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 null-terminated string overloads, + // but have to use ``std::string`` instead. ``dict_find_list`` will return a + // valid ``bdecode_node`` if the key is found _and_ it is a list. Otherwise + // it will return a default-constructed bdecode_node. + // + // Functions with the ``_value`` suffix return the value of the node + // directly, rather than the nodes. In case the node is not found, or it has + // a different type, a default value is returned (which can be specified). + bdecode_node dict_find(std::string key) const; + bdecode_node dict_find(char const* key) const; + std::pair dict_at(int i) const; + bdecode_node dict_find_dict(std::string key) const; + bdecode_node dict_find_dict(char const* key) const; + bdecode_node dict_find_list(char const* key) const; + bdecode_node dict_find_string(char const* key) const; + bdecode_node dict_find_int(char const* key) const; + std::string dict_find_string_value(char const* key + , char const* default_value = "") const; + boost::int64_t dict_find_int_value(char const* key + , boost::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. + boost::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* null-terminated. + // ``string_length()`` returns the number of bytes in the string. + std::string string_value() const; + char const* string_ptr() const; + int string_length() const; + + // resets the ``bdecoded_node`` to a default constructed state. If this is + // an owning node, the tree is freed and all child nodes are invalidated. + void clear(); + + // Swap contents. + void swap(bdecode_node& n); + + // pre-allocate 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); + +private: + bdecode_node(detail::bdecode_token const* tokens, char const* buf + , int len, int idx); + + // if this is the root node, that owns all the tokens, they live in this + // vector. If this is a sub-node, this field is not used, instead the + // m_root_tokens pointer points to the root node's token. + std::vector m_tokens; + + // this points to the root nodes token vector + // for the root node, this points to its own m_tokens member + detail::bdecode_token const* m_root_tokens; + + // this points to the original buffer that was parsed + char const* m_buffer; + int m_buffer_size; + + // 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; + + // 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; + mutable int m_last_token; + + // the number of elements in this list or dict (computed on the first + // call to dict_size() or list_size()) + mutable int m_size; +}; + +// 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-null, 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 = 0, int depth_limit = 100 + , int token_limit = 1000000); + +} + +#endif // TORRENT_BDECODE_HPP + diff --git a/include/libtorrent/bencode.hpp b/include/libtorrent/bencode.hpp new file mode 100644 index 0000000..a31d362 --- /dev/null +++ b/include/libtorrent/bencode.hpp @@ -0,0 +1,460 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + + +#ifndef TORRENT_BENCODE_HPP_INCLUDED +#define TORRENT_BENCODE_HPP_INCLUDED + + + +// OVERVIEW +// +// Bencoding is a common representation in bittorrent used for 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 state between sessions. +// +// Strings in bencoded structures are not necessarily representing 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`_. +// +// There are two mechanims to *decode* bencoded buffers in libtorrent. +// +// The most flexible one is `bdecode() bencode()`_, which returns a structure +// represented by entry. Oncea buffer has been decoded with this function, it +// can be discarded. The entry does not contain any references back to it. This +// means that bdecode() copies all the data out of the buffer and into its own +// hierarchy. This makes this function expensive, which might matter if you're +// parsing large amounts of data. +// +// Another consideration is that `bdecode() bencode()`_ is a recursive parser. +// For this reason, in order to avoid DoS attacks by triggering a stack +// overflow, there is a recursion limit. This limit is a sanity check to make +// sure it doesn't run the risk of busting the stack. +// +// The second mechanism is the decode function for 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. +// +// Not only is this function more efficient because of less memory allocation +// and data copy, the parser is also not recursive, which means it probably +// performs a little bit better and can have a higher recursion limit on the +// structures it's parsing. + +#include +#include +#include +#include // for distance + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/entry.hpp" +#include "libtorrent/config.hpp" + +#include "libtorrent/assert.hpp" +#include "libtorrent/io.hpp" // for write_string + +namespace libtorrent +{ + +#ifndef TORRENT_NO_DEPRECATE + // thrown by bdecode() if the provided bencoded buffer does not contain + // valid encoding. + struct invalid_encoding: std::exception + { + // hidden + virtual const char* what() const TORRENT_EXCEPTION_THROW_SPECIFIER + TORRENT_OVERRIDE TORRENT_FINAL + { return "invalid bencoding"; } + }; +#endif + + namespace detail + { + template + int write_integer(OutIt& out, entry::integer_type val) + { + // the stack allocated buffer for keeping the + // decimal representation of the number can + // not hold number bigger than this: + BOOST_STATIC_ASSERT(sizeof(entry::integer_type) <= 8); + char buf[21]; + int ret = 0; + for (char const* str = integer_to_str(buf, 21, val); + *str != 0; ++str) + { + *out = *str; + ++out; + ++ret; + } + return ret; + } + + 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 (entry::list_type::const_iterator i = e.list().begin(); i != e.list().end(); ++i) + ret += bencode_recursive(out, *i); + write_char(out, 'e'); + ret += 2; + break; + case entry::dictionary_t: + write_char(out, 'd'); + for (entry::dictionary_type::const_iterator i = e.dict().begin(); + i != e.dict().end(); ++i) + { + // 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 += e.preformatted().size(); + break; + case entry::undefined_t: + + // empty string + write_char(out, '0'); + write_char(out, ':'); + + ret += 2; + break; + } + return ret; + } + + 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; +#ifdef TORRENT_DEBUG + ret.m_type_queried = false; +#endif + return; + } + switch (*in) + { + + // ---------------------------------------------- + // integer + case 'i': + { + ++in; // 'i' + std::string 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() = strtoll(val.c_str(), &end_pointer, 10); +#ifdef TORRENT_DEBUG + 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().push_back(entry()); + entry& e = ret.list().back(); + bdecode_recursive(in, end, e, err, depth + 1); + if (err) + { +#ifdef TORRENT_DEBUG + ret.m_type_queried = false; +#endif + return; + } + if (in == end) + { + err = true; +#ifdef TORRENT_DEBUG + ret.m_type_queried = false; +#endif + return; + } + } +#ifdef TORRENT_DEBUG + 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) + { +#ifdef TORRENT_DEBUG + ret.m_type_queried = false; +#endif + return; + } + entry& e = ret[key.string()]; + bdecode_recursive(in, end, e, err, depth + 1); + if (err) + { +#ifdef TORRENT_DEBUG + ret.m_type_queried = false; +#endif + return; + } + if (in == end) + { + err = true; +#ifdef TORRENT_DEBUG + ret.m_type_queried = false; +#endif + return; + } + } +#ifdef TORRENT_DEBUG + ret.m_type_queried = false; +#endif + TORRENT_ASSERT(*in == 'e'); + ++in; // 'e' + } break; + + // ---------------------------------------------- + // string + default: + if (is_digit(boost::uint8_t(*in))) + { + std::string len_s = read_until(in, end, ':', err); + if (err) + { +#ifdef TORRENT_DEBUG + 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) + { +#ifdef TORRENT_DEBUG + ret.m_type_queried = false; +#endif + return; + } + } + else + { + err = true; +#ifdef TORRENT_DEBUG + ret.m_type_queried = false; +#endif + return; + } +#ifdef TORRENT_DEBUG + ret.m_type_queried = false; +#endif + } + } + } + + // These functions will encode data to bencoded or decode bencoded data. + // + // If possible, ``bdecode()`` producing a bdecode_node should be preferred + // over this function. + // + // 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. + // + // The ``OutIt`` and ``InIt`` are iterators + // (InputIterator_ and OutputIterator_ respectively). They + // are templates and are usually instantiated as ostream_iterator_, + // back_insert_iterator_ or istream_iterator_. These + // functions will assume that the iterator refers to a character + // (``char``). So, if you want to encode entry ``e`` into a buffer + // in memory, you can do it like this:: + // + // std::vector buffer; + // bencode(std::back_inserter(buf), e); + // + // .. _InputIterator: http://www.sgi.com/tech/stl/InputIterator.html + // .. _OutputIterator: http://www.sgi.com/tech/stl/OutputIterator.html + // .. _ostream_iterator: http://www.sgi.com/tech/stl/ostream_iterator.html + // .. _back_insert_iterator: http://www.sgi.com/tech/stl/back_insert_iterator.html + // .. _istream_iterator: http://www.sgi.com/tech/stl/istream_iterator.html + // + // If you want to decode a torrent file from a buffer in memory, you can do it like this:: + // + // std::vector buffer; + // // ... + // entry e = bdecode(buf.begin(), buf.end()); + // + // Or, if you have a raw char buffer:: + // + // const char* buf; + // // ... + // entry e = bdecode(buf, buf + data_size); + // + // Now we just need to know how to retrieve information from the entry. + // + // If ``bdecode()`` encounters invalid encoded data in the range given to it + // it will return a default constructed ``entry`` object. + template int bencode(OutIt out, const entry& e) + { + return detail::bencode_recursive(out, e); + } + template entry bdecode(InIt start, InIt end) + { + entry e; + bool err = false; + detail::bdecode_recursive(start, end, e, err, 0); +#ifdef TORRENT_DEBUG + TORRENT_ASSERT(e.m_type_queried == false); +#endif + if (err) return entry(); + return e; + } + template entry bdecode(InIt start, InIt end, int& len) + { + entry e; + bool err = false; + InIt s = start; + detail::bdecode_recursive(start, end, e, err, 0); + len = std::distance(s, start); + TORRENT_ASSERT(len >= 0); + if (err) return entry(); + return e; + } +} + +#endif // TORRENT_BENCODE_HPP_INCLUDED + diff --git a/include/libtorrent/bitfield.hpp b/include/libtorrent/bitfield.hpp new file mode 100644 index 0000000..cd81595 --- /dev/null +++ b/include/libtorrent/bitfield.hpp @@ -0,0 +1,264 @@ +/* + +Copyright (c) 2008-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BITFIELD_HPP_INCLUDED +#define TORRENT_BITFIELD_HPP_INCLUDED + +#include "libtorrent/assert.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/byteswap.hpp" + +#include // for memset and memcpy +#include // for malloc, free and realloc +#include // uint32_t +#include // for min() + +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(): m_buf(NULL) {} + bitfield(int bits): m_buf(NULL) + { resize(bits); } + bitfield(int bits, bool val): m_buf(NULL) + { resize(bits, val); } + bitfield(char const* b, int bits): m_buf(NULL) + { assign(b, bits); } + bitfield(bitfield const& rhs): m_buf(NULL) + { assign(rhs.data(), rhs.size()); } +#if __cplusplus > 199711L + bitfield(bitfield&& rhs): m_buf(rhs.m_buf) + { rhs.m_buf = NULL; } +#endif + + // hidden + ~bitfield() { dealloc(); } + + // copy bitfield from buffer ``b`` of ``bits`` number of bits, rounded up to + // the nearest byte boundary. + void assign(char const* b, int bits) + { + resize(bits); + if (bits > 0) + { + std::memcpy(m_buf, b, 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 + { return get_bit(index); } + + bool get_bit(int index) const + { + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < size()); + return (m_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) + { + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < size()); + m_buf[index / 32] &= aux::host_to_network(~(0x80000000 >> (index & 31))); + } + void set_bit(int index) + { + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < size()); + m_buf[index / 32] |= aux::host_to_network((0x80000000 >> (index & 31))); + } + + // returns true if all bits in the bitfield are set + bool all_set() const; + + bool none_set() const + { + const int words = num_words(); + for (int i = 0; i < words; ++i) + { + if (m_buf[i] != 0) return false; + } + return true; + } + + // returns the size of the bitfield in bits. + int size() const + { + return m_buf == NULL ? 0 : int(m_buf[-1]); + } + + int num_words() const + { + return (size() + 31) / 32; + } + + // returns true if the bitfield has zero size. + bool empty() const { return m_buf == NULL ? true : m_buf[-1] == 0; } + + // returns a pointer to the internal buffer of the bitfield. + char const* data() const { return reinterpret_cast(m_buf); } + +#ifndef TORRENT_NO_DEPRECATE + TORRENT_DEPRECATED + char const* bytes() const { return data(); } +#endif + + // copy operator + bitfield& operator=(bitfield const& rhs) + { + assign(rhs.data(), rhs.size()); + return *this; + } + + // count the number of bits in the bitfield that are set to 1. + int count() const; + + struct const_iterator + { + friend struct bitfield; + + typedef bool value_type; + typedef ptrdiff_t difference_type; + typedef bool const* pointer; + typedef bool& reference; + typedef std::forward_iterator_tag iterator_category; + + bool operator*() { return (*buf & aux::host_to_network(bit)) != 0; } + const_iterator& operator++() { inc(); return *this; } + const_iterator operator++(int) + { const_iterator ret(*this); inc(); return ret; } + const_iterator& operator--() { dec(); return *this; } + const_iterator operator--(int) + { const_iterator ret(*this); dec(); return ret; } + + const_iterator(): buf(0), bit(0x80000000) {} + bool operator==(const_iterator const& rhs) const + { return buf == rhs.buf && bit == rhs.bit; } + + bool operator!=(const_iterator const& rhs) const + { 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(boost::uint32_t const* ptr, int offset) + : buf(ptr), bit(0x80000000 >> offset) {} + boost::uint32_t const* buf; + boost::uint32_t bit; + }; + + const_iterator begin() const { return const_iterator(m_buf, 0); } + const_iterator end() const { return const_iterator( + m_buf + num_words() - (((size() & 31) == 0) ? 0 : 1), 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() + { + std::memset(m_buf, 0xff, size_t(num_words() * 4)); + clear_trailing_bits(); + } + void clear_all() + { + std::memset(m_buf, 0x00, size_t(num_words() * 4)); + } + + // make the bitfield empty, of zero size. + void clear() { dealloc(); } + + private: + + void clear_trailing_bits() + { + // clear the tail bits in the last byte + if (size() & 31) m_buf[num_words() - 1] &= aux::host_to_network(0xffffffff << (32 - (size() & 31))); + } + + void dealloc() + { + if (m_buf) std::free(m_buf-1); + m_buf = NULL; + } + + // the first element is not part of the bitfield, it's the + // number of bits. For this purpose, the m_buf actually points + // the element 1, not 0. To access the size (in bits), access + // m_buf[-1] + boost::uint32_t* m_buf; + }; + +} + +#endif // TORRENT_BITFIELD_HPP_INCLUDED + diff --git a/include/libtorrent/block_cache.hpp b/include/libtorrent/block_cache.hpp new file mode 100644 index 0000000..f9de386 --- /dev/null +++ b/include/libtorrent/block_cache.hpp @@ -0,0 +1,548 @@ +/* + +Copyright (c) 2010-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BLOCK_CACHE +#define TORRENT_BLOCK_CACHE + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/time.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/io_service_fwd.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/sliding_average.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/tailqueue.hpp" +#include "libtorrent/linked_list.hpp" +#include "libtorrent/disk_buffer_pool.hpp" +#include "libtorrent/file.hpp" // for iovec_t + +#if TORRENT_USE_ASSERTS +#include "libtorrent/disk_io_job.hpp" +#endif + +namespace libtorrent +{ + struct disk_io_job; + class piece_manager; + struct disk_buffer_pool; + struct cache_status; + struct block_cache_reference; + struct counters; + namespace aux { struct session_settings; } +#if TORRENT_USE_ASSERTS + class file_storage; +#endif + +#if TORRENT_USE_ASSERTS + struct piece_log_t + { + piece_log_t(int j, int b= -1): job(j), block(b) {} + int job; + int block; + + // these are "jobs" thar cause piece_refcount + // to be incremented + enum artificial_jobs + { + flushing = disk_io_job::num_job_ids, // 20 + flush_expired, + try_flush_write_blocks, + try_flush_write_blocks2, + flush_range, + clear_outstanding_jobs, + set_outstanding_jobs, + + last_job + }; + + static char const* const job_names[7]; + }; + + char const* job_name(int j); + + void print_piece_log(std::vector const& piece_log); + void assert_print_piece(cached_piece_entry const* pe); + +#endif + + extern const char* const job_action_name[]; + + struct TORRENT_EXTRA_EXPORT partial_hash + { + partial_hash(): offset(0) {} + // the number of bytes in the piece that has been hashed + int offset; + // the sha-1 context + hasher h; + }; + + struct cached_block_entry + { + cached_block_entry() + : buf(0) + , refcount(0) + , dirty(false) + , pending(false) + { +#if TORRENT_USE_ASSERTS + hashing_count = 0; + reading_count = 0; + flushing_count = 0; +#endif + } + + char* buf; + + enum { max_refcount = (1 << 30) - 1 }; + + // the number of references to this buffer. These references + // might be in outstanding asynchronous requests or in peer + // connection send buffers. We can't free the buffer until + // all references are gone and refcount reaches 0. The buf + // pointer in this struct doesn't count as a reference and + // is always the last to be cleared + boost::uint32_t refcount:30; + + // if this is true, this block needs to be written to + // disk before it's freed. Typically all blocks in a piece + // would either be dirty (write coalesce cache) or not dirty + // (read-ahead cache). Once blocks are written to disk, the + // dirty flag is cleared and effectively turns the block + // into a read cache block + boost::uint32_t dirty:1; + + // pending means that this buffer has not yet been filled in + // with valid data. There's an outstanding read job for this. + // If the dirty flag is set, it means there's an outstanding + // write job to write this block. + boost::uint32_t pending:1; + +#if TORRENT_USE_ASSERTS + // this many of the references are held by hashing operations + int hashing_count; + // this block is being used in this many peer's send buffers currently + int reading_count; + // the number of references held by flushing operations + int flushing_count; +#endif + }; + + // list_node is here to be able to link this cache entry + // into one of the LRU lists + struct TORRENT_EXTRA_EXPORT cached_piece_entry : list_node + { + cached_piece_entry(); + ~cached_piece_entry(); +#if __cplusplus >= 201103L + cached_piece_entry(cached_piece_entry const&) = default; + cached_piece_entry& operator=(cached_piece_entry const&) = default; +#endif + + bool ok_to_evict(bool ignore_hash = false) const + { + return refcount == 0 + && piece_refcount == 0 + && num_blocks == 0 + && !hashing + && read_jobs.size() == 0 + && outstanding_read == 0 + && (ignore_hash || !hash || hash->offset == 0); + } + + // storage this piece belongs to + boost::shared_ptr storage; + + // write jobs hanging off of this piece + tailqueue jobs; + + // read jobs waiting for the read job currently outstanding + // on this piece to complete. These are executed at that point. + tailqueue read_jobs; + + int get_piece() const { return piece; } + void* get_storage() const { return storage.get(); } + + bool operator==(cached_piece_entry const& rhs) const + { return storage.get() == rhs.storage.get() && piece == rhs.piece; } + + // if this is set, we'll be calculating the hash + // for this piece. This member stores the interim + // state while we're calculating the hash. + partial_hash* hash; + + // set to a unique identifier of a peer that last + // requested from this piece. + void* last_requester; + + // the pointers to the block data. If this is a ghost + // cache entry, there won't be any data here + boost::shared_array blocks; + + // the last time a block was written to this piece + // plus the minimum amount of time the block is guaranteed + // to stay in the cache + //TODO: make this 32 bits and to count seconds since the block cache was created + time_point expire; + + boost::uint64_t piece:22; + + // the number of dirty blocks in this piece + boost::uint64_t num_dirty:14; + + // the number of blocks in the cache for this piece + boost::uint64_t num_blocks:14; + + // the total number of blocks in this piece (and the number + // of elements in the blocks array) + boost::uint64_t blocks_in_piece:14; + + // ---- 64 bit boundary ---- + + // while we have an outstanding async hash operation + // working on this piece, 'hashing' is set to 1 + // When the operation returns, this is set to 0. + boost::uint32_t hashing:1; + + // if we've completed at least one hash job on this + // piece, and returned it. This is set to one + boost::uint32_t hashing_done:1; + + // if this is true, whenever refcount hits 0, + // this piece should be deleted + boost::uint32_t marked_for_deletion:1; + + // this is set to true once we flush blocks past + // the hash cursor. Once this happens, there's + // no point in keeping cache blocks around for + // it in avoid_readback mode + boost::uint32_t need_readback:1; + + // indicates which LRU list this piece is chained into + enum cache_state_t + { + // this is the LRU list for pieces with dirty blocks + write_lru, + + // this is the LRU list for volatile pieces. i.e. + // pieces with very low cache priority. These are + // always the first ones to be evicted. + volatile_read_lru, + + // this is the LRU list for read blocks that have + // been requested once + read_lru1, + + // the is the LRU list for read blocks that have + // been requested once recently, but then was evicted. + // if these are requested again, they will be moved + // to list 2, the frequently requested pieces + read_lru1_ghost, + + // this is the LRU of frequently used pieces. Any + // piece that has been requested by a second peer + // while pulled in to list 1 by a different peer + // is moved into this list + read_lru2, + + // this is the LRU of frequently used pieces but + // that has been recently evicted. If a piece in + // this list is requested, it's moved back into list 2. + read_lru2_ghost, + num_lrus + }; + + boost::uint32_t cache_state:3; + + // this is the number of threads that are currently holding + // a reference to this piece. A piece may not be removed from + // the cache while this is > 0 + boost::uint32_t piece_refcount:7; + + // if this is set to one, it means there is an outstanding + // flush_hashed job for this piece, and there's no need to + // issue another one. + boost::uint32_t outstanding_flush:1; + + // as long as there is a read operation outstanding on this + // piece, this is set to 1. Otherwise 0. + // the purpose is to make sure not two threads are reading + // the same blocks at the same time. If a new read job is + // added when this is 1, that new job should be hung on the + // read job queue (read_jobs). + boost::uint32_t outstanding_read:1; + + // the number of blocks that have >= 1 refcount + boost::uint32_t pinned:16; + + // ---- 32 bit boundary --- + + // the sum of all refcounts in all blocks + boost::uint32_t refcount; + +#if TORRENT_USE_ASSERTS + // the number of times this piece has finished hashing + int hash_passes; + + // this is a debug facility to keep a log + // of which operations have been run on this piece + std::vector piece_log; + + bool in_storage; + bool in_use; +#endif + }; + + // internal + inline std::size_t hash_value(cached_piece_entry const& p) + { + return std::size_t(p.storage.get()) + std::size_t(p.piece); + } + + struct TORRENT_EXTRA_EXPORT block_cache : disk_buffer_pool + { + block_cache(int block_size, io_service& ios + , boost::function const& trigger_trim); + + private: + + typedef boost::unordered_set cache_t; + + public: + + typedef cache_t::iterator iterator; + typedef cache_t::const_iterator const_iterator; + + // returns the number of blocks this job would cause to be read in + int pad_job(disk_io_job const* j, int blocks_in_piece + , int read_ahead) const; + + void reclaim_block(block_cache_reference const& ref); + + // returns a range of all pieces. This migh be a very + // long list, use carefully + std::pair all_pieces() const; + int num_pieces() const { return int(m_pieces.size()); } + + list_iterator write_lru_pieces() const + { return m_lru[cached_piece_entry::write_lru].iterate(); } + + int num_write_lru_pieces() const { return int(m_lru[cached_piece_entry::write_lru].size()); } + + // mark this piece for deletion. If there are no outstanding + // requests to this piece, it's removed immediately, and the + // passed in iterator will be invalidated + void mark_for_deletion(cached_piece_entry* p); + + // similar to mark_for_deletion, except for actually marking the + // piece for deletion. If the piece was actually deleted, + // the function returns true + bool evict_piece(cached_piece_entry* p, tailqueue& jobs); + + // if this piece is in L1 or L2 proper, move it to + // its respective ghost list + void move_to_ghost(cached_piece_entry* p); + + // returns the number of bytes read on success (cache hit) + // -1 on cache miss + int try_read(disk_io_job* j, bool expect_no_fail = false); + + // called when we're reading and we found the piece we're + // reading from in the hash table (not necessarily that we + // hit the block we needed) + void cache_hit(cached_piece_entry* p, void* requester, bool volatile_read); + + // free block from piece entry + void free_block(cached_piece_entry* pe, int block); + + // erase a piece (typically from the ghost list). Reclaim all + // its blocks and unlink it and free it. + void erase_piece(cached_piece_entry* p); + + // bump the piece 'p' to the back of the LRU list it's + // in (back == MRU) + // this is only used for the write cache + void bump_lru(cached_piece_entry* p); + + // move p into the correct lru queue + void update_cache_state(cached_piece_entry* p); + + // if the piece is marked for deletion and has a refcount + // of 0, this function will post any sync jobs and + // delete the piece from the cache + bool maybe_free_piece(cached_piece_entry* p); + + // either returns the piece in the cache, or allocates + // a new empty piece and returns it. + // cache_state is one of cache_state_t enum + cached_piece_entry* allocate_piece(disk_io_job const* j, int cache_state); + + // looks for this piece in the cache. If it's there, returns a pointer + // to it, otherwise 0. + cached_piece_entry* find_piece(block_cache_reference const& ref); + cached_piece_entry* find_piece(disk_io_job const* j); + cached_piece_entry* find_piece(piece_manager* st, int piece); + + // clear free all buffers marked as dirty with + // refcount of 0. + void abort_dirty(cached_piece_entry* p); + + // used to convert dirty blocks into non-dirty ones + // i.e. from being part of the write cache to being part + // of the read cache. it's used when flushing blocks to disk + void blocks_flushed(cached_piece_entry* pe, int const* flushed, int num_flushed); + + // adds a block to the cache, marks it as dirty and + // associates the job with it. When the block is + // flushed, the callback is posted + cached_piece_entry* add_dirty_block(disk_io_job* j); + + enum { blocks_inc_refcount = 1 }; + void insert_blocks(cached_piece_entry* pe, int block, file::iovec_t *iov + , int iov_len, disk_io_job* j, int flags = 0); + +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const; +#endif + + // try to remove num number of read cache blocks from the cache + // pick the least recently used ones first + // return the number of blocks that was requested to be evicted + // that couldn't be + int try_evict_blocks(int num, cached_piece_entry* ignore = 0); + + // try to evict a single volatile piece, if there is one. + void try_evict_one_volatile(); + + // if there are any dirty blocks + void clear(tailqueue& jobs); + + void update_stats_counters(counters& c) const; +#ifndef TORRENT_NO_DEPRECATE + void get_stats(cache_status* ret) const; +#endif + void set_settings(aux::session_settings const& sett, error_code& ec); + + enum reason_t { ref_hashing = 0, ref_reading = 1, ref_flushing = 2 }; + bool inc_block_refcount(cached_piece_entry* pe, int block, int reason); + void dec_block_refcount(cached_piece_entry* pe, int block, int reason); + + int pinned_blocks() const { return m_pinned_blocks; } + int read_cache_size() const { return m_read_cache_size; } + +#if TORRENT_USE_ASSERTS + void mark_deleted(file_storage const& fs); +#endif + + private: + + // returns number of bytes read on success, -1 on cache miss + // (just because the piece is in the cache, doesn't mean all + // the blocks are there) + int copy_from_piece(cached_piece_entry* p, disk_io_job* j, bool expect_no_fail = false); + + void free_piece(cached_piece_entry* p); + int drain_piece_bufs(cached_piece_entry& p, std::vector& buf); + + // block container + cache_t m_pieces; + + // linked list of all elements in m_pieces, in usage order + // the most recently used are in the tail. iterating from head + // to tail gives the least recently used entries first + // the read-list is for read blocks and the write-list is for + // dirty blocks that needs flushing before being evicted + // [0] = write-LRU + // [1] = read-LRU1 + // [2] = read-LRU1-ghost + // [3] = read-LRU2 + // [4] = read-LRU2-ghost + linked_list m_lru[cached_piece_entry::num_lrus]; + + // this is used to determine whether to evict blocks from + // L1 or L2. + enum cache_op_t + { + cache_miss, + ghost_hit_lru1, + ghost_hit_lru2 + }; + int m_last_cache_op; + + // the number of pieces to keep in the ARC ghost lists + // this is determined by being a fraction of the cache size + int m_ghost_size; + + // the is the max number of volatile read cache blocks are allowed in the + // cache. Once this is reached, other volatile blocks will start to be + // evicted. + int m_max_volatile_blocks; + + // the number of blocks (buffers) allocated by volatile pieces. + boost::uint32_t m_volatile_size; + + // the number of blocks in the cache + // that are in the read cache + boost::uint32_t m_read_cache_size; + + // the number of blocks in the cache + // that are in the write cache + boost::uint32_t m_write_cache_size; + + // the number of blocks that are currently sitting + // in peer's send buffers. If two peers are sending + // the same block, it counts as 2, even though there're + // no buffer duplication + boost::uint32_t m_send_buffer_blocks; + + // the number of blocks with a refcount > 0, i.e. + // they may not be evicted + int m_pinned_blocks; + +#if TORRENT_USE_ASSERTS + std::vector > m_deleted_storages; +#endif + }; + +} + +#endif // TORRENT_BLOCK_CACHE + diff --git a/include/libtorrent/bloom_filter.hpp b/include/libtorrent/bloom_filter.hpp new file mode 100644 index 0000000..582a342 --- /dev/null +++ b/include/libtorrent/bloom_filter.hpp @@ -0,0 +1,85 @@ +/* + +Copyright (c) 2010-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BLOOM_FILTER_HPP_INCLUDED +#define TORRENT_BLOOM_FILTER_HPP_INCLUDED + +#include "libtorrent/peer_id.hpp" // for sha1_hash +#include "libtorrent/config.hpp" // for sha1_hash + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include // for log() + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent +{ + TORRENT_EXTRA_EXPORT void set_bits(boost::uint8_t const* b, boost::uint8_t* bits, int len); + TORRENT_EXTRA_EXPORT bool has_bits(boost::uint8_t const* b, boost::uint8_t const* bits, int len); + TORRENT_EXTRA_EXPORT int count_zero_bits(boost::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) + { memcpy(bits, str, N); } + + void clear() { memset(bits, 0, N); } + + float size() const + { + const int c = (std::min)(count_zero_bits(bits, N), (N * 8) - 1); + const int m = N * 8; + return ::log(c / float(m)) / (2.f * ::log(1.f - 1.f/m)); + } + + bloom_filter() { clear(); } + + private: + boost::uint8_t bits[N]; + }; + +} + +#endif + diff --git a/include/libtorrent/broadcast_socket.hpp b/include/libtorrent/broadcast_socket.hpp new file mode 100644 index 0000000..8fa7983 --- /dev/null +++ b/include/libtorrent/broadcast_socket.hpp @@ -0,0 +1,161 @@ +/* + +Copyright (c) 2007-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_BROADCAST_SOCKET_HPP_INCLUDED +#define TORRENT_BROADCAST_SOCKET_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/io_service_fwd.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/error_code.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent +{ + + // TODO: 2 facto these functions out + TORRENT_EXTRA_EXPORT bool is_local(address const& a); + TORRENT_EXTRA_EXPORT bool is_loopback(address const& addr); + TORRENT_EXTRA_EXPORT bool is_multicast(address const& addr); + TORRENT_EXTRA_EXPORT bool is_any(address const& addr); + TORRENT_EXTRA_EXPORT bool is_teredo(address const& addr); + TORRENT_EXTRA_EXPORT int cidr_distance(address const& a1, address const& a2); + bool is_ip_address(char const* host); + + // determines if the operating system supports IPv6 + TORRENT_EXTRA_EXPORT bool supports_ipv6(); + + TORRENT_EXTRA_EXPORT int common_bits(unsigned char const* b1 + , unsigned char const* b2, int n); + + typedef boost::function receive_handler_t; + + class TORRENT_EXTRA_EXPORT broadcast_socket + { + public: + broadcast_socket(udp::endpoint const& multicast_endpoint); + ~broadcast_socket() { close(); } + + void open(receive_handler_t const& handler, io_service& ios + , error_code& ec, bool loopback = true); + + enum flags_t { broadcast = 1 }; + void send(char const* buffer, int size, error_code& ec, int flags = 0); + + void close(); + int num_send_sockets() const { return int(m_unicast_sockets.size()); } + void enable_ip_broadcast(bool e); + + private: + + struct socket_entry + { + socket_entry(boost::shared_ptr const& s) + : socket(s), broadcast(false) {} + socket_entry(boost::shared_ptr const& s + , address_v4 const& mask): socket(s), netmask(mask), broadcast(false) {} + boost::shared_ptr socket; + char buffer[1500]; + 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() + && socket->local_endpoint(ec).address().is_v4(); + } + address_v4 broadcast_address() const + { + error_code ec; +#if BOOST_VERSION < 104700 + return address_v4(socket->local_endpoint(ec).address().to_v4().to_ulong() | ((~netmask.to_ulong()) & 0xffffffff)); +#else + return address_v4::broadcast(socket->local_endpoint(ec).address().to_v4(), netmask); +#endif + } + }; + + void on_receive(socket_entry* s, error_code const& ec + , std::size_t bytes_transferred); + void open_unicast_socket(io_service& ios, address const& addr + , address_v4 const& mask); + void open_multicast_socket(io_service& ios, address const& addr + , bool loopback, error_code& ec); + + // if we're aborting, destruct the handler and return true + bool maybe_abort(); + + // these sockets are used to + // join the multicast group (on each interface) + // and receive multicast messages + std::list m_sockets; + // these sockets are not bound to any + // specific port and are used to + // send messages to the multicast group + // and receive unicast responses + std::list m_unicast_sockets; + udp::endpoint m_multicast_endpoint; + receive_handler_t m_on_receive; + + // the number of outstanding async operations + // we have on these sockets. The m_on_receive + // handler may not be destructed until this reaches + // 0, since it may be holding references to + // the broadcast_socket itself. + int m_outstanding_operations; + // when set to true, we're trying to shut down + // don't initiate new operations and once the + // outstanding counter reaches 0, destruct + // the handler object + bool m_abort; + }; +} + +#endif + diff --git a/include/libtorrent/bt_peer_connection.hpp b/include/libtorrent/bt_peer_connection.hpp new file mode 100644 index 0000000..8801727 --- /dev/null +++ b/include/libtorrent/bt_peer_connection.hpp @@ -0,0 +1,460 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +Copyright (c) 2007-2016, Arvid Norberg, Un Shyam +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BT_PEER_CONNECTION_HPP_INCLUDED +#define TORRENT_BT_PEER_CONNECTION_HPP_INCLUDED + +#include +#include +#include +#include + +#include "libtorrent/debug.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/buffer.hpp" +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/stat.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/peer_request.hpp" +#include "libtorrent/piece_block_progress.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/pe_crypto.hpp" + +namespace libtorrent +{ + class torrent; + + class TORRENT_EXTRA_EXPORT bt_peer_connection + : public peer_connection + { + friend class invariant_access; + public: + + // this is the constructor where the we are the active part. + // The peer_connection should handshake and verify that the + // other end has the correct id + bt_peer_connection(peer_connection_args const& pack + , peer_id const& pid); + + virtual void start() TORRENT_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(); + +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + bool supports_encryption() const + { return m_encrypted; } + bool rc4_encrypted() const + { return m_rc4_encrypted; } + + void switch_send_crypto(boost::shared_ptr crypto); + void switch_recv_crypto(boost::shared_ptr crypto); +#endif + + virtual int type() const TORRENT_OVERRIDE + { return peer_connection::bittorrent_connection; } + + enum message_type + { + // standard messages + msg_choke = 0, + msg_unchoke, + msg_interested, + msg_not_interested, + msg_have, + msg_bitfield, + msg_request, + msg_piece, + msg_cancel, + // DHT extension + msg_dht_port, + // FAST extension + msg_suggest_piece = 0xd, + msg_have_all, + msg_have_none, + msg_reject_request, + msg_allowed_fast, + + // extension protocol message + msg_extended = 20, + + num_supported_messages + }; + + enum hp_message_t + { + // msg_types + hp_rendezvous = 0, + hp_connect = 1, + hp_failed = 2, + + // error codes + hp_no_such_peer = 1, + hp_not_connected = 2, + hp_no_support = 3, + hp_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) TORRENT_OVERRIDE; + void on_receive(error_code const& error + , std::size_t bytes_transferred) TORRENT_OVERRIDE; + void on_receive_impl(std::size_t bytes_transferred); + +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + virtual int hit_send_barrier(std::vector& iovec) TORRENT_OVERRIDE; +#endif + + virtual void get_specific_peer_info(peer_info& p) const TORRENT_OVERRIDE; + virtual bool in_handshake() const TORRENT_OVERRIDE; + bool packet_finished() const { return m_recv_buffer.packet_finished(); } + +#ifndef TORRENT_DISABLE_EXTENSIONS + bool supports_holepunch() const { return m_holepunch_id != 0; } +#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_keepalive(); + void on_choke(int received); + void on_unchoke(int received); + void on_interested(int received); + void on_not_interested(int received); + void on_have(int received); + void on_bitfield(int received); + void on_request(int received); + void on_piece(int received); + void on_cancel(int received); + + // DHT extension + void on_dht_port(int received); + + // FAST extension + void on_suggest_piece(int received); + void on_have_all(int received); + void on_have_none(int received); + void on_reject_request(int received); + void on_allowed_fast(int received); +#ifndef TORRENT_DISABLE_EXTENSIONS + void on_holepunch(); + + void on_extended(int received); + + void on_extended_handshake(); +#endif + + typedef void (bt_peer_connection::*message_handler)(int received); + + // the following functions appends messages + // to the send buffer + void write_choke() TORRENT_OVERRIDE; + void write_unchoke() TORRENT_OVERRIDE; + void write_interested() TORRENT_OVERRIDE; + void write_not_interested() TORRENT_OVERRIDE; + void write_request(peer_request const& r) TORRENT_OVERRIDE; + void write_cancel(peer_request const& r) TORRENT_OVERRIDE; + void write_bitfield() TORRENT_OVERRIDE; + void write_have(int index) TORRENT_OVERRIDE; + void write_dont_have(int index) TORRENT_OVERRIDE; + void write_piece(peer_request const& r, disk_buffer_holder& buffer) TORRENT_OVERRIDE; + void write_keepalive() TORRENT_OVERRIDE; + void write_handshake(); +#ifndef TORRENT_DISABLE_EXTENSIONS + void write_extensions(); + void write_upload_only(); + void write_share_mode(); + void write_holepunch_msg(int type, tcp::endpoint const& ep, int error); +#endif + void write_metadata(std::pair req); + void write_metadata_request(std::pair req); + + // 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&) TORRENT_OVERRIDE; + void write_allow_fast(int piece) TORRENT_OVERRIDE; + void write_suggest(int piece) TORRENT_OVERRIDE; + + void on_connected() TORRENT_OVERRIDE; + void on_metadata() TORRENT_OVERRIDE; + +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const; + time_point m_last_choke; +#endif + + private: + + 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. + boost::optional downloading_piece_progress() const TORRENT_OVERRIDE; + +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + + // 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(char* write_buf, int len + , int crypto_field, int pad_size); + + // stream key (info hash of attached torrent) + // secret is the DH shared secret + // initializes m_enc_handler + void init_pe_rc4_handler(char const* secret, sha1_hash const& stream_key); + + // Returns offset at which bytestream (src, src + src_size) + // matches bytestream(target, target + target_size). + // If no sync found, return -1 + int get_syncoffset(char const* src, int src_size + , char const* target, int target_size) const; + + // helper to cut down on boilerplate + void rc4_decrypt(char* pos, int len); +#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 + virtual void append_const_send_buffer(char const* buffer, int size + , chained_buffer::free_buffer_fun destructor = &nop + , void* userdata = NULL, block_cache_reference ref + = block_cache_reference()) TORRENT_OVERRIDE; + +private: + + enum state_t + { +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + read_pe_dhkey = 0, + read_pe_syncvc, + read_pe_synchash, + read_pe_skey_vc, + read_pe_cryptofield, + read_pe_pad, + read_pe_ia, + init_bt_handshake, + read_protocol_identifier, +#else + read_protocol_identifier = 0, +#endif + read_info_hash, + read_peer_id, + + // handshake complete + read_packet_size, + read_packet + }; + +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + enum + { + handshake_len = 68, + dh_key_len = 96 + }; +#endif + + // state of on_receive. one of the enums in state_t + boost::uint8_t m_state; + + // 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) && !defined(TORRENT_DISABLE_EXTENSIONS) + // 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; + + crypto_receive_buffer m_recv_buffer; +#endif + + std::string m_client_version; + + // the peer ID we advertise for ourself + peer_id m_our_peer_id; + + // this is a queue of ranges that describes + // where in the send buffer actual payload + // data is located. This is currently + // only used to be able to gather statistics + // separately on payload and protocol data. + struct range + { + range(int s, int l) + : start(s) + , length(l) + { + TORRENT_ASSERT(s >= 0); + TORRENT_ASSERT(l > 0); + } + int start; + int length; + }; + + std::vector m_payloads; + +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + // initialized during write_pe1_2_dhkey, and destroyed on + // creation of m_enc_handler. Cannot reinitialize once + // initialized. + boost::scoped_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 + boost::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. + boost::scoped_array m_sync_vc; + + // (incoming only) synchronize hash with remote peer, holds + // the sync hash (hash("req1",secret)). Destroyed after the + // sync step. + boost::scoped_ptr m_sync_hash; +#endif // #if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + + static const message_handler m_message_handler[num_supported_messages]; + +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + // used to disconnect peer if sync points are not found within + // the maximum number of bytes + int m_sync_bytes_read; +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + // the message ID for upload only message + // 0 if not supported + boost::uint8_t m_upload_only_id; + + // the message ID for holepunch messages + boost::uint8_t m_holepunch_id; + + // the message ID for don't-have message + boost::uint8_t m_dont_have_id; + + // the message ID for share mode message + // 0 if not supported + boost::uint8_t m_share_mode_id; + + char m_reserved_bits[8]; +#endif + +#if defined TORRENT_DEBUG || defined TORRENT_RELEASE_ASSERTS + bool m_in_constructor; +#endif + + }; +} + +#endif // TORRENT_BT_PEER_CONNECTION_HPP_INCLUDED + diff --git a/include/libtorrent/buffer.hpp b/include/libtorrent/buffer.hpp new file mode 100644 index 0000000..e5393b3 --- /dev/null +++ b/include/libtorrent/buffer.hpp @@ -0,0 +1,254 @@ +/* +Copyright (c) 2007-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 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 LIBTORRENT_BUFFER_HPP +#define LIBTORRENT_BUFFER_HPP + +#include +#include // for numeric_limits +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/assert.hpp" +#include // malloc/free/realloc +#include +#include // for std::swap + +namespace libtorrent { + +class buffer +{ +public: + struct interval + { + interval() + : begin(0) + , end(0) + {} + + interval(char* b, char* e) + : begin(b) + , end(e) + {} + + char operator[](int index) const + { + TORRENT_ASSERT(begin + index < end); + return begin[index]; + } + + int left() const + { + TORRENT_ASSERT(end >= begin); + TORRENT_ASSERT(end - begin < INT_MAX); + return int(end - begin); + } + + char* begin; + char* end; + }; + + struct const_interval + { + const_interval(interval const& i) + : begin(i.begin) + , end(i.end) + {} + + const_interval(char const* b, char const* e) + : begin(b) + , end(e) + {} + + char operator[](int index) const + { + TORRENT_ASSERT(begin + index < end); + return begin[index]; + } + + bool operator==(const const_interval& p_interval) + { + return begin == p_interval.begin + && end == p_interval.end; + } + + int left() const + { + TORRENT_ASSERT(end >= begin); + TORRENT_ASSERT(end - begin < INT_MAX); + return int(end - begin); + } + + char const* begin; + char const* end; + }; + + buffer(std::size_t n = 0) + : m_begin(0) + , m_size(0) + , m_capacity(0) + { + if (n) resize(n); + } + + buffer(buffer const& b) + : m_begin(0) + , m_size(0) + , m_capacity(0) + { + if (b.size() == 0) return; + resize(b.size()); + std::memcpy(m_begin, b.begin(), b.size()); + } + +#if __cplusplus > 199711L + buffer(buffer&& b) + : m_begin(b.m_begin) + , m_size(b.m_size) + , m_capacity(b.m_capacity) + { + b.m_begin = NULL; + b.m_size = b.m_capacity = 0; + } + + buffer& operator=(buffer&& b) + { + if (&b == this) return *this; + std::free(m_begin); + m_begin = b.m_begin; + m_size = b.m_size; + m_capacity = b.m_capacity; + b.m_begin = NULL; + b.m_size = b.m_capacity = 0; + return *this; + } +#endif + + buffer& operator=(buffer const& b) + { + if (&b == this) return *this; + resize(b.size()); + if (b.size() == 0) return *this; + std::memcpy(m_begin, b.begin(), b.size()); + return *this; + } + + ~buffer() + { + std::free(m_begin); + } + + buffer::interval data() + { return interval(m_begin, m_begin + m_size); } + buffer::const_interval data() const + { return const_interval(m_begin, m_begin + m_size); } + + void resize(std::size_t n) + { + TORRENT_ASSERT(n < 0xffffffffu); + reserve(n); + m_size = boost::uint32_t(n); + } + + void insert(char* point, char const* first, char const* last) + { + std::size_t p = point - m_begin; + if (point == m_begin + m_size) + { + resize(size() + last - first); + std::memcpy(m_begin + p, first, last - first); + return; + } + + resize(size() + last - first); + std::memmove(m_begin + p + (last - first), m_begin + p, last - first); + std::memcpy(m_begin + p, first, last - first); + } + + void erase(char* b, char* e) + { + TORRENT_ASSERT(e <= m_begin + m_size); + TORRENT_ASSERT(b >= m_begin); + TORRENT_ASSERT(b <= e); + if (e == m_begin + m_size) + { + resize(b - m_begin); + return; + } + std::memmove(b, e, m_begin + m_size - e); + TORRENT_ASSERT(e >= b); + TORRENT_ASSERT(e - b <= std::numeric_limits::max()); + TORRENT_ASSERT(boost::uint32_t(e - b) <= m_size); + m_size -= e - b; + } + + void clear() { m_size = 0; } + std::size_t size() const { return m_size; } + std::size_t capacity() const { return m_capacity; } + void reserve(std::size_t n) + { + if (n <= capacity()) return; + TORRENT_ASSERT(n > 0); + TORRENT_ASSERT(n < 0xffffffffu); + + char* tmp = static_cast(std::realloc(m_begin, n)); +#ifndef BOOST_NO_EXCEPTIONS + if (tmp == NULL) throw std::bad_alloc(); +#endif + m_begin = tmp; + m_capacity = boost::uint32_t(n); + } + + bool empty() const { return m_size == 0; } + char& operator[](std::size_t i) { TORRENT_ASSERT(i < size()); return m_begin[i]; } + char const& operator[](std::size_t 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); + swap(m_capacity, b.m_capacity); + } +private: + char* m_begin; + boost::uint32_t m_size; + boost::uint32_t m_capacity; +}; + + +} + +#endif // LIBTORRENT_BUFFER_HPP + diff --git a/include/libtorrent/build_config.hpp b/include/libtorrent/build_config.hpp new file mode 100644 index 0000000..aade5e2 --- /dev/null +++ b/include/libtorrent/build_config.hpp @@ -0,0 +1,63 @@ +/* + +Copyright (c) 2010-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BUILD_CONFIG_HPP_INCLUDED +#define TORRENT_BUILD_CONFIG_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include +#include + +// TODO: 2 instead of using a dummy function to cause link errors when +// incompatible build configurations are used, make the namespace name +// depend on the configuration, and have a using declaration in the headers +// to pull it into libtorrent. +#if TORRENT_USE_IPV6 +#define TORRENT_CFG_IPV6 ipv6_ +#else +#define TORRENT_CFG_IPV6 noipv6_ +#endif + +#ifdef TORRENT_NO_DEPRECATE +#define TORRENT_CFG_DEPR nodeprecate_ +#else +#define TORRENT_CFG_DEPR deprecated_ +#endif + +#define TORRENT_CFG \ + BOOST_PP_CAT(TORRENT_CFG_IPV6, \ + TORRENT_CFG_DEPR) + +#define TORRENT_CFG_STRING BOOST_PP_STRINGIZE(TORRENT_CFG) + +#endif + diff --git a/include/libtorrent/chained_buffer.hpp b/include/libtorrent/chained_buffer.hpp new file mode 100644 index 0000000..d68cf0d --- /dev/null +++ b/include/libtorrent/chained_buffer.hpp @@ -0,0 +1,141 @@ +/* + +Copyright (c) 2007-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_CHAINED_BUFFER_HPP_INCLUDED +#define TORRENT_CHAINED_BUFFER_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/disk_io_job.hpp" // for block_cache_reference +#include "libtorrent/debug.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include +#include // for memcpy + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent +{ + 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 + } + + // destructs/frees the buffer (1st arg) with + // 2nd argument as userdata + typedef void (*free_buffer_fun)(char*, void*, block_cache_reference ref); + + struct buffer_t + { + free_buffer_fun free_fun; + void* userdata; + char* buf; // the first byte of the buffer + char* start; // the first byte to send/receive in the buffer + int size; // the total size of the buffer + int used_size; // this is the number of bytes to send/receive + block_cache_reference ref; + }; + + 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); + + void append_buffer(char* buffer, int s, int used_size + , free_buffer_fun destructor, void* userdata + , block_cache_reference ref = block_cache_reference()); + + void prepend_buffer(char* buffer, int s, int used_size + , free_buffer_fun destructor, void* userdata + , block_cache_reference ref = block_cache_reference()); + + // 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 false + char* append(char const* buf, int s); + + // tries to allocate memory from the end + // of the last buffer. If there isn't + // enough room, returns 0 + char* allocate_appendix(int s); + + std::vector const& build_iovec(int to_send); + + void clear(); + + void build_mutable_iovec(int bytes, std::vector& vec); + + ~chained_buffer(); + + private: + template + void build_vec(int bytes, std::vector& vec); + + // this is the list of all the buffers we want to + // send + std::deque m_vec; + + // this is the number of bytes in the send buf. + // this will always be equal to the sum of the + // size of all buffers in vec + int m_bytes; + + // the total size of all buffers in the chain + // including unused space + int m_capacity; + + // this is the vector of buffers used when + // invoking the async write call + std::vector m_tmp_vec; + +#if TORRENT_USE_ASSERTS + bool m_destructed; +#endif + }; +} + +#endif + diff --git a/include/libtorrent/choker.hpp b/include/libtorrent/choker.hpp new file mode 100644 index 0000000..8808207 --- /dev/null +++ b/include/libtorrent/choker.hpp @@ -0,0 +1,52 @@ +/* + +Copyright (c) 2014-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/time.hpp" // for time_duration +#include + +namespace libtorrent +{ + namespace aux { struct session_settings; } + class peer_connection; + + // sorts the vector of peers in-place. When returning, the top unchoke slots + // elements are the peers we should unchoke. This is similar to a partial + // sort. Only the unchoke slots first elements are sorted. + // the return value are the number of peers that should be unchoked. This + // is also the number of elements that are valid at the beginning of the + // peer list. Peers beyond this initial range are not sorted. + TORRENT_EXTRA_EXPORT int unchoke_sort(std::vector& peers + , int max_upload_rate, time_duration unchoke_interval + , aux::session_settings const& sett); + +} diff --git a/include/libtorrent/close_reason.hpp b/include/libtorrent/close_reason.hpp new file mode 100644 index 0000000..0be54e0 --- /dev/null +++ b/include/libtorrent/close_reason.hpp @@ -0,0 +1,157 @@ +/* + +Copyright (c) 2015-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#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 close_reason_t + { + // no reason specified. Generic close. + close_no_reason = 0, + + // we're already connected to + close_duplicate_peer_id, + + // this torrent has been removed, paused or stopped from this client. + close_torrent_removed, + + // client failed to allocate necessary memory for this peer connection + close_no_memory, + + // the source port of this peer is blocked + close_port_blocked, + + // the source IP has been blocked + close_blocked, + + // both ends of the connection are upload-only. staying connected would + // be redundant + close_upload_to_upload, + + // connection was closed because the other end is upload only and does + // not have any pieces we're interested in + close_not_interested_upload_only, + + // peer connection timed out (generic timeout) + close_timeout, + + // the peers have not been interested in each other for a very long time. + // disconnect + close_timed_out_interest, + + // the peer has not sent any message in a long time. + close_timed_out_activity, + + // the peer did not complete the handshake in too long + close_timed_out_handshake, + + // the peer sent an interested message, but did not send a request + // after a very long time after being unchoked. + close_timed_out_request, + + // the encryption mode is blocked + close_protocol_blocked, + + // the peer was disconnected in the hopes of finding a better peer + // in the swarm + close_peer_churn, + + // we have too many peers connected + close_too_many_connections, + + // we have too many file-descriptors open + close_too_many_files, + + // the encryption handshake failed + close_encryption_error = 256, + + // the info hash sent as part of the handshake was not what we expected + close_invalid_info_hash, + + close_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 + close_invalid_metadata, + + // the advertised metadata size + close_metadata_too_big, + + // invalid bittorrent messages + close_message_too_big, + close_invalid_message_id, + close_invalid_message, + close_invalid_piece_message, + close_invalid_have_message, + close_invalid_bitfield_message, + close_invalid_choke_message, + close_invalid_unchoke_message, + close_invalid_interested_message, + close_invalid_not_interested_message, + close_invalid_request_message, + close_invalid_reject_message, + close_invalid_allow_fast_message, + close_invalid_extended_message, + close_invalid_cancel_message, + close_invalid_dht_port_message, + close_invalid_suggest_message, + close_invalid_have_all_message, + close_invalid_dont_have_message, + close_invalid_have_none_message, + close_invalid_pex_message, + close_invalid_metadata_request_message, + close_invalid_metadata_message, + close_invalid_metadata_offset, + + // the peer sent a request while being choked + close_request_when_choked, + + // the peer sent corrupt data + close_corrupt_pieces, + + close_pex_message_too_big, + close_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..e74ec15 --- /dev/null +++ b/include/libtorrent/config.hpp @@ -0,0 +1,710 @@ +/* + +Copyright (c) 2005-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_CONFIG_HPP_INCLUDED +#define TORRENT_CONFIG_HPP_INCLUDED + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#define _FILE_OFFSET_BITS 64 + +#if !defined _MSC_VER || _MSC_VER >= 1600 +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS 1 +#endif +#ifndef __STDC_CONSTANT_MACROS +#define __STDC_CONSTANT_MACROS 1 +#endif +#endif + + +#include +#include +#include +#include +#include // for snprintf +#include // for IOV_MAX + +#include "libtorrent/export.hpp" + +#ifdef __linux__ +#include // for LINUX_VERSION_CODE and KERNEL_VERSION +#endif // __linux + +#if defined TORRENT_DEBUG_BUFFERS && !defined TORRENT_DISABLE_POOL_ALLOCATOR +#error TORRENT_DEBUG_BUFFERS only works if you also disable pool allocators with TORRENT_DISABLE_POOL_ALLOCATOR +#endif + +#if !defined BOOST_ASIO_SEPARATE_COMPILATION && !defined BOOST_ASIO_DYN_LINK +#define BOOST_ASIO_SEPARATE_COMPILATION +#endif + +#ifndef _MSC_VER +#ifndef __STDC_FORMAT_MACROS +#define __STDC_FORMAT_MACROS 1 +#endif +#include // for PRId64 et.al. +#endif + +#ifndef PRId64 +// MinGW uses microsofts runtime +#if defined _MSC_VER || defined __MINGW32__ +#define PRId64 "I64d" +#define PRIu64 "I64u" +#define PRIx64 "I64x" +#define PRIu32 "u" +#else +#define PRId64 "lld" +#define PRIu64 "llu" +#define PRIx64 "llx" +#define PRIu32 "u" +#endif +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +// ======= GCC ========= + +#if defined __GNUC__ + +// deprecation markup is only enabled when libtorrent +// headers are included by clients, not while building +// libtorrent itself +# if __GNUC__ >= 3 && !defined TORRENT_BUILDING_LIBRARY +# define TORRENT_DEPRECATED __attribute__ ((deprecated)) +# endif + +// ======= SUNPRO ========= + +#elif defined __SUNPRO_CC + +// SunPRO seems to have an overly-strict +// definition of POD types and doesn't +// seem to allow boost::array in unions +#define TORRENT_BROKEN_UNIONS 1 + +// ======= MSVC ========= + +#elif defined BOOST_MSVC + +#pragma warning(disable: 4258) +#pragma warning(disable: 4251) + +// class X needs to have dll-interface to be used by clients of class Y +#pragma warning(disable:4251) +// '_vsnprintf': This function or variable may be unsafe +#pragma warning(disable:4996) + +// deprecation markup is only enabled when libtorrent +// headers are included by clients, not while building +// libtorrent itself +#if !defined TORRENT_BUILDING_LIBRARY +# define TORRENT_DEPRECATED __declspec(deprecated) +#endif + +#endif + + +// ======= PLATFORMS ========= + + +// set up defines for target environments +// ==== AMIGA === +#if defined __AMIGA__ || defined __amigaos__ || defined __AROS__ +#define TORRENT_AMIGA +#define TORRENT_USE_IPV6 0 +#define TORRENT_USE_BOOST_THREAD 0 +#define TORRENT_USE_IOSTREAM 0 +// set this to 1 to disable all floating point operations +// (disables some float-dependent APIs) +#define TORRENT_NO_FPU 1 +#define TORRENT_USE_I2P 0 +#ifndef TORRENT_USE_ICONV +#define TORRENT_USE_ICONV 0 +#endif + +// ==== Darwin/BSD === +#elif (defined __APPLE__ && defined __MACH__) || defined __FreeBSD__ || defined __NetBSD__ \ + || defined __OpenBSD__ || defined __bsdi__ || defined __DragonFly__ \ + || defined __FreeBSD_kernel__ +#define TORRENT_BSD +// we don't need iconv on mac, because +// the locale is always utf-8 +#if defined __APPLE__ + +# define TORRENT_USE_OSATOMIC 1 +# ifndef TORRENT_USE_ICONV +# define TORRENT_USE_ICONV 0 +# define TORRENT_USE_LOCALE 0 +# endif +#include + +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1070 +// on OSX, use the built-in common crypto for built-in +# if !defined TORRENT_USE_OPENSSL && !defined TORRENT_USE_GCRYPT +# define TORRENT_USE_COMMONCRYPTO 1 +# endif // TORRENT_USE_OPENSSL +#endif // MAC_OS_X_VERSION_MIN_REQUIRED + +// execinfo.h is available in the MacOS X 10.5 SDK. +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 +#define TORRENT_USE_EXECINFO 1 +#endif + +#else // __APPLE__ +// FreeBSD has a reasonable iconv signature +// unless we're on glibc +#ifndef __GLIBC__ +# define TORRENT_ICONV_ARG (const char**) +#endif +#endif // __APPLE__ + +#define TORRENT_HAVE_MMAP 1 + +#define TORRENT_HAS_FALLOCATE 0 + +#define TORRENT_USE_IFADDRS 1 +#define TORRENT_USE_SYSCTL 1 +#define TORRENT_USE_IFCONF 1 + + +// ==== LINUX === +#elif defined __linux__ +#define TORRENT_LINUX + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30) +# define TORRENT_USE_PREADV 1 +# define TORRENT_USE_PREAD 0 +#else +# define TORRENT_USE_PREADV 0 +# define TORRENT_USE_PREAD 1 +#endif + +#define TORRENT_HAVE_MMAP 1 +#define TORRENT_USE_NETLINK 1 +#define TORRENT_USE_IFCONF 1 +#define TORRENT_HAS_SALEN 0 + +// ===== ANDROID ===== (almost linux, sort of) +#if defined __ANDROID__ +#define TORRENT_USE_PREADV 0 +#define TORRENT_USE_PREAD 1 +#define TORRENT_ANDROID +#define TORRENT_HAS_FALLOCATE 0 +#define TORRENT_USE_ICONV 0 +#define TORRENT_USE_IFADDRS 0 +#define TORRENT_USE_MEMALIGN 1 +#define TORRENT_USE_FDATASYNC 0 +#else // ANDROID +#define TORRENT_USE_IFADDRS 1 +#define TORRENT_USE_POSIX_MEMALIGN 1 +#define TORRENT_USE_FDATASYNC 1 + +// posix_fallocate() is available under this condition +#if _XOPEN_SOURCE >= 600 || _POSIX_C_SOURCE >= 200112L +#define TORRENT_HAS_FALLOCATE 1 +#else +#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__ +#define TORRENT_MINGW +#define TORRENT_WINDOWS +#ifndef TORRENT_USE_ICONV +# define TORRENT_USE_ICONV 0 +# define TORRENT_USE_LOCALE 1 +#endif +#define TORRENT_USE_RLIMIT 0 +#define TORRENT_USE_NETLINK 0 +#define TORRENT_USE_GETADAPTERSADDRESSES 1 +#define TORRENT_HAS_SALEN 0 +#define TORRENT_USE_GETIPFORWARDTABLE 1 +#define TORRENT_USE_INTERLOCKED_ATOMIC 1 +#ifndef TORRENT_USE_UNC_PATHS +# define TORRENT_USE_UNC_PATHS 1 +#endif +// these are emulated on windows +#define TORRENT_USE_PREADV 1 +#define TORRENT_USE_PWRITEV 1 + +// ==== WINDOWS === +#elif defined _WIN32 +#define TORRENT_WINDOWS +#ifndef TORRENT_USE_GETIPFORWARDTABLE +# define TORRENT_USE_GETIPFORWARDTABLE 1 +#endif +#define TORRENT_USE_GETADAPTERSADDRESSES 1 +#define TORRENT_HAS_SALEN 0 +// windows has its own functions to convert +#ifndef TORRENT_USE_ICONV +# define TORRENT_USE_ICONV 0 +# define TORRENT_USE_LOCALE 1 +#endif +#define TORRENT_USE_RLIMIT 0 +#define TORRENT_HAS_FALLOCATE 0 +#define TORRENT_USE_INTERLOCKED_ATOMIC 1 +#ifndef TORRENT_USE_UNC_PATHS +# define TORRENT_USE_UNC_PATHS 1 +#endif +// these are emulated on windows +#define TORRENT_USE_PREADV 1 +#define TORRENT_USE_PWRITEV 1 + +// ==== WINRT === +#if defined(WINAPI_FAMILY_PARTITION) +# if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP) \ + && !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) +# define TORRENT_WINRT +# define TORRENT_USE_CRYPTOGRAPHIC_BUFFER 1 +# endif +#endif + +// ==== SOLARIS === +#elif defined sun || defined __sun +#define TORRENT_SOLARIS +#define TORRENT_COMPLETE_TYPES_REQUIRED 1 +#define TORRENT_USE_IFCONF 1 +#define TORRENT_HAS_SALEN 0 +#define TORRENT_HAS_SEM_RELTIMEDWAIT 1 +#define TORRENT_HAVE_MMAP 1 +#define TORRENT_USE_SOLARIS_ATOMIC 1 + +// ==== BEOS === +#elif defined __BEOS__ || defined __HAIKU__ +#define TORRENT_BEOS +#include // B_PATH_NAME_LENGTH +#define TORRENT_HAS_FALLOCATE 0 +#define TORRENT_USE_BEOS_ATOMIC 1 +#ifndef TORRENT_USE_ICONV +#define TORRENT_USE_ICONV 0 +#endif + +// ==== GNU/Hurd === +#elif defined __GNU__ +#define TORRENT_HURD +#define TORRENT_USE_IFADDRS 1 +#define TORRENT_USE_IFCONF 1 + +// ==== eCS(OS/2) === +#elif defined __OS2__ +#define TORRENT_OS2 +#define TORRENT_HAS_FALLOCATE 0 +#define TORRENT_USE_IFCONF 1 +#define TORRENT_USE_SYSCTL 1 +#define TORRENT_USE_IPV6 0 +#define TORRENT_ICONV_ARG (const char**) +#define TORRENT_USE_WRITEV 0 +#define TORRENT_USE_READV 0 + +#else + +#ifdef _MSC_VER +#pragma message ( "unknown OS, assuming BSD" ) +#else +#warning "unknown OS, assuming BSD" +#endif + +#define TORRENT_BSD +#endif + +#if defined __GNUC__ && !(defined TORRENT_USE_OSATOMIC \ + || defined TORRENT_USE_INTERLOCKED_ATOMIC \ + || defined TORRENT_USE_BEOS_ATOMIC \ + || defined TORRENT_USE_SOLARIS_ATOMIC) +// atomic operations in GCC were introduced in 4.1.1 +# if (__GNUC__ >= 4 && __GNUC_MINOR__ >= 1 && __GNUC_PATCHLEVEL__ >= 1) || __GNUC__ > 4 +# define TORRENT_USE_GCC_ATOMIC 1 +# endif +#endif + +// on windows, NAME_MAX refers to Unicode characters +// on linux it refers to bytes (utf-8 encoded) +// TODO: Make this count Unicode characters instead of bytes on windows + +// windows +#if defined FILENAME_MAX +#define TORRENT_MAX_PATH FILENAME_MAX + +// beos +#elif defined B_PATH_NAME_LENGTH +#define TORRENT_MAX_PATH B_PATH_NAME_LENGTH + +// solaris +#elif defined MAXPATH +#define TORRENT_MAX_PATH MAXPATH + +// none of the above +#else +// this is the maximum number of characters in a +// path element / filename on windows and also on many filesystems commonly used +// on linux +#define TORRENT_MAX_PATH 255 + +#ifdef _MSC_VER +#pragma message ( "unknown platform, assuming the longest path is 255" ) +#else +#warning "unknown platform, assuming the longest path is 255" +#endif + +#endif + +#define TORRENT_UNUSED(x) (void)(x) + +#if (defined _MSC_VER && _MSC_VER < 1900) && !defined TORRENT_MINGW + +#include + +// internal +#ifdef __cplusplus +inline +#else +static +#endif +int snprintf(char* buf, int len, char const* fmt, ...) +{ + va_list lp; + int ret; + va_start(lp, fmt); + ret = _vsnprintf(buf, len, fmt, lp); + va_end(lp); + if (ret < 0) { buf[len-1] = 0; ret = len-1; } + return ret; +} + +#define strtoll _strtoi64 +#endif + +// at the highest warning level, clang actually warns about functions +// that could be marked noreturn. +#if defined __clang__ || defined __GNUC__ +#define TORRENT_NO_RETURN __attribute((noreturn)) +#else +#define TORRENT_NO_RETURN +#endif + +#ifdef _GLIBCXX_USE_NOEXCEPT +#define TORRENT_EXCEPTION_THROW_SPECIFIER _GLIBCXX_USE_NOEXCEPT +#else +#if __cplusplus <= 199711L || defined BOOST_NO_CXX11_NOEXCEPT +#define TORRENT_EXCEPTION_THROW_SPECIFIER throw() +#else +#define TORRENT_EXCEPTION_THROW_SPECIFIER noexcept +#endif +#endif // __GLIBC__ + +#if __cplusplus <= 199711L || defined BOOST_NO_CXX11_FINAL +#define TORRENT_FINAL +#else +#define TORRENT_FINAL final +#endif + +#if __cplusplus <= 199711L || defined BOOST_NO_CXX11_FINAL +#define TORRENT_OVERRIDE +#else +#define TORRENT_OVERRIDE override +#endif + +#ifndef TORRENT_ICONV_ARG +#define TORRENT_ICONV_ARG (char**) +#endif + +#if defined __GNUC__ || defined __clang__ +#define TORRENT_FORMAT(fmt, ellipsis) __attribute__((__format__(__printf__, fmt, ellipsis))) +#else +#define TORRENT_FORMAT(fmt, ellipsis) +#endif + +#ifndef TORRENT_USE_INTERLOCKED_ATOMIC +#define TORRENT_USE_INTERLOCKED_ATOMIC 0 +#endif + +#ifndef TORRENT_USE_GCC_ATOMIC +#define TORRENT_USE_GCC_ATOMIC 0 +#endif + +#ifndef TORRENT_USE_OSATOMIC +#define TORRENT_USE_OSATOMIC 0 +#endif + +#ifndef TORRENT_USE_BEOS_ATOMIC +#define TORRENT_USE_BEOS_ATOMIC 0 +#endif + +// libiconv presence detection is not implemented yet +#ifndef TORRENT_USE_ICONV +#define TORRENT_USE_ICONV 1 +#endif + +#ifndef TORRENT_HAS_SALEN +#define TORRENT_HAS_SALEN 1 +#endif + +#ifndef TORRENT_USE_GETADAPTERSADDRESSES +#define TORRENT_USE_GETADAPTERSADDRESSES 0 +#endif + +#ifndef TORRENT_USE_NETLINK +#define TORRENT_USE_NETLINK 0 +#endif + +#ifndef TORRENT_USE_EXECINFO +#define TORRENT_USE_EXECINFO 0 +#endif + +#ifndef TORRENT_USE_SYSCTL +#define TORRENT_USE_SYSCTL 0 +#endif + +#ifndef TORRENT_USE_GETIPFORWARDTABLE +#define TORRENT_USE_GETIPFORWARDTABLE 0 +#endif + +#ifndef TORRENT_HAS_SEM_RELTIMEDWAIT +#define TORRENT_HAS_SEM_RELTIMEDWAIT 0 +#endif + +#ifndef TORRENT_USE_MEMALIGN +#define TORRENT_USE_MEMALIGN 0 +#endif + +#ifndef TORRENT_USE_POSIX_MEMALIGN +#define TORRENT_USE_POSIX_MEMALIGN 0 +#endif + +#ifndef TORRENT_USE_LOCALE +#define TORRENT_USE_LOCALE 0 +#endif + +#ifndef TORRENT_BROKEN_UNIONS +#define TORRENT_BROKEN_UNIONS 0 +#endif + +#ifndef TORRENT_USE_WSTRING +#if !defined BOOST_NO_STD_WSTRING +#define TORRENT_USE_WSTRING 1 +#else +#define TORRENT_USE_WSTRING 0 +#endif // BOOST_NO_STD_WSTRING +#endif // TORRENT_USE_WSTRING + +#ifndef TORRENT_HAS_FALLOCATE +#define TORRENT_HAS_FALLOCATE 1 +#endif + +#ifndef TORRENT_DEPRECATED +#define TORRENT_DEPRECATED +#endif + +#ifndef TORRENT_USE_COMMONCRYPTO +#define TORRENT_USE_COMMONCRYPTO 0 +#endif + +#ifndef TORRENT_HAVE_MMAP +#define TORRENT_HAVE_MMAP 0 +#endif + +#ifndef TORRENT_COMPLETE_TYPES_REQUIRED +#define TORRENT_COMPLETE_TYPES_REQUIRED 0 +#endif + +#ifndef TORRENT_USE_FDATASYNC +#define TORRENT_USE_FDATASYNC 0 +#endif + +#ifndef TORRENT_USE_UNC_PATHS +#define TORRENT_USE_UNC_PATHS 0 +#endif + +#ifndef TORRENT_USE_RLIMIT +#define TORRENT_USE_RLIMIT 1 +#endif + +#ifndef TORRENT_USE_IFADDRS +#define TORRENT_USE_IFADDRS 0 +#endif + +#ifndef TORRENT_USE_IPV6 +#define TORRENT_USE_IPV6 1 +#endif + +// if preadv() exists, we assume pwritev() does as well +#ifndef TORRENT_USE_PREADV +#define TORRENT_USE_PREADV 0 +#endif + +// if pread() exists, we assume pwrite() does as well +#ifndef TORRENT_USE_PREAD +#define TORRENT_USE_PREAD 1 +#endif + +#ifndef TORRENT_NO_FPU +#define TORRENT_NO_FPU 0 +#endif + +#ifndef TORRENT_USE_IOSTREAM +#ifndef BOOST_NO_IOSTREAM +#define TORRENT_USE_IOSTREAM 1 +#else +#define TORRENT_USE_IOSTREAM 0 +#endif +#endif + +// whether function-local static variables are thread safe. In c++11 and later +// they are (except msvc) +#ifndef TORRENT_THREADSAFE_STATIC +#if __cplusplus < 199711L || (defined _MSC_VER && _MSC_VER <= 1800) +#define TORRENT_THREADSAFE_STATIC 0 +#else +#define TORRENT_THREADSAFE_STATIC 1 +#endif +#endif + +#ifndef TORRENT_USE_I2P +#define TORRENT_USE_I2P 1 +#endif + +#ifndef TORRENT_HAS_BOOST_UNORDERED +#define TORRENT_HAS_BOOST_UNORDERED 1 +#endif + +#if !defined TORRENT_IOV_MAX +#ifdef IOV_MAX +#define TORRENT_IOV_MAX IOV_MAX +#else +#define TORRENT_IOV_MAX INT_MAX +#endif +#endif + +#if !defined(TORRENT_READ_HANDLER_MAX_SIZE) +# ifdef _GLIBCXX_DEBUG +# define TORRENT_READ_HANDLER_MAX_SIZE 400 +# else +// if this is not divisible by 8, we're wasting space +# define TORRENT_READ_HANDLER_MAX_SIZE 336 +# endif +#endif + +#if !defined(TORRENT_WRITE_HANDLER_MAX_SIZE) +# ifdef _GLIBCXX_DEBUG +# define TORRENT_WRITE_HANDLER_MAX_SIZE 400 +# else +// if this is not divisible by 8, we're wasting space +# define TORRENT_WRITE_HANDLER_MAX_SIZE 336 +# endif +#endif + +#if defined _MSC_VER && _MSC_VER <= 1200 +// this is here to provide a standard-conforming for +// keyword for old versions of msvc. The pragmas are +// there to silence the warning it produces by using +// a constant as conditional +#define for \ + __pragma( warning(push) ) \ + __pragma( warning(disable:4127) ) \ + if (false) {} else \ + __pragma( warning(pop) ) + for +#endif + +#if TORRENT_BROKEN_UNIONS +#define TORRENT_UNION struct +#else +#define TORRENT_UNION union +#endif + +#if defined __GNUC__ +#define TORRENT_FUNCTION __PRETTY_FUNCTION__ +#else +#define TORRENT_FUNCTION __FUNCTION__ +#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 +#if defined TORRENT_DEBUG || defined TORRENT_RELEASE_ASSERTS +#define TORRENT_USE_ASSERTS 1 +#else +#define TORRENT_USE_ASSERTS 0 +#endif +#endif // TORRENT_USE_ASSERTS + +#if defined TORRENT_DEBUG && TORRENT_USE_ASSERTS \ + && !defined TORRENT_DISABLE_INVARIANT_CHECKS +#define TORRENT_USE_INVARIANT_CHECKS 1 +#else +#define TORRENT_USE_INVARIANT_CHECKS 0 +#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 + + +#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..1149f4b --- /dev/null +++ b/include/libtorrent/copy_ptr.hpp @@ -0,0 +1,69 @@ +/* + +Copyright (c) 2010-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_COPY_PTR +#define TORRENT_COPY_PTR + +namespace libtorrent +{ + template + struct copy_ptr + { + copy_ptr(): m_ptr(0) {} + copy_ptr(T* t): m_ptr(t) {} + copy_ptr(copy_ptr const& p): m_ptr(p.m_ptr ? new T(*p.m_ptr) : 0) {} + void reset(T* t = 0) { delete m_ptr; m_ptr = t; } + copy_ptr& operator=(copy_ptr const& p) + { + delete m_ptr; + m_ptr = p.m_ptr ? new T(*p.m_ptr) : 0; + return *this; + } + T* operator->() { return m_ptr; } + T const* operator->() const { return m_ptr; } + T& operator*() { return *m_ptr; } + T const& operator*() const { return *m_ptr; } + void swap(copy_ptr& p) + { + T* tmp = m_ptr; + m_ptr = p.m_ptr; + p.m_ptr = tmp; + } + operator bool() const { return m_ptr != 0; } + ~copy_ptr() { delete m_ptr; } + private: + T* m_ptr; + }; +} + +#endif // TORRENT_COPY_PTR + diff --git a/include/libtorrent/crc32c.hpp b/include/libtorrent/crc32c.hpp new file mode 100644 index 0000000..46741d3 --- /dev/null +++ b/include/libtorrent/crc32c.hpp @@ -0,0 +1,50 @@ +/* + +Copyright (c) 2014-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_CRC32C_HPP_INCLUDE +#define TORRENT_CRC32C_HPP_INCLUDE + +#include +#include "libtorrent/export.hpp" + +namespace libtorrent +{ + + // this is the crc32c (Castagnoli) polynomial + TORRENT_EXTRA_EXPORT boost::uint32_t crc32c_32(boost::uint32_t v); + TORRENT_EXTRA_EXPORT boost::uint32_t crc32c(boost::uint64_t const* v + , int num_words); +} + +#endif + + diff --git a/include/libtorrent/create_torrent.hpp b/include/libtorrent/create_torrent.hpp new file mode 100644 index 0000000..fa11150 --- /dev/null +++ b/include/libtorrent/create_torrent.hpp @@ -0,0 +1,490 @@ +/* + +Copyright (c) 2008-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_CREATE_TORRENT_HPP_INCLUDED +#define TORRENT_CREATE_TORRENT_HPP_INCLUDED + +#include "libtorrent/bencode.hpp" +#include "libtorrent/file_storage.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/storage.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/file.hpp" // for combine_path etc. + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include + +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +// OVERVIEW +// +// This section describes the functions and classes that are used +// to create torrent files. It is a layered API with low level classes +// and higher level convenience functions. A torrent is created in 4 +// steps: +// +// 1. first the files that will be part of the torrent are determined. +// 2. the torrent properties are set, such as tracker url, web seeds, +// DHT nodes etc. +// 3. Read through all the files in the torrent, SHA-1 all the data +// and set the piece hashes. +// 4. The torrent is bencoded into a file or buffer. +// +// If there are a lot of files and or deep directory hierarchies to +// traverse, step one can be time consuming. +// +// Typically step 3 is by far the most time consuming step, since it +// requires to read all the bytes from all the files in the torrent. +// +// All of these classes and functions are declared by including +// ``libtorrent/create_torrent.hpp``. +// +// example: +// +// .. code:: c++ +// +// file_storage fs; +// +// // recursively adds files in directories +// add_files(fs, "./my_torrent"); +// +// create_torrent t(fs); +// t.add_tracker("http://my.tracker.com/announce"); +// t.set_creator("libtorrent example"); +// +// // reads the files and calculates the hashes +// set_piece_hashes(t, "."); +// +// ofstream out("my_torrent.torrent", std::ios_base::binary); +// bencode(std::ostream_iterator(out), t.generate()); +// +namespace libtorrent +{ + class torrent_info; + + // 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 + { + // flags for create_torrent::create_torrent(). + enum flags_t + { + // 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. + optimize_alignment = 1, +#ifndef TORRENT_NO_DEPRECATE + // same as optimize_alignment, for backwards compatibility + optimize = 1, +#endif + + // This will create a merkle hash tree torrent. A merkle torrent cannot + // be opened in clients that don't specifically support merkle torrents. + // The benefit is that the resulting torrent file will be much smaller and + // not grow with more pieces. When this option is specified, it is + // recommended to have a fairly small piece size, say 64 kiB. + // When creating merkle torrents, the full hash tree is also generated + // and should be saved off separately. It is accessed through the + // create_torrent::merkle_tree() function. + merkle = 2, + + // 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. + modification_time = 4, + + // 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. + symlinks = 8, + + // to create a torrent that can be updated via a *mutable torrent* + // (see BEP38_). This also needs to be enabled for torrents that update + // another torrent. + // + // .. _BEP38: http://www.bittorrent.org/beps/bep_0038.html + mutable_torrent_support = 16 + }; + + // The ``piece_size`` is the size of each piece in bytes. It must + // be a multiple of 16 kiB. If a piece size of 0 is specified, a + // piece_size will be calculated such that the torrent file is roughly 40 kB. + // + // If a ``pad_size_limit`` is specified (other than -1), any file larger than + // the specified number of bytes will be preceded by a pad file to align it + // with the start of a piece. The pad_file_limit is ignored unless the + // ``optimize_alignment`` flag is passed. Typically it doesn't make sense + // to set this any lower than 4kiB. + // + // The overload that takes a ``torrent_info`` object will make a verbatim + // copy of its info dictionary (to preserve the info-hash). The copy of + // the info dictionary will be used by create_torrent::generate(). This means + // that none of the member functions of create_torrent that affects + // the content of the info dictionary (such as ``set_hash()``), will + // have any affect. + // + // The ``flags`` arguments specifies options for the torrent creation. It can + // be any combination of the flags defined by create_torrent::flags_t. + // + // ``alignment`` is used when pad files are enabled. This is the size + // eligible files are aligned to. The default is -1, which means the + // piece size of the torrent. + create_torrent(file_storage& fs, int piece_size = 0 + , int pad_file_limit = -1, int flags = optimize_alignment + , int alignment = -1); + create_torrent(torrent_info const& ti); + + // internal + ~create_torrent(); + + // This function will generate the .torrent file as a bencode tree. In order to + // generate the flat file, use the bencode() function. + // + // It may be useful to add custom entries to the torrent file before bencoding it + // and saving it to disk. + // + // If anything goes wrong during torrent generation, this function will return + // an empty ``entry`` structure. You can test for this condition by querying the + // type of the entry: + // + // .. code:: c++ + // + // file_storage fs; + // // add file ... + // create_torrent t(fs); + // // add trackers and piece hashes ... + // e = t.generate(); + // + // if (e.type() == entry::undefined_t) + // { + // // something went wrong + // } + // + // For instance, you cannot generate a torrent with 0 files in it. If you don't add + // any files to the ``file_storage``, torrent generation will fail. + entry generate() const; + + // returns an immutable reference to the file_storage used to create + // the torrent from. + file_storage const& files() const { return m_files; } + + // Sets the comment for the torrent. The string ``str`` should be utf-8 encoded. + // The comment in a torrent file is optional. + void set_comment(char const* str); + + // Sets the creator of the torrent. The string ``str`` should be utf-8 encoded. + // This is optional. + void set_creator(char const* str); + + // This sets the SHA-1 hash for the specified piece (``index``). You are required + // to set the hash for every piece in the torrent before generating it. If you have + // the files on disk, you can use the high level convenience function to do this. + // See set_piece_hashes(). + void set_hash(int index, sha1_hash const& h); + + // This sets the sha1 hash for this file. This hash will end up under the key ``sha1`` + // associated with this file (for multi-file torrents) or in the root info dictionary + // for single-file torrents. + void set_file_hash(int index, sha1_hash const& h); + + // This adds a url seed to the torrent. You can have any number of url seeds. For a + // single file torrent, this should be an HTTP url, pointing to a file with identical + // content as the file of the torrent. For a multi-file torrent, it should point to + // a directory containing a directory with the same name as this torrent, and all the + // files of the torrent in it. + // + // The second function, ``add_http_seed()`` adds an HTTP seed instead. + void add_url_seed(std::string const& url); + void add_http_seed(std::string const& 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 const& 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(std::string const& 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, + // loaded into a std::string. + void set_root_cert(std::string const& pem); + + // Sets and queries the private flag of the torrent. + // Torrents with the private flag set ask clients to not use any other + // sources than the tracker for peers, and to not advertise itself publicly, + // apart from the tracker. + void set_priv(bool p) { m_private = p; } + bool priv() const { return m_private; } + + // returns the number of pieces in the associated file_storage object. + int num_pieces() const { return m_files.num_pieces(); } + + // ``piece_length()`` returns the piece size of all pieces but the + // last one. ``piece_size()`` returns the size of the specified piece. + // these functions are just forwarding to the associated file_storage. + int piece_length() const { return m_files.piece_length(); } + int piece_size(int i) const { return m_files.piece_size(i); } + + // This function returns the merkle hash tree, if the torrent was created as a merkle + // torrent. The tree is created by ``generate()`` and won't be valid until that function + // has been called. When creating a merkle tree torrent, the actual tree itself has to + // be saved off separately and fed into libtorrent the first time you start seeding it, + // through the ``torrent_info::set_merkle_tree()`` function. From that point onwards, the + // tree will be saved in the resume data. + std::vector const& merkle_tree() const { return m_merkle_tree; } + + // Add similar torrents (by info-hash) or collections of similar torrents. + // Similar torrents are expected to share some files with this torrent. + // Torrents sharing a collection name with this torrent are also expected + // to share files with this torrent. A torrent may have more than one + // collection and more than one similar torrents. For more information, + // see `BEP 38`_. + // + // .. _`BEP 38`: http://www.bittorrent.org/beps/bep_0038.html + void add_similar_torrent(sha1_hash ih); + void add_collection(std::string c); + + private: + + file_storage& m_files; + // if m_info_dict is initialized, it is + // used instead of m_files to generate + // the info dictionary + entry m_info_dict; + + // the urls to the trackers + typedef std::pair announce_entry; + std::vector m_urls; + + std::vector m_url_seeds; + std::vector m_http_seeds; + + std::vector m_piece_hash; + + std::vector m_filehashes; + + std::vector m_similar; + std::vector m_collections; + + // if we're generating a merkle torrent, this is the + // merkle tree we got. This should be saved in fast-resume + // in order to start seeding the torrent + mutable std::vector m_merkle_tree; + + // dht nodes to add to the routing table/bootstrap from + typedef std::vector > nodes_t; + nodes_t m_nodes; + + // the hash that identifies this torrent + // is mutable because it's calculated + // lazily + mutable sha1_hash m_info_hash; + + // 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 multifile 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., is should not + // be announced on the dht + bool m_private:1; + + // if set to one, a merkle torrent will be generated + bool m_merkle_torrent:1; + + // if set, include the 'mtime' modification time in the + // torrent file + bool m_include_mtime:1; + + // if set, symbolic links are declared as such in + // the torrent file. The full data of the pointed-to + // file is still included + bool m_include_symlinks:1; + }; + + namespace detail + { + inline void nop(int) {} + } + + // Adds the file specified by ``path`` to the file_storage object. In case ``path`` + // refers to a directory, files will be added recursively from the directory. + // + // If specified, the predicate ``p`` is called once for every file and directory that + // is encountered. Files for which ``p`` returns true are added, and directories for + // which ``p`` returns true are traversed. ``p`` must have the following signature:: + // + // bool Pred(std::string const& p); + // + // The path that is passed in to the predicate is the full path of the file or + // directory. If no predicate is specified, all files are added, and all directories + // are traversed. + // + // The ".." directory is never traversed. + // + // The ``flags`` argument should be the same as the flags passed to the `create_torrent`_ + // constructor. + TORRENT_EXPORT void add_files(file_storage& fs, std::string const& file + , boost::function p, boost::uint32_t flags = 0); + TORRENT_EXPORT void add_files(file_storage& fs, std::string const& file + , boost::uint32_t flags = 0); + + // This function will assume that the files added to the torrent file exists at path + // ``p``, read those files and hash the content and set the hashes in the ``create_torrent`` + // object. The optional function ``f`` is called in between every hash that is set. ``f`` + // must have the following signature:: + // + // void Fun(int); + // + // 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 + , boost::function const& f, error_code& ec); + inline void set_piece_hashes(create_torrent& t, std::string const& p, error_code& ec) + { + set_piece_hashes(t, p, detail::nop, ec); + } +#ifndef BOOST_NO_EXCEPTIONS + inline void set_piece_hashes(create_torrent& t, std::string const& p) + { + error_code ec; + set_piece_hashes(t, p, detail::nop, ec); + if (ec) throw libtorrent_exception(ec); + } + template + void set_piece_hashes(create_torrent& t, std::string const& p, Fun f) + { + error_code ec; + set_piece_hashes(t, p, f, ec); + if (ec) throw libtorrent_exception(ec); + } +#endif + +#if TORRENT_USE_WSTRING + // wstring versions + + // all wstring APIs are deprecated since 0.16.11 + // instead, use the wchar -> utf8 conversion functions + // and pass in utf8 strings +#ifndef TORRENT_NO_DEPRECATE + + TORRENT_DEPRECATED + TORRENT_EXPORT void add_files(file_storage& fs, std::wstring const& wfile + , boost::function p, boost::uint32_t flags = 0); + + TORRENT_DEPRECATED + TORRENT_EXPORT void add_files(file_storage& fs, std::wstring const& wfile + , boost::uint32_t flags = 0); + + TORRENT_DEPRECATED + TORRENT_EXPORT void set_piece_hashes(create_torrent& t, std::wstring const& p + , boost::function f, error_code& ec); + + TORRENT_EXPORT void set_piece_hashes_deprecated(create_torrent& t + , std::wstring const& p + , boost::function f, error_code& ec); + +#ifndef BOOST_NO_EXCEPTIONS + template + TORRENT_DEPRECATED + void set_piece_hashes(create_torrent& t + , std::wstring const& p, Fun f) + { + error_code ec; + set_piece_hashes_deprecated(t, p, f, ec); + if (ec) throw libtorrent_exception(ec); + } + + TORRENT_DEPRECATED + inline void set_piece_hashes(create_torrent& t + , std::wstring const& p) + { + error_code ec; + set_piece_hashes_deprecated(t, p, detail::nop, ec); + if (ec) throw libtorrent_exception(ec); + } +#endif + + TORRENT_DEPRECATED + inline void set_piece_hashes(create_torrent& t + , std::wstring const& p, error_code& ec) + { + set_piece_hashes_deprecated(t, p, detail::nop, ec); + } +#endif // TORRENT_NO_DEPRECATE +#endif // TORRENT_USE_WSTRING + +} + +#endif + diff --git a/include/libtorrent/deadline_timer.hpp b/include/libtorrent/deadline_timer.hpp new file mode 100644 index 0000000..8ba2707 --- /dev/null +++ b/include/libtorrent/deadline_timer.hpp @@ -0,0 +1,58 @@ +/* + +Copyright (c) 2009-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_DEADLINE_TIMER_HPP_INCLUDED +#define TORRENT_DEADLINE_TIMER_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include + +#if defined TORRENT_BUILD_SIMULATOR +#include "simulator/simulator.hpp" +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent +{ +#if defined TORRENT_BUILD_SIMULATOR + typedef sim::asio::high_resolution_timer deadline_timer; +#else + typedef boost::asio::high_resolution_timer deadline_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..8f3f89f --- /dev/null +++ b/include/libtorrent/debug.hpp @@ -0,0 +1,210 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DEBUG_HPP_INCLUDED +#define TORRENT_DEBUG_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#if TORRENT_USE_ASSERTS && defined BOOST_HAS_PTHREADS +#include +#endif + +#if defined TORRENT_ASIO_DEBUGGING + +#include "libtorrent/assert.hpp" +#include "libtorrent/thread.hpp" +#include "libtorrent/time.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include + +#ifdef __MACH__ +#include +#include +#include + +const mach_msg_type_number_t task_events_info_count = TASK_EVENTS_INFO_COUNT; +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +std::string demangle(char const* name); + +namespace libtorrent +{ + struct async_t + { + async_t() : refs(0) {} + std::string stack; + int refs; + }; + + // defined in session_impl.cpp + extern std::map _async_ops; + extern int _async_ops_nthreads; + extern mutex _async_ops_mutex; + + // timestamp -> operation + struct wakeup_t + { + time_point timestamp; + boost::uint64_t context_switches; + char const* operation; + }; + extern std::deque _wakeups; + + inline bool has_outstanding_async(char const* name) + { + mutex::scoped_lock 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) + { + mutex::scoped_lock 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 != NULL) ++ptr; + else ptr = stack_text; + a.stack = ptr; + } + ++a.refs; + } + + inline void complete_async(char const* name) + { + mutex::scoped_lock 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 = teinfo.csw; +#else + w.context_switches = 0; +#endif + w.operation = name; + } + } + + inline void async_inc_threads() + { + mutex::scoped_lock l(_async_ops_mutex); + ++_async_ops_nthreads; + } + + inline void async_dec_threads() + { + mutex::scoped_lock l(_async_ops_mutex); + --_async_ops_nthreads; + } + + inline int log_async() + { + mutex::scoped_lock l(_async_ops_mutex); + int ret = 0; + for (std::map::iterator i = _async_ops.begin() + , end(_async_ops.end()); i != end; ++i) + { + if (i->second.refs <= _async_ops_nthreads - 1) continue; + ret += i->second.refs; + printf("%s: (%d)\n%s\n", i->first.c_str(), i->second.refs, i->second.stack.c_str()); + } + return ret; + } +} + +#endif // TORRENT_ASIO_DEBUGGING + +namespace libtorrent +{ +#if TORRENT_USE_ASSERTS && defined BOOST_HAS_PTHREADS + struct single_threaded + { + single_threaded(): m_single_thread(0) {} + ~single_threaded() { m_single_thread = 0; } + bool is_single_thread() const + { + if (m_single_thread == 0) + { + m_single_thread = pthread_self(); + return true; + } + return m_single_thread == pthread_self(); + } + bool is_not_thread() const + { + if (m_single_thread == 0) return true; + return m_single_thread != pthread_self(); + } + + void thread_started() + { m_single_thread = pthread_self(); } + + void transfer_ownership() + { m_single_thread = 0; } + + private: + mutable pthread_t m_single_thread; + }; +#else + struct single_threaded { + bool is_single_thread() const { return true; } + void thread_started() {} + bool is_not_thread() const {return true; } + }; +#endif +} + +#endif // TORRENT_DEBUG_HPP_INCLUDED + diff --git a/include/libtorrent/disk_buffer_holder.hpp b/include/libtorrent/disk_buffer_holder.hpp new file mode 100644 index 0000000..5130df8 --- /dev/null +++ b/include/libtorrent/disk_buffer_holder.hpp @@ -0,0 +1,129 @@ +/* + +Copyright (c) 2008-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DISK_BUFFER_HOLDER_HPP_INCLUDED +#define TORRENT_DISK_BUFFER_HOLDER_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/disk_io_job.hpp" // for block_cache_reference +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent +{ + struct disk_io_thread; + struct disk_observer; + + struct TORRENT_EXTRA_EXPORT buffer_allocator_interface + { + virtual char* allocate_disk_buffer(char const* category) = 0; + virtual void free_disk_buffer(char* b) = 0; + virtual void reclaim_block(block_cache_reference ref) = 0; + virtual char* allocate_disk_buffer(bool& exceeded + , boost::shared_ptr o + , char const* category) = 0; + protected: + ~buffer_allocator_interface() {} + }; + + // The disk buffer holder acts like a ``scoped_ptr`` that frees a disk buffer + // when it's destructed, unless it's released. ``release`` returns the disk + // buffer and transfers ownership and responsibility to free it to the caller. + // + // A disk buffer is freed by passing it to ``session_impl::free_disk_buffer()``. + // + // ``get()`` returns the pointer without transferring responsibility. If + // this buffer has been released, ``buffer()`` will return 0. + struct TORRENT_EXPORT disk_buffer_holder + { + // internal + disk_buffer_holder(buffer_allocator_interface& alloc, char* buf); + + // 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, disk_io_job const& j); + + // frees any unreleased disk buffer held by this object + ~disk_buffer_holder(); + + // return the held disk buffer and clear it from the + // holder. The responsibility to free it is passed on + // to the caller + char* release(); + + // return a pointer to the held buffer + char* get() const { return m_buf; } + + // set the holder object to hold the specified buffer + // (or NULL by default). If it's already holding a + // disk buffer, it will first be freed. + void reset(char* buf = 0); + void reset(disk_io_job const& j); + + // swap pointers of two disk buffer holders. + void swap(disk_buffer_holder& h) + { + TORRENT_ASSERT(&h.m_allocator == &m_allocator); + std::swap(h.m_buf, m_buf); + std::swap(h.m_ref, m_ref); + } + + block_cache_reference ref() const { return m_ref; } + + typedef char* (disk_buffer_holder::*unspecified_bool_type)(); + + // internal + operator unspecified_bool_type() const + { return m_buf == 0? 0: &disk_buffer_holder::release; } + + private: + // non-copyable + disk_buffer_holder& operator=(disk_buffer_holder const&); + disk_buffer_holder(disk_buffer_holder const*); + + buffer_allocator_interface& m_allocator; + char* m_buf; + block_cache_reference m_ref; + }; + +} + +#endif + diff --git a/include/libtorrent/disk_buffer_pool.hpp b/include/libtorrent/disk_buffer_pool.hpp new file mode 100644 index 0000000..79f46a1 --- /dev/null +++ b/include/libtorrent/disk_buffer_pool.hpp @@ -0,0 +1,195 @@ +/* + +Copyright (c) 2007-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DISK_BUFFER_POOL_HPP +#define TORRENT_DISK_BUFFER_POOL_HPP + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include + +#ifndef TORRENT_DISABLE_POOL_ALLOCATOR +#include "libtorrent/allocator.hpp" // for page_aligned_allocator +#include +#endif + +#if defined TORRENT_DEBUG || TORRENT_RELEASE_ASSERTS +#include +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/thread.hpp" +#include "libtorrent/io_service_fwd.hpp" +#include "libtorrent/file.hpp" // for iovec_t + +namespace libtorrent +{ + namespace aux { struct session_settings; } + class alert; + struct disk_observer; + + struct TORRENT_EXTRA_EXPORT disk_buffer_pool : boost::noncopyable + { + disk_buffer_pool(int block_size, io_service& ios + , boost::function const& trigger_trim); + ~disk_buffer_pool(); + +#if TORRENT_USE_ASSERTS + bool is_disk_buffer(char* buffer + , mutex::scoped_lock& l) const; + bool is_disk_buffer(char* buffer) const; +#endif + + char* allocate_buffer(char const* category); + char* allocate_buffer(bool& exceeded, boost::shared_ptr o + , char const* category); + void free_buffer(char* buf); + void free_multiple_buffers(char** bufvec, int numbufs); + + int allocate_iovec(file::iovec_t* iov, int iov_len); + void free_iovec(file::iovec_t* iov, int iov_len); + + int block_size() const { return m_block_size; } + + void release_memory(); + + boost::uint32_t in_use() const + { + mutex::scoped_lock l(m_pool_mutex); + return m_in_use; + } + boost::uint32_t num_to_evict(int num_needed = 0); + bool exceeded_max_size() const { return m_exceeded_max_size; } + + void set_settings(aux::session_settings const& sett, error_code& ec); + + protected: + + void free_buffer_impl(char* buf, mutex::scoped_lock& l); + char* allocate_buffer_impl(mutex::scoped_lock& l, char const* category); + + // number of bytes per block. The BitTorrent + // protocol defines the block size to 16 KiB. + const int m_block_size; + + // number of disk buffers currently allocated + int m_in_use; + + // cache size limit + int m_max_use; + + // if we have exceeded the limit, we won't start + // allowing allocations again until we drop below + // this low watermark + int m_low_watermark; + + // if we exceed the max number of buffers, we start + // adding up callbacks to this queue. Once the number + // of buffers in use drops below the low watermark, + // we start calling these functions back + std::vector > m_observers; + + // callback used to tell the cache it needs to free up some blocks + boost::function m_trigger_cache_trim; + + // set to true to throttle more allocations + bool m_exceeded_max_size; + + // this is the main thread io_service. Callbacks are + // posted on this in order to have them execute in + // the main thread. + io_service& m_ios; + + private: + + void check_buffer_level(mutex::scoped_lock& l); + + mutable mutex m_pool_mutex; + + int m_cache_buffer_chunk_size; + +#if TORRENT_HAVE_MMAP + // the file descriptor of the cache mmap file + int m_cache_fd; + // the pointer to the block of virtual address space + // making up the mmapped cache space + char* m_cache_pool; + // list of block indices that are not in use. block_index + // times 0x4000 + m_cache_pool is the address where the + // corresponding memory lives + std::vector m_free_list; +#endif + +#ifndef TORRENT_DISABLE_POOL_ALLOCATOR + // if this is true, all buffers are allocated + // from m_pool. If this is false, all buffers + // are allocated using page_aligned_allocator. + // if the settings change to prefer the other + // allocator, this bool will not switch over + // to match the settings until all buffers have + // been freed. That way, we never have a mixture + // of buffers allocated from different sources. + // in essence, this make the setting only take + // effect after a restart (which seems fine). + // or once the client goes idle for a while. + bool m_using_pool_allocator; + + // this is the actual user setting + bool m_want_pool_allocator; + + // memory pool for read and write operations + // and disk cache + boost::pool m_pool; +#endif + + // this is specifically exempt from release_asserts + // since it's a quite costly check. Only for debug + // builds. +#if defined TORRENT_DEBUG + std::set m_buffers_in_use; +#endif +#if TORRENT_USE_ASSERTS + int m_magic; + bool m_settings_set; +#endif + }; + +} + +#endif // TORRENT_DISK_BUFFER_POOL_HPP + diff --git a/include/libtorrent/disk_interface.hpp b/include/libtorrent/disk_interface.hpp new file mode 100644 index 0000000..a28c75a --- /dev/null +++ b/include/libtorrent/disk_interface.hpp @@ -0,0 +1,119 @@ +/* + +Copyright (c) 2012-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DISK_INTERFACE_HPP +#define TORRENT_DISK_INTERFACE_HPP + +#include +#include + +#include "libtorrent/bdecode.hpp" + +#include + +namespace libtorrent +{ + struct disk_io_job; + class piece_manager; + struct peer_request; + struct disk_observer; + struct file_pool; + struct add_torrent_params; + struct cache_status; + + struct TORRENT_EXTRA_EXPORT disk_interface + { + virtual void async_read(piece_manager* storage, peer_request const& r + , boost::function const& handler, void* requester + , int flags = 0) = 0; + virtual void async_write(piece_manager* storage, peer_request const& r + , disk_buffer_holder& buffer + , boost::function const& handler + , int flags = 0) = 0; + virtual void async_hash(piece_manager* storage, int piece, int flags + , boost::function const& handler, void* requester) = 0; + virtual void async_move_storage(piece_manager* storage, std::string const& p, int flags + , boost::function const& handler) = 0; + virtual void async_release_files(piece_manager* storage + , boost::function const& handler + = boost::function()) = 0; + virtual void async_check_fastresume(piece_manager* storage + , bdecode_node const* resume_data + , std::vector& links + , boost::function const& handler) = 0; +#ifndef TORRENT_NO_DEPRECATE + virtual void async_cache_piece(piece_manager* storage, int piece + , boost::function const& handler) = 0; + virtual void async_finalize_file(piece_manager*, int file + , boost::function const& handler + = boost::function()) = 0; +#endif + virtual void async_flush_piece(piece_manager* storage, int piece + , boost::function const& handler + = boost::function()) = 0; + virtual void async_stop_torrent(piece_manager* storage + , boost::function const& handler)= 0; + virtual void async_rename_file(piece_manager* storage, int index, std::string const& name + , boost::function const& handler) = 0; + virtual void async_delete_files(piece_manager* storage, int options + , boost::function const& handler) = 0; + virtual void async_save_resume_data(piece_manager* storage + , boost::function const& handler) = 0; + virtual void async_set_file_priority(piece_manager* storage + , std::vector const& prio + , boost::function const& handler) = 0; + virtual void async_load_torrent(add_torrent_params* params + , boost::function const& handler) = 0; + virtual void async_tick_torrent(piece_manager* storage + , boost::function const& handler) = 0; + + virtual void clear_read_cache(piece_manager* storage) = 0; + virtual void async_clear_piece(piece_manager* storage, int index + , boost::function const& handler) = 0; + virtual void clear_piece(piece_manager* storage, int index) = 0; + + virtual void update_stats_counters(counters& c) const = 0; + virtual void get_cache_info(cache_status* ret, bool no_pieces = true + , piece_manager const* storage = 0) const = 0; + + virtual file_pool& files() = 0; + +#if TORRENT_USE_ASSERTS + virtual bool is_disk_buffer(char* buffer) const = 0; +#endif + protected: + ~disk_interface() {} + }; +} + +#endif + diff --git a/include/libtorrent/disk_io_job.hpp b/include/libtorrent/disk_io_job.hpp new file mode 100644 index 0000000..8baa893 --- /dev/null +++ b/include/libtorrent/disk_io_job.hpp @@ -0,0 +1,247 @@ +/* + +Copyright (c) 2010-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DISK_IO_JOB_HPP +#define TORRENT_DISK_IO_JOB_HPP + +#include "libtorrent/time.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/tailqueue.hpp" +#include "libtorrent/peer_id.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent +{ + class entry; + class piece_manager; + struct cached_piece_entry; + struct bdecode_node; + class torrent_info; + + struct block_cache_reference + { + void* storage; + int piece; + int block; + }; + + // disk_io_jobs are allocated in a pool allocator in disk_io_thread + // they are always allocated from the network thread, posted + // (as pointers) to the disk I/O thread, and then passed back + // to the network thread for completion handling and to be freed. + // each disk_io_job can belong to one tailqueue. The job queue + // in the disk thread, is one, the jobs waiting on completion + // on a cache piece (in block_cache) is another, and a job + // waiting for a storage fence to be lowered is another. Jobs + // are never in more than one queue at a time. Only passing around + // pointers and chaining them back and forth into lists saves + // a lot of heap allocation churn of using general purpose + // containers. + struct TORRENT_EXTRA_EXPORT disk_io_job : tailqueue_node + , boost::noncopyable + { + disk_io_job(); + ~disk_io_job(); + + enum action_t + { + read + , write + , hash + , move_storage + , release_files + , delete_files + , check_fastresume + , save_resume_data + , rename_file + , stop_torrent +#ifndef TORRENT_NO_DEPRECATE + , cache_piece + , finalize_file +#endif + , flush_piece + , flush_hashed + , flush_storage + , trim_cache + , file_priority + , load_torrent + , clear_piece + , tick_storage + , resolve_links + + , num_job_ids + }; + + enum flags_t + { + sequential_access = 0x1, + + // this flag is set on a job when a read operation did + // not hit the disk, but found the data in the read cache. + cache_hit = 0x2, + + // force making a copy of the cached block, rather + // than getting a reference to the block already in + // the cache. + force_copy = 0x4, + + // 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 + fence = 0x8, + + // don't keep the read block in cache + volatile_read = 0x10, + + // this job is currently being performed, or it's hanging + // on a cache piece that may be flushed soon + in_progress = 0x20 + }; + + // for write jobs, returns true if its block + // is not dirty anymore + bool completed(cached_piece_entry const* pe, int block_size); + + // unique identifier for the peer when reading + void* requester; + + // for write, this points to the data to write, + // for read, the data read is returned here + // for other jobs, it may point to other job-specific types + // for move_storage and rename_file this is a string allocated + // with malloc() + // an entry* for save_resume_data + // for aiocb_complete this points to the aiocb that completed + // for get_cache_info this points to a cache_status object which + // is filled in + union + { + char* disk_block; + char* string; + entry* resume_data; + bdecode_node const* check_resume_data; + std::vector* priorities; + torrent_info* torrent_file; + int delete_options; + } buffer; + + // the disk storage this job applies to (if applicable) + boost::shared_ptr storage; + + // this is called when operation completes + boost::function 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 + { + // result for hash jobs + char piece_hash[20]; + + // 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. + std::vector* links; + + struct io_args + { + // if this is set, the read operation is required to + // release the block references once it's done sending + // the buffer. For aligned block requests (by far the + // most common) the buffers are not actually copied + // into the send buffer, but simply referenced. When this + // is set in a response to a read, the buffer needs to + // be de-referenced by sending a reclaim_block message + // back to the disk thread + block_cache_reference ref; + + // 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. + boost::uint32_t offset; + + // number of bytes 'buffer' points to. Used for read & write + boost::uint16_t buffer_size; + } io; + } d; + + // arguments used for read and write + // the piece this job applies to + boost::uint32_t piece:24; + + // the type of job this is + boost::uint32_t action:8; + + enum { operation_failed = -1 }; + + // return value of operation + boost::int32_t ret; + + // flags controlling this job + boost::uint8_t flags; + +#if defined TORRENT_DEBUG || defined TORRENT_RELEASE_ASSERTS + bool in_use:1; + + // 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:1; + + // set to true when the callback has been called once + // used to make sure we don't call it twice + mutable bool callback_called:1; + + // this is true when the job is blocked by a storage_fence + mutable bool blocked:1; +#endif + }; + +} + +#endif // TORRENT_DISK_IO_JOB_HPP + diff --git a/include/libtorrent/disk_io_thread.hpp b/include/libtorrent/disk_io_thread.hpp new file mode 100644 index 0000000..8fe8a30 --- /dev/null +++ b/include/libtorrent/disk_io_thread.hpp @@ -0,0 +1,627 @@ +/* + +Copyright (c) 2007-2016, Arvid Norberg, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DISK_IO_THREAD +#define TORRENT_DISK_IO_THREAD + +#include "libtorrent/config.hpp" +#include "libtorrent/storage.hpp" +#include "libtorrent/allocator.hpp" +#include "libtorrent/io_service.hpp" +#include "libtorrent/sliding_average.hpp" +#include "libtorrent/disk_io_job.hpp" +#include "libtorrent/disk_job_pool.hpp" +#include "libtorrent/block_cache.hpp" +#include "libtorrent/file_pool.hpp" +#include "libtorrent/disk_interface.hpp" +#include "libtorrent/performance_counters.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/thread.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include +#include +#include +#ifndef TORRENT_DISABLE_POOL_ALLOCATOR +#include +#endif +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent +{ + class alert; + struct add_torrent_params; + struct counters; + class alert_manager; + + struct cached_piece_info + { + piece_manager* storage; + + // holds one entry for each block in this piece. ``true`` represents + // the data for that block being in the disk cache and ``false`` means it's not. + std::vector blocks; + + // the time when a block was last written to this piece. The older + // a piece is, the more likely it is to be flushed to disk. + time_point last_use; + + // The index of the next block that needs to be hashed. + // Blocks are hashed as they are downloaded in order to not + // have to re-read them from disk once the piece is complete, to + // compare its hash against the hashes in the .torrent file. + int next_to_hash; + + // the piece index for this cache entry. + int piece; + + enum kind_t { read_cache = 0, write_cache = 1, volatile_read_cache = 2 }; + + // specifies if this piece is part of the read cache or the write cache. + kind_t kind; + + bool need_readback; + }; + + typedef tailqueue jobqueue_t; + + // this struct holds a number of statistics counters + // relevant for the disk io thread and disk cache. + struct TORRENT_EXPORT cache_status + { + // initializes all counters to 0 + cache_status() + : pieces() +#ifndef TORRENT_NO_DEPRECATE + , blocks_written(0) + , writes(0) + , blocks_read(0) + , blocks_read_hit(0) + , reads(0) + , queued_bytes(0) + , cache_size(0) + , write_cache_size(0) + , read_cache_size(0) + , pinned_blocks(0) + , total_used_buffers(0) + , average_read_time(0) + , average_write_time(0) + , average_hash_time(0) + , average_job_time(0) + , cumulative_job_time(0) + , cumulative_read_time(0) + , cumulative_write_time(0) + , cumulative_hash_time(0) + , total_read_back(0) + , read_queue_size(0) + , blocked_jobs(0) + , queued_jobs(0) + , peak_queued(0) + , pending_jobs(0) + , num_jobs(0) + , num_read_jobs(0) + , num_write_jobs(0) + , arc_mru_size(0) + , arc_mru_ghost_size(0) + , arc_mfu_size(0) + , arc_mfu_ghost_size(0) + , arc_write_size(0) + , arc_volatile_size(0) + , num_writing_threads(0) +#endif + { +#ifndef TORRENT_NO_DEPRECATE + memset(num_fence_jobs, 0, sizeof(num_fence_jobs)); +#endif + } + + std::vector pieces; + +#ifndef TORRENT_NO_DEPRECATE + // the total number of 16 KiB blocks written to disk + // since this session was started. + int blocks_written; + + // the total number of write operations performed since this + // session was started. + // + // The ratio (``blocks_written`` - ``writes``) / ``blocks_written`` represents + // the number of saved write operations per total write operations. i.e. a kind + // of cache hit ratio for the write cahe. + int writes; + + // the number of blocks that were requested from the + // bittorrent engine (from peers), that were served from disk or cache. + int blocks_read; + + // the number of blocks that was just copied from the read cache + // + // The ratio ``blocks_read_hit`` / ``blocks_read`` is the cache hit ratio + // for the read cache. + int blocks_read_hit; + + // the number of read operations used + int reads; + + // the number of bytes queued for writing, including bytes + // submitted to the OS for writing, but not yet complete + mutable boost::int64_t queued_bytes; + + // the number of 16 KiB blocks currently in the disk cache (both read and write). + // This includes both read and write cache. + int cache_size; + + // the number of blocks in the cache used for write cache + int write_cache_size; + + // the number of 16KiB blocks in the read cache. + int read_cache_size; + + // the number of blocks with a refcount > 0, i.e. + // they may not be evicted + int pinned_blocks; + + // the total number of buffers currently in use. + // This includes the read/write disk cache as well as send and receive buffers + // used in peer connections. + mutable int total_used_buffers; + + // the number of microseconds an average disk I/O job + // has to wait in the job queue before it get processed. + + // the time read jobs takes on average to complete + // (not including the time in the queue), in microseconds. This only measures + // read cache misses. + int average_read_time; + + // the time write jobs takes to complete, on average, + // in microseconds. This does not include the time the job sits in the disk job + // queue or in the write cache, only blocks that are flushed to disk. + int average_write_time; + + // the time hash jobs takes to complete on average, in + // microseconds. Hash jobs include running SHA-1 on the data (which for the most + // part is done incrementally) and sometimes reading back parts of the piece. It + // also includes checking files without valid resume data. + int average_hash_time; + int average_job_time; + + // the number of milliseconds spent in all disk jobs, and specific ones + // since the start of the session. Times are specified in milliseconds + int cumulative_job_time; + int cumulative_read_time; + int cumulative_write_time; + int cumulative_hash_time; + + // the number of blocks that had to be read back from disk because + // they were flushed before the SHA-1 hash got to hash them. If this + // is large, a larger cache could significantly improve performance + int total_read_back; + + // number of read jobs in the disk job queue + int read_queue_size; + + // number of jobs blocked because of a fence + int blocked_jobs; + + // number of jobs waiting to be issued (m_to_issue) + // average over 30 seconds + int queued_jobs; + + // largest ever seen number of queued jobs + int peak_queued; + + // number of jobs waiting to complete (m_pending) + // average over 30 seconds + int pending_jobs; + + // total number of disk job objects allocated right now + int num_jobs; + + // total number of disk read job objects allocated right now + int num_read_jobs; + + // total number of disk write job objects allocated right now + int num_write_jobs; + + // ARC cache stats. All of these counters are in number of pieces + // not blocks. A piece does not necessarily correspond to a certain + // number of blocks. The pieces in the ghost list never have any + // blocks in them + int arc_mru_size; + int arc_mru_ghost_size; + int arc_mfu_size; + int arc_mfu_ghost_size; + int arc_write_size; + int arc_volatile_size; + + // the number of threads currently writing to disk + int num_writing_threads; + + // counts only fence jobs that are currently blocking jobs + // not fences that are themself blocked + int num_fence_jobs[disk_io_job::num_job_ids]; +#endif + }; + + // this is a singleton consisting of the thread and a queue + // of disk io jobs + struct TORRENT_EXTRA_EXPORT disk_io_thread TORRENT_FINAL + : disk_job_pool + , disk_interface + , buffer_allocator_interface + { + disk_io_thread(io_service& ios + , counters& cnt + , void* userdata + , int block_size = 16 * 1024); + ~disk_io_thread(); + + void set_settings(settings_pack const* sett, alert_manager& alerts); + void set_num_threads(int i, bool wait = true); + + void abort(bool wait); + + void async_read(piece_manager* storage, peer_request const& r + , boost::function const& handler, void* requester + , int flags = 0) TORRENT_OVERRIDE; + void async_write(piece_manager* storage, peer_request const& r + , disk_buffer_holder& buffer + , boost::function const& handler + , int flags = 0) TORRENT_OVERRIDE; + void async_hash(piece_manager* storage, int piece, int flags + , boost::function const& handler, void* requester) TORRENT_OVERRIDE; + void async_move_storage(piece_manager* storage, std::string const& p, int flags + , boost::function const& handler) TORRENT_OVERRIDE; + void async_release_files(piece_manager* storage + , boost::function const& handler + = boost::function()) TORRENT_OVERRIDE; + void async_delete_files(piece_manager* storage, int options + , boost::function const& handler) TORRENT_OVERRIDE; + void async_check_fastresume(piece_manager* storage + , bdecode_node const* resume_data + , std::vector& links + , boost::function const& handler) TORRENT_OVERRIDE; + void async_save_resume_data(piece_manager* storage + , boost::function const& handler) TORRENT_OVERRIDE; + void async_rename_file(piece_manager* storage, int index, std::string const& name + , boost::function const& handler) TORRENT_OVERRIDE; + void async_stop_torrent(piece_manager* storage + , boost::function const& handler) TORRENT_OVERRIDE; +#ifndef TORRENT_NO_DEPRECATE + void async_cache_piece(piece_manager* storage, int piece + , boost::function const& handler) TORRENT_OVERRIDE; + void async_finalize_file(piece_manager* storage, int file + , boost::function const& handler + = boost::function()) TORRENT_OVERRIDE; +#endif + void async_flush_piece(piece_manager* storage, int piece + , boost::function const& handler + = boost::function()) TORRENT_OVERRIDE; + void async_set_file_priority(piece_manager* storage + , std::vector const& prio + , boost::function const& handler) TORRENT_OVERRIDE; + void async_load_torrent(add_torrent_params* params + , boost::function const& handler) TORRENT_OVERRIDE; + void async_tick_torrent(piece_manager* storage + , boost::function const& handler) TORRENT_OVERRIDE; + + void clear_read_cache(piece_manager* storage) TORRENT_OVERRIDE; + void async_clear_piece(piece_manager* storage, int index + , boost::function const& handler) TORRENT_OVERRIDE; + // this is not asynchronous and requires that the piece does not + // have any pending buffers. It's meant to be used for pieces that + // were just read and hashed and failed the hash check. + // there should be no read-operations left, and all buffers should + // be discardable + void clear_piece(piece_manager* storage, int index) TORRENT_OVERRIDE; + + // implements buffer_allocator_interface + void reclaim_block(block_cache_reference ref) TORRENT_OVERRIDE; + void free_disk_buffer(char* buf) TORRENT_OVERRIDE { m_disk_cache.free_buffer(buf); } + char* allocate_disk_buffer(char const* category) TORRENT_OVERRIDE + { + bool exceed = false; + return allocate_disk_buffer(exceed, boost::shared_ptr(), category); + } + + void trigger_cache_trim(); + char* allocate_disk_buffer(bool& exceeded, boost::shared_ptr o + , char const* category) TORRENT_OVERRIDE; + + bool exceeded_cache_use() const + { return m_disk_cache.exceeded_max_size(); } + + void update_stats_counters(counters& c) const TORRENT_OVERRIDE; + void get_cache_info(cache_status* ret, bool no_pieces = true + , piece_manager const* storage = 0) const TORRENT_OVERRIDE; + + // this submits all queued up jobs to the thread + void submit_jobs(); + + block_cache* cache() { return &m_disk_cache; } + +#if TORRENT_USE_ASSERTS + bool is_disk_buffer(char* buffer) const TORRENT_OVERRIDE + { return m_disk_cache.is_disk_buffer(buffer); } +#endif + + enum thread_type_t { + generic_thread, + hasher_thread + }; + + void thread_fun(int thread_id, thread_type_t type + , boost::shared_ptr w); + + virtual file_pool& files() TORRENT_OVERRIDE { return m_file_pool; } + + io_service& get_io_service() { return m_ios; } + + int prep_read_job_impl(disk_io_job* j, bool check_fence = true); + +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const; +#endif + + void maybe_issue_queued_read_jobs(cached_piece_entry* pe, + jobqueue_t& completed_jobs); + int do_read(disk_io_job* j, jobqueue_t& completed_jobs); + int do_uncached_read(disk_io_job* j); + + int do_write(disk_io_job* j, jobqueue_t& completed_jobs); + int do_uncached_write(disk_io_job* j); + + int do_hash(disk_io_job* j, jobqueue_t& completed_jobs); + int do_uncached_hash(disk_io_job* j); + + int do_move_storage(disk_io_job* j, jobqueue_t& completed_jobs); + int do_release_files(disk_io_job* j, jobqueue_t& completed_jobs); + int do_delete_files(disk_io_job* j, jobqueue_t& completed_jobs); + int do_check_fastresume(disk_io_job* j, jobqueue_t& completed_jobs); + int do_save_resume_data(disk_io_job* j, jobqueue_t& completed_jobs); + int do_rename_file(disk_io_job* j, jobqueue_t& completed_jobs); + int do_stop_torrent(disk_io_job* j, jobqueue_t& completed_jobs); + int do_read_and_hash(disk_io_job* j, jobqueue_t& completed_jobs); +#ifndef TORRENT_NO_DEPRECATE + int do_cache_piece(disk_io_job* j, jobqueue_t& completed_jobs); + int do_finalize_file(disk_io_job* j, jobqueue_t& completed_jobs); +#endif + int do_flush_piece(disk_io_job* j, jobqueue_t& completed_jobs); + int do_flush_hashed(disk_io_job* j, jobqueue_t& completed_jobs); + int do_flush_storage(disk_io_job* j, jobqueue_t& completed_jobs); + int do_trim_cache(disk_io_job* j, jobqueue_t& completed_jobs); + int do_file_priority(disk_io_job* j, jobqueue_t& completed_jobs); + int do_load_torrent(disk_io_job* j, jobqueue_t& completed_jobs); + int do_clear_piece(disk_io_job* j, jobqueue_t& completed_jobs); + int do_tick(disk_io_job* j, jobqueue_t& completed_jobs); + int do_resolve_links(disk_io_job* j, jobqueue_t& completed_jobs); + + void call_job_handlers(void* userdata); + + private: + + enum return_value_t + { + // the do_* functions can return this to indicate the disk + // job did not complete immediately, and shouldn't be posted yet + defer_handler = -200, + + // the job cannot be completed right now, put it back in the + // queue and try again later + retry_job = -201 + }; + + void add_completed_job(disk_io_job* j); + void add_completed_jobs(jobqueue_t& jobs); + void add_completed_jobs_impl(jobqueue_t& jobs + , jobqueue_t& completed_jobs); + + void fail_jobs(storage_error const& e, jobqueue_t& jobs_); + void fail_jobs_impl(storage_error const& e, jobqueue_t& src, jobqueue_t& dst); + + void check_cache_level(mutex::scoped_lock& l, jobqueue_t& completed_jobs); + + void perform_job(disk_io_job* j, jobqueue_t& completed_jobs); + + // this queues up another job to be submitted + void add_job(disk_io_job* j, bool user_add = true); + void add_fence_job(piece_manager* storage, disk_io_job* j + , bool user_add = true); + + // assumes l is locked (cache mutex). + // writes out the blocks [start, end) (releases the lock + // during the file operation) + int flush_range(cached_piece_entry* p, int start, int end + , jobqueue_t& completed_jobs, mutex::scoped_lock& l); + + // low level flush operations, used by flush_range + int build_iovec(cached_piece_entry* pe, int start, int end + , file::iovec_t* iov, int* flushing, int block_base_index = 0); + void flush_iovec(cached_piece_entry* pe, file::iovec_t const* iov, int const* flushing + , int num_blocks, storage_error& error); + void iovec_flushed(cached_piece_entry* pe + , int* flushing, int num_blocks, int block_offset + , storage_error const& error + , jobqueue_t& completed_jobs); + + // assumes l is locked (the cache mutex). + // assumes pe->hash to be set. + // If there are new blocks in piece 'pe' that have not been + // hashed by the partial_hash object attached to this piece, + // the piece will + void kick_hasher(cached_piece_entry* pe, mutex::scoped_lock& l); + + // flags to pass in to flush_cache() + enum flush_flags_t + { + // only flush read cache (this is cheap) + flush_read_cache = 1, + // flush read cache, and write cache + flush_write_cache = 2, + // flush read cache, delete write cache without flushing to disk + flush_delete_cache = 4, + // expect all pieces for the storage to have been + // cleared when flush_cache() returns. This is only + // used for asserts and only applies for fence jobs + flush_expect_clear = 8 + }; + void flush_cache(piece_manager* storage, boost::uint32_t flags, jobqueue_t& completed_jobs, mutex::scoped_lock& l); + void flush_expired_write_blocks(jobqueue_t& completed_jobs, mutex::scoped_lock& l); + void flush_piece(cached_piece_entry* pe, int flags, jobqueue_t& completed_jobs, mutex::scoped_lock& l); + + int try_flush_hashed(cached_piece_entry* p, int cont_blocks, jobqueue_t& completed_jobs, mutex::scoped_lock& l); + + void try_flush_write_blocks(int num, jobqueue_t& completed_jobs, mutex::scoped_lock& l); + + // used to batch reclaiming of blocks to once per cycle + void commit_reclaimed_blocks(); + + void maybe_flush_write_blocks(); + void execute_job(disk_io_job* j); + void immediate_execute(); + void abort_jobs(); + + // this is a counter which is atomically incremented + // by each thread as it's started up, in order to + // assign a unique id to each thread + boost::atomic m_num_threads; + + // set to true once we start shutting down + boost::atomic m_abort; + + // 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 + boost::atomic m_num_running_threads; + + // the actual threads running disk jobs + std::vector > m_threads; + + aux::session_settings m_settings; + + // userdata pointer for the complete_job function, which + // is posted to the network thread when jobs complete + void* m_userdata; + + // the last time we expired write blocks from the cache + time_point m_last_cache_expiry; + + time_point m_last_file_check; + + // LRU cache of open files + file_pool m_file_pool; + + // disk cache + mutable mutex m_cache_mutex; + block_cache m_disk_cache; + enum + { + cache_check_idle, + cache_check_active, + cache_check_reinvoke + }; + int m_cache_check_state; + + // 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; + + // average read time for cache misses (in microseconds) + average_accumulator m_read_time; + + // average write time (in microseconds) + average_accumulator m_write_time; + + // average hash time (in microseconds) + average_accumulator m_hash_time; + + // average time to serve a job (any job) in microseconds + average_accumulator m_job_time; + + // this is the main thread io_service. Callbacks are + // posted on this in order to have them execute in + // the main thread. + io_service& m_ios; + + // used to wake up the disk IO thread when there are new + // jobs on the job queue (m_queued_jobs) + condition_variable m_job_cond; + + // mutex to protect the m_queued_jobs list + mutable mutex m_job_mutex; + + // jobs queued for servicing + jobqueue_t m_queued_jobs; + + // when using more than 2 threads, this is + // used for just hashing jobs, just for threads + // dedicated to do hashing + condition_variable m_hash_job_cond; + jobqueue_t m_queued_hash_jobs; + + // used to rate limit disk performance warnings + time_point m_last_disk_aio_performance_warning; + + // 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 + mutex m_completed_jobs_mutex; + jobqueue_t m_completed_jobs; + + // these are blocks that have been returned by the main thread + // but they haven't been freed yet. This is used to batch + // reclaiming of blocks, to only need one mutex lock per cycle + std::vector m_blocks_to_reclaim; + + // when this is true, there is an outstanding message in the + // message queue that will reclaim all blocks in + // m_blocks_to_reclaim, there's no need to send another one + bool m_outstanding_reclaim_message; +#if TORRENT_USE_ASSERTS + int m_magic; +#endif + }; +} + +#endif + diff --git a/include/libtorrent/disk_job_pool.hpp b/include/libtorrent/disk_job_pool.hpp new file mode 100644 index 0000000..3b6915c --- /dev/null +++ b/include/libtorrent/disk_job_pool.hpp @@ -0,0 +1,77 @@ +/* + +Copyright (c) 2010-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DISK_JOB_POOL +#define TORRENT_DISK_JOB_POOL + +#include "libtorrent/config.hpp" +#include "libtorrent/thread.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent +{ + struct disk_io_job; + + struct TORRENT_EXTRA_EXPORT disk_job_pool + { + disk_job_pool(); + ~disk_job_pool(); + + disk_io_job* allocate_job(int type); + void free_job(disk_io_job* j); + void free_jobs(disk_io_job** j, int num); + + int jobs_in_use() const { return m_jobs_in_use; } + int read_jobs_in_use() const { return m_read_jobs; } + int write_jobs_in_use() const { return m_write_jobs; } + + private: + + // total number of in-use jobs + int m_jobs_in_use; + // total number of in-use read jobs + int m_read_jobs; + // total number of in-use write jobs + int m_write_jobs; + + mutex m_job_mutex; + boost::pool<> m_job_pool; + }; +} + +#endif // TORRENT_DISK_JOB_POOL + diff --git a/include/libtorrent/disk_observer.hpp b/include/libtorrent/disk_observer.hpp new file mode 100644 index 0000000..6478f01 --- /dev/null +++ b/include/libtorrent/disk_observer.hpp @@ -0,0 +1,52 @@ +/* + +Copyright (c) 2012-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DISK_OBSERVER_HPP +#define TORRENT_DISK_OBSERVER_HPP + +#include "libtorrent/config.hpp" + +namespace libtorrent +{ + struct TORRENT_EXTRA_EXPORT disk_observer + { + // called when the disk cache size has dropped + // below the low watermark again and we can + // resume downloading from peers + virtual void on_disk() = 0; + protected: + ~disk_observer() {} + }; +} + +#endif + diff --git a/include/libtorrent/ed25519.hpp b/include/libtorrent/ed25519.hpp new file mode 100644 index 0000000..bb343cc --- /dev/null +++ b/include/libtorrent/ed25519.hpp @@ -0,0 +1,32 @@ +#ifndef ED25519_HPP +#define ED25519_HPP + +#include "libtorrent/export.hpp" // for TORRENT_EXPORT +#include // for size_t + +enum +{ + ed25519_seed_size = 32, + ed25519_private_key_size = 64, + ed25519_public_key_size = 32, + ed25519_signature_size = 64, + ed25519_scalar_size = 32, + ed25519_shared_secret_size = 32 +}; + +extern "C" { + +#ifndef ED25519_NO_SEED +void TORRENT_EXPORT ed25519_create_seed(unsigned char *seed); +#endif + +void TORRENT_EXPORT ed25519_create_keypair(unsigned char *public_key, unsigned char *private_key, const unsigned char *seed); +void TORRENT_EXPORT ed25519_sign(unsigned char *signature, const unsigned char *message, size_t message_len, const unsigned char *public_key, const unsigned char *private_key); +int TORRENT_EXPORT ed25519_verify(const unsigned char *signature, const unsigned char *message, size_t message_len, const unsigned char *private_key); +void TORRENT_EXPORT ed25519_add_scalar(unsigned char *public_key, unsigned char *private_key, const unsigned char *scalar); +void TORRENT_EXPORT ed25519_key_exchange(unsigned char *shared_secret, const unsigned char *public_key, const unsigned char *private_key); + +} + +#endif // ED25519_HPP + diff --git a/include/libtorrent/entry.hpp b/include/libtorrent/entry.hpp new file mode 100644 index 0000000..e365fd5 --- /dev/null +++ b/include/libtorrent/entry.hpp @@ -0,0 +1,334 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_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 "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include +#include +#include +#if TORRENT_USE_IOSTREAM +#include +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/assert.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/max.hpp" + +namespace libtorrent +{ +#ifndef TORRENT_NO_DEPRECATE + struct lazy_entry; +#endif + struct bdecode_node; + + // thrown by any accessor function of entry if the accessor + // function requires a type different than the actual type + // of the entry object. + struct type_error : std::runtime_error + { + // internal + type_error(const char* error): std::runtime_error(error) {} + }; + + // 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. + typedef std::map dictionary_type; + typedef std::string string_type; + typedef std::list list_type; + typedef boost::int64_t integer_type; + typedef std::vector preformatted_type; + + // 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 const&); + entry(string_type const&); + entry(list_type const&); + entry(integer_type const&); + entry(preformatted_type const&); + + // construct an empty entry of the specified type. + // see data_type enum. + entry(data_type t); + + // hidden + entry(entry const& e); + + // hidden + entry(); + + // hidden + ~entry(); + + // hidden + bool operator==(entry const& e) const; + bool operator!=(entry const& e) const { return !(*this == e); } + + // copies the structure of the right hand side into this + // entry. +#ifndef TORRENT_NO_DEPRECATE + void operator=(lazy_entry const&); +#endif + void operator=(bdecode_node const&); + void operator=(entry const&); + void operator=(dictionary_type const&); + void operator=(string_type const&); + void operator=(list_type const&); + void operator=(integer_type const&); + void operator=(preformatted_type const&); + + // The ``integer()``, ``string()``, ``list()`` and ``dict()`` functions + // are accessors that return the respective type. If the ``entry`` object + // isn't of the type you request, the accessor will throw + // libtorrent_exception (which derives from ``std::runtime_error``). You + // can ask an ``entry`` for its type through the ``type()`` function. + // + // If you want to create an ``entry`` you give it the type you want it to + // have in its constructor, and then use one of the non-const accessors + // to get a reference which you then can assign the value you want it to + // have. + // + // The typical code to get info from a torrent file will then look like + // this: + // + // .. code:: c++ + // + // entry torrent_file; + // // ... + // + // // throws if this is not a dictionary + // entry::dictionary_type const& dict = torrent_file.dict(); + // entry::dictionary_type::const_iterator i; + // i = dict.find("announce"); + // if (i != dict.end()) + // { + // std::string tracker_url = i->second.string(); + // std::cout << tracker_url << "\n"; + // } + // + // + // The following code is equivalent, but a little bit shorter: + // + // .. code:: c++ + // + // entry torrent_file; + // // ... + // + // // throws if this is not a dictionary + // if (entry* i = torrent_file.find_key("announce")) + // { + // std::string tracker_url = i->string(); + // std::cout << tracker_url << "\n"; + // } + // + // + // To make it easier to extract information from a torrent file, the + // class torrent_info exists. + integer_type& integer(); + const integer_type& integer() const; + string_type& string(); + const string_type& string() const; + list_type& list(); + const list_type& list() const; + dictionary_type& dict(); + const dictionary_type& dict() const; + preformatted_type& preformatted(); + const preformatted_type& preformatted() const; + + // swaps the content of *this* with ``e``. + void swap(entry& e); + + // All of these functions requires the entry to be a dictionary, if it + // isn't they will throw ``libtorrent::type_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 ``libtorrent::type_error``. + entry& operator[](char const* key); + entry& operator[](std::string const& key); +#ifndef BOOST_NO_EXCEPTIONS + const entry& operator[](char const* key) const; + const entry& operator[](std::string const& key) const; +#endif + + // These functions requires the entry to be a dictionary, if it isn't + // they will throw ``libtorrent::type_error``. + // + // They will look for an element at the given key in the dictionary, if + // the element cannot be found, they will return 0. If an element with + // the given key is found, the return a pointer to it. + entry* find_key(char const* key); + entry const* find_key(char const* key) const; + entry* find_key(std::string const& key); + entry const* find_key(std::string const& key) const; + + // returns a pretty-printed string representation + // of the bencoded structure, with JSON-style syntax + std::string to_string() const; + + protected: + + void construct(data_type t); + void copy(const entry& e); + void destruct(); + + private: + + void to_string_impl(std::string& out, int indent) const; + +#if (defined(_MSC_VER) && _MSC_VER < 1310) || TORRENT_COMPLETE_TYPES_REQUIRED + // workaround for msvc-bug. + // assumes sizeof(map) == sizeof(map) + // and sizeof(list) == sizeof(list) + enum { union_size + = max5) + , sizeof(std::map) + , sizeof(string_type) + , sizeof(integer_type) + , sizeof(preformatted_type)>::value + }; +#else + enum { union_size + = max5::value + }; +#endif + integer_type data[(union_size + sizeof(integer_type) - 1) + / sizeof(integer_type)]; + + // 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 + boost::uint8_t m_type:7; + + public: + // in debug mode this is set to false by bdecode to indicate that the + // program has not yet queried the type of this entry, and should not + // assume that it has a certain type. This is asserted in the accessor + // functions. This does not apply if exceptions are used. + mutable boost::uint8_t m_type_queried:1; + }; + + namespace detail + { + TORRENT_EXPORT char const* integer_to_str(char* buf, int size + , 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 + +#ifndef BOOST_NO_EXCEPTIONS + // internal + TORRENT_NO_RETURN inline void throw_type_error() + { + throw libtorrent_exception(error_code(errors::invalid_entry_type + , get_libtorrent_category())); + } +#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..c82c8e1 --- /dev/null +++ b/include/libtorrent/enum_net.hpp @@ -0,0 +1,194 @@ +/* + +Copyright (c) 2007-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_ENUM_NET_HPP_INCLUDED +#define TORRENT_ENUM_NET_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include + +#if TORRENT_USE_IFCONF || TORRENT_USE_NETLINK || TORRENT_USE_SYSCTL +#include // for SO_BINDTODEVICE +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/io_service_fwd.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/socket.hpp" + +namespace libtorrent +{ + struct socket_type; + + // the interface should not have a netmask + struct ip_interface + { + address interface_address; + address netmask; + char name[64]; + int mtu; + }; + + struct ip_route + { + address destination; + address netmask; + address gateway; + char name[64]; + int mtu; + }; + + // returns a list of the configured IP interfaces + // on the machine + TORRENT_EXTRA_EXPORT std::vector enum_net_interfaces(io_service& ios + , error_code& ec); + + TORRENT_EXTRA_EXPORT std::vector enum_routes(io_service& ios + , error_code& ec); + + // return (a1 & mask) == (a2 & mask) + TORRENT_EXTRA_EXPORT bool match_addr_mask(address const& a1 + , address const& a2, address const& mask); + + // returns true if the specified address is on the same + // local network as us + TORRENT_EXTRA_EXPORT bool in_local_network(io_service& ios, address const& addr + , error_code& ec); + TORRENT_EXTRA_EXPORT bool in_local_network(std::vector const& net + , address const& addr); + + TORRENT_EXTRA_EXPORT address get_default_gateway(io_service& ios, error_code& ec); + +#ifdef SO_BINDTODEVICE + struct bind_to_device_opt + { + bind_to_device_opt(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 + const char* data(Protocol const&) const { return m_value; } + template + size_t size(Protocol const&) const { return IFNAMSIZ; } + char const* m_value; + }; +#endif + + // 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). + template + address bind_to_device(io_service& ios, Socket& sock + , boost::asio::ip::tcp const& protocol + , char const* device_name, int port, error_code& ec) + { + tcp::endpoint bind_ep(address_v4::any(), port); + + address ip = address::from_string(device_name, ec); + if (!ec) + { +#if TORRENT_USE_IPV6 + // 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(); +#endif + 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(); + +#ifdef SO_BINDTODEVICE + // try to use SO_BINDTODEVICE here, if that exists. If it fails, + // fall back to the mechanism we have below + sock.set_option(bind_to_device_opt(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 (int i = 0; i < int(ifs.size()); ++i) + { + // 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 (strcmp(ifs[i].name, device_name) != 0) continue; + if (ifs[i].interface_address.is_v4() != (protocol == boost::asio::ip::tcp::v4())) + continue; + + bind_ep.address(ifs[i].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 true if the given device exists + TORRENT_EXTRA_EXPORT bool has_interface(char const* name, io_service& ios + , error_code& ec); + + // returns the device name whose local address is ``addr``. If + // no such device is found, an empty string is returned. + TORRENT_EXTRA_EXPORT std::string device_for_address(address addr + , io_service& ios, error_code& ec); + +} + +#endif + diff --git a/include/libtorrent/error.hpp b/include/libtorrent/error.hpp new file mode 100644 index 0000000..2f3dfcc --- /dev/null +++ b/include/libtorrent/error.hpp @@ -0,0 +1,52 @@ +/* + +Copyright (c) 2009-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_ERROR_HPP_INCLUDED +#define TORRENT_ERROR_HPP_INCLUDED + +#include +#include "libtorrent/config.hpp" + +#if defined TORRENT_WINDOWS || defined TORRENT_CYGWIN +// asio assumes that the windows error codes are defined already +#include +#endif + +#include + +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..f09935c --- /dev/null +++ b/include/libtorrent/error_code.hpp @@ -0,0 +1,585 @@ +/* + +Copyright (c) 2008-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ERROR_CODE_HPP_INCLUDED +#define TORRENT_ERROR_CODE_HPP_INCLUDED + +#include +#include "libtorrent/config.hpp" +#include "libtorrent/string_util.hpp" // for allocate_string_copy +#include // free + +#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 + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#ifndef BOOST_SYSTEM_NOEXCEPT +#define BOOST_SYSTEM_NOEXCEPT TORRENT_EXCEPTION_THROW_SPECIFIER +#endif + +namespace libtorrent +{ + + namespace errors + { + // libtorrent uses boost.system's ``error_code`` class to represent + // errors. libtorrent has its own error category + // get_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 plaintext, 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 NAT-PMP router responded with an unsupported protocol version + unsupported_protocol_version = 120, + // You are not authorized to map ports on this NAT-PMP router + natpmp_not_authorized, + // The NAT-PMP router failed because of a network failure + network_failure, + // The NAT-PMP router failed because of lack of resources + no_resources, + // The NAT-PMP router failed because an unsupported opcode was sent + unsupported_opcode, + + + + // 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, + +#ifndef TORRENT_NO_DEPRECATE + // 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 + + // 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& get_libtorrent_category(); + + // returns the error_category for HTTP errors + TORRENT_EXPORT boost::system::error_category& get_http_category(); + + using boost::system::error_code; + using boost::system::error_condition; + + // internal + using boost::system::generic_category; + using boost::system::system_category; + +#ifndef BOOST_NO_EXCEPTIONS + struct TORRENT_EXPORT libtorrent_exception: std::exception + { + libtorrent_exception(error_code const& s): m_error(s), m_msg(0) {} + virtual const char* what() const TORRENT_EXCEPTION_THROW_SPECIFIER; + virtual ~libtorrent_exception() TORRENT_EXCEPTION_THROW_SPECIFIER; +#if __cplusplus >= 201103L + libtorrent_exception(libtorrent_exception const&) = default; + libtorrent_exception& operator=(libtorrent_exception const&) = default; +#endif + error_code error() const { return m_error; } + private: + error_code m_error; + mutable char* m_msg; + }; +#endif + + // used by storage to return errors + // also includes which underlying file the + // error happened on + struct TORRENT_EXPORT storage_error + { + storage_error(): file(-1), operation(0) {} + storage_error(error_code e): ec(e), file(-1), operation(0) {} + + operator bool() const { return ec.value() != 0; } + + // the error that occurred + error_code ec; + + // the file the error occurred on + boost::int32_t file:24; + + // A code from file_operation_t enum, indicating what + // kind of operation failed. + boost::uint32_t operation:8; + + enum file_operation_t { + none, + stat, + mkdir, + open, + rename, + remove, + copy, + read, + write, + fallocate, + alloc_cache_piece, + partfile_move, + partfile_read, + partfile_write, + check_resume, + hard_link + }; + + // Returns a string literal representing the file operation + // that failed. If there were no failure, it returns + // an empty string. + char const* operation_str() const + { + char const* ops[] = + { + "", "stat", "mkdir", "open", "rename", "remove", "copy" + , "read", "write", "fallocate", "allocate cache piece" + , "partfile move", "partfile read", "partfile write" + , "check resume", "hard_link" + }; + return ops[operation]; + } + }; + +} + +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; }; + + 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/export.hpp b/include/libtorrent/export.hpp new file mode 100644 index 0000000..87536af --- /dev/null +++ b/include/libtorrent/export.hpp @@ -0,0 +1,96 @@ +/* + +Copyright (c) 2005-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_EXPORT_HPP_INCLUDED +#define TORRENT_EXPORT_HPP_INCLUDED + +#if !defined(BOOST_COMPILER_CONFIG) && !defined(BOOST_NO_COMPILER_CONFIG) +# include +#endif +#ifdef BOOST_COMPILER_CONFIG +# include BOOST_COMPILER_CONFIG +#endif + +#if !defined(BOOST_PLATFORM_CONFIG) && !defined(BOOST_NO_PLATFORM_CONFIG) +# include +#endif +#ifdef BOOST_PLATFORM_CONFIG +# include BOOST_PLATFORM_CONFIG +#endif + +// backwards compatibility with older versions of boost +#if !defined BOOST_SYMBOL_EXPORT && !defined BOOST_SYMBOL_IMPORT +# if defined _MSC_VER || defined __MINGW32__ +# define BOOST_SYMBOL_EXPORT __declspec(dllexport) +# define BOOST_SYMBOL_IMPORT __declspec(dllimport) +# elif __GNU__ >= 4 +# define BOOST_SYMBOL_EXPORT __attribute__((visibility("default"))) +# define BOOST_SYMBOL_IMPORT __attribute__((visibility("default"))) +# else +# define BOOST_SYMBOL_EXPORT +# define BOOST_SYMBOL_IMPORT +# endif +#endif + +#if defined TORRENT_BUILDING_SHARED +# define TORRENT_EXPORT BOOST_SYMBOL_EXPORT +#elif defined TORRENT_LINKING_SHARED +# define TORRENT_EXPORT BOOST_SYMBOL_IMPORT +#endif + +// when this is specified, export a bunch of extra +// symbols, mostly for the unit tests to reach +#if defined TORRENT_EXPORT_EXTRA +# if defined TORRENT_BUILDING_SHARED +# define TORRENT_EXTRA_EXPORT BOOST_SYMBOL_EXPORT +# elif defined TORRENT_LINKING_SHARED +# define TORRENT_EXTRA_EXPORT BOOST_SYMBOL_IMPORT +# endif +#endif + +#ifndef TORRENT_EXPORT +# define TORRENT_EXPORT +#endif + +#ifndef TORRENT_EXTRA_EXPORT +# define TORRENT_EXTRA_EXPORT +#endif + +// only export this type if deprecated functions are enabled +#ifdef TORRENT_NO_DEPRECATE +#define TORRENT_DEPRECATED_EXPORT TORRENT_EXTRA_EXPORT +#else +#define TORRENT_DEPRECATED_EXPORT TORRENT_EXPORT +#endif + +#endif + diff --git a/include/libtorrent/extensions.hpp b/include/libtorrent/extensions.hpp new file mode 100644 index 0000000..92e4686 --- /dev/null +++ b/include/libtorrent/extensions.hpp @@ -0,0 +1,515 @@ +/* + +Copyright (c) 2006-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_EXTENSIONS_HPP_INCLUDED +#define TORRENT_EXTENSIONS_HPP_INCLUDED + +// OVERVIEW +// +// libtorrent has a plugin interface for implementing extensions to the protocol. +// These can be general extensions for transferring metadata or peer exchange +// extensions, or it could be used to provide a way to customize the protocol +// to fit a particular (closed) network. +// +// In short, the plugin interface makes it possible to: +// +// * register extension messages (sent in the extension handshake), see +// extensions_. +// * add data and parse data from the extension handshake. +// * send extension messages and standard bittorrent messages. +// * override or block the handling of standard bittorrent messages. +// * save and restore state via the session state +// * see all alerts that are posted +// +// .. _extensions: extension_protocol.html +// +// a word of caution +// ----------------- +// +// Writing your own plugin is a very easy way to introduce serious bugs such as +// dead locks and race conditions. Since a plugin has access to internal +// structures it is also quite easy to sabotage libtorrent's operation. +// +// All the callbacks in this interface are called with the main libtorrent thread +// mutex locked. And they 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 the mutex to be locked. Furthermore, you would +// also need to have a mutex on 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). See `boost thread's mutex`_. 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. +// +// .. _`boost thread's mutex`: http://www.boost.org/doc/html/mutex.html +// +// +// 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:: +// +// boost::shared_ptr (*)(torrent_handle const&, void*); +// +// The second argument is the userdata passed to ``session::add_torrent()`` or +// ``torrent_handle::add_extension()``. +// +// The function should return a ``boost::shared_ptr`` which +// may or may not be 0. If it is a null pointer, 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 ``boost::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; +// +// virtual std::auto_ptr clone() const +// { return std::auto_ptr(new name(\*this)); } +// +// static const int static_category = **; +// virtual int 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 + + +#ifndef TORRENT_DISABLE_EXTENSIONS + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include +#include "libtorrent/config.hpp" +#include "libtorrent/buffer.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/sha1_hash.hpp" // for sha1_hash +#include "libtorrent/error_code.hpp" +#include "libtorrent/session_handle.hpp" + +namespace libtorrent +{ + struct peer_plugin; + struct peer_request; + class entry; + struct bdecode_node; + struct disk_buffer_holder; + struct bitfield; + class alert; + struct torrent_plugin; + struct add_torrent_params; + struct peer_connection_handle; + struct torrent_handle; + + // Functions of this type are called to handle incoming DHT requests + typedef boost::function dht_extension_handler_t; + + // Map of query strings to handlers. Note that query strings are limited to 15 bytes. + // see max_dht_query_length + typedef std::vector > dht_extensions_t; + + // 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() {} + + // these are flags that can be returned by implemented_features() + // indicating which callbacks this plugin is interested in + enum feature_flags_t + { + // 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. + optimistic_unchoke_feature = 1, + + // include this bit if your plugin needs to have on_tick() called + tick_feature = 2 + }; + + // 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 boost::uint32_t implemented_features() { return 0; } + + // this is called by the session every time a new torrent is added. + // The ``torrent*`` points to the internal torrent object created + // for the new torrent. The ``void*`` is the userdata pointer as + // passed in via add_torrent_params. + // + // If the plugin returns a torrent_plugin instance, it will be added + // to the new torrent. Otherwise, return an empty shared_ptr to a + // torrent_plugin (the default). + virtual boost::shared_ptr new_torrent(torrent_handle const&, void*) + { return boost::shared_ptr(); } + + // called when plugin is added to a session + virtual void added(session_handle) {} + + // called after a plugin is added + // allows the plugin to register DHT requests it would like to handle + virtual void register_dht_extensions(dht_extensions_t&) {} + + // called when an alert is posted alerts that are filtered are not posted + virtual void on_alert(alert const*) {} + + // return true if the add_torrent_params should be added + virtual bool on_unknown_torrent(sha1_hash const& /* info_hash */ + , peer_connection_handle const& /* pc */, add_torrent_params& /* p */) + { return false; } + + // called once per second + virtual void on_tick() {} + + // called when choosing peers to optimistically unchoke. peer's will be + // unchoked in the order they appear in the given vector. if + // the plugin returns true then the ordering provided will be used and no + // other plugin will be allowed to change it. If your plugin expects this + // to be called, make sure to include the flag + // ``optimistic_unchoke_feature`` in the return value from + // implemented_features(). + virtual bool on_optimistic_unchoke(std::vector& /* peers */) + { return false; } + + // called when saving settings state + virtual void save_state(entry&) const {} + + // called when loading settings state + virtual void load_state(bdecode_node const&) {} + }; + + // 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() {} + + // 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 boost::shared_ptr new_connection(peer_connection_handle const&) + { return boost::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(int /*index*/) {} + virtual void on_piece_failed(int /*index*/) {} + + // 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 unpaused 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 unpause. + // + // 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(int /*s*/) {} + + // called when the torrent is unloaded from RAM + // and loaded again, respectively + // unload is called right before the torrent is + // unloaded and load is called right after it's + // loaded. i.e. the full torrent state is available + // when these callbacks are called. + virtual void on_unload() {} + virtual void on_load() {} + + // called every time policy::add_peer is called + // src is a bitmask of which sources this peer + // has been seen from. flags is a bitmask of: + + enum flags_t { + // this is the first time we see this peer + first_time = 1, + // this peer was not added because it was + // filtered by the IP filter + filtered = 2 + }; + + // 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&, + int /*src*/, int /*flags*/) {} + }; + + // 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 char const* 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& /*ec*/) {} + + // 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 BT 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(char const* /*reserved_bits*/) { 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(int /*index*/) { return false; } + virtual bool on_dont_have(int /*index*/) { 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(int /*index*/) { return false; } + virtual bool on_request(peer_request const&) { return false; } + virtual bool on_piece(peer_request const& /*piece*/ + , disk_buffer_holder& /*data*/) { return false; } + virtual bool on_cancel(peer_request const&) { return false; } + virtual bool on_reject(peer_request const&) { return false; } + virtual bool on_suggest(int /*index*/) { return false; } + + // called after a choke message has been sent to the peer + virtual void sent_unchoke() {} + + // called after piece data has been sent to the peer + // this can be used for stats book keeping + virtual void sent_payload(int /* bytes */) {} + + // called when libtorrent think this peer should be disconnected. + // if the plugin returns false, the peer will not be disconnected. + virtual bool can_disconnect(error_code const& /*ec*/) { return true; } + + // called when an extended message is received. If returning true, + // the message is not processed by any other plugin and if false + // is returned the next plugin in the chain will receive it to + // be able to handle it. This is not called for web seeds. + // thus function may be called more than once per incoming message, but + // only the last of the calls will the ``body`` size equal the ``length``. + // i.e. Every time another fragment of the message is received, this + // function will be called, until finally the whole message has been + // received. The purpose of this is to allow early disconnects for invalid + // messages and for reporting progress of receiving large messages. + virtual bool on_extended(int /*length*/, int /*msg*/, + buffer::const_interval /*body*/) + { return false; } + + // this is not called for web seeds + virtual bool on_unknown_message(int /*length*/, int /*msg*/, + buffer::const_interval /*body*/) + { return false; } + + // called when a piece that this peer participated in either + // fails or passes the hash_check + virtual void on_piece_pass(int /*index*/) {} + virtual void on_piece_failed(int /*index*/) {} + + // 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; } + }; + + struct TORRENT_EXPORT crypto_plugin + { + // hidden + virtual ~crypto_plugin() {} + + virtual void set_incoming_key(unsigned char const* key, int len) = 0; + virtual void set_outgoing_key(unsigned char const* key, int len) = 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 retrieved from the passed in vector. The + // vector must be cleared if no additional data is to be inserted. + virtual int encrypt(std::vector& /*send_vec*/) = 0; + + // decrypt the provided buffers. + // 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 void decrypt(std::vector& /*receive_vec*/ + , int& /* consume */, int& /*produce*/, int& /*packet_size*/) = 0; + }; +} + +#endif + +#endif // TORRENT_EXTENSIONS_HPP_INCLUDED + diff --git a/include/libtorrent/extensions/lt_trackers.hpp b/include/libtorrent/extensions/lt_trackers.hpp new file mode 100644 index 0000000..b97973e --- /dev/null +++ b/include/libtorrent/extensions/lt_trackers.hpp @@ -0,0 +1,63 @@ +/* + +Copyright (c) 2008-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_LT_TRACKERS_HPP_INCLUDED +#define TORRENT_LT_TRACKERS_HPP_INCLUDED + +#ifndef TORRENT_NO_DEPRECATE +#ifndef TORRENT_DISABLE_EXTENSIONS + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent +{ + struct torrent_plugin; + struct torrent_handle; + + // constructor function for the trackers exchange extension. This can + // either be passed in the add_torrent_params::extensions field, or + // via torrent_handle::add_extension(). + TORRENT_DEPRECATED + boost::shared_ptr TORRENT_EXPORT create_lt_trackers_plugin(torrent_handle const&, void*); +} + +#endif // TORRENT_DISABLE_EXTENSIONS +#endif // TORRENT_NO_DEPRECATE + +#endif // TORRENT_LT_TRACKERS_HPP_INCLUDED + diff --git a/include/libtorrent/extensions/metadata_transfer.hpp b/include/libtorrent/extensions/metadata_transfer.hpp new file mode 100644 index 0000000..248c677 --- /dev/null +++ b/include/libtorrent/extensions/metadata_transfer.hpp @@ -0,0 +1,69 @@ +/* + +Copyright (c) 2006-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_METADATA_TRANSFER_HPP_INCLUDED +#define TORRENT_METADATA_TRANSFER_HPP_INCLUDED + +#ifndef TORRENT_DISABLE_EXTENSIONS + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#ifndef TORRENT_NO_DEPRECATE + +namespace libtorrent +{ + struct torrent_plugin; + struct torrent_handle; + +#ifndef TORRENT_NO_DEPRECATE + // constructor function for the metadata transfer extension. This + // extension has been superseded by the ut_metadata extension and + // is deprecated. It can be either be passed in the + // add_torrent_params::extensions field, or + // via torrent_handle::add_extension(). + TORRENT_DEPRECATED + TORRENT_EXPORT boost::shared_ptr + create_metadata_plugin(torrent_handle const&, void*); +#endif +} +#endif + +#endif // TORRENT_DISABLE_EXTENSIONS + +#endif // TORRENT_METADATA_TRANSFER_HPP_INCLUDED + diff --git a/include/libtorrent/extensions/smart_ban.hpp b/include/libtorrent/extensions/smart_ban.hpp new file mode 100644 index 0000000..73147c8 --- /dev/null +++ b/include/libtorrent/extensions/smart_ban.hpp @@ -0,0 +1,62 @@ +/* + +Copyright (c) 2007-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_SMART_BAN_HPP_INCLUDED +#define TORRENT_SMART_BAN_HPP_INCLUDED + +#ifndef TORRENT_DISABLE_EXTENSIONS + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent +{ + struct torrent_plugin; + struct torrent_handle; + + // constructor function for the smart ban extension. The extension keeps + // track of the data peers have sent us for failing pieces and once the + // piece completes and passes the hash check bans the peers that turned + // out to have sent corrupt data. + // This function can either be passed in the add_torrent_params::extensions + // field, or via torrent_handle::add_extension(). + TORRENT_EXPORT boost::shared_ptr create_smart_ban_plugin(torrent_handle const&, void*); +} + +#endif // TORRENT_DISABLE_EXTENSIONS + +#endif // TORRENT_SMART_BAN_HPP_INCLUDED + diff --git a/include/libtorrent/extensions/ut_metadata.hpp b/include/libtorrent/extensions/ut_metadata.hpp new file mode 100644 index 0000000..8c03d89 --- /dev/null +++ b/include/libtorrent/extensions/ut_metadata.hpp @@ -0,0 +1,65 @@ +/* + +Copyright (c) 2007-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_UT_METADATA_HPP_INCLUDED +#define TORRENT_UT_METADATA_HPP_INCLUDED + +#ifndef TORRENT_DISABLE_EXTENSIONS + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent +{ + struct torrent_plugin; + struct torrent_handle; + + // constructor function for the ut_metadata extension. The ut_metadata + // extension allows peers to request the .torrent file (or more + // specifically the 'info'-dictionary of the .torrent file) from each + // other. This is the main building block in making magnet links work. + // This extension is enabled by default unless explicitly disabled in + // the session constructor. + // + // This can either be passed in the add_torrent_params::extensions field, or + // via torrent_handle::add_extension(). + TORRENT_EXPORT boost::shared_ptr create_ut_metadata_plugin(torrent_handle const&, void*); +} + +#endif // TORRENT_DISABLE_EXTENSIONS +#endif // TORRENT_UT_METADATA_HPP_INCLUDED + diff --git a/include/libtorrent/extensions/ut_pex.hpp b/include/libtorrent/extensions/ut_pex.hpp new file mode 100644 index 0000000..00da8c1 --- /dev/null +++ b/include/libtorrent/extensions/ut_pex.hpp @@ -0,0 +1,69 @@ +/* + +Copyright (c) 2006, MassaRoddel +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_UT_PEX_EXTENSION_HPP_INCLUDED +#define TORRENT_UT_PEX_EXTENSION_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#ifndef TORRENT_DISABLE_EXTENSIONS + +#include "libtorrent/socket.hpp" // for endpoint + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent +{ + struct torrent_plugin; + struct peer_plugin; + struct torrent_handle; + + // constructor function for the ut_pex extension. The ut_pex + // extension allows peers to gossip about their connections, allowing + // the swarm stay well connected and peers aware of more peers in the + // swarm. This extension is enabled by default unless explicitly disabled in + // the session constructor. + // + // This can either be passed in the add_torrent_params::extensions field, or + // via torrent_handle::add_extension(). + TORRENT_EXPORT boost::shared_ptr create_ut_pex_plugin(torrent_handle const&, void*); + + bool was_introduced_by(peer_plugin const* pp, tcp::endpoint const& ep); +} + +#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..ff48618 --- /dev/null +++ b/include/libtorrent/file.hpp @@ -0,0 +1,361 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_FILE_HPP_INCLUDED +#define TORRENT_FILE_HPP_INCLUDED + +#include +#include + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include + +#ifdef TORRENT_WINDOWS +// windows part +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#include +#include +#else +// posix part +#define _FILE_OFFSET_BITS 64 + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 600 +#endif + +#include +#include +#include +#include +#include // for DIR + +#undef _FILE_OFFSET_BITS + +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/error_code.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/time.hpp" + +namespace libtorrent +{ +#ifdef TORRENT_WINDOWS + typedef HANDLE handle_type; +#else + typedef int handle_type; +#endif + + struct file_status + { + boost::int64_t file_size; + boost::uint64_t atime; + boost::uint64_t mtime; + boost::uint64_t ctime; + 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; + }; + + // internal flags for stat_file + enum { dont_follow_links = 1 }; + TORRENT_EXTRA_EXPORT void stat_file(std::string const& f, file_status* s + , error_code& ec, int flags = 0); + TORRENT_EXTRA_EXPORT void rename(std::string const& f + , std::string const& newf, error_code& ec); + TORRENT_EXTRA_EXPORT void create_directories(std::string const& f + , error_code& ec); + TORRENT_EXTRA_EXPORT void create_directory(std::string const& f + , error_code& ec); + TORRENT_EXTRA_EXPORT void remove_all(std::string const& f + , error_code& ec); + TORRENT_EXTRA_EXPORT void remove(std::string const& f, error_code& ec); + TORRENT_EXTRA_EXPORT bool exists(std::string const& f, error_code& ec); + TORRENT_EXTRA_EXPORT bool exists(std::string const& f); + TORRENT_EXTRA_EXPORT boost::int64_t file_size(std::string const& f); + TORRENT_EXTRA_EXPORT bool is_directory(std::string const& f + , error_code& ec); + TORRENT_EXTRA_EXPORT void recursive_copy(std::string const& old_path + , std::string const& new_path, error_code& ec); + TORRENT_EXTRA_EXPORT void copy_file(std::string const& f + , std::string const& newf, error_code& ec); + TORRENT_EXTRA_EXPORT void move_file(std::string const& f + , std::string const& newf, error_code& ec); + + // file is expected to exist, link will be created to point to it. If hard + // links are not supported by the filesystem or OS, the file will be copied. + TORRENT_EXTRA_EXPORT void hard_link(std::string const& file + , std::string const& link, error_code& ec); + + TORRENT_EXTRA_EXPORT std::string split_path(std::string const& f); + TORRENT_EXTRA_EXPORT char const* next_path_element(char const* p); + TORRENT_EXTRA_EXPORT std::string extension(std::string const& f); + TORRENT_EXTRA_EXPORT std::string remove_extension(std::string const& f); + TORRENT_EXTRA_EXPORT void replace_extension(std::string& f, std::string const& ext); + TORRENT_EXTRA_EXPORT bool is_root_path(std::string const& f); + + + // 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); + TORRENT_EXTRA_EXPORT char const* filename_cstr(char 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(std::string const& lhs + , std::string const& rhs); + TORRENT_EXTRA_EXPORT void append_path(std::string& branch + , std::string const& leaf); + TORRENT_EXTRA_EXPORT void append_path(std::string& branch + , char const* str, int len); + // internal used by create_torrent.hpp + TORRENT_EXTRA_EXPORT std::string complete(std::string const& f); + TORRENT_EXTRA_EXPORT bool is_complete(std::string const& f); + TORRENT_EXTRA_EXPORT std::string current_working_directory(); +#if TORRENT_USE_UNC_PATHS + TORRENT_EXTRA_EXPORT std::string canonicalize_path(std::string const& f); +#endif + + // TODO: move this into a separate header file, TU pair + class TORRENT_EXTRA_EXPORT directory : public boost::noncopyable + { + public: + directory(std::string const& path, error_code& ec); + ~directory(); + void next(error_code& ec); + std::string file() const; + boost::uint64_t inode() const; + bool done() const { return m_done; } + private: +#ifdef TORRENT_WINDOWS + HANDLE m_handle; + int m_inode; +#if TORRENT_USE_WSTRING + WIN32_FIND_DATAW m_fd; +#else + WIN32_FIND_DATAA m_fd; +#endif +#else + DIR* m_handle; + // the dirent struct contains a zero-sized + // array at the end, it will end up referring + // to the m_name field + struct dirent m_dirent; + char m_name[TORRENT_MAX_PATH + 1]; // +1 to make room for null +#endif + bool m_done; + }; + + struct file; + +#ifdef TORRENT_DEBUG_FILE_LEAKS + struct file_handle + { + file_handle(); + file_handle(file* f); + file_handle(file_handle const& fh); + ~file_handle(); + file* operator->(); + file const* operator->() const; + file& operator*(); + file const& operator*() const; + file* get(); + file const* get() const; + operator bool() const; + file_handle& reset(file* f = NULL); + + char stack[2048]; + private: + boost::shared_ptr m_file; + }; + + void TORRENT_EXTRA_EXPORT print_open_files(char const* event, char const* name); +#else + typedef boost::shared_ptr file_handle; +#endif + + struct TORRENT_EXTRA_EXPORT file: boost::noncopyable + { + // the open mode for files. Used for the file constructor or + // file::open(). + enum open_mode_t + { + // open the file for reading only + read_only = 0, + + // open the file for writing only + write_only = 1, + + // open the file for reading and writing + read_write = 2, + + // the mask for the bits determining read or write mode + rw_mask = read_only | write_only | read_write, + + // open the file in sparse mode (if supported by the + // filesystem). + sparse = 0x4, + + // don't update the access timestamps on the file (if + // supported by the operating system and filesystem). + // this generally improves disk performance. + no_atime = 0x8, + + // open the file for random access. This disables read-ahead + // logic + random_access = 0x10, + + // prevent the file from being opened by another process + // while it's still being held open by this handle + lock_file = 0x20, + + // don't put any pressure on the OS disk cache + // because of access to this file. We expect our + // files to be fairly large, and there is already + // a cache at the bittorrent block level. This + // may improve overall system performance by + // leaving running applications in the page cache + no_cache = 0x40, + + // this is only used for readv/writev flags + coalesce_buffers = 0x100, + + // when creating a file, set the hidden attribute (windows only) + attribute_hidden = 0x200, + + // when creating a file, set the executable attribute + attribute_executable = 0x400, + + // the mask of all attribute bits + attribute_mask = attribute_hidden | attribute_executable + }; + +#ifdef TORRENT_WINDOWS + struct iovec_t + { + void* iov_base; + size_t iov_len; + }; +#else + typedef iovec iovec_t; +#endif + + // use a typedef for the type of iovec_t::iov_base + // since it may differ +#ifdef TORRENT_SOLARIS + typedef char* iovec_base_t; +#else + typedef void* iovec_base_t; +#endif + + file(); + file(std::string const& p, int m, error_code& ec); + ~file(); + + bool open(std::string const& p, int m, error_code& ec); + bool is_open() const; + void close(); + bool set_size(boost::int64_t size, error_code& ec); + + int open_mode() const { return m_open_mode; } + + boost::int64_t writev(boost::int64_t file_offset, iovec_t const* bufs, int num_bufs + , error_code& ec, int flags = 0); + boost::int64_t readv(boost::int64_t file_offset, iovec_t const* bufs, int num_bufs + , error_code& ec, int flags = 0); + + boost::int64_t get_size(error_code& ec) const; + + // return the offset of the first byte that + // belongs to a data-region + boost::int64_t sparse_end(boost::int64_t start) const; + + handle_type native_handle() const { return m_file_handle; } + +#ifdef TORRENT_DISK_STATS + boost::uint32_t file_id() const { return m_file_id; } +#endif + +#ifdef TORRENT_DEBUG_FILE_LEAKS + void print_info(FILE* out) const; +#endif + + private: + + handle_type m_file_handle; +#ifdef TORRENT_DISK_STATS + boost::uint32_t m_file_id; +#endif + + int m_open_mode; +#if defined TORRENT_WINDOWS + static bool has_manage_volume_privs; +#endif + +#ifdef TORRENT_DEBUG_FILE_LEAKS + std::string m_file_path; +#endif + }; + + TORRENT_EXTRA_EXPORT int bufs_size(file::iovec_t const* bufs, int num_bufs); + +} + +#endif // TORRENT_FILE_HPP_INCLUDED + diff --git a/include/libtorrent/file_pool.hpp b/include/libtorrent/file_pool.hpp new file mode 100644 index 0000000..27f495e --- /dev/null +++ b/include/libtorrent/file_pool.hpp @@ -0,0 +1,149 @@ +/* + +Copyright (c) 2006-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_FILE_POOL_HPP +#define TORRENT_FILE_POOL_HPP + +#include +#include "libtorrent/file.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/thread.hpp" +#include "libtorrent/file_storage.hpp" +#include "libtorrent/aux_/time.hpp" + +namespace libtorrent +{ + struct pool_file_status + { + // the index of the file this entry refers to into the ``file_storage`` + // file list of this torrent. This starts indexing at 0. + int file_index; + + // a (high precision) timestamp of when the file was last used. + time_point last_use; + + // ``open_mode`` is a bitmask of the file flags this file is currently opened with. These + // are the flags used in the ``file::open()`` function. This enum is defined as a member + // of the ``file`` class. + // + // :: + // + // enum + // { + // read_only = 0, + // write_only = 1, + // read_write = 2, + // rw_mask = 3, + // no_buffer = 4, + // sparse = 8, + // no_atime = 16, + // random_access = 32, + // lock_file = 64, + // }; + // + // 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. + int open_mode; + }; + + // this is an internal cache of open file handles. It's primarily used by + // storage_interface implementations. It provides semi weak guarantees of + // not opening more file handles than specified. Given multiple threads, + // each with the ability to lock a file handle (via smart pointer), there + // may be windows where more file handles are open. + struct TORRENT_EXPORT file_pool : boost::noncopyable + { + // ``size`` specifies the number of allowed files handles + // to hold open at any given time. + file_pool(int size = 40); + ~file_pool(); + + // return an open file handle to file at ``file_index`` in the + // file_storage ``fs`` opened at save path ``p``. ``m`` is the + // file open mode (see file::open_mode_t). + file_handle open_file(void* st, std::string const& p + , int file_index, file_storage const& fs, int m, error_code& ec); + // release all files belonging to the specified storage_interface (``st``) + // the overload that takes ``file_index`` releases only the file with + // that index in storage ``st``. + void release(void* st = NULL); + void release(void* st, int file_index); + + // update the allowed number of open file handles to ``size``. + void resize(int size); + + // returns the current limit of number of allowed open file handles held + // by the file_pool. + int size_limit() const { return m_size; } + + // internal + void set_low_prio_io(bool b) { m_low_prio_io = b; } + void get_status(std::vector* files, void* st) const; + +#if TORRENT_USE_ASSERTS + bool assert_idle_files(void* st) const; + + // remember that this storage has had + // its files deleted. We may not open any + // files from it again + void mark_deleted(file_storage const& fs); +#endif + + private: + + void remove_oldest(mutex::scoped_lock& l); + + int m_size; + bool m_low_prio_io; + + struct lru_file_entry + { + lru_file_entry(): key(0), last_use(aux::time_now()), mode(0) {} + mutable file_handle file_ptr; + void* key; + time_point last_use; + int mode; + }; + + // maps storage pointer, file index pairs to the + // lru entry for the file + typedef std::map, lru_file_entry> file_set; + + file_set m_files; +#if TORRENT_USE_ASSERTS + std::vector > m_deleted_storages; +#endif + mutable mutex m_mutex; + }; +} + +#endif diff --git a/include/libtorrent/file_storage.hpp b/include/libtorrent/file_storage.hpp new file mode 100644 index 0000000..069397c --- /dev/null +++ b/include/libtorrent/file_storage.hpp @@ -0,0 +1,655 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_FILE_STORAGE_HPP_INCLUDED +#define TORRENT_FILE_STORAGE_HPP_INCLUDED + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include + +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/assert.hpp" +#include "libtorrent/peer_request.hpp" +#include "libtorrent/peer_id.hpp" + +namespace libtorrent +{ + struct file; + +#ifndef TORRENT_NO_DEPRECATE + // information about a file in a file_storage + struct TORRENT_EXPORT file_entry + { + // hidden + file_entry(); + // hidden + ~file_entry(); +#if __cplusplus >= 201103L + file_entry(file_entry const&) = default; + file_entry& operator=(file_entry const&) = default; +#endif + + // the full path of this file. The paths are unicode strings + // encoded in UTF-8. + std::string path; + + // the path which this is a symlink to, or empty if this is + // not a symlink. This field is only used if the ``symlink_attribute`` is set. + std::string symlink_path; + + // the offset of this file inside the torrent + boost::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. + boost::int64_t size; + + // the offset in the file where the storage should start. The normal + // case is to have this set to 0, so that the storage starts saving data at the start + // if the file. In cases where multiple files are mapped into the same file though, + // the ``file_base`` should be set to an offset so that the different regions do + // not overlap. This is used when mapping "unselected" files into a so-called part + // file. + boost::int64_t file_base; + + // 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 zeroes, 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 boundry. 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_NO_DEPRECATE + + // internal + struct TORRENT_DEPRECATED_EXPORT internal_file_entry + { + friend class file_storage; +#ifdef TORRENT_DEBUG + // for torrent_info::invariant_check + friend class torrent_info; +#endif + + internal_file_entry() + : offset(0) + , symlink_index(not_a_symlink) + , no_root_dir(false) + , size(0) + , name_len(name_is_owned) + , pad_file(false) + , hidden_attribute(false) + , executable_attribute(false) + , symlink_attribute(false) + , name(NULL) + , path_index(-1) + {} + + internal_file_entry(internal_file_entry const& fe); + internal_file_entry& operator=(internal_file_entry const& fe); + + ~internal_file_entry(); + + void set_name(char const* n, bool borrow_string = false, int string_len = 0); + std::string filename() const; + char const* filename_ptr() const { return name; } + int filename_len() const + { return name_len == name_is_owned?int(strlen(name)):int(name_len); } + + enum { + name_is_owned = (1<<12)-1, + not_a_symlink = (1<<15)-1 + }; + + // the offset of this file inside the torrent + boost::uint64_t offset:48; + + // index into file_storage::m_symlinks or not_a_symlink + // if this is not a symlink + boost::uint64_t symlink_index:15; + + // if this is true, don't include m_name as part of the + // path to this file + boost::uint64_t no_root_dir:1; + + // the size of this file + boost::uint64_t size:48; + + // the number of characters in the name. If this is + // name_is_owned, name is null 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 doesn not belong + // to this object, and it's not null terminated + boost::uint64_t name_len:12; + boost::uint64_t pad_file:1; + boost::uint64_t hidden_attribute:1; + boost::uint64_t executable_attribute:1; + boost::uint64_t symlink_attribute:1; + + // make it available for logging + private: + // This string is not necessarily null terminated! + // that's why it's private, to keep people away from it + char const* name; + public: + + // the index into file_storage::m_paths. To get + // the full path to this file, concatenate the path + // from that array with the 'name' field in + // this struct + // values for path_index include: + // -1 means no path (i.e. single file torrent) + // -2, it means the filename + // in this field contains the full, absolute path + // to the file + int path_index; + }; + + // represents a window of a file in a torrent. + // + // The ``file_index`` refers to the index of the file (in the torrent_info). + // To get the path and filename, use ``file_path()`` and give the ``file_index`` + // as argument. The ``offset`` is the byte offset in the file where the range + // starts, and ``size`` is the number of bytes this range is. The size + offset + // will never be greater than the file size. + struct TORRENT_EXPORT file_slice + { + // the index of the file + int file_index; + + // the offset from the start of the file, in bytes + boost::int64_t offset; + + // the size of the window, in bytes + boost::int64_t size; + }; + + // The ``file_storage`` class represents a file list and the piece + // size. Everything necessary to interpret a regular bittorrent storage + // file structure. + class TORRENT_EXPORT file_storage + { + friend class torrent_info; + public: + // hidden + file_storage(); + // hidden + ~file_storage(); + file_storage(file_storage const& f); + file_storage& operator=(file_storage 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. + bool is_valid() const { return m_piece_length > 0; } + + // file attribute flags + enum flags_t + { + // the file is a pad file. It's required to contain zeroes + // at it will not be saved to disk. Its purpose is to make + // the following file start on a piece boundary. + pad_file = 1, + + // this file has the hidden attribute set. This is primarily + // a windows attribute + attribute_hidden = 2, + + // this file has the executable attribute set. + attribute_executable = 4, + + // this file is a symbolic link. It should have a link + // target string associated with it. + attribute_symlink = 8 + }; + + // 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`` points to a string of ``filename_len`` + // bytes that 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 NULL, the filename from ``path`` is used and not + // borrowed. In this case ``filename_len`` is ignored. + // + // The ``path`` argument is the full path (in the torrent file) to + // the file to add. Note that this is not supposed to be an absolute + // path, but it is expected to include the name of the torrent as the + // first path element. + // + // ``file_size`` is the size of the file in bytes. + // + // The ``file_flags`` argument sets attributes on the file. The file + // attributes is an extension and may not work in all bittorrent clients. + // + // For possible file attributes, see file_storage::flags_t. + // + // The ``mtime`` argument is optional and can be set to 0. If non-zero, + // it is the posix time of the last modification time of this file. + // + // ``symlink_path`` is the path the file is a symlink to. To make this a + // symlink you also need to set the file_storage::flag_symlink file flag. + // + // If more files than one are added, certain restrictions to their paths + // apply. In a multi-file file storage (torrent), all files must share + // the same root directory. + // + // That is, the first path element of all files must be the same. + // This shared path element is also set to the name of the torrent. It + // can be changed by calling ``set_name``. + void add_file_borrow(char const* filename, int filename_len + , std::string const& path, boost::int64_t file_size + , boost::uint32_t file_flags = 0, char const* filehash = 0 + , boost::int64_t mtime = 0, std::string const& symlink_path = ""); + void add_file(std::string const& path, boost::int64_t file_size, int file_flags = 0 + , std::time_t mtime = 0, std::string const& symlink_path = ""); + + // renames the file at ``index`` to ``new_filename``. Keep in mind + // that filenames are expected to be UTF-8 encoded. + void rename_file(int index, std::string const& new_filename); + +#ifndef TORRENT_NO_DEPRECATE + TORRENT_DEPRECATED + void add_file(file_entry const& fe, char const* filehash = NULL); + +#if TORRENT_USE_WSTRING + // all wstring APIs are deprecated since 0.16.11 + // instead, use the wchar -> utf8 conversion functions + // and pass in utf8 strings + TORRENT_DEPRECATED + void add_file(std::wstring const& p, boost::int64_t size, int flags = 0 + , std::time_t mtime = 0, std::string const& s_p = ""); + TORRENT_DEPRECATED + void rename_file(int index, std::wstring const& new_filename); + TORRENT_DEPRECATED + void set_name(std::wstring const& n); + + void rename_file_deprecated(int index, std::wstring const& new_filename); +#endif // TORRENT_USE_WSTRING +#endif // TORRENT_NO_DEPRECATE + + // 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(int piece, boost::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 ove 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(int file, boost::int64_t offset, int size) const; + +#ifndef TORRENT_NO_DEPRECATE + // all functions depending on internal_file_entry + // were deprecated in 1.0. Use the variants that take an + // index instead + typedef std::vector::const_iterator iterator; + typedef std::vector::const_reverse_iterator reverse_iterator; + + TORRENT_DEPRECATED + iterator file_at_offset(boost::int64_t offset) const; + TORRENT_DEPRECATED + iterator begin() const { return m_files.begin(); } + TORRENT_DEPRECATED + iterator end() const { return m_files.end(); } + TORRENT_DEPRECATED + reverse_iterator rbegin() const { return m_files.rbegin(); } + TORRENT_DEPRECATED + reverse_iterator rend() const { return m_files.rend(); } + TORRENT_DEPRECATED + internal_file_entry const& internal_at(int index) const + { + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < int(m_files.size())); + return m_files[index]; + } + 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(boost::int64_t offset) const; + file_entry at_deprecated(int index) const; +#endif // TORRENT_NO_DEPRECATE + + // returns the number of files in the file_storage + int num_files() const + { return int(m_files.size()); } + + // returns the total number of bytes all the files in this torrent spans + boost::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; } + + // set and get the size of each piece in this torrent. This size is typically an even power + // of 2. It doesn't have to be though. It should be divisible by 16kiB however. + void set_piece_length(int l) { m_piece_length = l; } + int piece_length() const { TORRENT_ASSERT(m_piece_length > 0); return m_piece_length; } + + // returns the piece size of ``index``. This will be the same as piece_length(), except + // for the last piece, which may be shorter. + int piece_size(int 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) + { + using std::swap; + swap(ti.m_files, m_files); + swap(ti.m_num_files, m_num_files); + swap(ti.m_file_hashes, m_file_hashes); + swap(ti.m_symlinks, m_symlinks); + swap(ti.m_mtime, m_mtime); +#ifndef TORRENT_NO_DEPRECATE + swap(ti.m_file_base, m_file_base); +#endif + 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); + } + + // deallocates most of the memory used by this + // instance, leaving it only partially usable + void unload(); + + // returns true when populated with at least one file + bool is_loaded() const { return !m_files.empty(); } + + // if pad_file_limit >= 0, files larger than that limit will be padded, + // default is to not add any padding (-1). The alignment specifies the + // alignment files should be padded to. This defaults to the piece size + // (-1) but it may also make sense to set it to 16 kiB, or something + // divisible by 16 kiB. + // If pad_file_limit is 0, every file will be padded (except empty ones). + // ``tail_padding`` indicates whether aligned files also are padded at + // the end to make them end aligned. This is required for mutable + // torrents, since piece hashes are compared + void optimize(int pad_file_limit = -1, int alignment = -1 + , bool tail_padding = false); + + // These functions are used to query attributes of files at + // a given index. + // + // The ``hash()`` is a sha-1 hash of the file, or 0 if none was + // provided in the torrent file. This can potentially be used to + // join a bittorrent network with other file sharing networks. + // + // The ``mtime()`` is the modification time is the posix + // time when a file was last modified when the torrent + // was created, or 0 if it was not included in the torrent file. + // + // ``file_path()`` returns the full path to a file. + // + // ``file_size()`` returns the size of a file. + // + // ``pad_file_at()`` returns true if the file at the given + // index is a pad-file. + // + // ``file_name()`` returns *just* the name of the file, whereas + // ``file_path()`` returns the path (inside the torrent file) with + // the filename appended. + // + // ``file_offset()`` returns the byte offset within the torrent file + // where this file starts. It can be used to map the file to a piece + // index (given the piece size). + sha1_hash hash(int index) const; + std::string const& symlink(int index) const; + time_t mtime(int index) const; + std::string file_path(int index, std::string const& save_path = "") const; + std::string file_name(int index) const; + boost::int64_t file_size(int index) const; + bool pad_file_at(int index) const; + boost::int64_t file_offset(int index) const; + + // returns the crc32 hash of file_path(index) + boost::uint32_t file_path_hash(int 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(boost::unordered_set& table) const; + + // flags indicating various attributes for files in + // a file_storage. + enum file_flags_t + { + // this file is a pad file. The creator of the + // torrent promises the file is entirely filled with + // zeroes and does not need to be downloaded. The + // purpose is just to align the next file to either + // a block or piece boundary. + flag_pad_file = 1, + + // this file is hidden (sets the hidden attribute + // on windows) + flag_hidden = 2, + + // this file is executable (sets the executable bit + // on posix like systems) + flag_executable = 4, + + // this file is a symlink. The symlink target is + // specified in a separate field + flag_symlink = 8 + }; + + std::vector const& paths() const { return m_paths; } + + // returns a bitmask of flags from file_flags_t that apply + // to file at ``index``. + int file_flags(int 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(int index) const; + + // returns the index of the file at the given offset in the torrent + int file_index_at_offset(boost::int64_t offset) const; + + // low-level function. returns a pointer to the internal storage for + // the filename. This string may not be null terminated! + // the ``file_name_len()`` function returns the length of the filename. + char const* file_name_ptr(int index) const; + int file_name_len(int index) const; + +#ifndef TORRENT_NO_DEPRECATE + // deprecated in 1.1 + boost::int64_t file_base_deprecated(int index) const; + TORRENT_DEPRECATED + boost::int64_t file_base(int index) const; + TORRENT_DEPRECATED + void set_file_base(int index, boost::int64_t off); + + // these were deprecated in 1.0. Use the versions that take an index instead + TORRENT_DEPRECATED + sha1_hash hash(internal_file_entry const& fe) const; + TORRENT_DEPRECATED + std::string const& symlink(internal_file_entry const& fe) const; + TORRENT_DEPRECATED + time_t mtime(internal_file_entry const& fe) const; + TORRENT_DEPRECATED + int file_index(internal_file_entry const& fe) const; + TORRENT_DEPRECATED + boost::int64_t file_base(internal_file_entry const& fe) const; + TORRENT_DEPRECATED + void set_file_base(internal_file_entry const& fe, boost::int64_t off); + TORRENT_DEPRECATED + std::string file_path(internal_file_entry const& fe, std::string const& save_path = "") const; + TORRENT_DEPRECATED + std::string file_name(internal_file_entry const& fe) const; + TORRENT_DEPRECATED + boost::int64_t file_size(internal_file_entry const& fe) const; + TORRENT_DEPRECATED + bool pad_file_at(internal_file_entry const& fe) const; + TORRENT_DEPRECATED + boost::int64_t file_offset(internal_file_entry const& fe) const; +#endif + + // if the backing buffer changed for this storage, this is the pointer + // offset to add to any pointers to make them point into the new buffer + void apply_pointer_offset(ptrdiff_t off); + + private: + + void add_pad_file(int size + , std::vector::iterator& i + , boost::int64_t& offset + , int& pad_file_counter); + + // the number of bytes in a regular piece + // (i.e. not the potentially truncated last piece) + int m_piece_length; + + // the number of pieces in the torrent + int m_num_pieces; + + void update_path_index(internal_file_entry& e, std::string const& path + , bool set_name = true); + void reorder_file(int index, int dst); + + // the list of files that this torrent consists of + std::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. + std::vector m_file_hashes; + + // for files that are symlinks, the symlink + // path_index in the internal_file_entry indexes + // this vector of strings + std::vector m_symlinks; + + // the modification times of each file. This vector + // is empty if no file have a modification time. + // each element corresponds to the file with the same + // index in m_files + std::vector m_mtime; + +#ifndef TORRENT_NO_DEPRECATE + // if any file has a non-zero file base (i.e. multiple + // files residing in the same physical file at different + // offsets) + std::vector m_file_base; +#endif + + // all unique paths files have. The internal_file_entry::path_index + // points into this array. The paths don't include the root directory + // name for multi-file torrents. The m_name field need to be + // prepended to these paths, and the filename of a specific file + // entry appended, to form full file paths + std::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 + boost::int64_t m_total_size; + + // the number of files. This is used when + // the torrent is unloaded + int m_num_files; + }; +} + +#endif // TORRENT_FILE_STORAGE_HPP_INCLUDED + diff --git a/include/libtorrent/fingerprint.hpp b/include/libtorrent/fingerprint.hpp new file mode 100644 index 0000000..78f3026 --- /dev/null +++ b/include/libtorrent/fingerprint.hpp @@ -0,0 +1,130 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_FINGERPRINT_HPP_INCLUDED +#define TORRENT_FINGERPRINT_HPP_INCLUDED + +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/export.hpp" + +namespace libtorrent +{ + + // 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 + { + + // The constructor takes a ``char const*`` that should point to a string constant containing + // exactly two characters. These are the characters that should be unique for your client. Make + // sure not to clash with anybody else. Here are some taken id's: + // + // +----------+-----------------------+ + // | id chars | client | + // +==========+=======================+ + // | 'AZ' | Azureus | + // +----------+-----------------------+ + // | 'LT' | libtorrent (default) | + // +----------+-----------------------+ + // | 'BX' | BittorrentX | + // +----------+-----------------------+ + // | 'MT' | Moonlight Torrent | + // +----------+-----------------------+ + // | 'TS' | Torrent Storm | + // +----------+-----------------------+ + // | 'SS' | Swarm Scope | + // +----------+-----------------------+ + // | 'XT' | Xan Torrent | + // +----------+-----------------------+ + // + // 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. + 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]; + } + + // generates the actual string put in the peer-id, and return it. + std::string to_string() const + { + char s[100]; + snprintf(s, 100, "-%c%c%c%c%c%c-" + , name[0], name[1] + , version_to_char(major_version) + , version_to_char(minor_version) + , version_to_char(revision_version) + , version_to_char(tag_version)); + return s; + } + + char name[2]; + int major_version; + int minor_version; + int revision_version; + int tag_version; + + private: + + char version_to_char(int v) const + { + if (v >= 0 && v < 10) return char('0' + v); + else if (v >= 10) return char('A' + (v - 10)); + TORRENT_ASSERT(false); + return '0'; + } + + }; + +} + +#endif // TORRENT_FINGERPRINT_HPP_INCLUDED + diff --git a/include/libtorrent/gzip.hpp b/include/libtorrent/gzip.hpp new file mode 100644 index 0000000..7df9954 --- /dev/null +++ b/include/libtorrent/gzip.hpp @@ -0,0 +1,131 @@ +/* + +Copyright (c) 2007-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_GZIP_HPP_INCLUDED +#define TORRENT_GZIP_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/error_code.hpp" + +#include + +namespace libtorrent +{ + + TORRENT_EXTRA_EXPORT void inflate_gzip( + char const* in, int size + , std::vector& buffer + , int maximum_size + , error_code& error); + + // get the ``error_category`` for zip errors + TORRENT_EXPORT boost::system::error_category& get_gzip_category(); + + namespace gzip_errors + { + // libtorrent uses boost.system's ``error_code`` class to represent errors. libtorrent has + // its own error category get_gzip_category() with the error codes defined by error_code_enum. + enum error_code_enum + { + // Not an error + no_error = 0, + + // the supplied gzip buffer has invalid header + invalid_gzip_header, + + // the gzip buffer would inflate to more bytes than the specified + // maximum size, and was rejected. + inflated_data_too_large, + + // available inflate data did not terminate + data_did_not_terminate, + + // output space exhausted before completing inflate + space_exhausted, + + // invalid block type (type == 3) + invalid_block_type, + + // stored block length did not match one's complement + invalid_stored_block_length, + + // dynamic block code description: too many length or distance codes + too_many_length_or_distance_codes, + + // dynamic block code description: code lengths codes incomplete + code_lengths_codes_incomplete, + + // dynamic block code description: repeat lengths with no first length + repeat_lengths_with_no_first_length, + + // dynamic block code description: repeat more than specified lengths + repeat_more_than_specified_lengths, + + // dynamic block code description: invalid literal/length code lengths + invalid_literal_length_code_lengths, + + // dynamic block code description: invalid distance code lengths + invalid_distance_code_lengths, + + // invalid literal/length or distance code in fixed or dynamic block + invalid_literal_code_in_block, + + // distance is too far back in fixed or dynamic block + distance_too_far_back_in_block, + + // an unknown error occurred during gzip inflation + unknown_gzip_error, + + // the number of error codes + error_code_max + }; + + // hidden + TORRENT_EXPORT boost::system::error_code make_error_code(error_code_enum e); + } + +} + +namespace boost { namespace system { + +template<> +struct is_error_code_enum +{ static const bool value = true; }; + +template<> +struct is_error_condition_enum +{ static const bool value = true; }; + +} } + +#endif + diff --git a/include/libtorrent/hasher.hpp b/include/libtorrent/hasher.hpp new file mode 100644 index 0000000..2e6a6a5 --- /dev/null +++ b/include/libtorrent/hasher.hpp @@ -0,0 +1,126 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_HASHER_HPP_INCLUDED +#define TORRENT_HASHER_HPP_INCLUDED + +#include "libtorrent/peer_id.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#ifdef TORRENT_USE_GCRYPT +#include + +#elif TORRENT_USE_COMMONCRYPTO + +#include + +#elif defined TORRENT_USE_OPENSSL + +extern "C" +{ +#include +} + +#else +#include "libtorrent/sha1.hpp" +#endif + +namespace libtorrent +{ + // this is a SHA-1 hash class. + // + // You use it by first instantiating it, then call ``update()`` to feed it + // with data. i.e. you don't have to keep the entire buffer of which you want to + // create the hash in memory. You can feed the hasher parts of it at a time. When + // You have fed the hasher with all the data, you call ``final()`` and it + // will return the sha1-hash of the data. + // + // The constructor that takes a ``char const*`` and an integer will construct the + // sha1 context and feed it the data passed in. + // + // If you want to reuse the hasher object once you have created a hash, you have to + // call ``reset()`` to reinitialize it. + // + // The sha1-algorithm used 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(const char* data, int len); + +#ifdef TORRENT_USE_GCRYPT + hasher(hasher const& h); + hasher& operator=(hasher const& h); +#endif + + // append the following bytes to what is being hashed + hasher& update(std::string const& data) { update(data.c_str(), int(data.size())); return *this; } + hasher& update(const char* 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(); + + ~hasher(); + + private: + +#ifdef TORRENT_USE_GCRYPT + gcry_md_hd_t m_context; +#elif TORRENT_USE_COMMONCRYPTO + CC_SHA1_CTX m_context; +#elif defined TORRENT_USE_OPENSSL + SHA_CTX m_context; +#else + sha_ctx m_context; +#endif + }; + +} + +#endif // TORRENT_HASHER_HPP_INCLUDED + diff --git a/include/libtorrent/heterogeneous_queue.hpp b/include/libtorrent/heterogeneous_queue.hpp new file mode 100644 index 0000000..bed8c2b --- /dev/null +++ b/include/libtorrent/heterogeneous_queue.hpp @@ -0,0 +1,218 @@ +/* + +Copyright (c) 2015-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_HETEROGENEOUS_QUEUE_HPP_INCLUDED +#define TORRENT_HETEROGENEOUS_QUEUE_HPP_INCLUDED + +#include + +#include +#include +#include + +#include "libtorrent/assert.hpp" + +namespace libtorrent { + + template + struct heterogeneous_queue + { + heterogeneous_queue() + : m_storage(NULL) + , m_capacity(0) + , m_size(0) + , m_num_items(0) + {} + + // TODO: 2 add emplace_back() version + template + typename boost::enable_if >::type + push_back(U const& a) + { + // the size of the type rounded up to pointer alignment + const int object_size = (sizeof(U) + sizeof(*m_storage) - 1) + / sizeof(*m_storage); + + // +1 for the length prefix + if (m_size + object_size + header_size > m_capacity) + grow_capacity(object_size); + + uintptr_t* ptr = m_storage + m_size; + + // length prefix + header_t* hdr = reinterpret_cast(ptr); + hdr->len = object_size; + hdr->move = &move; + ptr += header_size; + + // construct in-place + new (ptr) U(a); + + // if we constructed the object without throwing any exception + // update counters to indicate the new item is in there + ++m_num_items; + m_size += header_size + object_size; + } + + void get_pointers(std::vector& out) + { + out.clear(); + + uintptr_t* ptr = m_storage; + uintptr_t const* const end = m_storage + m_size; + while (ptr < end) + { + header_t* hdr = reinterpret_cast(ptr); + ptr += header_size; + 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() + { + uintptr_t* ptr = m_storage; + uintptr_t const* const end = m_storage + m_size; + while (ptr < end) + { + header_t* hdr = reinterpret_cast(ptr); + ptr += header_size; + TORRENT_ASSERT(ptr + hdr->len <= end); + T* a = reinterpret_cast(ptr); + a->~T(); + ptr += hdr->len; + } + m_size = 0; + m_num_items = 0; + } + + T* front() + { + if (m_size == 0) return NULL; + + TORRENT_ASSERT(m_size > 1); + uintptr_t* ptr = m_storage; + TORRENT_ASSERT(reinterpret_cast(ptr)->len <= m_size); + ptr += header_size; + return reinterpret_cast(ptr); + } + + ~heterogeneous_queue() + { + clear(); + delete[] m_storage; + } + + private: + + // non-copyable + heterogeneous_queue(heterogeneous_queue const&); + heterogeneous_queue& operator=(heterogeneous_queue const&); + + // this header is put in front of every element. It tells us + // how many uintptr_t's 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 + { + int len; + void (*move)(uintptr_t* dst, uintptr_t* src); + }; + + const static int header_size = (sizeof(header_t) + sizeof(uintptr_t) + - 1) / sizeof(uintptr_t); + + void grow_capacity(int const size) + { + int const amount_to_grow = (std::max)(size + header_size + , (std::max)(m_capacity * 3 / 2, 128)); + + uintptr_t* new_storage = new uintptr_t[m_capacity + amount_to_grow]; + + uintptr_t* src = m_storage; + uintptr_t* dst = new_storage; + uintptr_t const* const end = m_storage + m_size; + while (src < end) + { + header_t* src_hdr = reinterpret_cast(src); + header_t* dst_hdr = reinterpret_cast(dst); + *dst_hdr = *src_hdr; + src += header_size; + dst += header_size; + TORRENT_ASSERT(src + src_hdr->len <= end); + // TODO: if this throws, should we do anything? + src_hdr->move(dst, src); + src += src_hdr->len; + dst += src_hdr->len; + } + + delete[] m_storage; + m_storage = new_storage; + m_capacity += amount_to_grow; + } + + template + static void move(uintptr_t* dst, uintptr_t* src) + { + U* rhs = reinterpret_cast(src); +#if __cplusplus >= 201103L + new (dst) U(std::move(*rhs)); +#else + new (dst) U(*rhs); +#endif + rhs->~U(); + } + + uintptr_t* m_storage; + // number of uintptr_t's allocated under m_storage + int m_capacity; + // the number of uintptr_t's used under m_storage + int m_size; + // the number of objects allocated under m_storage + int m_num_items; + }; +} + +#endif + diff --git a/include/libtorrent/hex.hpp b/include/libtorrent/hex.hpp new file mode 100644 index 0000000..e7dce89 --- /dev/null +++ b/include/libtorrent/hex.hpp @@ -0,0 +1,75 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_HEX_HPP_INCLUDED +#define TORRENT_HEX_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/error_code.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent +{ + namespace detail { + + TORRENT_EXTRA_EXPORT int hex_to_int(char in); + TORRENT_EXTRA_EXPORT bool is_hex(char const *in, int len); + + } + + // 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_EXPORT std::string to_hex(std::string const& s); + TORRENT_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_EXPORT bool from_hex(char const *in, int len, char* out); + +} + +#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..c1049e2 --- /dev/null +++ b/include/libtorrent/http_connection.hpp @@ -0,0 +1,252 @@ +/* + +Copyright (c) 2007-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_HTTP_CONNECTION +#define TORRENT_HTTP_CONNECTION + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef TORRENT_USE_OPENSSL +#include +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#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/socket_type.hpp" +#include "libtorrent/session_settings.hpp" + +#include "libtorrent/i2p_stream.hpp" + +namespace libtorrent +{ + +struct http_connection; +struct resolver_interface; + +const int default_max_bottled_buffer_size = 2*1024*1024; + +typedef boost::function http_handler; + +typedef boost::function http_connect_handler; + +typedef boost::function&)> http_filter_handler; + +// when bottled, the last two arguments to the handler +// will always be 0 +struct TORRENT_EXTRA_EXPORT http_connection + : boost::enable_shared_from_this + , boost::noncopyable +{ + http_connection(io_service& ios + , resolver_interface& resolver + , http_handler const& handler + , bool bottled = true + , int max_bottled_buffer_size = default_max_bottled_buffer_size + , http_connect_handler const& ch = http_connect_handler() + , http_filter_handler const& fh = http_filter_handler() +#ifdef TORRENT_USE_OPENSSL + , ssl::context* ssl_ctx = 0 +#endif + ); + + 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 = 0, int handle_redirects = 5 + , std::string const& user_agent = std::string() + , address const& bind_addr = address_v4::any() + , int resolve_flags = 0, std::string const& auth_ = std::string() +#if TORRENT_USE_I2P + , i2p_connection* i2p_conn = 0 +#endif + ); + + void start(std::string const& hostname, int port + , time_duration timeout, int prio = 0, aux::proxy_settings const* ps = 0 + , bool ssl = false, int handle_redirect = 5 + , address const& bind_addr = address_v4::any() + , int resolve_flags = 0 +#if TORRENT_USE_I2P + , i2p_connection* i2p_conn = 0 +#endif + ); + + void close(bool force = false); + + socket_type const& socket() const { return m_sock; } + + std::vector const& endpoints() const { return m_endpoints; } + +private: + +#if TORRENT_USE_I2P + void connect_i2p_tracker(char const* destination); + void on_i2p_resolve(error_code const& e + , char const* destination); +#endif + void on_resolve(error_code const& e + , std::vector
    const& addresses); + void connect(); + void on_connect(error_code const& e); + void on_write(error_code const& e); + void on_read(error_code const& e, std::size_t bytes_transferred); + static void on_timeout(boost::weak_ptr p + , error_code const& e); + void on_assign_bandwidth(error_code const& e); + + void callback(error_code e, char* data = NULL, int size = 0); + + std::vector m_recvbuffer; + + std::string m_hostname; + std::string m_url; + std::string m_user_agent; + + std::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; + + socket_type m_sock; + +#ifdef TORRENT_USE_OPENSSL + ssl::context* m_ssl_ctx; + bool m_own_ssl_context; +#endif + +#if TORRENT_USE_I2P + i2p_connection* m_i2p_conn; +#endif + resolver_interface& m_resolver; + + http_parser m_parser; + http_handler m_handler; + http_connect_handler m_connect_handler; + http_filter_handler m_filter_handler; + deadline_timer m_timer; + + time_duration m_read_timeout; + 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. address_v4::any() + // means do not bind + address 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 + int m_resolve_flags; + + boost::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; + + // 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; + + // true if the connection is using ssl + bool m_ssl; + + bool m_abort; + + // true while waiting for an async_connect + bool m_connecting; +}; + +} + +#endif diff --git a/include/libtorrent/http_parser.hpp b/include/libtorrent/http_parser.hpp new file mode 100644 index 0000000..2a0fe2b --- /dev/null +++ b/include/libtorrent/http_parser.hpp @@ -0,0 +1,176 @@ +/* + +Copyright (c) 2008-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_HTTP_PARSER_HPP_INCLUDED +#define TORRENT_HTTP_PARSER_HPP_INCLUDED + +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/config.hpp" +#include "libtorrent/buffer.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 }; + http_parser(int flags = 0); + ~http_parser(); + std::string const& header(char const* key) const + { + static std::string empty; + std::multimap::const_iterator i + = m_header.find(key); + if (i == m_header.end()) return empty; + return i->second; + } + + 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; } + buffer::const_interval get_body() const; + bool header_finished() const { return m_state == read_body; } + bool finished() const { return m_finished; } + boost::tuple incoming(buffer::const_interval recv_buffer + , bool& error); + int body_start() const { return m_body_start_pos; } + boost::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 + int collapse_chunk_headers(char* buffer, int size) 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(buffer::const_interval buf + , boost::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: + boost::int64_t m_recv_pos; + std::string m_method; + std::string m_path; + std::string m_protocol; + std::string m_server_message; + + boost::int64_t m_content_length; + boost::int64_t m_range_start; + boost::int64_t m_range_end; + + std::multimap m_header; + buffer::const_interval 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) + boost::int64_t m_cur_chunk_end; + + int m_status_code; + + // the sum of all chunk headers read so far + int m_chunk_header_size; + + int m_partial_chunk_header; + + // controls some behaviors of the parser + int m_flags; + + int m_body_start_pos; + + enum { read_status, read_header, read_body, error_state } m_state; + + // this is true if the server is HTTP/1.0 or + // if it sent "connection: close" + bool m_connection_close; + bool m_chunked_encoding; + bool m_finished; + }; + +} + +#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..92cca8e --- /dev/null +++ b/include/libtorrent/http_seed_connection.hpp @@ -0,0 +1,132 @@ +/* + +Copyright (c) 2008-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_HTTP_SEED_CONNECTION_HPP_INCLUDED +#define TORRENT_HTTP_SEED_CONNECTION_HPP_INCLUDED + +#include +#include +#include +#include + +#include "libtorrent/debug.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/config.hpp" +#include "libtorrent/web_connection_base.hpp" +#include "libtorrent/disk_buffer_holder.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/piece_block_progress.hpp" +#include "libtorrent/http_parser.hpp" + +namespace libtorrent +{ + class torrent; + struct peer_request; + + namespace detail + { + struct session_impl; + } + + class TORRENT_EXTRA_EXPORT http_seed_connection + : public web_connection_base + { + friend class invariant_access; + public: + + // this is the constructor where the we are the active part. + // The peer_conenction should handshake and verify that the + // other end has the correct id + http_seed_connection(peer_connection_args const& pack + , web_seed_t& web); + + virtual int type() const TORRENT_OVERRIDE + { return peer_connection::http_seed_connection; } + + // called from the main loop when this connection has any + // work to do. + virtual void on_receive(error_code const& error + , std::size_t bytes_transferred) TORRENT_OVERRIDE; + + std::string const& url() const TORRENT_OVERRIDE { return m_url; } + + virtual void get_specific_peer_info(peer_info& p) const TORRENT_OVERRIDE; + virtual void disconnect(error_code const& ec, operation_t op, int error = 0) TORRENT_OVERRIDE; + + virtual void write_request(peer_request const& r) TORRENT_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. + boost::optional downloading_piece_progress() const TORRENT_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 + boost::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 + boost::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..efc30fe --- /dev/null +++ b/include/libtorrent/http_stream.hpp @@ -0,0 +1,128 @@ +/* + +Copyright (c) 2007-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_HTTP_STREAM_HPP_INCLUDED +#define TORRENT_HTTP_STREAM_HPP_INCLUDED + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/proxy_base.hpp" +#include "libtorrent/string_util.hpp" + +namespace libtorrent { + +class http_stream : public proxy_base +{ +public: + + explicit http_stream(io_service& io_service) + : proxy_base(io_service) + , m_no_connect(false) + {} + + void set_no_connect(bool c) { m_no_connect = c; } + + void set_username(std::string const& user + , std::string const& password) + { + m_user = user; + m_password = password; + } + + void set_dst_name(std::string const& host) + { + m_dst_name = host; + } + + void close(error_code& ec) + { + m_dst_name.clear(); + proxy_base::close(ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + void close() + { + m_dst_name.clear(); + proxy_base::close(); + } +#endif + + template + void async_connect(endpoint_type const& endpoint, Handler const& handler) + { + m_remote_endpoint = endpoint; + + // the connect is split up in the following steps: + // 1. resolve name of proxy server + // 2. connect to proxy server + // 3. send HTTP CONNECT method and possibly username+password + // 4. read CONNECT response + + // to avoid unnecessary copying of the handler, + // store it in a shared_ptr + boost::shared_ptr h(new handler_type(handler)); + + tcp::resolver::query q(m_hostname, to_string(m_port).elems); + m_resolver.async_resolve(q, boost::bind( + &http_stream::name_lookup, this, _1, _2, h)); + } + +private: + + void name_lookup(error_code const& e, tcp::resolver::iterator i + , boost::shared_ptr h); + void connected(error_code const& e, boost::shared_ptr h); + void handshake1(error_code const& e, boost::shared_ptr h); + void handshake2(error_code const& e, boost::shared_ptr 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..7abf1ed --- /dev/null +++ b/include/libtorrent/http_tracker_connection.hpp @@ -0,0 +1,110 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_HTTP_TRACKER_CONNECTION_HPP_INCLUDED +#define TORRENT_HTTP_TRACKER_CONNECTION_HPP_INCLUDED + +#include +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/config.hpp" +#include "libtorrent/lazy_entry.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/tracker_manager.hpp" +#include "libtorrent/i2p_stream.hpp" +#include "libtorrent/error_code.hpp" + +namespace libtorrent +{ + + struct http_connection; + class entry; + class http_parser; + struct bdecode_node; + struct peer_entry; + + namespace aux { struct session_settings; } + + class TORRENT_EXTRA_EXPORT http_tracker_connection + : public tracker_connection + { + friend class tracker_manager; + public: + + http_tracker_connection( + io_service& ios + , tracker_manager& man + , tracker_request const& req + , boost::weak_ptr c); + + void start(); + void close(); + + private: + + boost::shared_ptr shared_from_this() + { + return boost::static_pointer_cast( + tracker_connection::shared_from_this()); + } + + void on_filter(http_connection& c, std::vector& endpoints); + void on_connect(http_connection& c); + void on_response(error_code const& ec, http_parser const& parser + , char const* data, int size); + + virtual void on_timeout(error_code const&) {} + + tracker_manager& m_man; + boost::shared_ptr m_tracker_connection; + address m_tracker_ip; +#if TORRENT_USE_I2P + i2p_connection* m_i2p_conn; +#endif + }; + + TORRENT_EXTRA_EXPORT tracker_response parse_tracker_response( + char const* data, int size, error_code& ec + , int flags, sha1_hash 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..39c5e37 --- /dev/null +++ b/include/libtorrent/i2p_stream.hpp @@ -0,0 +1,244 @@ +/* + +Copyright (c) 2009-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_I2P_STREAM_HPP_INCLUDED +#define TORRENT_I2P_STREAM_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#if TORRENT_USE_I2P + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/proxy_base.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/string_util.hpp" + +namespace libtorrent { + + namespace i2p_error { + + // error values for the i2p_category error_category. + enum i2p_error_code + { + no_error = 0, + parse_failed, + cant_reach_peer, + i2p_error, + invalid_key, + invalid_id, + timeout, + key_not_found, + duplicated_id, + num_errors + }; + + // hidden + TORRENT_EXPORT boost::system::error_code make_error_code(i2p_error_code e); + } + + // returns the error category for I2P errors + TORRENT_EXPORT boost::system::error_category& get_i2p_category(); + +class i2p_stream : public proxy_base +{ +public: + + explicit i2p_stream(io_service& io_service); + ~i2p_stream(); + + enum command_t + { + cmd_none, + cmd_create_session, + cmd_connect, + cmd_accept, + cmd_name_lookup, + cmd_incoming + }; + + void set_command(command_t c) { m_command = c; } + + void set_session_id(char const* id) { m_id = id; } + + void set_destination(std::string const& d) { m_dest = d; } + std::string const& destination() { return m_dest; } + + template + void async_connect(endpoint_type const&, Handler const& handler) + { + // since we don't support regular endpoints, just ignore the one + // provided and use m_dest. + + // the connect is split up in the following steps: + // 1. resolve name of proxy server + // 2. connect to SAM bridge + // 4 send command message (CONNECT/ACCEPT) + + // to avoid unnecessary copying of the handler, + // store it in a shaed_ptr + boost::shared_ptr h(new handler_type(handler)); + + tcp::resolver::query q(m_hostname, to_string(m_port).elems); + m_resolver.async_resolve(q, boost::bind( + &i2p_stream::do_connect, this, _1, _2, h)); + } + + std::string name_lookup() const { return m_name_lookup; } + void set_name_lookup(char const* name) { m_name_lookup = name; } + + void send_name_lookup(boost::shared_ptr h); + +private: + // explicitly disallow assignment, to silence msvc warning + i2p_stream& operator=(i2p_stream const&); + + void do_connect(error_code const& e, tcp::resolver::iterator i + , boost::shared_ptr h); + void connected(error_code const& e, boost::shared_ptr h); + void start_read_line(error_code const& e, boost::shared_ptr h); + void read_line(error_code const& e, boost::shared_ptr h); + void send_connect(boost::shared_ptr h); + void send_accept(boost::shared_ptr h); + void send_session_create(boost::shared_ptr h); + + // send and receive buffer + std::vector m_buffer; + char const* m_id; + int m_command; // 0 = connect, 1 = accept + std::string m_dest; + std::string m_name_lookup; + + enum state_t + { + read_hello_response, + read_connect_response, + read_accept_response, + read_session_create_response, + read_name_lookup_response + }; + + int m_state; +#if TORRENT_USE_ASSERTS + int m_magic; +#endif +}; + +class i2p_connection +{ +public: + i2p_connection(io_service& ios); + ~i2p_connection(); + + aux::proxy_settings proxy() const; + + bool is_open() const + { + return m_sam_socket + && m_sam_socket->is_open() + && m_state != sam_connecting; + } + void open(std::string const& hostname, int port, i2p_stream::handler_type const& h); + void close(error_code&); + + char const* session_id() const { return m_session_id.c_str(); } + std::string const& local_endpoint() const { return m_i2p_local_endpoint; } + + typedef boost::function name_lookup_handler; + void async_name_lookup(char const* name, name_lookup_handler handler); + +private: + // explicitly disallow assignment, to silence msvc warning + i2p_connection& operator=(i2p_connection const&); + + void on_sam_connect(error_code const& ec, i2p_stream::handler_type const& h + , boost::shared_ptr); + void do_name_lookup(std::string const& name + , name_lookup_handler const& h); + void on_name_lookup(error_code const& ec + , name_lookup_handler handler + , boost::shared_ptr); + + void set_local_endpoint(error_code const& ec, char const* dest + , i2p_stream::handler_type const& h); + + // to talk to i2p SAM bridge + boost::shared_ptr m_sam_socket; + std::string m_hostname; + int m_port; + + // our i2p endpoint key + std::string m_i2p_local_endpoint; + std::string m_session_id; + + std::list > m_name_lookup; + + enum state_t + { + sam_connecting, + sam_name_lookup, + sam_idle + }; + + state_t m_state; + + io_service& m_io_service; +}; + +} + +namespace boost { namespace system { + +template<> +struct is_error_code_enum +{ static const bool value = true; }; + +template<> +struct is_error_condition_enum +{ static const bool value = true; }; + +} } + +#endif // TORRENT_USE_I2P + +#endif + diff --git a/include/libtorrent/identify_client.hpp b/include/libtorrent/identify_client.hpp new file mode 100644 index 0000000..e1816a7 --- /dev/null +++ b/include/libtorrent/identify_client.hpp @@ -0,0 +1,73 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_IDENTIFY_CLIENT_HPP_INCLUDED +#define TORRENT_IDENTIFY_CLIENT_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/peer_id.hpp" +#include "libtorrent/fingerprint.hpp" + +namespace libtorrent +{ + + // TODO: hide these declarations when deprecaated functions are disabled, and + // expose them internally in a header under aux_. + + // 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 TORRENT_DEPRECATED + std::string identify_client(const peer_id& p); + + // 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 TORRENT_DEPRECATED + boost::optional + client_fingerprint(peer_id const& p); + +} + +#endif // TORRENT_IDENTIFY_CLIENT_HPP_INCLUDED + diff --git a/include/libtorrent/instantiate_connection.hpp b/include/libtorrent/instantiate_connection.hpp new file mode 100644 index 0000000..3998344 --- /dev/null +++ b/include/libtorrent/instantiate_connection.hpp @@ -0,0 +1,57 @@ +/* + +Copyright (c) 2007-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_INSTANTIATE_CONNECTION +#define TORRENT_INSTANTIATE_CONNECTION + +#include "libtorrent/socket_type.hpp" + +namespace libtorrent +{ + namespace aux { + struct proxy_settings; + } + + struct utp_socket_manager; + struct socket_type; + + // instantiate a socket_type (s) according to the specified criteria + TORRENT_EXTRA_EXPORT bool instantiate_connection(io_service& ios + , aux::proxy_settings const& ps, socket_type& s + , void* ssl_context + , utp_socket_manager* sm + , bool peer_connection + , bool tracker_connection); +} + +#endif + diff --git a/include/libtorrent/invariant_check.hpp b/include/libtorrent/invariant_check.hpp new file mode 100644 index 0000000..60c260b --- /dev/null +++ b/include/libtorrent/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 "libtorrent/config.hpp" + +#if TORRENT_USE_INVARIANT_CHECKS + +namespace libtorrent +{ + + class invariant_access + { + public: + template + static void check_invariant(T const& self) + { + self.check_invariant(); + } + }; + + template + void check_invariant(T const& x) + { + invariant_access::check_invariant(x); + } + + struct invariant_checker {}; + + template + struct invariant_checker_impl : invariant_checker + { + invariant_checker_impl(T const& self_) + : self(self_) + { + TORRENT_TRY + { + check_invariant(self); + } + TORRENT_CATCH_ALL + { + TORRENT_ASSERT(false); + } + } + + invariant_checker_impl(invariant_checker_impl const& rhs) + : self(rhs.self) {} + + ~invariant_checker_impl() + { + TORRENT_TRY + { + check_invariant(self); + } + TORRENT_CATCH_ALL + { + TORRENT_ASSERT(false); + } + } + + T const& self; + + private: + invariant_checker_impl& operator=(invariant_checker_impl const&); + }; + + template + invariant_checker_impl make_invariant_checker(T const& x) + { + return invariant_checker_impl(x); + } +} + +#define INVARIANT_CHECK \ + invariant_checker const& _invariant_check = make_invariant_checker(*this); \ + (void)_invariant_check +#else +#define INVARIANT_CHECK do {} TORRENT_WHILE_0 +#endif + +#endif // TORRENT_INVARIANT_ACCESS_HPP_INCLUDED diff --git a/include/libtorrent/io.hpp b/include/libtorrent/io.hpp new file mode 100644 index 0000000..83f9dc3 --- /dev/null +++ b/include/libtorrent/io.hpp @@ -0,0 +1,173 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_IO_HPP_INCLUDED +#define TORRENT_IO_HPP_INCLUDED + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include +#include // for copy +#include // for memcpy + +namespace libtorrent +{ + namespace detail + { + template struct type {}; + + // reads an integer from a byte stream + // in big endian byte order and converts + // it to native endianess + template + inline T read_impl(InIt& start, type) + { + T ret = 0; + for (int i = 0; i < int(sizeof(T)); ++i) + { + ret <<= 8; + ret |= static_cast(*start); + ++start; + } + return ret; + } + + template + boost::uint8_t read_impl(InIt& start, type) + { + return static_cast(*start++); + } + + template + boost::int8_t read_impl(InIt& start, type) + { + return static_cast(*start++); + } + + template + inline void write_impl(T val, OutIt& start) + { + for (int i = int(sizeof(T))-1; i >= 0; --i) + { + *start = static_cast((val >> (i * 8)) & 0xff); + ++start; + } + } + + // -- adaptors + + template + boost::int64_t read_int64(InIt& start) + { return read_impl(start, type()); } + + template + boost::uint64_t read_uint64(InIt& start) + { return read_impl(start, type()); } + + template + boost::uint32_t read_uint32(InIt& start) + { return read_impl(start, type()); } + + template + boost::int32_t read_int32(InIt& start) + { return read_impl(start, type()); } + + template + boost::int16_t read_int16(InIt& start) + { return read_impl(start, type()); } + + template + boost::uint16_t read_uint16(InIt& start) + { return read_impl(start, type()); } + + template + boost::int8_t read_int8(InIt& start) + { return read_impl(start, type()); } + + template + boost::uint8_t read_uint8(InIt& start) + { return read_impl(start, type()); } + + + template + void write_uint64(boost::uint64_t val, OutIt& start) + { write_impl(val, start); } + + template + void write_int64(boost::int64_t val, OutIt& start) + { write_impl(val, start); } + + template + void write_uint32(boost::uint32_t val, OutIt& start) + { write_impl(val, start); } + + template + void write_int32(boost::int32_t val, OutIt& start) + { write_impl(val, start); } + + template + void write_uint16(boost::uint16_t val, OutIt& start) + { write_impl(val, start); } + + template + void write_int16(boost::int16_t val, OutIt& start) + { write_impl(val, start); } + + template + void write_uint8(boost::uint8_t val, OutIt& start) + { write_impl(val, start); } + + template + void write_int8(boost::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 (std::string::const_iterator i = val.begin() + , end(val.end()); i != end; ++i) + *out++ = *i; + return int(val.length()); + } + } +} + +#endif // TORRENT_IO_HPP_INCLUDED diff --git a/include/libtorrent/io_service.hpp b/include/libtorrent/io_service.hpp new file mode 100644 index 0000000..4fc3931 --- /dev/null +++ b/include/libtorrent/io_service.hpp @@ -0,0 +1,72 @@ +/* + +Copyright (c) 2009-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_IO_SERVICE_HPP_INCLUDED +#define TORRENT_IO_SERVICE_HPP_INCLUDED + +#ifdef __OBJC__ +#define Protocol Protocol_ +#endif + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include + +#if defined TORRENT_WINDOWS || defined TORRENT_CYGWIN +// asio assumes that the windows error codes are defined already +#include +#endif + +#include + +#if defined TORRENT_BUILD_SIMULATOR +#include "simulator/simulator.hpp" +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#ifdef __OBJC__ +#undef Protocol +#endif + +namespace libtorrent +{ +#if defined TORRENT_BUILD_SIMULATOR + typedef sim::asio::io_service io_service; +#else + typedef boost::asio::io_service io_service; +#endif +} + +#endif + + diff --git a/include/libtorrent/io_service_fwd.hpp b/include/libtorrent/io_service_fwd.hpp new file mode 100644 index 0000000..c14997d --- /dev/null +++ b/include/libtorrent/io_service_fwd.hpp @@ -0,0 +1,70 @@ +/* + +Copyright (c) 2009-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_IO_SERVICE_FWD_HPP_INCLUDED +#define TORRENT_IO_SERVICE_FWD_HPP_INCLUDED + +#ifdef __OBJC__ +#define Protocol Protocol_ +#endif + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#ifdef __OBJC__ +#undef Protocol +#endif + +#if defined TORRENT_BUILD_SIMULATOR +namespace sim { namespace asio { + struct io_service; +}} +#endif + +namespace boost { namespace asio { + class io_service; +}} + +namespace libtorrent +{ +#if defined TORRENT_BUILD_SIMULATOR + typedef sim::asio::io_service io_service; +#else + typedef boost::asio::io_service io_service; +#endif +} + +#endif + diff --git a/include/libtorrent/ip_filter.hpp b/include/libtorrent/ip_filter.hpp new file mode 100644 index 0000000..88131e8 --- /dev/null +++ b/include/libtorrent/ip_filter.hpp @@ -0,0 +1,359 @@ +/* + +Copyright (c) 2005-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_IP_FILTER_HPP +#define TORRENT_IP_FILTER_HPP + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include + +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/address.hpp" +#include "libtorrent/assert.hpp" + +namespace libtorrent +{ + +// hidden +inline bool operator<=(address const& lhs + , address const& rhs) +{ + return lhs < rhs || lhs == rhs; +} + +template +struct ip_range +{ + Addr first; + Addr last; + boost::uint32_t flags; +}; + +namespace detail +{ + + template + Addr zero() + { + Addr zero; + std::fill(zero.begin(), zero.end(), 0); + return zero; + } + + template<> + inline boost::uint16_t zero() { return 0; } + + template + Addr plus_one(Addr const& a) + { + Addr tmp(a); + for (int i = int(tmp.size()) - 1; i >= 0; --i) + { + if (tmp[i] < (std::numeric_limits::max)()) + { + tmp[i] += 1; + break; + } + tmp[i] = 0; + } + return tmp; + } + + inline boost::uint16_t plus_one(boost::uint16_t val) { return val + 1; } + + template + Addr minus_one(Addr const& a) + { + Addr tmp(a); + for (int i = int(tmp.size()) - 1; i >= 0; --i) + { + if (tmp[i] > 0) + { + tmp[i] -= 1; + break; + } + tmp[i] = (std::numeric_limits::max)(); + } + return tmp; + } + + inline boost::uint16_t minus_one(boost::uint16_t val) { return val - 1; } + + template + Addr max_addr() + { + Addr tmp; + std::fill(tmp.begin(), tmp.end() + , (std::numeric_limits::max)()); + return Addr(tmp); + } + + template<> + inline boost::uint16_t max_addr() + { return (std::numeric_limits::max)(); } + + // this is the generic implementation of + // a filter for a specific address type. + // it works with IPv4 and IPv6 + template + class filter_impl + { + public: + + filter_impl() + { + // make the entire ip-range non-blocked + m_access_list.insert(range(zero(), 0)); + } + + void add_rule(Addr first, Addr last, int flags) + { + TORRENT_ASSERT(!m_access_list.empty()); + TORRENT_ASSERT(first < last || first == last); + + typename range_t::iterator i = m_access_list.upper_bound(first); + typename range_t::iterator 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); + + boost::uint32_t first_access = i->access; + boost::uint32_t last_access = boost::prior(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() && boost::prior(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(boost::next(i), j); + if (i->start == first) + { + // we can do this const-cast 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()); + } + + boost::uint32_t access(Addr const& addr) const + { + TORRENT_ASSERT(!m_access_list.empty()); + typename range_t::const_iterator 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 && (boost::next(i) == m_access_list.end() + || addr < boost::next(i)->start)); + return i->access; + } + + template + std::vector > export_filter() const + { + std::vector > ret; + ret.reserve(m_access_list.size()); + + for (typename range_t::const_iterator i = m_access_list.begin() + , end(m_access_list.end()); i != end;) + { + ip_range r; + r.first = ExternalAddressType(i->start); + r.flags = i->access; + + ++i; + if (i == end) + r.last = ExternalAddressType(max_addr()); + else + r.last = ExternalAddressType(minus_one(i->start)); + + ret.push_back(r); + } + return ret; + } + + private: + + struct range + { + range(Addr addr, int a = 0): start(addr), access(a) {} + 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 + boost::uint32_t access; + }; + + typedef std::set range_t; + range_t m_access_list; + + }; + +} + +// The ``ip_filter`` class is a set of rules that uniquely categorizes all +// ip addresses as allowed or disallowed. The default constructor creates +// a single rule that allows all addresses (0.0.0.0 - 255.255.255.255 for +// the IPv4 range, and the equivalent range covering all addresses for the +// IPv6 range). +// +// A default constructed ip_filter does not filter any address. +struct TORRENT_EXPORT ip_filter +{ + // the flags defined for an IP range + enum access_flags + { + // indicates that IPs in this range should not be connected + // to nor accepted as incoming connections + blocked = 1 + }; + + // Adds a rule to the filter. ``first`` and ``last`` defines a range of + // ip addresses that will be marked with the given flags. The ``flags`` + // can currently be 0, which means allowed, or ``ip_filter::blocked``, which + // means disallowed. + // + // precondition: + // ``first.is_v4() == last.is_v4() && first.is_v6() == last.is_v6()`` + // + // postcondition: + // ``access(x) == flags`` for every ``x`` in the range [``first``, ``last``] + // + // This means that in a case of overlapping ranges, the last one applied takes + // precedence. + void add_rule(address first, address last, boost::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. + int access(address const& addr) const; + +#if TORRENT_USE_IPV6 + typedef boost::tuple > + , std::vector > > filter_tuple_t; +#else + typedef std::vector > filter_tuple_t; +#endif + + // This function will return the current state of the filter in the minimum number of + // ranges possible. They are sorted from ranges in low addresses to high addresses. Each + // entry in the returned vector is a range with the access control specified in its + // ``flags`` field. + // + // The return value is a tuple containing two range-lists. One for IPv4 addresses + // and one for IPv6 addresses. + filter_tuple_t export_filter() const; + +// void print() const; + +private: + + detail::filter_impl m_filter4; +#if TORRENT_USE_IPV6 + detail::filter_impl m_filter6; +#endif +}; + +// the port filter maps non-overlapping port ranges to flags. This +// is primarily used to indicate whether a range of ports should +// be connected to or not. The default is to have the full port +// range (0-65535) set to flag 0. +class TORRENT_EXPORT port_filter +{ +public: + + // the defined flags for a port range + enum access_flags + { + // this flag indicates that destination ports in the + // range should not be connected to + blocked = 1 + }; + + // set the flags for the specified port range (``first``, ``last``) to + // ``flags`` overwriting any existing rule for those ports. The range + // is inclusive, i.e. the port ``last`` also has the flag set on it. + void add_rule(boost::uint16_t first, boost::uint16_t last, boost::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 acces_flags. + int access(boost::uint16_t port) const; + +private: + + detail::filter_impl m_filter; + +}; + +} + +#endif + diff --git a/include/libtorrent/ip_voter.hpp b/include/libtorrent/ip_voter.hpp new file mode 100644 index 0000000..c60cd2a --- /dev/null +++ b/include/libtorrent/ip_voter.hpp @@ -0,0 +1,131 @@ +/* + +Copyright (c) 2013-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_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 + +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, int source_type, address const& source); + + address external_address() const { return m_external_address; } + + private: + + bool maybe_rotate(); + + struct external_ip_t + { + external_ip_t(): sources(0), num_votes(0) {} + + bool add_vote(sha1_hash const& k, int type); + + // we want to sort decending + 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 sources > 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 + boost::uint16_t sources; + // the total number of votes for this IP + boost::uint16_t num_votes; + }; + + // 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; + }; + + // this keeps track of multiple external IPs (for now, just IPv6 and IPv4, but + // it could be extended to deal with loopback and local network addresses as well) + struct TORRENT_EXTRA_EXPORT external_ip + { + // 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, int source_type, address const& source); + + // the external IP as it would be observed from `ip` + address external_address(address const& ip) const; + + private: + + // for now, assume one external IPv4 and one external IPv6 address + // 0 = IPv4 1 = IPv6 + // TODO: 1 instead, have one instance per possible subnet, global IPv4, global IPv6, loopback, 192.168.x.x, 10.x.x.x, etc. + ip_voter m_vote_group[2]; + }; + +} + +#endif + diff --git a/include/libtorrent/kademlia/dht_observer.hpp b/include/libtorrent/kademlia/dht_observer.hpp new file mode 100644 index 0000000..9ea84ee --- /dev/null +++ b/include/libtorrent/kademlia/dht_observer.hpp @@ -0,0 +1,85 @@ +/* + +Copyright (c) 2012-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 DHT_OBSERVER_HPP +#define DHT_OBSERVER_HPP + +#include "libtorrent/config.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/kademlia/msg.hpp" + +namespace libtorrent { namespace dht +{ + struct TORRENT_EXTRA_EXPORT dht_logger + { + enum module_t + { + tracker, + node, + routing_table, + rpc_manager, + traversal + }; + + enum message_direction_t + { + incoming_message, + outgoing_message + }; + + virtual void log(module_t m, char const* fmt, ...) TORRENT_FORMAT(3,4) = 0; + virtual void log_packet(message_direction_t dir, char const* pkt, int len + , udp::endpoint node) = 0; + + protected: + ~dht_logger() {} + }; + + struct TORRENT_EXTRA_EXPORT dht_observer : dht_logger + { + virtual void set_external_address(address const& addr + , address const& source) = 0; + virtual address external_address() = 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(char const* query, int query_len + , dht::msg const& request, entry& response) = 0; + + protected: + ~dht_observer() {} + }; +}} + +#endif + diff --git a/include/libtorrent/kademlia/dht_storage.hpp b/include/libtorrent/kademlia/dht_storage.hpp new file mode 100644 index 0000000..7e57522 --- /dev/null +++ b/include/libtorrent/kademlia/dht_storage.hpp @@ -0,0 +1,218 @@ +/* + +Copyright (c) 2012-2016, Arvid Norberg, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DHT_STORAGE_HPP +#define TORRENT_DHT_STORAGE_HPP + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include +#include +#include + +namespace libtorrent +{ + struct dht_settings; + class entry; +} + +namespace libtorrent { +namespace dht +{ + // This structure hold the relevant counters for the storage + struct TORRENT_EXPORT dht_storage_counters + { + boost::int32_t torrents; + boost::int32_t peers; + boost::int32_t immutable_data; + boost::int32_t mutable_data; + }; + + // 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(). + // + struct TORRENT_EXPORT dht_storage_interface + { +#ifndef TORRENT_NO_DEPRECATE + // 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 function retrieve the peers tracked by the DHT + // corresponding to the given info_hash. You can specify if + // you want only seeds and/or you are scraping the data. + // + // For future implementers: + // If the torrent tracked contains a name, such a name + // must be stored as a string in peers["n"] + // + // If the scrape parameter is true, you should fill these keys:: + // + // peers["BFpe"] - with the standard bit representation of a + // 256 bloom filter containing the downloaders + // peers["BFsd"] - with the standard bit representation of a + // 256 bloom filter containing the seeders + // + // If the scrape parameter is false, you should fill the + // key peers["values"] with a list containing a subset of + // peers tracked by the given info_hash. Such a list should + // consider the value of dht_settings::max_peers_reply. + // If noseed is true only peers marked as no seed should be included. + // + // returns true if an entry with the info_hash is found and + // the data is returned inside the (entry) out parameter peers. + // + virtual bool get_peers(sha1_hash const& info_hash + , bool noseed, bool scrape + , 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 + , std::string const& name, bool seed) = 0; + + // This function retrieves the immutable item given its target hash. + // + // For future implementers: + // The value should be returned as an entry in the key item["v"]. + // + // returns true if the item is found and the data is returned + // inside the (entry) out parameter item. + // + virtual bool get_immutable_item(sha1_hash const& target + , entry& item) const = 0; + + // Store the item's data. This layer is only for storage. + // The authentication of the item is performed by the upper layer. + // + // For implementers: + // This data can be stored only if the target is not already + // present. The implementation should consider the value of + // dht_settings::max_dht_items. + // + virtual void put_immutable_item(sha1_hash const& target + , char const* buf, int size + , 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 + , boost::int64_t& 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 + , boost::int64_t seq, bool force_fill + , entry& item) const = 0; + + // Store the item's data. This layer is only for storage. + // The authentication of the item is performed by the upper layer. + // + // For implementers: + // The sequence number should be checked if the item is already + // present. The implementation should consider the value of + // dht_settings::max_dht_items. + // + virtual void put_mutable_item(sha1_hash const& target + , char const* buf, int size + , char const* sig + , boost::int64_t seq + , char const* pk + , char const* salt, int salt_size + , address const& addr) = 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; + + virtual dht_storage_counters counters() const = 0; + + virtual ~dht_storage_interface() {} + }; + + typedef boost::function dht_storage_constructor_type; + + TORRENT_EXPORT dht_storage_interface* dht_default_storage_constructor(sha1_hash const& id + , dht_settings const& settings); + +} } // namespace libtorrent::dht + +#endif //TORRENT_DHT_STORAGE_HPP diff --git a/include/libtorrent/kademlia/dht_tracker.hpp b/include/libtorrent/kademlia/dht_tracker.hpp new file mode 100644 index 0000000..7474b62 --- /dev/null +++ b/include/libtorrent/kademlia/dht_tracker.hpp @@ -0,0 +1,177 @@ +/* + +Copyright (c) 2006-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_DISABLE_DHT + +#ifndef TORRENT_DHT_TRACKER +#define TORRENT_DHT_TRACKER + +#include +#include +#include +#include +#include +#include + +#include "libtorrent/kademlia/node.hpp" +#include "libtorrent/kademlia/node_id.hpp" +#include "libtorrent/kademlia/traversal_algorithm.hpp" +#include "libtorrent/kademlia/dos_blocker.hpp" + +#include "libtorrent/session_settings.hpp" +#include "libtorrent/udp_socket.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/thread.hpp" +#include "libtorrent/deadline_timer.hpp" + +namespace libtorrent +{ + namespace aux { struct session_impl; } + struct counters; +#ifndef TORRENT_NO_DEPRECATE + struct session_status; +#endif +} + +namespace libtorrent { namespace dht +{ + struct dht_tracker; + + struct dht_tracker TORRENT_FINAL + : udp_socket_interface + , udp_socket_observer + , boost::enable_shared_from_this + { + dht_tracker(dht_observer* observer, rate_limited_udp_socket& sock + , dht_settings const& settings, counters& cnt + , dht_storage_constructor_type storage_constructor + , entry const& state); + virtual ~dht_tracker(); + + void start(entry const& bootstrap + , 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(); + + void add_node(udp::endpoint node); + void add_router_node(udp::endpoint const& node); + + entry state() const; + + enum flags_t { flag_seed = 1, flag_implied_port = 2 }; + void get_peers(sha1_hash const& ih + , boost::function const&)> f); + void announce(sha1_hash const& ih, int listen_port, int flags + , boost::function const&)> f); + + void get_item(sha1_hash const& target + , boost::function cb); + + // key is a 32-byte binary string, the public key to look up. + // the salt is optional + void get_item(char const* key + , boost::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 + , boost::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(char const* key + , boost::function cb + , boost::function data_cb, std::string salt = std::string()); + + // send an arbitrary DHT request directly to a node + void direct_request(udp::endpoint ep, entry& e + , boost::function f); + +#ifndef TORRENT_NO_DEPRECATE + void dht_status(session_status& s); +#endif + void dht_status(std::vector& table + , std::vector& requests); + void update_stats_counters(counters& c) const; + + // translate bittorrent kademlia message into the generic kademlia message + // used by the library + virtual bool incoming_packet(error_code const& ec + , udp::endpoint const&, char const* buf, int size); + + private: + + boost::shared_ptr self() + { return shared_from_this(); } + + void connection_timeout(error_code const& e); + void refresh_timeout(error_code const& e); + void refresh_key(error_code const& e); + + // implements udp_socket_interface + virtual bool has_quota(); + virtual bool send_packet(libtorrent::entry& e, udp::endpoint const& addr + , int send_flags); + + // 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; + node m_dht; + rate_limited_udp_socket& m_sock; + dht_logger* m_log; + + std::vector m_send_buf; + dos_blocker m_blocker; + + deadline_timer m_key_refresh_timer; + deadline_timer m_connection_timer; + deadline_timer m_refresh_timer; + dht_settings const& m_settings; + + bool m_abort; + + // used to resolve hostnames for nodes + udp::resolver m_host_resolver; + }; +}} + +#endif +#endif diff --git a/include/libtorrent/kademlia/direct_request.hpp b/include/libtorrent/kademlia/direct_request.hpp new file mode 100644 index 0000000..7c5945d --- /dev/null +++ b/include/libtorrent/kademlia/direct_request.hpp @@ -0,0 +1,95 @@ +/* + +Copyright (c) 2014, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DIRECT_REQUEST_HPP +#define TORRENT_DIRECT_REQUEST_HPP + +#include +#include +#include + +namespace libtorrent { namespace dht +{ + +struct direct_traversal : traversal_algorithm +{ + typedef boost::function message_callback; + + direct_traversal(node& node + , node_id target + , message_callback cb) + : traversal_algorithm(node, target) + , m_cb(cb) + {} + + virtual char const* name() const { return "direct_traversal"; } + + void invoke_cb(msg const& m) + { + if (!m_cb.empty()) + { + m_cb(m); + m_cb.clear(); + done(); + } + } + +protected: + message_callback m_cb; +}; + +struct direct_observer : observer +{ + direct_observer(boost::intrusive_ptr const& algo + , udp::endpoint const& ep, node_id const& id) + : observer(algo, ep, id) + {} + + virtual void reply(msg const& m) + { + flags |= flag_done; + static_cast(algorithm())->invoke_cb(m); + } + + virtual void timeout() + { + if (flags & flag_done) return; + flags |= flag_done; + bdecode_node e; + msg m(e, target_ep()); + static_cast(algorithm())->invoke_cb(m); + } +}; + +}} // namespace libtorrent::dht + +#endif //TORRENT_DIRECT_REQUEST_HPP diff --git a/include/libtorrent/kademlia/dos_blocker.hpp b/include/libtorrent/kademlia/dos_blocker.hpp new file mode 100644 index 0000000..8309502 --- /dev/null +++ b/include/libtorrent/kademlia/dos_blocker.hpp @@ -0,0 +1,95 @@ +/* + +Copyright (c) 2006-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_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 addr, time_point now, dht_logger* logger); + + void set_rate_limit(int l) + { + TORRENT_ASSERT(l > 0); + m_message_rate_limit = l; + } + + void set_block_timer(int t) + { + TORRENT_ASSERT(t > 0); + m_block_timeout = t; + } + + private: + + // used to ignore abusive dht nodes + struct node_ban_entry + { + node_ban_entry(): count(0) {} + address src; + time_point limit; + int count; + }; + + enum { 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/find_data.hpp b/include/libtorrent/kademlia/find_data.hpp new file mode 100644 index 0000000..9fea0f1 --- /dev/null +++ b/include/libtorrent/kademlia/find_data.hpp @@ -0,0 +1,104 @@ +/* + +Copyright (c) 2006-2016, Arvid Norberg & Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef FIND_DATA_050323_HPP +#define FIND_DATA_050323_HPP + +#include +#include +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include + +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { namespace dht +{ + +typedef std::vector packet_t; + +class rpc_manager; +class node; + +// -------- find data ----------- + +struct find_data : traversal_algorithm +{ + typedef boost::function > const&)> nodes_callback; + + find_data(node & node, node_id target + , nodes_callback const& ncallback); + + void got_write_token(node_id const& n, std::string const& write_token); + + virtual void start(); + + virtual char const* name() const; + + node_id const target() const { return m_target; } + +protected: + + virtual void done(); + virtual observer_ptr new_observer(void* ptr, udp::endpoint const& ep + , node_id const& id); + + nodes_callback m_nodes_callback; + std::map m_write_tokens; + bool m_done; +}; + +struct find_data_observer : traversal_observer +{ + find_data_observer( + boost::intrusive_ptr const& algorithm + , udp::endpoint const& ep, node_id const& id) + : traversal_observer(algorithm, ep, id) + {} + + virtual void reply(msg const&); +}; + +} } // namespace libtorrent::dht + +#endif // FIND_DATA_050323_HPP + diff --git a/include/libtorrent/kademlia/get_item.hpp b/include/libtorrent/kademlia/get_item.hpp new file mode 100644 index 0000000..2b6b592 --- /dev/null +++ b/include/libtorrent/kademlia/get_item.hpp @@ -0,0 +1,92 @@ +/* + +Copyright (c) 2013, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef LIBTORRENT_GET_ITEM_HPP +#define LIBTORRENT_GET_ITEM_HPP + +#include +#include +#include + +namespace libtorrent { namespace dht +{ + +class get_item : public find_data +{ +public: + typedef boost::function data_callback; + + void got_data(bdecode_node const& v, + char const* pk, + boost::uint64_t seq, + char const* sig); + + // for immutable itms + get_item(node& dht_node + , node_id target + , data_callback const& dcallback + , nodes_callback const& ncallback); + + // for mutable items + get_item(node& dht_node + , char const* pk + , std::string const& salt + , data_callback const& dcallback + , nodes_callback const& ncallback); + + virtual char const* name() const; + +protected: + virtual observer_ptr new_observer(void* ptr, udp::endpoint const& ep, node_id const& id); + virtual bool invoke(observer_ptr o); + virtual void done(); + + data_callback m_data_callback; + item m_data; + bool m_immutable; +}; + +class get_item_observer : public find_data_observer +{ +public: + get_item_observer( + boost::intrusive_ptr const& algorithm + , udp::endpoint const& ep, node_id const& id) + : find_data_observer(algorithm, ep, id) + {} + + virtual void reply(msg const&); +}; + +} } // namespace libtorrent::dht + +#endif // LIBTORRENT_GET_ITEM_HPP diff --git a/include/libtorrent/kademlia/get_peers.hpp b/include/libtorrent/kademlia/get_peers.hpp new file mode 100644 index 0000000..e71265c --- /dev/null +++ b/include/libtorrent/kademlia/get_peers.hpp @@ -0,0 +1,108 @@ +/* + +Copyright (c) 2006-2016, Arvid Norberg & Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef LIBTORRENT_GET_PEERS_HPP +#define LIBTORRENT_GET_PEERS_HPP + +#include + +namespace libtorrent { namespace dht +{ + +struct get_peers : find_data +{ + typedef boost::function const&)> data_callback; + + void got_peers(std::vector const& peers); + + get_peers(node& dht_node, node_id target + , data_callback const& dcallback + , nodes_callback const& ncallback + , bool noseeds); + + virtual char const* name() const; + +protected: + virtual bool invoke(observer_ptr o); + virtual observer_ptr new_observer(void* ptr, udp::endpoint const& ep, node_id const& id); + + data_callback m_data_callback; + bool m_noseeds; +}; + +struct obfuscated_get_peers : get_peers +{ + typedef get_peers::nodes_callback done_callback; + + obfuscated_get_peers(node& dht_node, node_id target + , data_callback const& dcallback + , nodes_callback const& ncallback + , bool noseeds); + + virtual char const* name() const; + +protected: + + virtual observer_ptr new_observer(void* ptr, udp::endpoint const& ep, + node_id const& id); + virtual bool invoke(observer_ptr o); + virtual void done(); +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( + boost::intrusive_ptr const& algorithm + , udp::endpoint const& ep, node_id const& id) + : find_data_observer(algorithm, ep, id) + {} + + virtual void reply(msg const&); +}; + +struct obfuscated_get_peers_observer : traversal_observer +{ + obfuscated_get_peers_observer( + boost::intrusive_ptr const& algorithm + , udp::endpoint const& ep, node_id const& id) + : traversal_observer(algorithm, ep, id) + {} + virtual void reply(msg const&); +}; + +} } // namespace libtorrent::dht + +#endif // LIBTORRENT_GET_PEERS_HPP diff --git a/include/libtorrent/kademlia/item.hpp b/include/libtorrent/kademlia/item.hpp new file mode 100644 index 0000000..767d34a --- /dev/null +++ b/include/libtorrent/kademlia/item.hpp @@ -0,0 +1,137 @@ +/* + +Copyright (c) 2013, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef LIBTORRENT_ITEM_HPP +#define LIBTORRENT_ITEM_HPP + +#include +#include +#include +#include +#include +#include + +namespace libtorrent { namespace dht +{ + +// calculate the target hash for an immutable item. +sha1_hash TORRENT_EXTRA_EXPORT item_target_id( + std::pair v); + +// calculate the target hash for a mutable item. +sha1_hash TORRENT_EXTRA_EXPORT item_target_id(std::pair salt + , char const* pk); + +bool TORRENT_EXTRA_EXPORT verify_mutable_item( + std::pair v + , std::pair salt + , boost::uint64_t seq + , char const* pk + , char 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. +void TORRENT_EXPORT sign_mutable_item( + std::pair v + , std::pair salt + , boost::uint64_t seq + , char const* pk + , char const* sk + , char* sig); + +enum +{ + item_pk_len = 32, + item_sk_len = 64, + item_sig_len = 64 +}; + +class TORRENT_EXTRA_EXPORT item +{ +public: + item() : m_seq(0), m_mutable(false) {} + item(char const* pk, std::string const& salt); + item(entry const& v) { assign(v); } + item(entry const& v + , std::pair salt + , boost::uint64_t seq, char const* pk, char const* sk); + item(bdecode_node const& v) { assign(v); } + + void assign(entry const& v) + { + assign(v, std::pair(static_cast(NULL) + , 0), 0, NULL, NULL); + } + void assign(entry const& v, std::pair salt + , boost::uint64_t seq, char const* pk, char const* sk); + void assign(bdecode_node const& v) + { + assign(v, std::pair(static_cast(NULL) + , 0), 0, NULL, NULL); + } + bool assign(bdecode_node const& v, std::pair salt + , boost::uint64_t seq, char const* pk, char const* sig); + void assign(entry const& v, std::string salt, boost::uint64_t seq + , char const* pk, char 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; } + boost::array const& pk() const + { return m_pk; } + boost::array const& sig() const + { return m_sig; } + boost::uint64_t seq() const { return m_seq; } + std::string const& salt() const { return m_salt; } + +private: + entry m_value; + std::string m_salt; + boost::array m_pk; + boost::array m_sig; + boost::uint64_t m_seq; + bool m_mutable; +}; + +} } // namespace libtorrent::dht + +#endif // LIBTORRENT_ITEM_HPP diff --git a/include/libtorrent/kademlia/msg.hpp b/include/libtorrent/kademlia/msg.hpp new file mode 100644 index 0000000..caa015c --- /dev/null +++ b/include/libtorrent/kademlia/msg.hpp @@ -0,0 +1,112 @@ +/* + +Copyright (c) 2007-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_KADEMLIA_MSG_HPP +#define TORRENT_KADEMLIA_MSG_HPP + +#include +#include "libtorrent/socket.hpp" +#include "libtorrent/kademlia/node_id.hpp" + +namespace libtorrent { + +struct bdecode_node; +class entry; + +namespace dht { + +typedef std::vector packet_t; +typedef std::vector nodes_t; +typedef std::vector peers_t; + +struct msg +{ + msg(bdecode_node const& m, udp::endpoint const& ep): message(m), addr(ep) {} + // the message + bdecode_node const& message; + + // the address of the process sending or receiving + // the message. + udp::endpoint addr; +private: + // explicitly disallow assignment, to silence msvc warning + msg& operator=(msg const&); +}; + +struct key_desc_t +{ + char const* name; + int type; + int size; + int flags; + + enum { + // this argument is optional, parsing will not + // fail if it's not present + optional = 1, + // for dictionaries, the following entries refer + // to child nodes to this node, up until and including + // the next item that has the last_child flag set. + // these flags are nestable + parse_children = 2, + // this is the last item in a child dictionary + last_child = 4, + // the size argument refers to that the size + // has to be divisible by the number, instead + // of having that exact size + size_divisible = 8 + }; +}; + +// generate an error response message +void incoming_error(entry& e, char const* msg, int error_code = 203); + +// given a redundant name to avoid clashing with libtorrent::detail +namespace dht_detail { + +TORRENT_EXPORT bool verify_message(bdecode_node const& msg, key_desc_t const desc[] + , bdecode_node ret[], int size, char* error, int error_size); + +} + +// 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], char* error, int error_size) +{ + return dht_detail::verify_message(msg, desc, ret, Size, error, error_size); +} + +} } + +#endif diff --git a/include/libtorrent/kademlia/node.hpp b/include/libtorrent/kademlia/node.hpp new file mode 100644 index 0000000..c55503e --- /dev/null +++ b/include/libtorrent/kademlia/node.hpp @@ -0,0 +1,253 @@ +/* + +Copyright (c) 2006-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 NODE_HPP +#define NODE_HPP + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include // for udp::endpoint +#include +#include +#include +#include + +#include +#include + +#include "libtorrent/socket.hpp" + +namespace libtorrent { + class alert_manager; + struct alert_dispatcher; + class alert; + struct counters; + struct dht_routing_bucket; +} + +namespace libtorrent { namespace dht +{ + +struct traversal_algorithm; +struct dht_observer; + +void TORRENT_EXTRA_EXPORT write_nodes_entry(entry& r, nodes_t const& nodes); + +struct null_type {}; + +class announce_observer : public observer +{ +public: + announce_observer(boost::intrusive_ptr const& algo + , udp::endpoint const& ep, node_id const& id) + : observer(algo, ep, id) + {} + + void reply(msg const&) { flags |= flag_done; } +}; + +struct udp_socket_interface +{ + virtual bool has_quota() = 0; + virtual bool send_packet(entry& e, udp::endpoint const& addr, int flags) = 0; +protected: + ~udp_socket_interface() {} +}; + +class TORRENT_EXTRA_EXPORT node : boost::noncopyable +{ +public: + node(udp_socket_interface* sock + , libtorrent::dht_settings const& settings, node_id nid + , dht_observer* observer, counters& cnt + , dht_storage_constructor_type storage_constructor = dht_default_storage_constructor); + + ~node(); + + void update_node_id(); + + void tick(); + void bootstrap(std::vector const& nodes + , find_data::nodes_callback const& f); + void add_router_node(udp::endpoint router); + + void unreachable(udp::endpoint const& ep); + void incoming(msg const& m); + +#ifndef TORRENT_NO_DEPRECATE + int num_torrents() const { return m_storage->num_torrents(); } + int num_peers() const { return m_storage->num_peers(); } +#endif + + int bucket_size(int bucket); + + node_id const& nid() const { return m_id; } + + boost::tuple size() const { return m_table.size(); } + boost::int64_t num_global_nodes() const + { return m_table.num_global_nodes(); } + +#ifndef TORRENT_NO_DEPRECATE + int data_size() const { return int(m_storage->num_torrents()); } +#endif + +#if defined TORRENT_DEBUG + void print_state(std::ostream& os) const + { m_table.print_state(os); } +#endif + + enum flags_t { flag_seed = 1, flag_implied_port = 2 }; + void get_peers(sha1_hash const& info_hash + , boost::function const&)> dcallback + , boost::function > const&)> ncallback + , bool noseeds); + void announce(sha1_hash const& info_hash, int listen_port, int flags + , boost::function const&)> f); + + void direct_request(udp::endpoint ep, entry& e + , boost::function f); + + void get_item(sha1_hash const& target, boost::function f); + void get_item(char const* pk, std::string const& salt, boost::function f); + + void put_item(sha1_hash const& target, entry const& data, boost::function f); + void put_item(char const* pk, std::string const& salt + , boost::function f + , boost::function data_cb); + + bool verify_token(std::string const& token, char const* info_hash + , udp::endpoint const& addr) const; + + std::string generate_token(udp::endpoint const& addr, char 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 respons and if the + // bucket is not full. + void add_node(udp::endpoint node); + + void replacement_cache(bucket_t& nodes) const + { m_table.replacement_cache(nodes); } + + int branch_factor() const { return m_settings.search_branching; } + + void add_traversal_algorithm(traversal_algorithm* a) + { + mutex_t::scoped_lock l(m_mutex); + m_running_requests.insert(a); + } + + void remove_traversal_algorithm(traversal_algorithm* a) + { + mutex_t::scoped_lock l(m_mutex); + m_running_requests.erase(a); + } + + void status(std::vector& table + , std::vector& requests); + + void update_stats_counters(counters& c) const; + +#ifndef TORRENT_NO_DEPRECATE + void status(libtorrent::session_status& s); +#endif + + libtorrent::dht_settings const& settings() const { return m_settings; } + counters& stats_counters() const { return m_counters; } + + dht_observer* observer() const { return m_observer; } +private: + + void send_single_refresh(udp::endpoint const& ep, int bucket + , node_id const& id = node_id()); + void lookup_peers(sha1_hash const& info_hash, entry& reply + , bool noseed, bool scrape) const; + bool lookup_torrents(sha1_hash const& target, entry& reply + , char* tags) const; + + libtorrent::dht_settings const& m_settings; + + typedef libtorrent::mutex mutex_t; + mutex_t m_mutex; + + // this list must be destructed after the rpc manager + // since it might have references to it + std::set m_running_requests; + + void incoming_request(msg const& h, entry& e); + + node_id m_id; + +public: + routing_table m_table; + rpc_manager m_rpc; + +private: + dht_observer* m_observer; + + 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 + int m_secret[2]; + + udp_socket_interface* m_sock; + counters& m_counters; + + boost::scoped_ptr m_storage; +}; + +} } // namespace libtorrent::dht + +#endif // NODE_HPP diff --git a/include/libtorrent/kademlia/node_entry.hpp b/include/libtorrent/kademlia/node_entry.hpp new file mode 100644 index 0000000..41b7acf --- /dev/null +++ b/include/libtorrent/kademlia/node_entry.hpp @@ -0,0 +1,86 @@ +/* + +Copyright (c) 2006-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 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 + +namespace libtorrent { namespace dht +{ + +struct TORRENT_EXTRA_EXPORT node_entry +{ + node_entry(node_id const& id_, udp::endpoint ep, int roundtriptime = 0xffff + , bool pinged = false); + node_entry(udp::endpoint ep); + node_entry(); + 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 udp::endpoint(address_v4(a), p); } + bool confirmed() const { return timeout_count == 0; } + address addr() const { return address_v4(a); } + int port() const { return p; } + +#ifndef TORRENT_DISABLE_LOGGING + time_point first_seen; +#endif + + // the time we last received a response for a request to this peer + time_point last_queried; + + node_id id; + + address_v4::bytes_type a; + boost::uint16_t p; + + // the average RTT of this node + boost::uint16_t rtt; + + // the number of times this node has failed to + // respond in a row + boost::uint8_t timeout_count; +}; + +} } // namespace libtorrent::dht + +#endif + diff --git a/include/libtorrent/kademlia/node_id.hpp b/include/libtorrent/kademlia/node_id.hpp new file mode 100644 index 0000000..affdba5 --- /dev/null +++ b/include/libtorrent/kademlia/node_id.hpp @@ -0,0 +1,81 @@ +/* + +Copyright (c) 2006-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 NODE_ID_HPP +#define NODE_ID_HPP + +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/config.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/address.hpp" + +namespace libtorrent { namespace dht +{ + +struct node_entry; + +typedef libtorrent::sha1_hash node_id; + +// returns the distance between the two nodes +// using the kademlia XOR-metric +node_id TORRENT_EXTRA_EXPORT distance(node_id const& n1, node_id const& n2); + +// returns true if: distance(n1, ref) < distance(n2, ref) +bool TORRENT_EXTRA_EXPORT 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. +int TORRENT_EXTRA_EXPORT distance_exp(node_id const& n1, node_id const& n2); + +node_id TORRENT_EXTRA_EXPORT generate_id(address const& external_ip); +node_id TORRENT_EXTRA_EXPORT generate_random_id(); +void TORRENT_EXTRA_EXPORT make_id_secret(node_id& in); +node_id TORRENT_EXTRA_EXPORT generate_secret_id(); +bool TORRENT_EXTRA_EXPORT verify_secret_id(node_id const& nid); +node_id TORRENT_EXTRA_EXPORT generate_id_impl(address const& ip_, boost::uint32_t r); + +bool TORRENT_EXTRA_EXPORT verify_id(node_id const& nid, address const& source_ip); +bool TORRENT_EXTRA_EXPORT matching_prefix(node_entry const& n, int mask, int prefix, int bucket_index); +node_id TORRENT_EXTRA_EXPORT generate_prefix_mask(int bits); + +} } // namespace libtorrent::dht + +#endif // NODE_ID_HPP + diff --git a/include/libtorrent/kademlia/observer.hpp b/include/libtorrent/kademlia/observer.hpp new file mode 100644 index 0000000..59089da --- /dev/null +++ b/include/libtorrent/kademlia/observer.hpp @@ -0,0 +1,180 @@ +/* + +Copyright (c) 2007-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 OBSERVER_HPP +#define OBSERVER_HPP + +#include +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { +namespace dht { + +struct dht_observer; +struct observer; +struct msg; +struct traversal_algorithm; + +// defined in rpc_manager.cpp +TORRENT_EXTRA_EXPORT void intrusive_ptr_add_ref(observer const*); +TORRENT_EXTRA_EXPORT void intrusive_ptr_release(observer const*); + +struct TORRENT_EXTRA_EXPORT observer : boost::noncopyable +{ + friend TORRENT_EXTRA_EXPORT void intrusive_ptr_add_ref(observer const*); + friend TORRENT_EXTRA_EXPORT void intrusive_ptr_release(observer const*); + + observer(boost::intrusive_ptr const& a + , udp::endpoint const& ep, node_id const& id) + : m_sent() + , m_algorithm(a) + , m_id(id) + , m_refs(0) + , m_port(0) + , m_transaction_id() + , flags(0) + { + TORRENT_ASSERT(a); +#if defined TORRENT_DEBUG || defined TORRENT_RELEASE_ASSERTS + m_in_constructor = true; + m_was_sent = false; + m_was_abandoned = false; + m_in_use = true; +#endif + set_target(ep); + } + + // 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 (flags & flag_short_timeout) != 0; } + + // 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; } + + void set_transaction_id(boost::uint16_t tid) + { m_transaction_id = tid; } + + boost::uint16_t transaction_id() const + { return m_transaction_id; } + + enum { + flag_queried = 1, + flag_initial = 2, + flag_no_id = 4, + flag_short_timeout = 8, + flag_failed = 16, + flag_ipv6_address = 32, + flag_alive = 64, + flag_done = 128 + }; + +protected: + + void done(); + +private: + + time_point m_sent; + + const boost::intrusive_ptr m_algorithm; + + node_id m_id; + + TORRENT_UNION addr_t + { +#if TORRENT_USE_IPV6 + address_v6::bytes_type v6; +#endif + address_v4::bytes_type v4; + } m_addr; + + // reference counter for intrusive_ptr + mutable boost::uint16_t m_refs; + + boost::uint16_t m_port; + + // the transaction ID for this call + boost::uint16_t m_transaction_id; +public: + unsigned char flags; + +#if defined TORRENT_DEBUG || defined TORRENT_RELEASE_ASSERTS + bool m_in_constructor:1; + bool m_was_sent:1; + bool m_was_abandoned:1; + bool m_in_use:1; +#endif +}; + +typedef boost::intrusive_ptr observer_ptr; + +} } + +#endif + diff --git a/include/libtorrent/kademlia/put_data.hpp b/include/libtorrent/kademlia/put_data.hpp new file mode 100644 index 0000000..1c4ece7 --- /dev/null +++ b/include/libtorrent/kademlia/put_data.hpp @@ -0,0 +1,95 @@ +/* + +Copyright (c) 2006-2016, Arvid Norberg, Thomas Yuan +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PUT_DATA_HPP +#define TORRENT_PUT_DATA_HPP + +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { namespace dht +{ +struct msg; +class node; + +struct put_data: traversal_algorithm +{ + typedef boost::function put_callback; + + put_data(node& node, put_callback const& callback); + + virtual char const* name() const TORRENT_OVERRIDE; + virtual void start() TORRENT_OVERRIDE; + + void set_data(item const& data) { m_data = data; } + + void set_targets(std::vector > const& targets); + +protected: + + virtual void done() TORRENT_OVERRIDE; + virtual bool invoke(observer_ptr o) TORRENT_OVERRIDE; + + put_callback m_put_callback; + item m_data; + bool m_done; +}; + +struct put_data_observer : traversal_observer +{ + put_data_observer( + boost::intrusive_ptr const& algorithm + , udp::endpoint const& ep, node_id const& id, std::string const& token) + : traversal_observer(algorithm, ep, id) + , m_token(token) + { + } + + virtual void reply(msg const&) { done(); } + + std::string m_token; +}; + +} } // namespace libtorrent::dht + +#endif // TORRENT_PUT_DATA_HPP + diff --git a/include/libtorrent/kademlia/refresh.hpp b/include/libtorrent/kademlia/refresh.hpp new file mode 100644 index 0000000..e0d9a46 --- /dev/null +++ b/include/libtorrent/kademlia/refresh.hpp @@ -0,0 +1,71 @@ +/* + +Copyright (c) 2006-2016, Arvid Norberg & Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef REFRESH_050324_HPP +#define REFRESH_050324_HPP + +#include +#include +#include + +namespace libtorrent { namespace dht +{ + +class routing_table; +class rpc_manager; + +class bootstrap : public get_peers +{ +public: + typedef get_peers::nodes_callback done_callback; + + bootstrap(node& dht_node, node_id target + , done_callback const& callback); + virtual char const* name() const; + + observer_ptr new_observer(void* ptr, udp::endpoint const& ep + , node_id const& id); + + void trim_seed_nodes(); + +protected: + + virtual bool invoke(observer_ptr o); + + virtual void done(); + +}; + +} } // namespace libtorrent::dht + +#endif // REFRESH_050324_HPP + diff --git a/include/libtorrent/kademlia/routing_table.hpp b/include/libtorrent/kademlia/routing_table.hpp new file mode 100644 index 0000000..24d8b02 --- /dev/null +++ b/include/libtorrent/kademlia/routing_table.hpp @@ -0,0 +1,270 @@ +/* + +Copyright (c) 2006-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 ROUTING_TABLE_HPP +#define ROUTING_TABLE_HPP + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include +#include +#include +#include +#include + +namespace libtorrent +{ +#ifndef TORRENT_NO_DEPRECATE + struct session_status; +#endif + struct dht_routing_bucket; +} + +namespace libtorrent { namespace dht +{ +struct dht_logger; + +typedef std::vector bucket_t; + +struct routing_table_node +{ + bucket_t replacements; + bucket_t live_nodes; +}; + +// 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). + +namespace impl +{ + template + inline void forwarder(void* userdata, node_entry const& node) + { + F* f = reinterpret_cast(userdata); + (*f)(node); + } +} + +class TORRENT_EXTRA_EXPORT routing_table : boost::noncopyable +{ +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. + typedef std::vector table_t; + + routing_table(node_id const& id, int bucket_size + , dht_settings const& settings + , dht_logger* log); + +#ifndef TORRENT_NO_DEPRECATE + void status(session_status& s) const; +#endif + + void status(std::vector& s) const; + + void node_failed(node_id const& id, udp::endpoint const& ep); + + // adds an endpoint that will never be added to + // the routing table + void add_router_node(udp::endpoint router); + + // iterates over the router nodes added + typedef std::set::const_iterator router_iterator; + router_iterator router_begin() const { return m_router_nodes.begin(); } + router_iterator router_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 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 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 id); + + node_entry const* next_refresh(); + + enum + { + // nodes that have not been pinged are considered failed by this flag + include_failed = 1 + }; + + // fills the vector with the count nodes from our buckets that + // are nearest to the given id. + void find_node(node_id const& id, std::vector& l + , int options, int count = 0); + void remove_node(node_entry* n + , table_t::iterator bucket) ; + + int bucket_size(int bucket) const + { + int num_buckets = 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()); + } + + template + void for_each_node(F f) + { + for_each_node(&impl::forwarder, &impl::forwarder, reinterpret_cast(&f)); + } + + void for_each_node(void (*)(void*, node_entry const&) + , void (*)(void*, node_entry const&), void* userdata) const; + + 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 + boost::tuple size() const; + + boost::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 m_buckets.size(); } + + void replacement_cache(bucket_t& nodes) const; + +#if defined TORRENT_DEBUG + // used for debug and monitoring purposes. This will print out + // the state of the routing table to the given stream + void print_state(std::ostream& os) const; +#endif + + int bucket_limit(int bucket) const; + +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const; +#endif + + bool is_full(int bucket) const; + +private: + +#ifndef TORRENT_DISABLE_LOGGING + dht_logger* m_log; +#endif + + table_t::iterator find_bucket(node_id const& id); + + 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 + node_entry* find_node(udp::endpoint const& ep + , routing_table::table_t::iterator* bucket); + + dht_settings const& m_settings; + + // (k-bucket, replacement cache) pairs + // the first entry is the bucket the furthest + // away from our own ID. Each time the bucket + // closest to us (m_buckets.back()) has more than + // bucket size nodes in it, another bucket is + // added to the end and it's split up between them + table_t m_buckets; + + node_id m_id; // our own node id + + // 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. Currently only for + // IPv4 + boost::unordered_multiset m_ips; + + // constant called k in paper + int m_bucket_size; +}; + +} } // 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..a1ae527 --- /dev/null +++ b/include/libtorrent/kademlia/rpc_manager.hpp @@ -0,0 +1,143 @@ +/* + +Copyright (c) 2006-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 RPC_MANAGER_HPP +#define RPC_MANAGER_HPP + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include +#include +#include + +#if TORRENT_HAS_BOOST_UNORDERED +#include +#else +#include +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include +#include +#include +#include + +#include "libtorrent/time.hpp" + +namespace libtorrent { namespace aux { struct session_impl; } } + +namespace libtorrent { struct dht_settings; } + +namespace libtorrent { namespace dht +{ + +struct dht_logger; +struct udp_socket_interface; + +struct TORRENT_EXTRA_EXPORT null_observer : public observer +{ + null_observer(boost::intrusive_ptr const& a + , udp::endpoint const& ep, node_id const& id): observer(a, ep, id) {} + virtual void reply(msg const&) { flags |= flag_done; } +}; + +class routing_table; + +class TORRENT_EXTRA_EXPORT rpc_manager +{ +public: + + rpc_manager(node_id const& our_id + , dht_settings const& settings + , routing_table& table + , udp_socket_interface* sock + , 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 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 + + void* allocate_observer(); + void free_observer(void* ptr); + + int num_allocated_observers() const { return m_allocated_observers; } + + void update_node_id(node_id const& id) { m_our_id = id; } + +private: + + boost::uint32_t calc_connection_id(udp::endpoint addr); + + mutable boost::pool<> m_pool_allocator; + +#if TORRENT_HAS_BOOST_UNORDERED + typedef boost::unordered_multimap transactions_t; +#else + typedef std::multimap transactions_t; +#endif + transactions_t m_transactions; + + udp_socket_interface* m_sock; + dht_logger* m_log; + dht_settings const& m_settings; + routing_table& m_table; + time_point m_timer; + node_id m_our_id; + boost::uint32_t m_allocated_observers:31; + boost::uint32_t m_destructing:1; +}; + +} } // namespace libtorrent::dht + +#endif + + diff --git a/include/libtorrent/kademlia/traversal_algorithm.hpp b/include/libtorrent/kademlia/traversal_algorithm.hpp new file mode 100644 index 0000000..d048286 --- /dev/null +++ b/include/libtorrent/kademlia/traversal_algorithm.hpp @@ -0,0 +1,146 @@ +/* + +Copyright (c) 2006-2016, Arvid Norberg & Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TRAVERSAL_ALGORITHM_050324_HPP +#define TRAVERSAL_ALGORITHM_050324_HPP + +#include +#include + +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { struct dht_lookup; } +namespace libtorrent { namespace dht +{ + +class rpc_manager; +class node; + +// this class may not be instantiated as a stack object +struct TORRENT_EXTRA_EXPORT traversal_algorithm : boost::noncopyable +{ + void traverse(node_id const& id, udp::endpoint addr); + void finished(observer_ptr o); + + enum flags_t { prevent_request = 1, short_timeout = 2 }; + void failed(observer_ptr o, int flags = 0); + virtual ~traversal_algorithm(); + void status(dht_lookup& l); + + void* allocate_observer(); + void free_observer(void* ptr); + + virtual char const* name() const; + virtual void start(); + + node_id const& target() const { return m_target; } + + void resort_results(); + void add_entry(node_id const& id, udp::endpoint addr, unsigned char flags); + + traversal_algorithm(node & node, node_id target); + int invoke_count() const { return m_invoke_count; } + int branch_factor() const { return m_branch_factor; } + + node& get_node() const { return m_node; } + +protected: + + // 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(void* ptr + , udp::endpoint const& ep, node_id const& id); + + virtual bool invoke(observer_ptr) { return false; } + + friend void intrusive_ptr_add_ref(traversal_algorithm* p) + { + TORRENT_ASSERT(p->m_ref_count < 0xffff); + p->m_ref_count++; + } + + friend void intrusive_ptr_release(traversal_algorithm* p) + { + if (--p->m_ref_count == 0) + delete p; + } + + node & m_node; + std::vector m_results; + node_id const m_target; + boost::uint16_t m_ref_count; + boost::uint16_t m_invoke_count; + boost::uint16_t m_branch_factor; + boost::uint16_t m_responses; + boost::uint16_t m_timeouts; + + // the IP addresses of the nodes in m_results + std::set m_peer4_prefixes; +// no IPv6 support yet anyway +// std::set m_peer6_prefixes; +}; + +struct traversal_observer : observer +{ + traversal_observer( + boost::intrusive_ptr const& algorithm + , udp::endpoint const& ep, node_id const& id) + : observer(algorithm, ep, id) + {} + + // parses out "nodes" and keeps traversing + virtual void reply(msg const&); +}; + +} } // namespace libtorrent::dht + +#endif // TRAVERSAL_ALGORITHM_050324_HPP + diff --git a/include/libtorrent/lazy_entry.hpp b/include/libtorrent/lazy_entry.hpp new file mode 100644 index 0000000..94fdfe4 --- /dev/null +++ b/include/libtorrent/lazy_entry.hpp @@ -0,0 +1,414 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_LAZY_ENTRY_HPP_INCLUDED +#define TORRENT_LAZY_ENTRY_HPP_INCLUDED + +#ifndef TORRENT_NO_DEPRECATE + +#include +#include +#include +#include +#include +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/bdecode.hpp" // for error codes + +namespace libtorrent +{ + struct lazy_entry; + + // This function decodes bencoded_ data. + // + // .. _bencoded: http://wiki.theory.org/index.php/BitTorrentSpecification + // + // The lazy bdecoder and lazy_entry has been deprecated in favour of + // bdecode_node and its corresponding bdecode() function. + // + // *lazy* refers to the fact that it doesn't copy any actual data out of the + // bencoded buffer. It builds a tree of ``lazy_entry`` which has pointers into + // the bencoded buffer. This makes it very fast and efficient. On top of that, + // it is not recursive, which saves a lot of stack space when parsing deeply + // nested trees. However, in order to protect against potential attacks, the + // ``depth_limit`` and ``item_limit`` control how many levels deep the tree is + // allowed to get. With recursive parser, a few thousand levels would be enough + // to exhaust the threads stack and terminate the process. The ``item_limit`` + // protects against very large structures, not necessarily deep. Each bencoded + // item in the structure causes the parser to allocate some amount of memory, + // this memory is constant regardless of how much data actually is stored in + // the item. One potential attack is to create a bencoded list of hundreds of + // thousands empty strings, which would cause the parser to allocate a significant + // amount of memory, perhaps more than is available on the machine, and effectively + // provide a denial of service. The default item limit is set as a reasonable + // upper limit for desktop computers. Very few torrents have more items in them. + // The limit corresponds to about 25 MB, which might be a bit much for embedded + // systems. + // + // ``start`` and ``end`` defines the bencoded buffer to be decoded. ``ret`` is + // the ``lazy_entry`` which is filled in with the whole decoded tree. ``ec`` + // is a reference to an ``error_code`` which is set to describe the error encountered + // in case the function fails. ``error_pos`` is an optional pointer to an int, + // which will be set to the byte offset into the buffer where an error occurred, + // in case the function fails. + TORRENT_DEPRECATED + TORRENT_EXPORT int lazy_bdecode(char const* start, char const* end + , lazy_entry& ret, error_code& ec, int* error_pos = 0 + , int depth_limit = 1000, int item_limit = 1000000); + +#ifndef TORRENT_NO_DEPRECATE + // for backwards compatibility, does not report error code + // deprecated in 0.16 + TORRENT_DEPRECATED + TORRENT_EXPORT int lazy_bdecode(char const* start, char const* end + , lazy_entry& ret, int depth_limit = 1000, int item_limit = 1000000); +#endif + + // this is a string that is not NULL-terminated. Instead it + // comes with a length, specified in bytes. This is particularly + // useful when parsing bencoded structures, because strings are + // not NULL-terminated internally, and requiring NULL termination + // would require copying the string. + // + // see lazy_entry::string_pstr(). + struct TORRENT_EXPORT pascal_string + { + // construct a string pointing to the characters at ``p`` + // of length ``l`` characters. No NULL termination is required. + pascal_string(char const* p, int l): len(l), ptr(p) {} + + // the number of characters in the string. + int len; + + // the pointer to the first character in the string. This is + // not NULL terminated, but instead consult the ``len`` field + // to know how many characters follow. + char const* ptr; + + // lexicographical comparison of strings. Order is consisten + // with memcmp. + bool operator<(pascal_string const& rhs) const + { + return std::memcmp(ptr, rhs.ptr, (std::min)(len, rhs.len)) < 0 + || len < rhs.len; + } + }; + + struct lazy_dict_entry; + + // this object represent a node in a bencoded structure. It is a variant + // type whose concrete type is one of: + // + // 1. dictionary (maps strings -> lazy_entry) + // 2. list (sequence of lazy_entry, i.e. heterogenous) + // 3. integer + // 4. string + // + // There is also a ``none`` type, which is used for uninitialized + // lazy_entries. + struct TORRENT_EXPORT lazy_entry + { + // The different types a lazy_entry can have + enum entry_type_t + { + none_t, dict_t, list_t, string_t, int_t + }; + + // internal + lazy_entry() : m_begin(0), m_len(0), m_size(0), m_type(none_t) + { m_data.start = NULL; } + + // tells you which specific type this lazy entry has. + // See entry_type_t. The type determines which subset of + // member functions are valid to use. + entry_type_t type() const { return entry_type_t(m_type); } + + // start points to the first decimal digit + // length is the number of digits + void construct_int(char const* start, int length) + { + TORRENT_ASSERT(m_type == none_t); + m_type = int_t; + m_data.start = start; + m_size = length; + m_begin = start - 1; // include 'i' + m_len = length + 2; // include 'e' + } + + // requires the type to be an integer. return the integer value + boost::int64_t int_value() const; + + // internal + void construct_string(char const* start, int length); + + // the string is not null-terminated! + // use string_length() to determine how many bytes + // are part of the string. + char const* string_ptr() const + { + TORRENT_ASSERT(m_type == string_t); + return m_data.start; + } + + // this will return a null terminated string + // it will write to the source buffer! + char const* string_cstr() const + { + TORRENT_ASSERT(m_type == string_t); + const_cast(m_data.start)[m_size] = 0; + return m_data.start; + } + + // if this is a string, returns a pascal_string + // representing the string value. + pascal_string string_pstr() const + { + TORRENT_ASSERT(m_type == string_t); + return pascal_string(m_data.start, m_size); + } + + // if this is a string, returns the string as a std::string. + // (which requires a copy) + std::string string_value() const + { + TORRENT_ASSERT(m_type == string_t); + return std::string(m_data.start, m_size); + } + + // if the lazy_entry is a string, returns the + // length of the string, in bytes. + int string_length() const + { return m_size; } + + // internal + void construct_dict(char const* begin) + { + TORRENT_ASSERT(m_type == none_t); + m_type = dict_t; + m_size = 0; + m_begin = begin; + } + + // internal + lazy_entry* dict_append(char const* name); + // internal + void pop(); + + // if this is a dictionary, look for a key ``name``, and return + // a pointer to its value, or NULL if there is none. + lazy_entry* dict_find(char const* name); + lazy_entry const* dict_find(char const* name) const + { return const_cast(this)->dict_find(name); } + lazy_entry* dict_find(std::string const& name); + lazy_entry const* dict_find(std::string const& name) const + { return const_cast(this)->dict_find(name); } + lazy_entry const* dict_find_string(char const* name) const; + + // if this is a dictionary, look for a key ``name`` whose value + // is a string. If such key exist, return a pointer to + // its value, otherwise NULL. + std::string dict_find_string_value(char const* name) const; + pascal_string dict_find_pstr(char const* name) const; + + // if this is a dictionary, look for a key ``name`` whose value + // is an int. If such key exist, return a pointer to its value, + // otherwise NULL. + boost::int64_t dict_find_int_value(char const* name + , boost::int64_t default_val = 0) const; + lazy_entry const* dict_find_int(char const* name) const; + + // these functions require that ``this`` is a dictionary. + // (this->type() == dict_t). They look for an element with the + // specified name in the dictionary. ``dict_find_dict`` only + // finds dictionaries and ``dict_find_list`` only finds lists. + // if no key with the corresponding value of the right type is + // found, NULL is returned. + lazy_entry const* dict_find_dict(char const* name) const; + lazy_entry const* dict_find_dict(std::string const& name) const; + lazy_entry const* dict_find_list(char const* name) const; + + // if this is a dictionary, return the key value pair at + // position ``i`` from the dictionary. + std::pair dict_at(int i) const; + + // requires that ``this`` is a dictionary. return the + // number of items in it + int dict_size() const + { + TORRENT_ASSERT(m_type == dict_t); + return m_size; + } + + // internal + void construct_list(char const* begin) + { + TORRENT_ASSERT(m_type == none_t); + m_type = list_t; + m_size = 0; + m_begin = begin; + } + + // internal + lazy_entry* list_append(); + + // requires that ``this`` is a list. return + // the item at index ``i``. + lazy_entry* list_at(int i) + { + TORRENT_ASSERT(m_type == list_t); + TORRENT_ASSERT(i < int(m_size)); + return &m_data.list[i+1]; + } + lazy_entry const* list_at(int i) const + { return const_cast(this)->list_at(i); } + + // these functions require ``this`` to have the type list. + // (this->type() == list_t). ``list_string_value_at`` returns + // the string at index ``i``. ``list_pstr_at`` + // returns a pascal_string of the string value at index ``i``. + // if the element at ``i`` is not a string, an empty string + // is returned. + std::string list_string_value_at(int i) const; + pascal_string list_pstr_at(int i) const; + + // this function require ``this`` to have the type list. + // (this->type() == list_t). returns the integer value at + // index ``i``. If the element at ``i`` is not an integer + // ``default_val`` is returned, which defaults to 0. + boost::int64_t list_int_value_at(int i, boost::int64_t default_val = 0) const; + + // if this is a list, return the number of items in it. + int list_size() const + { + TORRENT_ASSERT(m_type == list_t); + return int(m_size); + } + + // internal: end points one byte passed last byte in the source + // buffer backing the bencoded structure. + void set_end(char const* end) + { + TORRENT_ASSERT(end > m_begin); + TORRENT_ASSERT(end - m_begin < INT_MAX); + m_len = int(end - m_begin); + } + + // internal + void clear(); + + // internal: releases ownership of any memory allocated + void release() + { + m_data.start = NULL; + m_size = 0; + m_type = none_t; + } + + // internal + ~lazy_entry() + { clear(); } + + // returns pointers into the source buffer where + // this entry has its bencoded data + std::pair data_section() const; + + // swap values of ``this`` and ``e``. + void swap(lazy_entry& e) + { + using std::swap; + boost::uint32_t tmp = e.m_type; + e.m_type = m_type; + m_type = tmp; + tmp = e.m_size; + e.m_size = m_size; + m_size = tmp; + swap(m_data.start, e.m_data.start); + swap(m_begin, e.m_begin); + swap(m_len, e.m_len); + } + + private: + + int capacity() const; + + union data_t + { + // for the dict and list arrays, the first item is not part + // of the array. Instead its m_len member indicates the capacity + // of the allocation + lazy_dict_entry* dict; + lazy_entry* list; + char const* start; + } m_data; + + // used for dictionaries and lists to record the range + // in the original buffer they are based on + char const* m_begin; + + // the number of bytes this entry extends in the + // bencoded buffer + boost::uint32_t m_len; + + // if list or dictionary, the number of items + boost::uint32_t m_size:29; + // element type (dict, list, int, string) + boost::uint32_t m_type:3; + + // non-copyable + lazy_entry(lazy_entry const&); + lazy_entry const& operator=(lazy_entry const&); + }; + + struct lazy_dict_entry + { + char const* name; + lazy_entry val; + }; + + // print the bencoded structure in a human-readable format to a string + // that's returned. + TORRENT_DEPRECATED + TORRENT_EXPORT std::string print_entry(lazy_entry const& e + , bool single_line = false, int indent = 0); + + // defined in bdecode.cpp + TORRENT_EXTRA_EXPORT char const* parse_int(char const* start + , char const* end, char delimiter, boost::int64_t& val + , bdecode_errors::error_code_enum& ec); + +} + +#endif // TORRENT_NO_DEPRECATE + +#endif + diff --git a/include/libtorrent/link.hpp b/include/libtorrent/link.hpp new file mode 100644 index 0000000..3f9eda4 --- /dev/null +++ b/include/libtorrent/link.hpp @@ -0,0 +1,77 @@ +/* + +Copyright (c) 2011-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_LINK_HPP_INCLUDED +#define TORRENT_LINK_HPP_INCLUDED + +namespace libtorrent +{ + 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(std::vector& list, int link_index) + { + if (index == -1) return; + TORRENT_ASSERT(index >= 0 && index < int(list.size())); + int 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(std::vector& list, T* self) + { + if (index >= 0) return; + TORRENT_ASSERT(index == -1); + index = int(list.size()); + list.push_back(self); + } + }; +} + +#endif + diff --git a/include/libtorrent/linked_list.hpp b/include/libtorrent/linked_list.hpp new file mode 100644 index 0000000..10f9390 --- /dev/null +++ b/include/libtorrent/linked_list.hpp @@ -0,0 +1,158 @@ +/* + +Copyright (c) 2012-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_LINKED_LIST_HPP +#define TORRENT_LINKED_LIST_HPP + +#include "libtorrent/assert.hpp" + +namespace libtorrent +{ + template + struct list_node + { + list_node() : prev(0), next(0) {} + T* prev; + T* next; + }; + + template + struct list_iterator + { + template + friend struct linked_list; + + T const* get() const { return m_current; } + T* get() { return m_current; } + void next() { m_current = m_current->next; } + void prev() { m_current = m_current->prev; } + + private: + list_iterator(T* cur) + : m_current(cur) {} + // the current element + T* m_current; + }; + + // T must derive from list_node. Having an enable_if here would require T + // to be a complete type, which is a bit too restrictive. + template + struct linked_list + { + linked_list(): m_first(NULL), m_last(NULL), m_size(0) {} + + list_iterator iterate() const + { return list_iterator(m_first); } + + void erase(T* e) + { +#if TORRENT_USE_ASSERTS + T* tmp = m_first; + bool found = false; + while (tmp) + { + if (tmp == e) + { + found = true; + break; + } + tmp = tmp->next; + } + TORRENT_ASSERT(found); +#endif + if (e == m_first) + { + TORRENT_ASSERT(e->prev == 0); + m_first = e->next; + } + if (e == m_last) + { + TORRENT_ASSERT(e->next == 0); + m_last = e->prev; + } + if (e->prev) e->prev->next = e->next; + if (e->next) e->next->prev = e->prev; + e->next = 0; + e->prev = 0; + TORRENT_ASSERT(m_size > 0); + --m_size; + TORRENT_ASSERT(m_last == 0 || m_last->next == 0); + } + void push_front(T* e) + { + TORRENT_ASSERT(e->next == 0); + TORRENT_ASSERT(e->prev== 0); + TORRENT_ASSERT(m_last == 0 || m_last->next == 0); + e->prev = 0; + e->next = m_first; + if (m_first) m_first->prev = e; + else m_last = e; + m_first = e; + ++m_size; + } + void push_back(T* e) + { + TORRENT_ASSERT(e->next == 0); + TORRENT_ASSERT(e->prev== 0); + TORRENT_ASSERT(m_last == 0 || m_last->next == 0); + e->prev = m_last; + e->next = 0; + if (m_last) m_last->next = e; + else m_first = e; + m_last = e; + ++m_size; + } + T* get_all() + { + TORRENT_ASSERT(m_last == 0 || m_last->next == 0); + TORRENT_ASSERT(m_first == 0 || m_first->prev == 0); + T* e = m_first; + m_first = 0; + m_last = 0; + m_size = 0; + return e; + } + T* back() { return m_last; } + T* front() { return m_first; } + T const* back() const { return m_last; } + T const* front() const { return m_first; } + int size() const { return m_size; } + bool empty() const { return m_size == 0; } + private: + T* m_first; + T* m_last; + int m_size; + }; +} + +#endif // LINKED_LIST_HPP + diff --git a/include/libtorrent/lsd.hpp b/include/libtorrent/lsd.hpp new file mode 100644 index 0000000..e1e9738 --- /dev/null +++ b/include/libtorrent/lsd.hpp @@ -0,0 +1,122 @@ +/* + +Copyright (c) 2007-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_LSD_HPP +#define TORRENT_LSD_HPP + +#include "libtorrent/socket.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/broadcast_socket.hpp" +#include "libtorrent/deadline_timer.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include + +#ifndef TORRENT_DISABLE_LOGGING +#include +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent +{ + +typedef boost::function peer_callback_t; +#ifndef TORRENT_DISABLE_LOGGING +typedef boost::function log_callback_t; +#endif + +class lsd : public boost::enable_shared_from_this +{ +public: + lsd(io_service& ios, peer_callback_t const& cb +#ifndef TORRENT_DISABLE_LOGGING + , log_callback_t const& log +#endif + ); + ~lsd(); + + void start(error_code& ec); + + void announce(sha1_hash const& ih, int listen_port, bool broadcast = false); + void close(); + +private: + + boost::shared_ptr self() { return shared_from_this(); } + + void announce_impl(sha1_hash const& ih, int listen_port + , bool broadcast, int retry_count); + void resend_announce(error_code const& e, sha1_hash const& ih + , int listen_port, int retry_count); + void on_announce(udp::endpoint const& from, char* buffer + , std::size_t bytes_transferred); + + peer_callback_t m_callback; + + // the udp socket used to send and receive + // multicast messages on + broadcast_socket m_socket; +#if TORRENT_USE_IPV6 + broadcast_socket m_socket6; +#endif +#ifndef TORRENT_DISABLE_LOGGING + log_callback_t m_log_cb; + 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; +#if TORRENT_USE_IPV6 + bool m_disabled6; +#endif +}; + +} + + +#endif + diff --git a/include/libtorrent/magnet_uri.hpp b/include/libtorrent/magnet_uri.hpp new file mode 100644 index 0000000..9b0f7b6 --- /dev/null +++ b/include/libtorrent/magnet_uri.hpp @@ -0,0 +1,87 @@ +/* + +Copyright (c) 2007-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_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" + +namespace libtorrent +{ + struct torrent_handle; + class session; + + // Generates a magnet URI from the specified torrent. If the torrent + // handle is invalid, an empty string is returned. + // + // For more information about magnet links, see magnet-links_. + // + std::string TORRENT_EXPORT make_magnet_uri(torrent_handle const& handle); + std::string TORRENT_EXPORT make_magnet_uri(torrent_info const& info); + +#ifndef TORRENT_NO_DEPRECATE +#ifndef BOOST_NO_EXCEPTIONS + // deprecated in 0.14 + TORRENT_DEPRECATED + torrent_handle TORRENT_EXPORT add_magnet_uri(session& ses, std::string const& uri + , std::string const& save_path + , storage_mode_t storage_mode = storage_mode_sparse + , bool paused = false + , storage_constructor_type sc = default_storage_constructor + , void* userdata = 0); + + // deprecated in 0.16. Instead, pass in the magnet link as add_torrent_params::url + TORRENT_DEPRECATED + torrent_handle TORRENT_EXPORT 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 + torrent_handle TORRENT_EXPORT add_magnet_uri(session& ses, std::string const& uri + , add_torrent_params const& p, error_code& ec); + +#endif + + // This function parses out information from the magnet link and populates the + // add_torrent_params object. + TORRENT_EXPORT void parse_magnet_uri(std::string const& uri, add_torrent_params& p, error_code& ec); + + // internal, delete when merge in master + TORRENT_EXTRA_EXPORT void parse_magnet_uri_peers(std::string const& uri, std::vector& peers); +} + +#endif + diff --git a/include/libtorrent/max.hpp b/include/libtorrent/max.hpp new file mode 100644 index 0000000..5ac1ab1 --- /dev/null +++ b/include/libtorrent/max.hpp @@ -0,0 +1,126 @@ +/* + +Copyright (c) 2009-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_MAX_TYPE +#define TORRENT_MAX_TYPE + +namespace libtorrent +{ + + template + struct min { enum { value = v1 + struct max { enum { value = v1>v2?v1:v2 }; }; + + template + struct max3 + { + enum + { + temp = max::value, + value = max::value + }; + }; + + template + struct max4 + { + enum + { + temp1 = max::value, + temp2 = max::value, + value = max::value + }; + }; + + template + struct max5 + { + enum + { + temp = max4::value, + value = max::value + }; + }; + + template + struct max6 + { + enum + { + temp1 = max::value, + temp2 = max::value, + temp3 = max::value, + value = max3::value + }; + }; + + template + struct max7 + { + enum + { + temp1 = max::value, + temp2 = max::value, + temp3 = max3::value, + value = max3::value + }; + }; + + template + struct max8 + { + enum + { + temp1 = max::value, + temp2 = max3::value, + temp3 = max3::value, + value = max3::value + }; + }; + + template + struct max9 + { + enum + { + temp1 = max3::value, + temp2 = max3::value, + temp3 = max3::value, + value = max3::value + }; + }; +} + +#endif + diff --git a/include/libtorrent/natpmp.hpp b/include/libtorrent/natpmp.hpp new file mode 100644 index 0000000..cc1a2a7 --- /dev/null +++ b/include/libtorrent/natpmp.hpp @@ -0,0 +1,182 @@ +/* + +Copyright (c) 2007-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_NATPMP_HPP +#define TORRENT_NATPMP_HPP + +#include "libtorrent/io_service_fwd.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/thread.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/deadline_timer.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent +{ + +// int: port mapping index +// int: external port +// std::string: error message +typedef boost::function portmap_callback_t; +typedef boost::function log_callback_t; + +class natpmp : public boost::enable_shared_from_this +{ +public: + natpmp(io_service& ios, portmap_callback_t const& cb + , log_callback_t const& lcb); + + void start(); + + // maps the ports, if a port is set to 0 + // it will not be mapped + enum protocol_type { none = 0, udp = 1, tcp = 2 }; + int add_mapping(protocol_type p, int external_port, int local_port); + void delete_mapping(int mapping_index); + bool get_mapping(int mapping_index, int& local_port, int& external_port, int& protocol) const; + + void close(); + +private: + + boost::shared_ptr self() { return shared_from_this(); } + + void update_mapping(int i, mutex::scoped_lock& l); + void send_map_request(int i, mutex::scoped_lock& l); + void send_get_ip_address_request(mutex::scoped_lock& l); + void resend_request(int i, error_code const& e); + void on_reply(error_code const& e + , std::size_t bytes_transferred); + void try_next_mapping(int i, mutex::scoped_lock& l); + void update_expiration_timer(mutex::scoped_lock& l); + void mapping_expired(error_code const& e, int i); + void close_impl(mutex::scoped_lock& l); + + void log(char const* msg, mutex::scoped_lock& l); + void disable(error_code const& ec, mutex::scoped_lock& l); + + struct mapping_t + { + enum action_t { action_none, action_add, action_delete }; + mapping_t() + : action(action_none) + , local_port(0) + , external_port(0) + , protocol(none) + , map_sent(false) + , outstanding_request(false) + {} + + // indicates that the mapping has changed + // and needs an update + int action; + + // the time the port mapping will expire + time_point expires; + + // the local port for this mapping. If this is set + // to 0, the mapping is not in use + int local_port; + + // the external (on the NAT router) port + // for the mapping. This is the port we + // should announce to others + int external_port; + + int protocol; + + // set to true when the first map request is sent + bool map_sent; + + // set to true while we're waiting for a response + bool outstanding_request; + }; + + portmap_callback_t m_callback; + log_callback_t m_log_callback; + + std::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 + int m_currently_mapping; + + // current retry count + int m_retry_count; + + // used to receive responses in + char m_response_buffer[16]; + + // router external IP address + 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 + int m_next_refresh; + + bool m_disabled; + + bool m_abort; + + mutable mutex m_mutex; +}; + +} + + +#endif + diff --git a/include/libtorrent/network_thread_pool.hpp b/include/libtorrent/network_thread_pool.hpp new file mode 100644 index 0000000..23af939 --- /dev/null +++ b/include/libtorrent/network_thread_pool.hpp @@ -0,0 +1,83 @@ +/* + +Copyright (c) 2011-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_NETWORK_THREAD_POOL_HPP_INCLUDED +#define TORRENT_NETWORK_THREAD_POOL_HPP_INCLUDED + +#include "libtorrent/thread_pool.hpp" +#include +#include + +namespace libtorrent +{ + + class peer_connection; + class buffer; + + struct socket_job + { + socket_job() : type(none), vec(NULL), recv_buf(NULL), buf_size(0) {} +#if __cplusplus >= 201103L + socket_job(socket_job const&) = default; + socket_job& operator=(socket_job const&) = default; +#endif + + enum job_type_t + { + read_job = 0, + write_job, + none + }; + + job_type_t type; + + // used for write jobs + std::vector const* vec; + // used for read jobs + char* recv_buf; + int buf_size; + boost::array read_vec; + + boost::shared_ptr peer; + // defined in session_impl.cpp + ~socket_job(); + }; + + // defined in session_impl.cpp + struct network_thread_pool : thread_pool + { + void process_job(socket_job const& j, bool post); + }; +} + +#endif // TORRENT_NETWORK_THREAD_POOL_HPP_INCLUDED + diff --git a/include/libtorrent/operations.hpp b/include/libtorrent/operations.hpp new file mode 100644 index 0000000..524b664 --- /dev/null +++ b/include/libtorrent/operations.hpp @@ -0,0 +1,105 @@ +/* + +Copyright (c) 2015-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + + +#ifndef TORRENT_OPERATIONS_HPP_INCLUDED +#define TORRENT_OPERATIONS_HPP_INCLUDED + +namespace libtorrent +{ + // these constants are used to identify the operation that failed, causing a + // peer to disconnect + enum operation_t + { + // this is used when the bittorrent logic + // determines to disconnect + op_bittorrent = 0, + + // a call to iocontrol failed + op_iocontrol, + + // a call to getpeername failed (querying the remote IP of a + // connection) + op_getpeername, + + // a call to getname failed (querying the local IP of a + // connection) + op_getname, + + // an attempt to allocate a receive buffer failed + op_alloc_recvbuf, + + // an attempt to allocate a send buffer failed + op_alloc_sndbuf, + + // writing to a file failed + op_file_write, + + // reading from a file failed + op_file_read, + + // a non-read and non-write file operation failed + op_file, + + // a socket write operation failed + op_sock_write, + + // a socket read operation failed + op_sock_read, + + // a call to open(), to create a socket socket failed + op_sock_open, + + // a call to bind() on a socket failed + op_sock_bind, + + // an attempt to query the number of bytes available to read from a socket + // failed + op_available, + + // a call related to bittorrent protocol encryption failed + op_encryption, + + // an attempt to connect a socket failed + op_connect, + + // establishing an SSL connection failed + op_ssl_handshake, + + // a connection failed to satisfy the bind interface setting + op_get_interface + }; + +} + +#endif // TORRENT_OPERATIONS_HPP_INCLUDED + diff --git a/include/libtorrent/packet_buffer.hpp b/include/libtorrent/packet_buffer.hpp new file mode 100644 index 0000000..e713a71 --- /dev/null +++ b/include/libtorrent/packet_buffer.hpp @@ -0,0 +1,142 @@ +/* + +Copyright (c) 2010-2016, Arvid Norberg, Daniel Wallin. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PACKET_BUFFER_HPP_INCLUDED +#define TORRENT_PACKET_BUFFER_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "boost/cstdint.hpp" +#include + +namespace libtorrent +{ + // 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_impl + { + public: + typedef boost::uint32_t index_type; + + packet_buffer_impl(); + ~packet_buffer_impl(); + + void* insert(index_type idx, void* value); + + std::size_t size() const + { return m_size; } + + std::size_t capacity() const + { return m_capacity; } + + void* at(index_type idx) const; + + void* remove(index_type idx); + + void reserve(std::size_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: + void** m_storage; + std::size_t m_capacity; + + // this is the total number of elements that are occupied + // in the array + std::size_t m_size; + + // This defines the first index that is part of the m_storage. + // last is one passed the last used slot + index_type m_first; + index_type m_last; + }; + + template + class packet_buffer : packet_buffer_impl + { + public: + + using packet_buffer_impl::index_type; + using packet_buffer_impl::size; + using packet_buffer_impl::capacity; + using packet_buffer_impl::reserve; + using packet_buffer_impl::cursor; + using packet_buffer_impl::span; + + T* insert(index_type i, T* p) + { + return static_cast(packet_buffer_impl::insert(i, p)); + } + + T* at(index_type idx) const + { return static_cast(packet_buffer_impl::at(idx)); } + + T* remove(index_type idx) + { return static_cast(packet_buffer_impl::remove(idx)); } + }; + +} + +#endif // TORRENT_PACKET_BUFFER_HPP_INCLUDED + diff --git a/include/libtorrent/parse_url.hpp b/include/libtorrent/parse_url.hpp new file mode 100644 index 0000000..902de84 --- /dev/null +++ b/include/libtorrent/parse_url.hpp @@ -0,0 +1,57 @@ +/* + +Copyright (c) 2008-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PARSE_URL_HPP_INCLUDED +#define TORRENT_PARSE_URL_HPP_INCLUDED + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include +#include "libtorrent/config.hpp" +#include "libtorrent/error_code.hpp" + +namespace libtorrent +{ + + // returns protocol, auth, hostname, port, path + TORRENT_EXTRA_EXPORT boost::tuple + parse_url_components(std::string url, error_code& ec); + +} + +#endif + diff --git a/include/libtorrent/part_file.hpp b/include/libtorrent/part_file.hpp new file mode 100644 index 0000000..87c8e22 --- /dev/null +++ b/include/libtorrent/part_file.hpp @@ -0,0 +1,118 @@ +/* + +Copyright (c) 2012-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/config.hpp" +#include "libtorrent/file.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/thread.hpp" // for mutex + +namespace libtorrent +{ + struct TORRENT_EXTRA_EXPORT part_file + { + // create a part file at 'path', that can hold 'num_pieces' pieces. + // each piece being 'piece_size' number of bytes + part_file(std::string const& path, std::string const& name, int num_pieces, int piece_size); + ~part_file(); + + int writev(file::iovec_t const* bufs, int num_bufs, int piece, int offset, error_code& ec); + int readv(file::iovec_t const* bufs, int num_bufs, int 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(int piece); + + void move_partfile(std::string const& path, error_code& ec); + + void import_file(file& f, boost::int64_t offset, boost::int64_t size, error_code& ec); + void export_file(file& f, boost::int64_t offset, boost::int64_t size, error_code& ec); + + // flush the metadata + void flush_metadata(error_code& ec); + + private: + + void open_file(int mode, error_code& ec); + void flush_metadata_impl(error_code& ec); + + std::string m_path; + std::string m_name; + + // allocate a slot and return the slot index + int allocate_slot(int 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 + 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 + int m_num_allocated; + + // the max number of pieces in the torrent this part file is + // backing + int m_max_pieces; + + // number of bytes each piece contains + int 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 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; + + // maps a piece index to the part-file slot it is stored in + boost::unordered_map m_piece_map; + + // this is the file handle to the part file + file m_file; + }; +} + diff --git a/include/libtorrent/pe_crypto.hpp b/include/libtorrent/pe_crypto.hpp new file mode 100644 index 0000000..5cf7e4a --- /dev/null +++ b/include/libtorrent/pe_crypto.hpp @@ -0,0 +1,152 @@ +/* + +Copyright (c) 2007-2016, Un Shyam & Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + +#ifndef TORRENT_PE_CRYPTO_HPP_INCLUDED +#define TORRENT_PE_CRYPTO_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +// RC4 state from libtomcrypt +struct rc4 { + int x, y; + unsigned char buf[256]; +}; + +void TORRENT_EXTRA_EXPORT rc4_init(const unsigned char* in, unsigned long len, rc4 *state); +unsigned long TORRENT_EXTRA_EXPORT rc4_encrypt(unsigned char *out, unsigned long outlen, rc4 *state); + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/receive_buffer.hpp" +#include "libtorrent/peer_id.hpp" // For sha1_hash +#include "libtorrent/extensions.hpp" +#include "libtorrent/assert.hpp" + +#include + +namespace libtorrent +{ + class TORRENT_EXTRA_EXPORT dh_key_exchange + { + public: + dh_key_exchange(); + bool good() const { return true; } + + // Get local public key, always 96 bytes + char const* get_local_key() const; + + // read remote_pubkey, generate and store shared secret in + // m_dh_shared_secret. + int compute_secret(const char* remote_pubkey); + + char const* get_secret() const { return m_dh_shared_secret; } + + sha1_hash const& get_hash_xor_mask() const { return m_xor_mask; } + + private: + + int get_local_key_size() const + { return sizeof(m_dh_local_key); } + + char m_dh_local_key[96]; + char m_dh_local_secret[96]; + char m_dh_shared_secret[96]; + sha1_hash m_xor_mask; + }; + + struct encryption_handler + { + int encrypt(std::vector& iovec); + int decrypt(crypto_receive_buffer& recv_buffer, std::size_t& bytes_transferred); + + bool switch_send_crypto(boost::shared_ptr crypto + , int pending_encryption); + + void switch_recv_crypto(boost::shared_ptr crypto + , crypto_receive_buffer& recv_buffer); + + bool is_send_plaintext() const + { + return m_send_barriers.empty() || m_send_barriers.back().next != INT_MAX; + } + + bool is_recv_plaintext() const + { + return m_dec_handler.get() == NULL; + } + + private: + struct barrier + { + barrier(boost::shared_ptr plugin, int n) + : enc_handler(plugin), next(n) {} + boost::shared_ptr enc_handler; + // number of bytes to next barrier + int next; + }; + std::list m_send_barriers; + boost::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(unsigned char const* key, int len) TORRENT_OVERRIDE; + void set_outgoing_key(unsigned char const* key, int len) TORRENT_OVERRIDE; + + int encrypt(std::vector& buf) TORRENT_OVERRIDE; + void decrypt(std::vector& buf + , int& consume + , int& produce + , int& packet_size) TORRENT_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_PE_CRYPTO_HPP_INCLUDED +#endif // TORRENT_DISABLE_ENCRYPTION + diff --git a/include/libtorrent/peer.hpp b/include/libtorrent/peer.hpp new file mode 100644 index 0000000..defdc3f --- /dev/null +++ b/include/libtorrent/peer.hpp @@ -0,0 +1,74 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_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; + boost::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; + boost::uint16_t port; + }; + +#if TORRENT_USE_IPV6 + struct ipv6_peer_entry + { + address_v6::bytes_type ip; + boost::uint16_t port; + }; +#endif + +} + +#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..a7bc61c --- /dev/null +++ b/include/libtorrent/peer_class.hpp @@ -0,0 +1,149 @@ +/* + +Copyright (c) 2011-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_PEER_CLASS_HPP_INCLUDED +#define TORRENT_PEER_CLASS_HPP_INCLUDED + +#include "libtorrent/bandwidth_limit.hpp" +#include "libtorrent/assert.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent +{ + typedef boost::uint8_t peer_class_t; + + struct 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 : boost::enable_shared_from_this + { + friend struct peer_class_pool; + + peer_class(std::string const& l) + : ignore_unchoke_slots(false) + , connection_limit_factor(100) + , label(l) + , references(1) + { + priority[0] = 1; + priority[1] = 1; + } + + void set_info(peer_class_info const* pci); + void get_info(peer_class_info* pci) const; + + void set_upload_limit(int limit); + void set_download_limit(int limit); + + // the bandwidth channels, upload and download + // keeps track of the current quotas + bandwidth_channel channel[2]; + + bool ignore_unchoke_slots; + int connection_limit_factor; + + // priority for bandwidth allocation + // in rate limiter. One for upload and one + // for download + int priority[2]; + + // the name of this peer class + std::string label; + + private: + int references; + + }; + + struct TORRENT_EXTRA_EXPORT peer_class_pool + { + peer_class_t new_peer_class(std::string const& 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 + std::vector > 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..e1e5160 --- /dev/null +++ b/include/libtorrent/peer_class_set.hpp @@ -0,0 +1,70 @@ +/* + +Copyright (c) 2011-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_PEER_CLASS_SET_HPP_INCLUDED +#define TORRENT_PEER_CLASS_SET_HPP_INCLUDED + +#include "libtorrent/peer_class.hpp" +#include + +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 + boost::uint8_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 + boost::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..3f45097 --- /dev/null +++ b/include/libtorrent/peer_class_type_filter.hpp @@ -0,0 +1,141 @@ +/* + +Copyright (c) 2012-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_PEER_CLASS_TYPE_FILTER_HPP_INCLUDED +#define TORRENT_PEER_CLASS_TYPE_FILTER_HPP_INCLUDED + +#include // for memset +#include + +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 peer_class_type_filter + { + peer_class_type_filter() + { + memset(m_peer_class_type_mask, 0xff, sizeof(m_peer_class_type_mask)); + memset(m_peer_class_type, 0, sizeof(m_peer_class_type)); + } + + enum socket_type_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 st, int peer_class) + { + TORRENT_ASSERT(peer_class >= 0); + TORRENT_ASSERT(peer_class < 32); + if (peer_class < 0 || peer_class > 31) return; + + TORRENT_ASSERT(st < num_socket_types && st >= 0); + if (st < 0 || st >= num_socket_types) return; + m_peer_class_type[st] |= 1 << peer_class; + } + void remove(socket_type_t st, int peer_class) + { + TORRENT_ASSERT(peer_class >= 0); + TORRENT_ASSERT(peer_class < 32); + if (peer_class < 0 || peer_class > 31) return; + + TORRENT_ASSERT(st < num_socket_types && st >= 0); + if (st < 0 || st >= num_socket_types) return; + m_peer_class_type[st] &= ~(1 << 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 st, int peer_class) + { + TORRENT_ASSERT(peer_class >= 0); + TORRENT_ASSERT(peer_class < 32); + if (peer_class < 0 || peer_class > 31) return; + + TORRENT_ASSERT(st < num_socket_types && st >= 0); + if (st < 0 || st >= num_socket_types) return; + m_peer_class_type_mask[st] &= ~(1 << peer_class); + } + void allow(socket_type_t st, int peer_class) + { + TORRENT_ASSERT(peer_class >= 0); + TORRENT_ASSERT(peer_class < 32); + if (peer_class < 0 || peer_class > 31) return; + + TORRENT_ASSERT(st < num_socket_types && st >= 0); + if (st < 0 || st >= num_socket_types) return; + m_peer_class_type_mask[st] |= 1 << 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``). + boost::uint32_t apply(int st, boost::uint32_t peer_class_mask) + { + TORRENT_ASSERT(st < num_socket_types && st >= 0); + if (st < 0 || 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; + } + + private: + // maps socket type to a bitmask that's used to filter out + // (mask) bits from the m_peer_class_filter. + boost::uint32_t m_peer_class_type_mask[5]; + // peer class bitfield added based on socket type + boost::uint32_t m_peer_class_type[5]; + }; + +} + +#endif + diff --git a/include/libtorrent/peer_connection.hpp b/include/libtorrent/peer_connection.hpp new file mode 100644 index 0000000..aee0160 --- /dev/null +++ b/include/libtorrent/peer_connection.hpp @@ -0,0 +1,1294 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEER_CONNECTION_HPP_INCLUDED +#define TORRENT_PEER_CONNECTION_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/buffer.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/stat.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/peer_request.hpp" +#include "libtorrent/piece_block_progress.hpp" +#include "libtorrent/bandwidth_limit.hpp" +#include "libtorrent/socket_type_fwd.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/chained_buffer.hpp" +#include "libtorrent/disk_buffer_holder.hpp" +#include "libtorrent/bitfield.hpp" +#include "libtorrent/bandwidth_socket.hpp" +#include "libtorrent/socket_type_fwd.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/piece_picker.hpp" // for piece_block +#include "libtorrent/socket.hpp" // for tcp::endpoint +#include "libtorrent/io_service_fwd.hpp" +#include "libtorrent/receive_buffer.hpp" +#include "libtorrent/aux_/allocating_handler.hpp" + +#ifndef TORRENT_DISABLE_LOGGING +#include "libtorrent/debug.hpp" +#endif + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include +#include // for std::forward + +#include +#include +#include +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent +{ + class torrent; + struct peer_info; + struct disk_io_job; + struct disk_interface; + struct torrent_peer; + +#ifndef TORRENT_DISABLE_EXTENSIONS + struct peer_plugin; +#endif + + namespace aux + { + struct session_interface; + } + + struct pending_block + { + pending_block(piece_block const& b) + : block(b), send_buffer_offset(not_in_buffer), not_wanted(false) + , timed_out(false), busy(false) + {} + + piece_block block; + + enum { 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. + boost::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 + boost::uint32_t not_wanted:1; + boost::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 + boost::uint32_t busy:1; + + bool operator==(pending_block const& b) + { + return b.block == block + && b.not_wanted == not_wanted + && b.timed_out == timed_out; + } + }; + + struct has_block + { +#if __cplusplus >= 201103L + has_block(has_block const&) = default; +#endif + + has_block(piece_block const& b): block(b) {} + piece_block const& block; + bool operator()(pending_block const& pb) const + { return pb.block == block; } + private: + // explicitly disallow assignment, to silence msvc warning + has_block& operator=(has_block const&); + }; + + // argument pack passed to peer_connection constructor + struct peer_connection_args + { + aux::session_interface* ses; + aux::session_settings const* sett; + counters* stats_counters; + buffer_allocator_interface* allocator; + disk_interface* disk_thread; + io_service* ios; + boost::weak_ptr tor; + boost::shared_ptr s; + tcp::endpoint endp; + torrent_peer* peerinfo; + }; + + // internal + inline void nop(char*, void*, block_cache_reference) {} + + struct TORRENT_EXTRA_EXPORT peer_connection_hot_members + { + // if tor is set, this is an outgoing connection + peer_connection_hot_members( + boost::weak_ptr t + , aux::session_interface& ses + , aux::session_settings const& sett) + : m_torrent(t) + , m_ses(ses) + , m_settings(sett) + , m_disconnecting(false) + , m_connecting(!t.expired()) + , m_endgame_mode(false) + , m_snubbed(false) + , m_interesting(false) + , m_choked(true) + , m_corked(false) + , m_ignore_stats(false) + {} + + protected: + + // the pieces the other end have + 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 + boost::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 peer_connection socket is + // corked, similar to the linux TCP feature TCP_CORK. + // we won't send anything to the actual socket, just + // buffer messages up in the application layer send + // buffer, and send it once we're uncorked. + bool m_corked: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; + private: + // explicitly disallow assignment, to silence msvc warning + peer_connection_hot_members& operator=(peer_connection_hot_members const&); + }; + + class TORRENT_EXTRA_EXPORT peer_connection + : public peer_connection_hot_members + , public bandwidth_socket + , public peer_class_set + , public disk_observer + , public peer_connection_interface + , public boost::enable_shared_from_this + { + friend class invariant_access; + friend struct network_thread_pool; + friend class torrent; + public: + + enum connection_type + { + bittorrent_connection = 0, + url_seed_connection = 1, + http_seed_connection = 2 + }; + + virtual int type() const = 0; + + enum channels + { + upload_channel, + download_channel, + num_channels + }; + + peer_connection(peer_connection_args const& pack); + + // this function is called after it has been constructed and properly + // reference counted. It is safe to call self() in this function + // and schedule events with references to itself (that is not safe to + // do in the constructor). + virtual void start(); + + virtual ~peer_connection(); + + void set_peer_info(torrent_peer* pi) + { + TORRENT_ASSERT(m_peer_info == 0 || pi == 0 ); + TORRENT_ASSERT(pi != NULL || m_disconnect_started); + m_peer_info = pi; + } + + torrent_peer* peer_info_struct() const + { 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(boost::shared_ptr); + peer_plugin const* find_plugin(char const* 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(int o) + { m_picker_options = o; } + + int prefer_contiguous_blocks() const + { + if (on_parole()) return 1; + return m_prefer_contiguous_blocks; + } + + bool on_parole() const; + + int picker_options() const; + + void prefer_contiguous_blocks(int num) + { m_prefer_contiguous_blocks = (std::min)(num, 255); } + + 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; } + + boost::uint32_t peer_rank() const; + + void fast_reconnect(bool r); + bool fast_reconnect() const { return m_fast_reconnect; } + + // this is called when we receive a new piece + // (and it has passed the hash check) + void received_piece(int index); + + // this adds an announcement in the announcement queue + // it will let the peer know that we have the given piece + void announce_piece(int index); + + // this will tell the peer to announce the given piece + // and only allow it to request that piece + void superseed_piece(int replace_piece, int new_piece); + bool super_seeded_piece(int index) const + { + return m_superseed_piece[0] == index + || m_superseed_piece[1] == index; + } + + // 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; } + + void set_share_mode(bool m); + bool share_mode() const { return m_share_mode; } + + void set_upload_only(bool u); + bool upload_only() const { return m_upload_only; } + + void set_holepunch_mode(); + + // will send a keep-alive message to the peer + void keep_alive(); + + peer_id const& pid() const { return m_peer_id; } + void set_pid(const peer_id& peer_id) { m_peer_id = peer_id; } + bool has_piece(int i) const; + + std::vector const& download_queue() const; + std::vector const& request_queue() const; + std::vector const& upload_queue() const; + + void clear_request_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 { 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(); + + virtual void get_peer_info(peer_info& p) const; + + // 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 + boost::weak_ptr associated_torrent() const + { return m_torrent; } + + stat const& statistics() const { return m_statistics; } + void add_stat(boost::int64_t downloaded, boost::int64_t uploaded); + 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); + + void timeout_requests(); + + boost::shared_ptr get_socket() const { return m_socket; } + tcp::endpoint const& remote() const { return m_remote; } + tcp::endpoint local_endpoint() const { return m_local; } + + bitfield const& get_bitfield() const; + std::vector const& allowed_fast(); + std::vector const& suggested_pieces() const { return m_suggested_pieces; } + + time_point connected_time() const { return m_connect; } + time_point last_received() const { return m_last_receive; } + + // this will cause this peer_connection to be disconnected. + virtual void disconnect(error_code const& ec + , operation_t op, int error = 0); + + // called when a connect attempt fails (not when an + // established connection fails) + void connect_failed(error_code const& e); + bool is_disconnecting() const { 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; } + + // This is called for every peer right after the upload + // bandwidth has been distributed among them + // It will reset the used bandwidth to 0. + void reset_upload_quota(); + + // trust management. + virtual void received_valid_data(int index); + // returns false if the peer should not be + // disconnected + virtual bool received_invalid_data(int 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 { 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 { 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(); + + void increase_est_reciprocation_rate(); + void decrease_est_reciprocation_rate(); + int est_reciprocation_rate() const { return m_est_reciprocation_rate; } + +#ifndef TORRENT_DISABLE_LOGGING + void peer_log(peer_log_alert::direction_t direction + , char const* event, char const* fmt, ...) const TORRENT_FORMAT(4,5); + void peer_log(peer_log_alert::direction_t direction + , char const* event) const; +#endif + +#ifndef TORRENT_DISABLE_LOGGING + time_point m_connect_time; + time_point m_bitfield_time; + time_point m_unchoke_time; +#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(int piece_index); + void incoming_dont_have(int piece_index); + void incoming_bitfield(bitfield const& bits); + void incoming_request(peer_request const& r); + void incoming_piece(peer_request const& p, disk_buffer_holder& data); + 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(int index); + void incoming_suggest(int 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(int piece); + + void snub_peer(); + // reject any request in the request + // queue from this piece + void reject_piece(int 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); + + // adds a block to the request queue + // returns true if successful, false otherwise + enum flags_t { req_time_critical = 1, req_busy = 2 }; + bool add_request(piece_block const& b, int flags = 0); + + // 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); + +#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, the boost::optional + // will be invalid. + virtual boost::optional + downloading_piece_progress() const; + + enum message_type_flags { message_type_request = 1 }; + void send_buffer(char const* begin, int size, int flags = 0); + void setup_send(); + + void cork_socket() { TORRENT_ASSERT(!m_corked); m_corked = true; } + bool is_corked() const { return m_corked; } + void uncork_socket(); + + void append_send_buffer(char* buffer, int size + , chained_buffer::free_buffer_fun destructor = &nop + , void* userdata = NULL, block_cache_reference ref + = block_cache_reference()); + + virtual void append_const_send_buffer(char const* buffer, int size + , chained_buffer::free_buffer_fun destructor = &nop + , void* userdata = NULL, block_cache_reference ref + = block_cache_reference()); + +#ifndef TORRENT_NO_DEPRECATE +#ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES + void set_country(char const* c) + { + TORRENT_ASSERT(strlen(c) == 2); + m_country[0] = c[0]; + m_country[1] = c[1]; + } + bool has_country() const { return m_country[0] != 0; } +#endif +#endif // TORRENT_NO_DEPRECATE + + 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; + +#ifdef TORRENT_DEBUG + bool piece_failed; +#endif + + time_t last_seen_complete() const { return m_last_seen_complete; } + void set_last_seen_complete(int ago) { m_last_seen_complete = time(0) - ago; } + + boost::int64_t uploaded_in_last_round() const + { return m_statistics.total_payload_upload() - m_uploaded_at_last_round; } + + boost::int64_t downloaded_in_last_round() const + { return m_statistics.total_payload_download() - m_downloaded_at_last_round; } + + boost::int64_t uploaded_since_unchoked() const + { return m_statistics.total_payload_upload() - m_uploaded_at_last_unchoke; } + + // the time we last unchoked this peer + time_point time_of_last_unchoke() const + { return m_last_unchoke; } + + // called when the disk write buffer is drained again, and we can + // start downloading payload again + void on_disk(); + + int num_reading_bytes() const { return m_reading_bytes; } + + enum sync_t { read_async, read_sync }; + void setup_receive(); + + boost::shared_ptr self() + { + TORRENT_ASSERT(!m_in_constructor); + return shared_from_this(); + } + + counters& stats_counters() const { return m_counters; } + + int get_priority(int channel) const; + + protected: + + size_t try_read(sync_t s, error_code& ec); + + 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(int index) = 0; + virtual void write_dont_have(int index) = 0; + virtual void write_keepalive() = 0; + virtual void write_piece(peer_request const& r, disk_buffer_holder& buffer) = 0; + virtual void write_suggest(int piece) = 0; + virtual void write_bitfield() = 0; + + virtual void write_reject_request(peer_request const& r) = 0; + virtual void write_allow_fast(int piece) = 0; + + virtual void on_connected() = 0; + virtual void on_tick() {} + + 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; + + virtual int hit_send_barrier(std::vector&) + { return INT_MAX; } + + bool allocate_disk_receive_buffer(int disk_buffer_size); + + void attach_to_torrent(sha1_hash const& ih); + + bool verify_piece(peer_request const& p) const; + + void update_desired_queue_size(); + + // called from the main loop when this connection has any + // work to do. + 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); + + // _nb means null_buffers. i.e. we just know the socket is + // readable at this point, we don't know how much has been received + void on_receive_data_nb(error_code const& error + , std::size_t bytes_transferred); + + void receive_data_impl(error_code const& error + , std::size_t bytes_transferred, int read_loops); + + void set_send_barrier(int bytes) + { + TORRENT_ASSERT(bytes == INT_MAX || bytes <= send_buffer_size()); + m_send_barrier = bytes; + } + + int get_send_barrier() const { return m_send_barrier; } + + virtual int timeout() const; + + io_service& get_io_service() { return m_ios; } + + private: + // explicitly disallow assignment, to silence msvc warning + peer_connection& operator=(peer_connection const&); + + void do_update_interest(); + int preferred_caching() const; + void fill_send_buffer(); + void on_disk_read_complete(disk_io_job const* j, peer_request r + , time_point issue_time); + void on_disk_write_complete(disk_io_job const* j + , peer_request r, boost::shared_ptr t); + void on_seed_mode_hashed(disk_io_job const* j); + int request_timeout() const; + void check_graceful_pause(); + + int wanted_transfer(int channel); + int request_bandwidth(int channel, int bytes = 0); + + boost::shared_ptr m_socket; + + // the queue of blocks we have requested + // from this peer + std::vector m_download_queue; + + // the queue of requests we have got + // from this peer that haven't been issued + // to the disk thread yet + std::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 + char m_channel_state[2]; + + protected: + receive_buffer m_recv_buffer; + + // number of bytes this peer can send and receive + int m_quota[2]; + + // the blocks we have reserved in the piece + // picker and will request from this peer. + std::vector m_request_queue; + + // this is the limit on the number of outstanding requests + // we have to this peer. This is initialized to the settings + // in the settings_pack. But it may be lowered + // if the peer is known to require a smaller limit (like BitComet). + // or if the extended handshake sets a limit. + // web seeds also has a limit on the queue size. + int m_max_out_request_queue; + + // this is the peer we're actually talking to + // it may not necessarily be the peer we're + // connected to, in case we use a proxy + tcp::endpoint m_remote; + + public: + chained_buffer m_send_buffer; + private: + + // the disk thread to use to issue disk jobs to + disk_interface& m_disk_thread; + + public: + buffer_allocator_interface& m_allocator; + private: + + // io service + io_service& m_ios; + + public: +#ifndef TORRENT_DISABLE_EXTENSIONS + typedef std::list > extension_list_t; + extension_list_t m_extensions; +#endif + private: + + // the average rate of receiving complete piece messages + sliding_average<20> m_piece_rate; + sliding_average<20> m_send_rate; + + // 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<20> m_request_time; + + // keep the io_service running as long as we + // have peer connections + io_service::work m_work; + + // the time when we last got a part of a + // piece packet from this peer + time_point m_last_piece; + + // the time we sent a request to + // this peer the last time + time_point m_last_request; + // the time we received the last + // piece request from the peer + time_point m_last_incoming_request; + + // the time when we unchoked this peer + time_point m_last_unchoke; + + // if we're unchoked by this peer, this + // was the time + time_point m_last_unchoked; + + // the time we last choked this peer. min_time() in + // case we never unchoked it + time_point m_last_choke; + + // timeouts + time_point m_last_receive; + time_point m_last_sent; + + // the last time we filled our send buffer with payload + // this is used for timeouts + time_point 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/ + time_point m_requested; + + // a timestamp when the remote download rate + // was last updated + time_point m_remote_dl_update; + + // the time when async_connect was called + // or when the incoming connection was established + time_point m_connect; + + // the time when this peer sent us a not_interested message + // the last time. + time_point m_became_uninterested; + + // the time when we sent a not_interested message to + // this peer the last time. + time_point m_became_uninteresting; + + // 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 + boost::int64_t m_downloaded_at_last_round; + boost::int64_t m_uploaded_at_last_round; + + // 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. + boost::int64_t m_uploaded_at_last_unchoke; + + // the number of payload bytes downloaded last second tick + boost::int32_t m_downloaded_last_second; + + // the number of payload bytes uploaded last second tick + boost::int32_t m_uploaded_last_second; + + // 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; + + aux::handler_storage m_read_handler_storage; + aux::handler_storage m_write_handler_storage; + + // we have suggested these pieces to the peer + // don't suggest it again + bitfield m_sent_suggested_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 + std::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 + std::vector m_suggested_pieces; + + // the time when this peer last saw a complete copy + // of this torrent + time_t m_last_seen_complete; + + // the block we're currently receiving. Or + // (-1, -1) if we're not receiving one + piece_block m_receiving_block; + + // 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; + + // the bandwidth channels, upload and download + // keeps track of the current quotas + bandwidth_channel m_bandwidth_channel[num_channels]; + + protected: + // 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; + + // the number of time critical requests + // queued up in the m_request_queue that + // soon will be committed to the download + // queue. This is included in download_queue_time() + // so that it can be used while adding more + // requests and take the previous requests + // into account without submitting it all + // immediately + int m_queued_time_critical; + + // 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; + + // 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 + int m_picker_options; + + // the number of invalid piece-requests + // we have got from this peer. If the request + // queue gets empty, and there have been + // invalid requests, we can assume the + // peer is waiting for those pieces. + // we can then clear its download queue + // by sending choke, unchoke. + int m_num_invalid_requests; + + // if [0] is -1, superseeding 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 + int m_superseed_piece[2]; + + // pieces downloaded since last second + // timer timeout; used for determining + // approx download rate + int m_remote_pieces_dled; + + // approximate peer download rate + int m_remote_dl_rate; + + // the number of bytes send to the disk-io + // thread that hasn't yet been completely written. + int m_outstanding_writing_bytes; + + // max transfer rates seen on this peer + int m_download_rate_peak; + int m_upload_rate_peak; + + // when using the BitTyrant choker, this is our + // estimated reciprocation rate. i.e. the rate + // we need to send to this peer for it to unchoke + // us + int m_est_reciprocation_rate; + + // stop sending data after this many bytes, INT_MAX = inf + int m_send_barrier; + + // the number of request we should queue up + // at the remote end. + // TODO: 2 rename this target queue size + boost::uint16_t m_desired_queue_size; + +#ifndef TORRENT_NO_DEPRECATE +#ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES + // in case the session settings is set + // to resolve countries, this is set to + // the two character country code this + // peer resides in. + char m_country[2]; +#endif +#endif // TORRENT_NO_DEPRECATE + + // 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. + boost::uint8_t m_prefer_contiguous_blocks; + + // 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 + boost::uint8_t m_disk_read_failures; + + // 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. + boost::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; + + // set to true if this peer is in share mode + bool m_share_mode:1; + + // set to true when this peer is only uploading + bool m_upload_only:1; + + // this is set to true once the bitfield is received + bool m_bitfield_received:1; + + // if this is set to true, the client will not + // pick any pieces from this peer + bool m_no_download:1; + + // set to true when we've sent the first round of suggests + bool m_sent_suggests:1; + + // 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; + + template + aux::allocating_handler + make_read_handler(Handler const& handler) + { + return aux::allocating_handler( + handler, m_read_handler_storage + ); + } + + template + aux::allocating_handler + make_write_handler(Handler const& handler) + { + return aux::allocating_handler( + handler, m_write_handler_storage + ); + } + +#if TORRENT_USE_ASSERTS + public: + bool m_in_constructor; + bool m_disconnect_started; + bool m_initialized; + int m_in_use; + int m_received_in_piece; + bool m_destructed; + // this is true while there is an outstanding + // async write job on the socket + bool m_socket_is_writing; + bool is_single_thread() const; +#endif + }; + + struct cork + { + cork(peer_connection& p): m_pc(p), m_need_uncork(false) + { + if (m_pc.is_corked()) return; + m_pc.cork_socket(); + m_need_uncork = true; + } + ~cork() { if (m_need_uncork) m_pc.uncork_socket(); } + private: + peer_connection& m_pc; + bool m_need_uncork; + + cork& operator=(cork const&); + }; + +} + +#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..a2b8d5c --- /dev/null +++ b/include/libtorrent/peer_connection_handle.hpp @@ -0,0 +1,151 @@ +/* + +Copyright (c) 2015-2016, Arvid Norberg, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEER_CONNECTION_HANDLE_HPP_INCLUDED +#define TORRENT_PEER_CONNECTION_HANDLE_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/operations.hpp" +#include "libtorrent/alert_types.hpp" + +namespace libtorrent +{ + +class peer_connection; +class bt_peer_connection; +struct torrent_handle; +struct peer_plugin; +struct peer_info; +struct crypto_plugin; + +typedef boost::system::error_code error_code; + +// hidden +struct TORRENT_EXPORT peer_connection_handle +{ + peer_connection_handle(boost::weak_ptr impl) + : m_connection(impl) + {} + + int type() const; + + void add_extension(boost::shared_ptr); + peer_plugin const* find_plugin(char const* type); + + bool is_seed() const; + + bool upload_only() const; + + peer_id const& pid() const; + bool has_piece(int 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, int error = 0); + 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; + +#ifndef TORRENT_DISABLE_LOGGING + + void peer_log(peer_log_alert::direction_t direction + , char const* event, char const* fmt = "", ...) const TORRENT_FORMAT(4,5); + +#endif // TORRENT_DISABLE_LOGGING + + bool can_disconnect(error_code const& ec) const; + + bool has_metadata() const; + + bool in_handshake() const; + + void send_buffer(char const* begin, int size, int flags = 0); + + time_t last_seen_complete() const; + time_point time_of_last_unchoke() const; + + bool operator==(peer_connection_handle const& o) const + { return !(m_connection < o.m_connection) && !(o.m_connection < m_connection); } + bool operator!=(peer_connection_handle const& o) const + { return m_connection < o.m_connection || o.m_connection < m_connection; } + bool operator<(peer_connection_handle const& o) const + { return m_connection < o.m_connection; } + + boost::shared_ptr native_handle() const + { + return m_connection.lock(); + } + +private: + boost::weak_ptr m_connection; +}; + +struct TORRENT_EXPORT bt_peer_connection_handle : public peer_connection_handle +{ + explicit bt_peer_connection_handle(peer_connection_handle pc) + : peer_connection_handle(pc) + {} + + bool packet_finished() const; + bool support_extensions() const; + + bool supports_encryption() const; + + void switch_send_crypto(boost::shared_ptr crypto); + void switch_recv_crypto(boost::shared_ptr crypto); + + boost::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..da80053 --- /dev/null +++ b/include/libtorrent/peer_connection_interface.hpp @@ -0,0 +1,75 @@ +/* + +Copyright (c) 2013-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_PEER_CONNECTION_INTERFACE_HPP +#define TORRENT_PEER_CONNECTION_INTERFACE_HPP + +#include "libtorrent/socket.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/operations.hpp" // for operation_t enum + +namespace libtorrent +{ + struct torrent_peer; + class stat; + struct peer_info; + + // TODO: make this interface smaller! + struct TORRENT_EXTRA_EXPORT peer_connection_interface + { + virtual tcp::endpoint const& remote() const = 0; + virtual tcp::endpoint local_endpoint() const = 0; + virtual void disconnect(error_code const& ec + , operation_t op, int error = 0) = 0; + virtual peer_id const& 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(boost::int64_t downloaded, boost::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 void peer_log(peer_log_alert::direction_t direction + , char const* event, char const* fmt = "", ...) const 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..0614b9f --- /dev/null +++ b/include/libtorrent/peer_id.hpp @@ -0,0 +1,48 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEER_ID_HPP_INCLUDED +#define TORRENT_PEER_ID_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/sha1_hash.hpp" + +namespace libtorrent +{ + typedef sha1_hash peer_id; +#ifndef TORRENT_NO_DEPRECATE + typedef sha1_hash big_number; +#endif +} + +#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..11d21e4 --- /dev/null +++ b/include/libtorrent/peer_info.hpp @@ -0,0 +1,452 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_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" + +namespace libtorrent +{ + // holds information and statistics about one peer + // that libtorrent is connected to + struct TORRENT_EXPORT peer_info + { + // a string describing the software at the other end of the connection. + // In some cases this information is not available, then it will contain + // a string that may give away something about which software is running + // in the other end. In the case of a web seed, the server type and + // version will be a part of this string. + std::string client; + + // a bitfield, with one bit per piece in the torrent. Each bit tells you + // if the peer has that piece (if it's set to 1) or if the peer miss that + // piece (set to 0). + 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. + boost::int64_t total_download; + boost::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; + + // 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. + enum peer_flags_t + { + // **we** are interested in pieces from this peer. + interesting = 0x1, + + // **we** have choked this peer. + choked = 0x2, + + // the peer is interested in **us** + remote_interested = 0x4, + + // the peer has choked **us**. + remote_choked = 0x8, + + // means that this peer supports the + // `extension protocol`__. + // + // __ extension_protocol.html + supports_extensions = 0x10, + + // 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. + local_connection = 0x20, + + // The connection is opened, and waiting for the + // handshake. Until the handshake is done, the peer + // cannot be identified. + handshake = 0x40, + + // The connection is in a half-open state (i.e. it is + // being connected). + connecting = 0x80, + +#ifndef TORRENT_NO_DEPRECATE + // 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. + queued = 0x100, +#else + // hidden + deprecated__ = 0x100, +#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. + on_parole = 0x200, + + // This peer is a seed (it has all the pieces). + seed = 0x400, + + // 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. + optimistic_unchoke = 0x800, + + // 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. + snubbed = 0x1000, + + // 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. + upload_only = 0x2000, + + // 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. + endgame_mode = 0x4000, + + // 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. + holepunched = 0x8000, + + // indicates that this socket is runnin on top of the + // I2P transport. + i2p_socket = 0x10000, + + // indicates that this socket is a uTP socket + utp_socket = 0x20000, + + // indicates that this socket is running on top of an SSL + // (TLS) channel + ssl_socket = 0x40000, + + // this connection is obfuscated with RC4 + rc4_encrypted = 0x100000, + + // the handshake of this connection was obfuscated + // with a diffie-hellman exchange + plaintext_encrypted = 0x200000 + }; + + // tells you in which state the peer is in. It is set to + // any combination of the peer_flags_t enum. + boost::uint32_t flags; + + // the flags indicating which sources a peer can + // have come from. A peer may have been seen from + // multiple sources + enum peer_source_flags + { + // The peer was received from the tracker. + tracker = 0x1, + + // The peer was received from the kademlia DHT. + dht = 0x2, + + // The peer was received from the peer exchange + // extension. + pex = 0x4, + + // The peer was received from the local service + // discovery (The peer is on the local network). + lsd = 0x8, + + // The peer was added from the fast resume data. + resume_data = 0x10, + + // we received an incoming connection from this peer + incoming = 0x20 + }; + + // a combination of flags describing from which sources this peer + // was received. See peer_source_flags. + boost::uint32_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; + + 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; + + // 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 budy 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. + int downloading_piece_index; + int downloading_block_index; + int downloading_progress; + int downloading_total; + + // the kind of connection this is. Used for the connection_type field. + enum connection_type_t + { + // Regular bittorrent connection + standard_bittorrent = 0, + + // HTTP connection using the `BEP 19`_ protocol + web_seed = 1, + + // HTTP connection using the `BEP 17`_ protocol + http_seed = 2 + }; + + // the kind of connection this peer uses. See connection_type_t. + int connection_type; + + // an estimate of the rate this peer is downloading at, in + // bytes per second. + int remote_dl_rate; + + // 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 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; + + // 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. + int estimated_reciprocation_rate; + + // 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; + + // bits for the read_state and write_state + enum bw_state + { + // The peer is not waiting for any external events to + // send or receive data. + bw_idle = 0, + + // The peer is waiting for the rate limiter. + bw_limit = 1, + + // 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. + bw_network = 2, + + // The peer is waiting for the disk I/O thread to catch + // up writing buffers to disk before downloading more. + bw_disk = 4 + }; +#ifndef TORRENT_NO_DEPRECATE + enum bw_state_deprecated { bw_torrent = bw_limit, bw_global = bw_limit }; +#endif + + // bitmasks indicating what state this peer + // is in with regards to sending and receiving data. The states are declared in the + // bw_state enum. + char read_state; + char write_state; + +#ifndef TORRENT_NO_DEPRECATE + // country code deprecated in 1.1 + + // the two letter `ISO 3166 country code`__ for the country the peer is + // connected from. If the country hasn't been resolved yet, both chars + // are set to 0. If the resolution failed for some reason, the field is + // set to "--". If the resolution service returns an invalid country + // code, it is set to "!!". The ``countries.nerd.dk`` service is used to + // look up countries. This field will remain set to 0 unless the torrent + // is set to resolve countries, see `resolve_countries()`_. + // + // __ http://www.iso.org/iso/en/prods-services/iso3166ma/02iso-3166-code-lists/list-en1.html + char country[2]; + + // 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. + int upload_limit; + 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. + boost::int64_t load_balancing; +#endif + + }; + + // internal + struct TORRENT_EXPORT peer_list_entry + { + // internal + enum flags_t + { + banned = 1 + }; + + // internal + tcp::endpoint ip; + // internal + int flags; + // internal + boost::uint8_t failcount; + // internal + boost::uint8_t source; + }; + +} + +#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..ae7d9d0 --- /dev/null +++ b/include/libtorrent/peer_list.hpp @@ -0,0 +1,283 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_POLICY_HPP_INCLUDED +#define TORRENT_POLICY_HPP_INCLUDED + +#include +#include +#include "libtorrent/string_util.hpp" // for allocate_string_copy +#include "libtorrent/request_blocks.hpp" // for source_rank + +#include "libtorrent/torrent_peer.hpp" +#include "libtorrent/piece_picker.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/peer_connection_interface.hpp" + +namespace libtorrent +{ + + struct external_ip; + struct ip_filter; + class port_filter; + 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 + { + torrent_state() + : is_paused(false) + , is_finished(false) + , allow_multiple_connections_per_ip(false) + , first_time_seen(false) + , max_peerlist_size(1000) + , min_reconnect_time(60) + , loop_counter(0) + , ip(NULL), port(0) + , max_failcount(3) + , peer_allocator(NULL) + {} + bool is_paused; + bool is_finished; + bool allow_multiple_connections_per_ip; + + // 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; + + int max_peerlist_size; + int min_reconnect_time; + + // the number of iterations over the peer list for this operation + int loop_counter; + + // 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 const* ip; + int port; + + // the number of times a peer must fail before it's no longer considered + // a connect candidate + int max_failcount; + + // this must be set to a torrent_peer allocator + torrent_peer_allocator_interface* peer_allocator; + + // 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; + }; + + class TORRENT_EXTRA_EXPORT peer_list : single_threaded + { + public: + + peer_list(); + +#if TORRENT_USE_I2P + torrent_peer* add_i2p_peer(char const* destination, int src, char flags + , torrent_state* state); +#endif + + enum + { + // these flags match the flags passed in ut_pex + // messages + flag_encryption = 0x1, + flag_seed = 0x2, + flag_utp = 0x4, + flag_holepunch = 0x8 + }; + + // this is called once for every torrent_peer we get from + // the tracker, pex, lsd or dht. + torrent_peer* add_peer(const tcp::endpoint& remote + , int source, char flags, torrent_state* state); + + // false means duplicate connection + bool update_peer_port(int port, torrent_peer* p, int 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()); } + +#ifdef TORRENT_OPTIMIZE_MEMORY_USAGE + typedef std::vector peers_t; +#else + typedef std::deque peers_t; +#endif + + typedef peers_t::iterator iterator; + typedef peers_t::const_iterator const_iterator; + iterator begin_peer() { return m_peers.begin(); } + iterator end_peer() { return m_peers.end(); } + const_iterator begin_peer() const { return m_peers.begin(); } + const_iterator end_peer() 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 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, int src, int flags + , tcp::endpoint const& remote, char const* destination); + bool insert_peer(torrent_peer* p, iterator iter, int flags, torrent_state* state); + + bool compare_peer_erase(torrent_peer const& lhs, torrent_peer const& rhs) const; + bool compare_peer(torrent_peer const* lhs, torrent_peer const* rhs + , external_ip const& external, int source_port) const; + + void find_connect_candidates(std::vector& peers + , int session_time, torrent_state* state); + + bool is_connect_candidate(torrent_peer const& p) const; + bool is_erase_candidate(torrent_peer const& p) const; + bool is_force_erase_candidate(torrent_peer const& pe) const; + bool should_erase_immediately(torrent_peer const& p) const; + + enum flags_t { force_erase = 1 }; + void erase_peers(torrent_state* state, int flags = 0); + + peers_t m_peers; + + // this should be NULL 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 number of seeds in the torrent_peer list + boost::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. + boost::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; + + // 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; + + // if a peer has failed this many times or more, we don't consider + // it a connect candidate anymore. + int m_max_failcount; + }; + +} + +#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..64e49ca --- /dev/null +++ b/include/libtorrent/peer_request.hpp @@ -0,0 +1,58 @@ +/* + +Copyright (c) 2006-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_PEER_REQUEST_HPP_INCLUDED +#define TORRENT_PEER_REQUEST_HPP_INCLUDED + +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. + int piece; + // the offset within that piece where the range starts. + int start; + // the size of the range, in bytes. + int length; + + // returns true if the right hand side peer_request refers to the same + // range as this does. + bool operator==(peer_request const& r) const + { return piece == r.piece && start == r.start && length == r.length; } + }; +} + +#endif // TORRENT_PEER_REQUEST_HPP_INCLUDED + diff --git a/include/libtorrent/performance_counters.hpp b/include/libtorrent/performance_counters.hpp new file mode 100644 index 0000000..53170d0 --- /dev/null +++ b/include/libtorrent/performance_counters.hpp @@ -0,0 +1,466 @@ +/* + +Copyright (c) 2013-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_PERFORMANCE_COUNTERS_HPP_INCLUDED +#define TORRENT_PERFORMANCE_COUNTERS_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/thread.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent +{ + struct TORRENT_EXTRA_EXPORT counters + { + enum stats_counter_t + { + // the number of peers that were disconnected this + // tick due to protocol error + error_peers, + disconnected_peers, + eof_peers, + connreset_peers, + connrefused_peers, + connaborted_peers, + notconnected_peers, + perm_peers, + buffer_peers, + unreachable_peers, + broken_pipe_peers, + addrinuse_peers, + no_access_peers, + invalid_arg_peers, + aborted_peers, + + piece_requests, + max_piece_requests, + invalid_piece_requests, + choked_piece_requests, + cancelled_piece_requests, + piece_rejects, + error_incoming_peers, + error_outgoing_peers, + error_rc4_peers, + error_encrypted_peers, + error_tcp_peers, + error_utp_peers, + + // the number of times the piece picker was + // successfully invoked, split by the reason + // it was invoked + reject_piece_picks, + unchoke_piece_picks, + incoming_redundant_piece_picks, + incoming_piece_picks, + end_game_piece_picks, + snubbed_piece_picks, + interesting_piece_picks, + hash_fail_piece_picks, + + // these counters indicate which parts + // of the piece picker CPU is spent in + piece_picker_partial_loops, + piece_picker_suggest_loops, + piece_picker_sequential_loops, + piece_picker_reverse_rare_loops, + piece_picker_rare_loops, + piece_picker_rand_start_loops, + piece_picker_rand_loops, + piece_picker_busy_loops, + + // reasons to disconnect peers + connect_timeouts, + uninteresting_peers, + timeout_peers, + no_memory_peers, + too_many_peers, + transport_timeout_peers, + num_banned_peers, + banned_for_hash_failure, + + // connection attempts (not necessarily successful) + connection_attempts, + // the number of iterations over the peer list when finding + // a connect candidate + connection_attempt_loops, + // 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, + + torrent_evicted_counter, + + // bittorrent message counters + // TODO: should keepalives be in here too? + // how about dont-have, share-mode, upload-only + num_incoming_choke, + num_incoming_unchoke, + num_incoming_interested, + num_incoming_not_interested, + num_incoming_have, + num_incoming_bitfield, + num_incoming_request, + num_incoming_piece, + num_incoming_cancel, + num_incoming_dht_port, + num_incoming_suggest, + num_incoming_have_all, + num_incoming_have_none, + num_incoming_reject, + num_incoming_allowed_fast, + num_incoming_ext_handshake, + num_incoming_pex, + num_incoming_metadata, + num_incoming_extended, + + num_outgoing_choke, + num_outgoing_unchoke, + num_outgoing_interested, + num_outgoing_not_interested, + num_outgoing_have, + num_outgoing_bitfield, + num_outgoing_request, + num_outgoing_piece, + num_outgoing_cancel, + num_outgoing_dht_port, + num_outgoing_suggest, + num_outgoing_have_all, + num_outgoing_have_none, + num_outgoing_reject, + num_outgoing_allowed_fast, + num_outgoing_ext_handshake, + num_outgoing_pex, + num_outgoing_metadata, + num_outgoing_extended, + + num_piece_passed, + num_piece_failed, + + num_have_pieces, + num_total_pieces_added, + + num_blocks_written, + num_blocks_read, + num_blocks_hashed, + num_blocks_cache_hits, + num_write_ops, + num_read_ops, + num_read_back, + + disk_read_time, + disk_write_time, + disk_hash_time, + disk_job_time, + + waste_piece_timed_out, + waste_piece_cancelled, + waste_piece_unknown, + waste_piece_seed, + waste_piece_end_game, + waste_piece_closing, + + sent_payload_bytes, + sent_bytes, + sent_ip_overhead_bytes, + sent_tracker_bytes, + recv_payload_bytes, + recv_bytes, + recv_ip_overhead_bytes, + recv_tracker_bytes, + + recv_failed_bytes, + recv_redundant_bytes, + + dht_messages_in, + dht_messages_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_invalid_announce, + dht_invalid_get_peers, + dht_invalid_put, + dht_invalid_get, + + // uTP counters. + utp_packet_loss, + utp_timeout, + utp_packets_in, + utp_packets_out, + utp_fast_retransmit, + utp_packet_resend, + utp_samples_above_target, + utp_samples_below_target, + utp_payload_pkts_in, + utp_payload_pkts_out, + utp_invalid_pkts_in, + utp_redundant_pkts_in, + + // the buffer sizes accepted by + // socket send calls. The larger + // the more efficient. The size is + // 1 << n, where n is the number + // at the end of the counter name + + // 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, + // 16384, 32768, 65536, 131072, 262144, 524288, 1048576 + socket_send_size3, + socket_send_size4, + socket_send_size5, + socket_send_size6, + socket_send_size7, + socket_send_size8, + socket_send_size9, + socket_send_size10, + socket_send_size11, + socket_send_size12, + socket_send_size13, + socket_send_size14, + socket_send_size15, + socket_send_size16, + socket_send_size17, + socket_send_size18, + socket_send_size19, + socket_send_size20, + + // the buffer sizes returned by + // socket recv calls. The larger + // the more efficient. The size is + // 1 << n, where n is the number + // at the end of the counter name + + // 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, + // 16384, 32768, 65536, 131072, 262144, 524288, 1048576 + socket_recv_size3, + socket_recv_size4, + socket_recv_size5, + socket_recv_size6, + socket_recv_size7, + socket_recv_size8, + socket_recv_size9, + socket_recv_size10, + socket_recv_size11, + socket_recv_size12, + socket_recv_size13, + socket_recv_size14, + socket_recv_size15, + socket_recv_size16, + socket_recv_size17, + socket_recv_size18, + socket_recv_size19, + socket_recv_size20, + + num_stats_counters + }; + + // == ALL FOLLOWING ARE GAUGES == + + // it is important that all gauges have a higher index than counters. + // This assumption is relied upon in other parts of the code + enum stats_gauge_t + { + num_checking_torrents = num_stats_counters, + num_stopped_torrents, + num_upload_only_torrents, // upload_only means finished + num_downloading_torrents, + num_seeding_torrents, + num_queued_seeding_torrents, + num_queued_download_torrents, + num_error_torrents, + + // the number of torrents that don't have the + // IP filter applied to them. + non_filter_torrents, + + // counters related to evicting torrents + num_loaded_torrents, + num_pinned_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, + + num_peers_half_open, + num_peers_connected, + num_peers_up_interested, + num_peers_down_interested, + num_peers_up_unchoked_all, + num_peers_up_unchoked_optimistic, + num_peers_up_unchoked, + num_peers_down_unchoked, + num_peers_up_requests, + num_peers_down_requests, + num_peers_up_disk, + num_peers_down_disk, + num_peers_end_game, + + write_cache_blocks, + read_cache_blocks, + request_latency, + pinned_blocks, + disk_blocks_in_use, + queued_disk_jobs, + num_running_disk_jobs, + num_read_jobs, + num_write_jobs, + num_jobs, + num_writing_threads, + num_running_threads, + blocked_disk_jobs, + queued_write_bytes, + num_unchoke_slots, + + num_fenced_read, + num_fenced_write, + num_fenced_hash, + num_fenced_move_storage, + num_fenced_release_files, + num_fenced_delete_files, + num_fenced_check_fastresume, + num_fenced_save_resume_data, + num_fenced_rename_file, + num_fenced_stop_torrent, + num_fenced_cache_piece, + num_fenced_flush_piece, + num_fenced_flush_hashed, + num_fenced_flush_storage, + num_fenced_trim_cache, + num_fenced_file_priority, + num_fenced_load_torrent, + num_fenced_clear_piece, + num_fenced_tick_storage, + + arc_mru_size, + arc_mru_ghost_size, + arc_mfu_size, + arc_mfu_ghost_size, + arc_write_size, + arc_volatile_size, + + dht_nodes, + dht_node_cache, + dht_torrents, + dht_peers, + dht_immutable_data, + dht_mutable_data, + dht_allocated_observers, + + has_incoming_connections, + + limiter_up_queue, + limiter_down_queue, + limiter_up_bytes, + limiter_down_bytes, + + // the number of uTP connections in each respective state + // these must be defined in the same order as the state_t enum + // in utp_stream + num_utp_idle, + num_utp_syn_sent, + num_utp_connected, + num_utp_fin_sent, + num_utp_close_wait, + num_utp_deleted, + + num_counters, + num_gauges_counters = num_counters - num_stats_counters + }; + + counters(); + + counters(counters const&); + counters& operator=(counters const&); + + // returns the new value + boost::int64_t inc_stats_counter(int c, boost::int64_t value = 1); + boost::int64_t operator[](int i) const; + + void set_value(int c, boost::int64_t value); + void blend_stats_counter(int c, boost::int64_t value, int ratio); + + 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 +#if BOOST_ATOMIC_LLONG_LOCK_FREE == 2 + boost::atomic m_stats_counter[num_counters]; +#else + // if the atomic type is't lock-free, use a single lock instead, for + // the whole array + mutable mutex m_mutex; + boost::int64_t m_stats_counter[num_counters]; +#endif + }; +} + +#endif + diff --git a/include/libtorrent/piece_block_progress.hpp b/include/libtorrent/piece_block_progress.hpp new file mode 100644 index 0000000..80a0112 --- /dev/null +++ b/include/libtorrent/piece_block_progress.hpp @@ -0,0 +1,57 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PIECE_BLOCK_PROGRESS_HPP_INCLUDED +#define TORRENT_PIECE_BLOCK_PROGRESS_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +namespace libtorrent +{ + struct piece_block_progress + { + // the piece and block index + // determines exactly which + // part of the torrent that + // is currently being downloaded + int piece_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..5f726f2 --- /dev/null +++ b/include/libtorrent/piece_picker.hpp @@ -0,0 +1,844 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ +#ifndef TORRENT_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 "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include + +#include +#include +#include + +#ifdef TORRENT_DEBUG_REFCOUNTS +#include +#endif + +#if TORRENT_USE_ASSERTS +#include +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/peer_id.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/time.hpp" + +namespace libtorrent +{ + + class torrent; + class peer_connection; + struct bitfield; + struct counters; + struct torrent_peer; + + struct TORRENT_EXTRA_EXPORT piece_block + { + static const piece_block invalid; + + piece_block() {} + piece_block(int p_index, int b_index) + : piece_index(p_index) + , block_index(b_index) + { + } + int piece_index; + int block_index; + + 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; } + }; + + class TORRENT_EXTRA_EXPORT piece_picker + { + public: + + enum + { + // the number of priority levels + priority_levels = 8, + // priority factor + prio_factor = 3 + }; + + struct block_info + { + block_info(): peer(0), num_peers(0), state(state_none) {} + // the peer this block was requested or + // downloaded from. + torrent_peer* peer; + // 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 + int piece_index; + std::set peers; +#endif + }; + + enum options_t + { + // pick rarest first + rarest_first = 1, + // pick the most common first, or the last pieces if sequential + reverse = 2, + // only pick pieces exclusively requested from this peer + on_parole = 4, + // always pick partial pieces before any other piece + prioritize_partials = 8, + // pick pieces in sequential order + sequential = 16, + // treat pieces with priority 6 and below as filtered + // to trigger end-game mode until all prio 7 pieces are + // completed + time_critical_mode = 32, + // only expands pieces (when prefer contiguous blocks is set) + // within properly aligned ranges, not the largest possible + // range of pieces. + align_expanded_pieces = 64 + }; + + struct downloading_piece + { + downloading_piece() : index((std::numeric_limits::max)()) + , info_idx((std::numeric_limits::max)()) + , finished(0) + , passed_hash_check(0) + , writing(0) + , locked(0) + , requested(0) + , outstanding_hash_check(0) {} + + bool operator<(downloading_piece const& rhs) const { return index < rhs.index; } + + // the index of the piece + boost::uint32_t index; + + // info about each block in this piece. this is an index into the + // m_block_info array, when multiplied by m_blocks_per_piece. + // The m_blocks_per_piece following entries contain information about + // all blocks in this piece. + boost::uint16_t info_idx; + + // the number of blocks in the finished state + boost::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. + boost::uint16_t passed_hash_check:1; + + // the number of blocks in the writing state + boost::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. + boost::uint16_t locked:1; + + // the number of blocks in the requested state + boost::uint16_t requested:15; + + // set to true while there is an outstanding + // hash check for this piece + boost::uint16_t outstanding_hash_check:1; + }; + + piece_picker(); + + void get_availability(std::vector& avail) const; + int get_availability(int piece) const; + + // increases the peer count for the given piece + // (is used when a HAVE message is received) + void inc_refcount(int index, const torrent_peer* peer); + void dec_refcount(int index, const torrent_peer* peer); + + // increases the peer count for the given piece + // (is used when a BITFIELD message is received) + void inc_refcount(bitfield const& bitmask, const torrent_peer* peer); + // decreases the peer count for the given piece + // (used when a peer disconnects) + void dec_refcount(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); + + // 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(int index); + void we_dont_have(int index); + + // the lowest piece index we do not have + int cursor() const { return m_cursor; } + + // one past the last piece we do not have. + int reverse_cursor() const { return m_reverse_cursor; } + + // sets all pieces to dont-have + void init(int blocks_per_piece, int blocks_in_last_piece, int total_num_pieces); + int num_pieces() const { return int(m_piece_map.size()); } + + bool have_piece(int index) const; + + bool is_downloading(int index) const + { + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < int(m_piece_map.size())); + + 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(int index, int prio); + + // returns the priority for the piece at 'index' + int piece_priority(int index) const; + + // returns the current piece priorities for all pieces + void piece_priorities(std::vector& pieces) const; + + // ========== start deprecation ============== + + // fills the bitmask with 1's for pieces that are filtered + void filtered_pieces(std::vector& mask) const; + + // ========== end deprecation ============== + + // pieces should be the vector that represents the pieces a + // client has. It returns a list of all pieces that this client + // has and that are interesting to download. It returns them in + // priority order. It doesn't care about the download flag. + // The user of this function must lookup if any piece is + // marked as being downloaded. If the user of this function + // decides to download a piece, it must mark it as being downloaded + // itself, by using the mark_as_downloading() member function. + // THIS IS DONE BY THE peer_connection::send_request() MEMBER FUNCTION! + // The last argument is the torrent_peer pointer for the peer that + // we'll download from. + // prefer_contiguous_blocks indicates how many blocks we would like + // to request contiguously. The blocks are not merged by the piece + // picker, but may be coalesced later by the peer_connection. + // this feature is used by web_peer_connection to request larger blocks + // at a time to mitigate limited pipelining and lack of keep-alive + // (i.e. higher overhead per request). + boost::uint32_t pick_pieces(bitfield const& pieces + , std::vector& interesting_blocks, int num_blocks + , int prefer_contiguous_blocks, torrent_peer* peer + , int 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(int piece, bitfield const& pieces + , std::vector& interesting_blocks + , std::vector& backup_blocks + , std::vector& backup_blocks2 + , int num_blocks, int prefer_contiguous_blocks + , torrent_peer* peer, std::vector const& ignore + , int options) const; + + // picks blocks only from downloading pieces + int add_blocks_downloading(downloading_piece const& dp + , 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 + , int 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 + +// int get_block_state(piece_block block) const; + + // 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 + , int options = 0); + + // returns true if the block was marked as writing, + // and false if the block is already finished or writing + bool mark_as_writing(piece_block block, torrent_peer* peer); + + void mark_as_canceled(piece_block block, torrent_peer* peer); + void mark_as_finished(piece_block block, torrent_peer* peer); + + // prevent blocks from being picked from this piece. + // to unlock the piece, call restore_piece() on it + void lock_piece(int piece); + + void write_failed(piece_block block); + int num_peers(piece_block block) const; + + void piece_passed(int index); + +// void mark_as_checking(int index); +// void mark_as_done_checking(int index); + + // returns information about the given piece + void piece_info(int index, piece_picker::downloading_piece& st) const; + + struct piece_stats_t + { + int peer_count; + int priority; + bool have; + bool downloading; + }; + + piece_stats_t piece_stats(int index) const; + + // if a piece had a hash-failure, it must be restored and + // made available for redownloading + void restore_piece(int index); + + // clears the given piece's download flag + // this means that this piece-block can be picked again + void abort_download(piece_block block, torrent_peer* peer = 0); + + // returns true if all blocks in this piece are finished + // or if we have the piece + bool is_piece_finished(int index) const; + + // returns true if we have the piece or if the piece + // has passed the hash check + bool has_piece_passed(int index) const; + + // returns the number of blocks there is in the given piece + int blocks_in_piece(int index) const; + + // the number of downloaded blocks that hasn't passed + // the hash-check yet + int unverified_blocks() const; + + // return the peer pointers to all peers that participated in + // this piece + void get_downloaders(std::vector& d, int index) const; + + std::vector get_download_queue() const; + int get_download_queue_size() const; + + void get_download_queue_sizes(int* partial + , int* full, int* finished, int* zero_prio) const; + + torrent_peer* get_downloader(piece_block block) const; + + // the number of filtered pieces we don't have + int num_filtered() const { return m_num_filtered; } + + // the number of filtered pieces we already have + int num_have_filtered() const { return m_num_have_filtered; } + + // number of pieces whose hash has passed _and_ they have + // been successfully flushed to disk. Including pieces we have + // also filtered with priority 0 but have anyway. + int num_have() const { return m_num_have; } + + // 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 we have all the pieces we wanted + bool is_finished() const { return m_num_have - m_num_have_filtered == int(m_piece_map.size()) - m_num_filtered; } + + bool is_seeding() const { return m_num_have == int(m_piece_map.size()); } + + // 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(int start, int end, int prio) const; + void verify_pick(std::vector const& picked + , bitfield const& bits) const; + + void check_peer_invariant(bitfield const& have, torrent_peer const* p) const; + void check_invariant(const torrent* t = 0) const; +#endif + + // functor that compares indices on downloading_pieces + struct has_index + { + has_index(int i): index(boost::uint32_t(i)) { TORRENT_ASSERT(i >= 0); } + bool operator()(const downloading_piece& p) const + { return p.index == index; } + boost::uint32_t index; + }; + + int blocks_in_last_piece() const + { return m_blocks_in_last_piece; } + + std::pair distributed_copies() const; + + void set_num_pad_files(int n) { m_num_pad_files = n; } + + // return the array of block_info objects for a given downloading_piece. + // this array has m_blocks_per_piece elements in it + block_info* blocks_for_piece(downloading_piece const& dp); + block_info const* blocks_for_piece(downloading_piece const& dp) const; + + private: + + friend struct piece_pos; + + boost::tuple requested_from( + piece_picker::downloading_piece const& p + , int num_blocks_in_piece, torrent_peer* peer) const; + + bool can_pick(int piece, bitfield const& bitmask) const; + bool is_piece_free(int piece, bitfield const& bitmask) const; + std::pair expand_piece(int piece, int whole_pieces + , bitfield const& have, int options) const; + + // only defined when TORRENT_PICKER_LOG is defined, used for debugging + // unit tests + void print_pieces() const; + + struct piece_pos + { + piece_pos() {} + piece_pos(int peer_count_, int index_) + : peer_count(unsigned(peer_count_)) + , download_state(piece_pos::piece_open) + , piece_priority(4) + , index(unsigned(index_)) + { + TORRENT_ASSERT(peer_count_ >= 0); + TORRENT_ASSERT(index_ >= 0); + } + + // download_state of this piece. + enum state_t + { + // the piece is partially downloaded or requested + piece_downloading, + // partial pieces where all blocks in the piece have been requested + piece_full, + // partial pieces where all blocks in the piece have been received + // and are either finished or writing + piece_finished, + // partial pieces whose priority is 0 + piece_zero_prio, + + // 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). + num_download_categories, + + // the piece is open to be picked + piece_open = num_download_categories, + + // 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 + piece_downloading_reverse, + piece_full_reverse + }; + + // returns one of the valid download categories of state_t or + // piece_open if this piece is not being downloaded + int download_queue() const + { + if (download_state == piece_downloading_reverse) + return piece_downloading; + if (download_state == piece_full_reverse) + return piece_full; + return download_state; + } + + bool reverse() const + { + return download_state == piece_downloading_reverse + || download_state == piece_full_reverse; + } + + void unreverse() + { + switch (download_state) + { + case piece_downloading_reverse: + download_state = piece_downloading; + break; + case piece_full_reverse: + download_state = piece_full; + break; + } + } + + void make_reverse() + { + switch (download_state) + { + case piece_downloading: + download_state = piece_downloading_reverse; + break; + case piece_full: + download_state = piece_full_reverse; + break; + } + } + + // the number of peers that has this piece + // (availability) +#ifdef TORRENT_OPTIMIZE_MEMORY_USAGE + boost::uint32_t peer_count : 9; +#else + boost::uint32_t peer_count : 16; +#endif + + // one of the enums from state_t. 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. + boost::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 + boost::uint32_t piece_priority : 3; + + // index in to the piece_info vector +#ifdef TORRENT_OPTIMIZE_MEMORY_USAGE + boost::uint32_t index : 17; +#else + boost::uint32_t index; +#endif + +#ifdef TORRENT_DEBUG_REFCOUNTS + // all the peers that have this piece + std::set have_peers; +#endif + + enum + { + // 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. +#ifdef TORRENT_OPTIMIZE_MEMORY_USAGE + we_have_index = 0x3ffff, +#else + we_have_index = 0xffffffff, +#endif + // the priority value that means the piece is filtered + filter_priority = 0, + // the max number the peer count can hold +#ifdef TORRENT_OPTIMIZE_MEMORY_USAGE + max_peer_count = 0x1ff +#else + max_peer_count = 0xffff +#endif + }; + + bool have() const { return index == we_have_index; } + void set_have() { index = we_have_index; TORRENT_ASSERT(have()); } + void set_not_have() { index = 0; TORRENT_ASSERT(!have()); } + bool downloading() const { return download_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 + || download_state == piece_full + || download_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 (download_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 p) const + { return index != p.index || peer_count != p.peer_count; } + + bool operator==(piece_pos p) const + { return index == p.index && peer_count == p.peer_count; } + }; + +#ifndef TORRENT_DEBUG_REFCOUNTS +#ifdef TORRENT_OPTIMIZE_MEMORY_USAGE + BOOST_STATIC_ASSERT(sizeof(piece_pos) == sizeof(char) * 4); +#else + BOOST_STATIC_ASSERT(sizeof(piece_pos) == sizeof(char) * 8); +#endif +#endif + + bool partial_compare_rarest_first(downloading_piece const* lhs + , downloading_piece const* rhs) const; + + void break_one_seed(); + + void update_pieces() const; + + // fills in the range [start, end) of pieces in + // m_pieces that have priority 'prio' + void priority_range(int prio, int* start, int* end); + + // adds the piece 'index' to m_pieces + void add(int index); + // removes the piece with the given priority and the + // elem_index in the m_pieces vector + void remove(int priority, int 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, int elem_index); + // shuffles the given piece inside it's priority range + void shuffle(int priority, int elem_index); + + typedef std::vector::iterator dlpiece_iter; + dlpiece_iter add_download_piece(int index); + void erase_download_piece(dlpiece_iter i); + + std::vector::const_iterator find_dl_piece(int queue, int index) const; + std::vector::iterator find_dl_piece(int queue, int index); + + // 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: + + // 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 std::vector m_piece_map; + + // the number of seeds. These are not added to + // the availability counters of the pieces + int m_seeds; + + // the number of pieces that have passed the hash check + int m_num_passed; + + // 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 std::vector m_pieces; + + // these are indices to the priority boundries inside + // the m_pieces vector. priority 0 always start at + // 0, priority 1 starts at m_priority_boundries[0] etc. + mutable std::vector m_priority_boundries; + + // 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). + std::vector m_downloads[piece_pos::num_download_categories]; + + // this holds the information of the blocks in partially downloaded + // pieces. the downloading_piece::info index point into this vector for + // its storage + std::vector m_block_info; + + // these are block ranges in m_block_info that are free. The numbers + // in here, when multiplied by m_blocks_per_piece is the index to the + // first block in the range that's free to use by a new downloading_piece. + // this is a free-list. + std::vector m_free_block_infos; + + boost::uint16_t m_blocks_per_piece; + boost::uint16_t m_blocks_in_last_piece; + + // 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 + int m_num_filtered; + + // the number of pieces we have that also are filtered + int m_num_have_filtered; + + // we have all pieces in the range [0, m_cursor) + // m_cursor is the first piece we don't have + int m_cursor; + + // 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 + int m_reverse_cursor; + + // the number of pieces we have (i.e. passed + flushed). + // This includes pieces that we have filtered but still have + int m_num_have; + + // this is the number of partial download pieces + // that may be caused by pad files. We raise the limit + // of number of partial pieces by this amount, to not + // prioritize pieces that intersect pad files for no + // apparent reason + int m_num_pad_files; + + // if this is set to true, it means update_pieces() + // has to be called before accessing m_pieces. + mutable bool m_dirty; + public: + +#ifdef TORRENT_OPTIMIZE_MEMORY_USAGE + enum { max_pieces = piece_pos::we_have_index - 1 }; +#else + enum { max_pieces = INT_MAX - 1 }; +#endif + + }; +} + +#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..3dcaaff --- /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(); + + boost::uint64_t total_physical_ram(); +} + +#endif // TORRENT_PLATFORM_UTIL_HPP + diff --git a/include/libtorrent/proxy_base.hpp b/include/libtorrent/proxy_base.hpp new file mode 100644 index 0000000..8bed55e --- /dev/null +++ b/include/libtorrent/proxy_base.hpp @@ -0,0 +1,267 @@ +/* + +Copyright (c) 2007-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_PROXY_BASE_HPP_INCLUDED +#define TORRENT_PROXY_BASE_HPP_INCLUDED + +#include "libtorrent/io.hpp" +#include "libtorrent/io_service_fwd.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/error_code.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { + +class proxy_base : boost::noncopyable +{ +public: + + typedef boost::function handler_type; + + typedef tcp::socket next_layer_type; + typedef tcp::socket::lowest_layer_type lowest_layer_type; + typedef tcp::socket::endpoint_type endpoint_type; + typedef tcp::socket::protocol_type protocol_type; + + explicit proxy_base(io_service& io_service); + ~proxy_base(); + + void set_proxy(std::string hostname, int port) + { + m_hostname = hostname; + m_port = port; + } + + template + void async_read_some(Mutable_Buffers const& buffers, Handler const& handler) + { + m_sock.async_read_some(buffers, handler); + } + + template + std::size_t read_some(Mutable_Buffers const& buffers, error_code& ec) + { + return m_sock.read_some(buffers, ec); + } + + template + std::size_t write_some(Const_Buffers const& buffers, error_code& ec) + { + return m_sock.write_some(buffers, ec); + } + + std::size_t available(error_code& ec) const + { return m_sock.available(ec); } + +#ifndef BOOST_NO_EXCEPTIONS + std::size_t available() const + { return m_sock.available(); } + + template + std::size_t read_some(Mutable_Buffers const& buffers) + { + return m_sock.read_some(buffers); + } + + template + std::size_t write_some(Const_Buffers const& buffers) + { + return m_sock.write_some(buffers); + } + + template + void io_control(IO_Control_Command& ioc) + { + m_sock.io_control(ioc); + } +#endif + + template + void io_control(IO_Control_Command& ioc, error_code& ec) + { + m_sock.io_control(ioc, ec); + } + + template + void async_write_some(Const_Buffers const& buffers, Handler const& handler) + { + m_sock.async_write_some(buffers, handler); + } + +#ifndef BOOST_NO_EXCEPTIONS + template + void set_option(SettableSocketOption const& opt) + { + m_sock.set_option(opt); + } +#endif + + template + error_code set_option(SettableSocketOption const& opt, error_code& ec) + { + return m_sock.set_option(opt, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + template + void get_option(GettableSocketOption& opt) + { + m_sock.get_option(opt); + } +#endif + + template + error_code get_option(GettableSocketOption& opt, error_code& ec) + { + return m_sock.get_option(opt, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + void bind(endpoint_type const& /* endpoint */) + { +// m_sock.bind(endpoint); + } +#endif + + error_code cancel(error_code& ec) + { + return m_sock.cancel(ec); + } + + void bind(endpoint_type const& /* endpoint */, error_code& /* ec */) + { + // the reason why we ignore binds here is because we don't + // (necessarily) yet know what address family the proxy + // will resolve to, and binding to the wrong one would + // break our connection attempt later. The caller here + // doesn't necessarily know that we're proxying, so this + // bind address is based on the final endpoint, not the + // proxy. + // TODO: it would be nice to remember the bind port and bind once we know where the proxy is +// m_sock.bind(endpoint, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + void open(protocol_type const&) + { +// m_sock.open(p); + } +#endif + + void open(protocol_type const&, error_code&) + { + // we need to ignore this for the same reason as stated + // for ignoring bind() +// m_sock.open(p, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + void close() + { + m_remote_endpoint = endpoint_type(); + m_sock.close(); + m_resolver.cancel(); + } +#endif + + void close(error_code& ec) + { + m_remote_endpoint = endpoint_type(); + m_sock.close(ec); + m_resolver.cancel(); + } + +#ifndef BOOST_NO_EXCEPTIONS + endpoint_type remote_endpoint() const + { + return m_remote_endpoint; + } +#endif + + endpoint_type remote_endpoint(error_code& ec) const + { + if (!m_sock.is_open()) ec = boost::asio::error::not_connected; + return m_remote_endpoint; + } + +#ifndef BOOST_NO_EXCEPTIONS + endpoint_type local_endpoint() const + { + return m_sock.local_endpoint(); + } +#endif + + endpoint_type local_endpoint(error_code& ec) const + { + return m_sock.local_endpoint(ec); + } + + io_service& get_io_service() + { + return m_sock.get_io_service(); + } + + lowest_layer_type& lowest_layer() + { + return m_sock.lowest_layer(); + } + + next_layer_type& next_layer() + { + return m_sock; + } + + bool is_open() const { return m_sock.is_open(); } + +protected: + + bool handle_error(error_code const& e, boost::shared_ptr const& h); + + tcp::socket m_sock; + std::string m_hostname; + int m_port; + + endpoint_type m_remote_endpoint; + + // TODO: 2 use the resolver interface that has a built-in cache + tcp::resolver m_resolver; +}; + +} + +#endif + diff --git a/include/libtorrent/puff.hpp b/include/libtorrent/puff.hpp new file mode 100644 index 0000000..5bd50b5 --- /dev/null +++ b/include/libtorrent/puff.hpp @@ -0,0 +1,31 @@ +/* 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 + */ + + +/* + * 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 */ diff --git a/include/libtorrent/random.hpp b/include/libtorrent/random.hpp new file mode 100644 index 0000000..884acc3 --- /dev/null +++ b/include/libtorrent/random.hpp @@ -0,0 +1,40 @@ +/* + +Copyright (c) 2011-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include + +namespace libtorrent +{ + boost::uint32_t TORRENT_EXTRA_EXPORT random(); + boost::uint32_t TORRENT_EXTRA_EXPORT randint(int i); +} diff --git a/include/libtorrent/receive_buffer.hpp b/include/libtorrent/receive_buffer.hpp new file mode 100644 index 0000000..db377ae --- /dev/null +++ b/include/libtorrent/receive_buffer.hpp @@ -0,0 +1,295 @@ +/* + +Copyright (c) 2014-2016, Arvid Norberg, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_RECEIVE_BUFFER_HPP_INCLUDED +#define TORRENT_RECEIVE_BUFFER_HPP_INCLUDED + +#include +#include +#include +#include + +namespace libtorrent { + +struct TORRENT_EXTRA_EXPORT receive_buffer +{ + friend struct crypto_receive_buffer; + + receive_buffer(buffer_allocator_interface& allocator) + : m_recv_start(0) + , m_recv_end(0) + , m_recv_pos(0) + , m_packet_size(0) + , m_soft_packet_size(0) + , m_disk_recv_buffer_size(0) + , m_disk_recv_buffer(allocator, 0) + {} + + 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(); + + bool packet_finished() const { return m_packet_size <= m_recv_pos; } + int pos() const { return m_recv_pos; } + int capacity() const { return m_recv_buffer.capacity() + m_disk_recv_buffer_size; } + + int regular_buffer_size() const + { + TORRENT_ASSERT(m_packet_size > 0); + return m_packet_size - m_disk_recv_buffer_size; + } + + // regular buffer only + boost::asio::mutable_buffer reserve(int size); + // with possible disk buffer usage + int reserve(boost::array& vec, int size); + + // tell the buffer we just receved 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() + + m_disk_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; } + + // make the buffer size dividible by 8 bytes (RC4 block size) + void clamp_size(); + + void set_soft_packet_size(int size) { m_soft_packet_size = size; } + + // 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. + buffer::const_interval get() const; + +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + // returns the entire regular buffer + // should only be used during the handshake + buffer::interval mutable_buffer(); + // returns the last 'bytes' from the receive buffer + void mutable_buffers(std::vector& vec, int bytes); +#endif + + void free_disk_buffer() + { + m_disk_recv_buffer.reset(); + m_disk_recv_buffer_size = 0; + } + + bool has_disk_buffer() const { return m_disk_recv_buffer; } + void assert_no_disk_buffer() const + { + TORRENT_ASSERT(!m_disk_recv_buffer); + TORRENT_ASSERT(m_disk_recv_buffer_size == 0); + } + + void assign_disk_buffer(char* buffer, int size); + char* release_disk_buffer(); + + // 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(); + bool normalized() const { return m_recv_start == 0; } + + void reset(int packet_size); + + bool can_recv_contiguous(int /*size*/) const { return true; } + +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const + { + TORRENT_ASSERT(m_recv_end >= m_recv_start); + TORRENT_ASSERT(bool(m_disk_recv_buffer) == (m_disk_recv_buffer_size > 0)); + } +#endif + +private: + // explicitly disallow assignment, to silence msvc warning + receive_buffer& operator=(receive_buffer const&); + + // recv_buf.begin (start of actual receive buffer) + // | + // | m_recv_start (logical start of current + // | | receive buffer, as perceived by upper layers) + // | | + // | | m_recv_pos (number of bytes consumed + // | | | by upper layer, from logical receive buffer) + // | | | + // | x---------x + // | | | recv_buf.end (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 + + // when not using contiguous receive buffers, there + // may be a disk_recv_buffer in the mix as well. Whenever + // m_disk_recv_buffer_size > 0 (and presumably also + // m_disk_recv_buffer != NULL) the disk buffer is imagined + // to be appended to the receive buffer right after m_recv_end. + + // the start of the logical receive buffer + int m_recv_start; + + // the number of valid, received bytes in m_recv_buffer + int m_recv_end; + + // 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; + + // the size (in bytes) of the bittorrent message + // we're currently receiving + int m_packet_size; + + // 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_soft_packet_size; + + int m_disk_recv_buffer_size; + + buffer m_recv_buffer; + + // if this peer is receiving a piece, this + // points to a disk buffer that the data is + // read into. This eliminates a memcopy from + // the receive buffer into the disk buffer + disk_buffer_holder m_disk_recv_buffer; +}; + +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) +// 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 crpyto layer while this class +// tracks the state of the bittorrent protocol. +struct crypto_receive_buffer +{ + crypto_receive_buffer(receive_buffer& next) + : m_recv_pos(INT_MAX) + , m_packet_size(0) + , m_soft_packet_size(0) + , m_connection_buffer(next) + {} + + buffer::interval mutable_buffer() { return m_connection_buffer.mutable_buffer(); } + char* release_disk_buffer() { return m_connection_buffer.release_disk_buffer(); } + bool has_disk_buffer() const { return m_connection_buffer.has_disk_buffer(); } + void assert_no_disk_buffer() const { m_connection_buffer.assert_no_disk_buffer(); } + + bool packet_finished() const; + + bool crypto_packet_finished() const + { + return m_recv_pos == INT_MAX || m_connection_buffer.packet_finished(); + } + + int packet_size() const; + + int crypto_packet_size() const + { + TORRENT_ASSERT(m_recv_pos != INT_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 != INT_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); + + void set_soft_packet_size(int size); + + int advance_pos(int bytes); + + buffer::const_interval get() const; + + bool can_recv_contiguous(int /*size*/) const + { + // TODO: Detect when the start of the next crpyto packet is aligned + // with the start of piece data and the crpyto packet is at least + // as large as the piece data. With a little extra work + // we could receive directly into a disk buffer in that case. + return m_recv_pos == INT_MAX; + } + + void mutable_buffers(std::vector& vec + , std::size_t bytes_transfered); + +private: + // explicitly disallow assignment, to silence msvc warning + crypto_receive_buffer& operator=(crypto_receive_buffer const&); + + int m_recv_pos; + int m_packet_size; + int m_soft_packet_size; + receive_buffer& m_connection_buffer; +}; +#endif // TORRENT_DISABLE_ENCRYPTION + +} // namespace libtorrent + +#endif // #ifndef TORRENT_RECEIVE_BUFFER_HPP_INCLUDED diff --git a/include/libtorrent/request_blocks.hpp b/include/libtorrent/request_blocks.hpp new file mode 100644 index 0000000..a5070d8 --- /dev/null +++ b/include/libtorrent/request_blocks.hpp @@ -0,0 +1,55 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_REQUEST_BLOCKS_HPP_INCLUDED +#define TORRENT_REQUEST_BLOCKS_HPP_INCLUDED + +namespace libtorrent +{ + class torrent; + class peer_connection; + + // returns false if the piece picker was not invoked, because + // of an early exit condition. In this case, the stats counter + // shouldn't be incremented, since it won't use any significant + // amount of CPU + bool request_a_block(torrent& t, peer_connection& c); + + // returns the rank of a peer's source. We have an affinity + // to connecting to peers with higher rank. This is to avoid + // problems when our peer list is diluted by stale peers from + // the resume data for instance + int source_rank(int source_bitmask); +} + +#endif + diff --git a/include/libtorrent/resolve_links.hpp b/include/libtorrent/resolve_links.hpp new file mode 100644 index 0000000..2449a39 --- /dev/null +++ b/include/libtorrent/resolve_links.hpp @@ -0,0 +1,89 @@ +/* + +Copyright (c) 2014-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_RESOLVE_LINKS_HPP +#define TORRENT_RESOLVE_LINKS_HPP + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/export.hpp" + +namespace libtorrent +{ + class torrent_info; + +#ifndef TORRENT_DISABLE_MUTABLE_TORRENTS + // this class is used for mutable torrents, to discover identical files + // in other torrents. + struct TORRENT_EXTRA_EXPORT resolve_links + { + struct TORRENT_EXTRA_EXPORT link_t + { + boost::shared_ptr ti; + std::string save_path; + int file_idx; + }; + + resolve_links(boost::shared_ptr ti); + + // check to see if any files are shared with this torrent + void match(boost::shared_ptr const& ti + , std::string const& save_path); + + std::vector const& get_links() const + { return m_links; } + + private: + // this is the torrent we're trying to find files for. + boost::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 + std::vector m_links; + + // maps file size to file index, in m_torrent_file + boost::unordered_multimap m_file_sizes; + }; +#endif // TORRENT_DISABLE_MUTABLE_TORRENTS + +} + +#endif + diff --git a/include/libtorrent/resolver.hpp b/include/libtorrent/resolver.hpp new file mode 100644 index 0000000..152f430 --- /dev/null +++ b/include/libtorrent/resolver.hpp @@ -0,0 +1,95 @@ +/* + +Copyright (c) 2013-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_RESOLVER_HPP_INCLUDE +#define TORRENT_RESOLVER_HPP_INCLUDE + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/error_code.hpp" +#include "libtorrent/io_service.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/resolver_interface.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/time.hpp" + +namespace libtorrent +{ + +struct TORRENT_EXTRA_EXPORT resolver TORRENT_FINAL : resolver_interface +{ + resolver(io_service& ios); + + virtual void async_resolve(std::string const& host, int flags + , callback_t const& h) TORRENT_OVERRIDE; + + virtual void abort() TORRENT_OVERRIDE; + +private: + + void on_lookup(error_code const& ec, tcp::resolver::iterator i + , resolver_interface::callback_t h, std::string hostname); + + struct dns_cache_entry + { + time_point last_seen; + std::vector
    addresses; + }; + + typedef boost::unordered_map cache_t; + cache_t m_cache; + io_service& m_ios; + + // all lookups in this resolver are aborted on shutdown. + tcp::resolver m_resolver; + + // lookups in this resolver are not aborted on shutdown + tcp::resolver m_critical_resolver; + + // max number of cached entries + int m_max_size; + + // timeout of cache entries + time_duration m_timeout; +}; + +} + +#endif + diff --git a/include/libtorrent/resolver_interface.hpp b/include/libtorrent/resolver_interface.hpp new file mode 100644 index 0000000..b410465 --- /dev/null +++ b/include/libtorrent/resolver_interface.hpp @@ -0,0 +1,77 @@ +/* + +Copyright (c) 2013-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_RESOLVER_INTERFACE_HPP_INCLUDE +#define TORRENT_RESOLVER_INTERFACE_HPP_INCLUDE + +#include +#include "libtorrent/error_code.hpp" +#include "libtorrent/address.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent +{ + +struct TORRENT_EXTRA_EXPORT resolver_interface +{ + typedef boost::function const&)> + callback_t; + + enum flags_t + { + // this flag will make async_resolve() always use the cache if we have an + // entry, regardless of how old it is. This is usefull when completing the + // lookup quickly is more important than accuracy + prefer_cache = 1, + + // set this flag for lookups that are not critical during shutdown. i.e. + // for looking up tracker names _except_ when stopping a tracker. + abort_on_shutdown = 2 + }; + + virtual void async_resolve(std::string const& host, int flags + , callback_t const& h) = 0; + + virtual void abort() = 0; +protected: + ~resolver_interface() {} +}; + +} + +#endif + diff --git a/include/libtorrent/rss.hpp b/include/libtorrent/rss.hpp new file mode 100644 index 0000000..78b7b33 --- /dev/null +++ b/include/libtorrent/rss.hpp @@ -0,0 +1,290 @@ +/* + +Copyright (c) 2010-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_RSS_HPP_INCLUDED +#define TORRENT_RSS_HPP_INCLUDED + +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/add_torrent_params.hpp" + +#include +#include + +#ifndef TORRENT_NO_DEPRECATE +namespace libtorrent +{ + namespace aux + { struct session_impl; } + + class session; + struct bdecode_node; + + // represents one item from an RSS feed. Specifically + // a feed of torrents. + // + struct TORRENT_EXPORT feed_item + { + feed_item(); +#if __cplusplus >= 201103L + feed_item(feed_item const&) = default; + feed_item & operator=(feed_item const&) = default; +#endif + ~feed_item(); + + // these are self explanatory and may be empty if the feed does not specify + // those fields. + std::string url; + std::string uuid; + std::string title; + std::string description; + std::string comment; + std::string category; + + // the total size of the content the torrent refers to, or -1 + // if no size was specified by the feed. + boost::int64_t size; + + // the handle to the torrent, if the session is already downloading + // this torrent. + torrent_handle handle; + + // the info-hash of the torrent, or cleared (i.e. all zeroes) if + // the feed does not specify the info-hash. + sha1_hash info_hash; + }; + + // given a feed_item ``f``, add the torrent it refers to to session ``s``. +#ifndef BOOST_NO_EXCEPTIONS + torrent_handle TORRENT_EXPORT add_feed_item(session& s, feed_item const& fi + , add_torrent_params const& p); +#endif + torrent_handle TORRENT_EXPORT add_feed_item(session& s, feed_item const& fi + , add_torrent_params const& p, error_code& ec); + + // the feed_settings object is all the information + // and configuration for a specific feed. All of + // these settings can be changed by the user + // after adding the feed + struct TORRENT_EXPORT feed_settings + { + feed_settings() + : auto_download(true) + , auto_map_handles(true) + , default_ttl(30) + {} + + std::string url; + + // By default ``auto_download`` is true, which means all torrents in + // the feed will be downloaded. Set this to false in order to manually + // add torrents to the session. You may react to the rss_alert when + // a feed has been updated to poll it for the new items in the feed + // when adding torrents manually. When torrents are added automatically, + // an add_torrent_alert is posted which includes the torrent handle + // as well as the error code if it failed to be added. You may also call + // ``session::get_torrents()`` to get the handles to the new torrents. + bool auto_download; + + // ``auto_map_handles`` defaults to true and determines whether or + // not to set the ``handle`` field in the feed_item, returned + // as the feed status. If auto-download is enabled, this setting + // is ignored. If auto-download is not set, setting this to false + // will save one pass through all the feed items trying to find + // corresponding torrents in the session. + bool auto_map_handles; + + // The ``default_ttl`` is the default interval for refreshing a feed. + // This may be overridden by the feed itself (by specifying the ```` + // tag) and defaults to 30 minutes. The field specifies the number of + // minutes between refreshes. + int default_ttl; + + // If torrents are added automatically, you may want to set the + // ``add_args`` to appropriate values for download directory etc. + // This object is used as a template for adding torrents from feeds, + // but some torrent specific fields will be overridden by the + // individual torrent being added. For more information on the + // add_torrent_params, see async_add_torrent() and add_torrent(). + add_torrent_params add_args; + }; + + // holds information about the status of an RSS feed. Retrieved by + // calling get_feed_status() on feed_handle. + struct TORRENT_EXPORT feed_status + { + feed_status(): last_update(0), next_update(0) + , updating(false), ttl(0) {} + + // the URL of the feed. + std::string url; + + // the name of the feed (as specified by the feed itself). This + // may be empty if we have not recevied a response from the RSS server yet, + // or if the feed does not specify a title. + std::string title; + + // the feed description (as specified by the feed itself). + // This may be empty if we have not received a response from the RSS server + // yet, or if the feed does not specify a description. + std::string description; + + // the posix time of the last successful response from the feed. + time_t last_update; + + // the number of seconds, from now, when the feed will be + // updated again. + int next_update; + + // true if the feed is currently being updated (i.e. waiting for + // DNS resolution, connecting to the server or waiting for the response to the + // HTTP request, or receiving the response). + bool updating; + + // a vector of all items that we have received from the feed. See + // feed_item for more information. + std::vector items; + + // set to the appropriate error code if the feed encountered an + // error. See error_code for more info. + error_code error; + + // the current refresh time (in minutes). It's either the configured + // default ttl, or the ttl specified by the feed. + int ttl; + }; + + struct feed; + + // The ``feed_handle`` refers to a specific RSS feed that is watched by the session. + struct TORRENT_EXPORT feed_handle + { + feed_handle() {} + + // Forces an update/refresh of the feed. Regular updates of the feed is managed + // by libtorrent, be careful to not call this too frequently since it may + // overload the RSS server. + void update_feed(); + + // Queries the RSS feed for information, including all the items in the feed. + // see feed_status. + feed_status get_feed_status() const; + + // Sets and gets settings for this feed. For more information on the + // available settings, see add_feed(). + void set_settings(feed_settings const& s); + feed_settings settings() const; + private: + friend struct aux::session_impl; + friend struct feed; + feed_handle(boost::weak_ptr const& p); + boost::weak_ptr m_feed_ptr; + }; + + struct feed_state; + class http_parser; + + boost::shared_ptr TORRENT_EXPORT new_feed(aux::session_impl& ses, feed_settings const& sett); + + // this is the internal object holding all state about an + // RSS feed. All user interaction with this object + // goes through the feed_handle, which makes sure all calls + // are posted to the network thread + struct TORRENT_EXTRA_EXPORT feed : boost::enable_shared_from_this + { + friend void parse_feed(feed_state& f, int token, char const* name, int len + , char const* val, int val_len); + + feed(aux::session_impl& ses, feed_settings const& feed); + + void on_feed(error_code const& ec, http_parser const& parser + , char const* data, int size); + + int update_feed(); + + aux::session_impl& session() const { return m_ses; } + + void set_settings(feed_settings const& s); + void get_settings(feed_settings* s) const; + void get_feed_status(feed_status* ret) const; + + int next_update(time_t now) const; + + void load_state(bdecode_node const& rd); + void save_state(entry& rd) const; + + private: + friend struct aux::session_impl; + + // explicitly disallow assignment, to silence msvc warning + feed& operator=(feed const&); + + void add_item(feed_item const& item); + + feed_handle my_handle(); + + error_code m_error; + std::vector m_items; + + // these are all the URLs we've seen in the items list. + // it's used to avoid adding duplicate entries to the actual + // item vector + std::set m_urls; + + // these are URLs that have been added to the session + // once. If we see them again, and they're not in the + // session, don't add them again, since it means they + // were removed from the session. It maps URLs to the + // posix time when they were added. The timestamp is + // used to prune this list by removing the oldest ones + // when the size gets too big + std::map m_added; + + std::string m_title; + std::string m_description; + time_t m_last_attempt; + time_t m_last_update; + // refresh rate of this feed in minutes + int m_ttl; + // the number of update failures in a row + int m_failures; + // true while waiting for the server to respond + bool m_updating; + feed_settings m_settings; + + aux::session_impl& m_ses; + }; + +} +#endif // TORRENT_NO_DEPRECATE + +#endif + diff --git a/include/libtorrent/session.hpp b/include/libtorrent/session.hpp new file mode 100644 index 0000000..f16f41e --- /dev/null +++ b/include/libtorrent/session.hpp @@ -0,0 +1,300 @@ +/* + +Copyright (c) 2006-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_SESSION_HPP_INCLUDED +#define TORRENT_SESSION_HPP_INCLUDED + +#include +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/config.hpp" +#include "libtorrent/version.hpp" +#include "libtorrent/build_config.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/io_service.hpp" + +#include "libtorrent/storage.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/session_handle.hpp" +#include "libtorrent/thread.hpp" + +#ifndef TORRENT_NO_DEPRECATE +#include "libtorrent/rss.hpp" +#include "libtorrent/fingerprint.hpp" +#endif + +#ifdef TORRENT_USE_OPENSSL +// this is a nasty openssl macro +#ifdef set_key +#undef set_key +#endif +#endif + +namespace libtorrent +{ + // 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 inteded 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 void min_memory_usage(settings_pack& set); + TORRENT_EXPORT void high_performance_seed(settings_pack& set); + +#ifndef TORRENT_NO_DEPRECATE + TORRENT_DEPRECATED + TORRENT_EXPORT session_settings min_memory_usage(); + TORRENT_DEPRECATED + TORRENT_EXPORT session_settings high_performance_seed(); +#endif + +#ifndef TORRENT_CFG +#error TORRENT_CFG is not defined! +#endif + + void TORRENT_EXPORT TORRENT_CFG(); + + namespace aux + { + struct session_impl; + } + + // this is a holder for the internal session implementation object. Once the + // session destruction is explicitly initiated, this holder is used to + // synchronize the completion of the shutdown. The lifetime of this object + // may outlive session, causing the session destructor to not block. The + // session_proxy destructor will block however, until the underlying session + // is done shutting down. + class TORRENT_EXPORT session_proxy + { + friend class session; + public: + // default constructor, does not refer to any session + // implementation object. + session_proxy() {} + ~session_proxy(); +#if __cplusplus >= 201103L + session_proxy(session_proxy const&) = default; + session_proxy& operator=(session_proxy const&) = default; +#endif + private: + session_proxy( + boost::shared_ptr ios + , boost::shared_ptr t + , boost::shared_ptr impl) + : m_io_service(ios) + , m_thread(t) + , m_impl(impl) + {} + boost::shared_ptr m_io_service; + boost::shared_ptr m_thread; + boost::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::apply_settings()`` member function. To change one or more + // configuration options, create a settings_pack. object and fill it with + // the settings to be set and pass it in to ``session::apply_settings()``. + // + // see apply_settings(). + class TORRENT_EXPORT session: public boost::noncopyable, public session_handle + { + public: + + // Constructs the session obects which acts as the container of torrents. + // It provides configuration options across torrents (such as rate limits, + // disk cache, ip filter etc.). In order to avoid a race condition between + // starting the session and configuring it, you can pass in a + // settings_pack object. Its settings will take effect before the session + // starts up. + // + // The ``flags`` parameter can be used to start default features (upnp & + // nat-pmp) and default plugins (ut_metadata, ut_pex and smart_ban). The + // default is to start those features. If you do not want them to start, + // pass 0 as the flags parameter. + session(settings_pack const& pack = settings_pack() + , int flags = start_default_features | add_default_plugins) + : session_handle(NULL) + { + TORRENT_CFG(); + start(flags, pack, NULL); + } + + // overload of the constructor that takes an external io_service to run + // the session object on. This is primarily useful for tests that may want + // to run multiple sessions on a single io_service, or low resource + // systems where additional threads are expensive and sharing an + // io_service with other events is fine. + // + // .. warning:: + // The session object does not cleanly terminate with an external + // ``io_service``. The ``io_service::run()`` call _must_ have returned + // before it's safe to destruct the session. Which means you *MUST* + // call session::abort() and save the session_proxy first, then + // destruct the session object, then sync with the io_service, then + // destruct the session_proxy object. + session(settings_pack const& pack + , io_service& ios + , int flags = start_default_features | add_default_plugins) + : session_handle(NULL) + { + TORRENT_CFG(); + start(flags, pack, &ios); + } + +#ifndef TORRENT_NO_DEPRECATE + TORRENT_DEPRECATED + session(fingerprint const& print + , int flags = start_default_features | add_default_plugins + , boost::uint32_t alert_mask = alert::error_notification) + : session_handle(NULL) + { + TORRENT_CFG(); + settings_pack pack; + pack.set_int(settings_pack::alert_mask, alert_mask); + pack.set_str(settings_pack::peer_fingerprint, print.to_string()); + if ((flags & start_default_features) == 0) + { + 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, pack, NULL); + } + + TORRENT_DEPRECATED + session(fingerprint const& print + , std::pair listen_port_range + , char const* listen_interface = "0.0.0.0" + , int flags = start_default_features | add_default_plugins + , int alert_mask = alert::error_notification) + : session_handle(NULL) + { + TORRENT_CFG(); + 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, 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 == NULL) listen_interface = "0.0.0.0"; + 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) == 0) + { + 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, pack, NULL); + } +#endif // TORRENT_NO_DEPRECATE + + // The destructor of session will notify all trackers that our torrents + // have been shut down. If some trackers are down, they will time out. + // All this before the destructor of session returns. So, it's advised + // that any kind of interface (such as windows) are closed before + // destructing the session object. Because it can take a few second for + // it to finish. The timeout can be set with apply_settings(). + ~session(); + + // In case you want to destruct the session asynchronously, you can + // request a session destruction proxy. If you don't do this, the + // destructor of the session object will block while the trackers are + // contacted. If you keep one ``session_proxy`` to the session when + // destructing it, the destructor will not block, but start to close down + // the session, the destructor of the proxy will then synchronize the + // threads. So, the destruction of the session is performed from the + // ``session`` destructor call until the ``session_proxy`` destructor + // call. The ``session_proxy`` does not have any operations on it (since + // the session is being closed down, no operations are allowed on it). + // The only valid operation is calling the destructor:: + // + // class session_proxy + // { + // public: + // session_proxy(); + // ~session_proxy() + // }; + session_proxy abort(); + + private: + + void start(int flags, settings_pack const& pack, io_service* ios); + + // data shared between the main thread + // and the working thread + boost::shared_ptr m_io_service; + boost::shared_ptr m_thread; + boost::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..c696bcb --- /dev/null +++ b/include/libtorrent/session_handle.hpp @@ -0,0 +1,1090 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SESSION_HANDLE_HPP_INCLUDED +#define TORRENT_SESSION_HANDLE_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/disk_io_thread.hpp" // for cached_piece_info +#include "libtorrent/alert.hpp" // alert::error_notification +#include "libtorrent/peer_class.hpp" +#include "libtorrent/peer_class_type_filter.hpp" +#include "libtorrent/session_settings.hpp" + +#include "libtorrent/kademlia/dht_storage.hpp" + +#ifndef TORRENT_NO_DEPRECATE +#include "libtorrent/rss.hpp" +#endif + +namespace libtorrent +{ + struct plugin; + struct torrent_plugin; + class torrent; + struct ip_filter; + class port_filter; + class alert; + +#ifndef TORRENT_NO_DEPRECATE + struct session_status; +#endif + + typedef boost::function& + , error_code&)> user_load_function_t; + + struct TORRENT_EXPORT session_handle + { + session_handle() : m_impl(NULL) {} + + session_handle(aux::session_impl* impl) + : m_impl(impl) + {} + + bool is_valid() const { return m_impl != NULL; } + + // TODO: 2 the ip filter should probably be saved here too + + // flags that determines which aspects of the session should be + // saved when calling save_state(). + enum save_state_flags_t + { + // saves settings (i.e. the settings_pack) + save_settings = 0x001, + + // saves dht_settings + save_dht_settings = 0x002, + + // saves dht state such as nodes and node-id, possibly accelerating + // joining the DHT if provided at next session startup. + save_dht_state = 0x004, + + // save pe_settings + save_encryption_settings = 0x020 + +#ifndef TORRENT_NO_DEPRECATE + , + save_as_map = 0x040, + // saves RSS feeds + save_feeds = 0x080, + save_proxy = 0x008, + save_i2p_proxy = 0x010, + save_dht_proxy = save_proxy, + save_peer_proxy = save_proxy, + save_web_proxy = save_proxy, + save_tracker_proxy = save_proxy +#endif + }; + + // loads and saves all session settings, including dht_settings, + // encryption settings and proxy settings. ``save_state`` writes all keys + // to the ``entry`` that's passed in, which needs to either not be + // initialized, or initialized as a dictionary. + // + // ``load_state`` expects a bdecode_node which can be built from a bencoded + // buffer with bdecode(). + // + // The ``flags`` argument is used to filter which parts of the session + // state to save or load. By default, all state is saved/restored (except + // for the individual torrents). see save_state_flags_t + void save_state(entry& e, boost::uint32_t flags = 0xffffffff) const; + void load_state(bdecode_node const& e, boost::uint32_t flags = 0xffffffff); + + // .. 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. + void get_torrent_status(std::vector* ret + , boost::function const& pred + , boost::uint32_t flags = 0) const; + void refresh_torrent_status(std::vector* ret + , boost::uint32_t flags = 0) 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 torrent_handle::status_flags_t. + void post_torrent_updates(boost::uint32_t flags = 0xffffffff); + + // This function will post a session_stats_alert object, containing a + // snapshot of the performance counters from the internals of libtorrent. + // To interpret these counters, query the session via + // session_stats_metrics(). + // + // For more information, see the session-statistics_ section. + void post_session_stats(); + + // This will cause a dht_stats_alert to be posted. + void post_dht_stats(); + + // internal + io_service& get_io_service(); + + // ``find_torrent()`` looks for a torrent with the given info-hash. In + // case there is such a torrent in the session, a torrent_handle to that + // torrent is returned. In case the torrent cannot be found, an invalid + // torrent_handle is returned. + // + // See ``torrent_handle::is_valid()`` to know if the torrent was found or + // not. + // + // ``get_torrents()`` returns a vector of torrent_handles to all the + // torrents currently in the session. + torrent_handle find_torrent(sha1_hash const& info_hash) const; + std::vector get_torrents() const; + + // You add torrents through the add_torrent() function where you give an + // object with all the parameters. The add_torrent() overloads will block + // until the torrent has been added (or failed to be added) and returns + // an error code and a torrent_handle. In order to add torrents more + // efficiently, consider using async_add_torrent() which returns + // immediately, without waiting for the torrent to add. Notification of + // the torrent being added is sent as add_torrent_alert. + // + // The overload that does not take an error_code throws an exception on + // error and is not available when building without exception support. + // The torrent_handle returned by add_torrent() can be used to retrieve + // information about the torrent's progress, its peers etc. It is also + // used to abort a torrent. + // + // If the torrent you are trying to add already exists in the session (is + // either queued for checking, being checked or downloading) + // ``add_torrent()`` will throw libtorrent_exception 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. + // + // all torrent_handles must be destructed before the session is destructed! +#ifndef BOOST_NO_EXCEPTIONS + torrent_handle add_torrent(add_torrent_params const& params); +#endif + torrent_handle add_torrent(add_torrent_params const& params, error_code& ec); + void async_add_torrent(add_torrent_params const& params); + +#ifndef BOOST_NO_EXCEPTIONS +#ifndef TORRENT_NO_DEPRECATE + // deprecated in 0.14 + TORRENT_DEPRECATED + torrent_handle add_torrent( + torrent_info const& ti + , std::string const& save_path + , entry const& resume_data = entry() + , storage_mode_t storage_mode = storage_mode_sparse + , bool paused = false + , storage_constructor_type sc = default_storage_constructor); + + // deprecated in 0.14 + TORRENT_DEPRECATED + torrent_handle add_torrent( + char const* tracker_url + , sha1_hash const& info_hash + , char const* name + , std::string const& save_path + , entry const& resume_data = entry() + , storage_mode_t storage_mode = storage_mode_sparse + , bool paused = false + , storage_constructor_type sc = default_storage_constructor + , void* userdata = 0); +#endif +#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; + + // This function enables dynamic-loading-of-torrent-files_. When a + // torrent is unloaded but needs to be availabe 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 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); + void set_load_function(user_load_function_t fun); + +#ifndef TORRENT_NO_DEPRECATE + // 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; + + // deprecated in libtorrent 1.1 + // fills out the supplied vector with information for each piece that is + // currently in the disk cache for the torrent with the specified + // info-hash (``ih``). + TORRENT_DEPRECATED + void get_cache_info(sha1_hash const& ih + , std::vector& ret) const; + + // Returns status of the disk cache for this session. + // For more information, see the cache_status type. + TORRENT_DEPRECATED + cache_status get_cache_status() const; +#endif + + enum { disk_cache_no_pieces = 1 }; + + // Fills in the cache_status struct with information about the given torrent. + // If ``flags`` is ``session::disk_cache_no_pieces`` the ``cache_status::pieces`` field + // will not be set. This may significantly reduce the cost of this call. + void get_cache_info(cache_status* ret, torrent_handle h = torrent_handle(), int flags = 0) const; + +#ifndef TORRENT_NO_DEPRECATE + // This adds an RSS feed to the session. The feed will be refreshed + // regularly and optionally add all torrents from the feed, as they + // appear. + // + // Before adding the feed, you must set the ``url`` field to the feed's + // url. It may point to an RSS or an atom feed. The returned feed_handle + // is a handle which is used to interact with the feed, things like + // forcing a refresh or querying for information about the items in the + // feed. For more information, see feed_handle. + TORRENT_DEPRECATED + feed_handle add_feed(feed_settings const& feed); + + // Removes a feed from being watched by the session. When this + // call returns, the feed handle is invalid and won't refer + // to any feed. + TORRENT_DEPRECATED + void remove_feed(feed_handle h); + + // Returns a list of all RSS feeds that are being watched by the session. + TORRENT_DEPRECATED + void get_feeds(std::vector& f) 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 + + // ``set_dht_settings`` sets some parameters available to the dht node. + // See dht_settings for more information. + // + // ``is_dht_running()`` returns true if the DHT support has been started + // and false + // otherwise. + // + // ``get_dht_settings()`` returns the current settings + void set_dht_settings(dht_settings const& settings); + bool is_dht_running() const; + dht_settings get_dht_settings() const; + + // ``set_dht_storage`` set a dht custom storage constructor function + // to be used internally when the dht is created. + // + // Since the dht storage is a critical component for the dht behavior, + // this function will only be effective the next time the dht is started. + // If you never touch this feature, a default map-memory based storage + // is used. + // + // If you want to make sure the dht is initially created with your + // custom storage, create a session with the setting + // ``settings_pack::enable_dht`` to false, set your constructor function + // and call ``apply_settings`` with ``settings_pack::enable_dht`` to true. + void set_dht_storage(dht::dht_storage_constructor_type sc); + + // ``add_dht_node`` takes a host name and port pair. That endpoint will be + // pinged, and if a valid DHT reply is received, the node will be added to + // the routing table. + // + // ``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``. + void add_dht_node(std::pair const& node); + void add_dht_router(std::pair const& node); + + // 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(boost::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. + // + // boost::array& signature + // the signature authenticating the current value. This may be zeroes + // 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. + // + // boost::uint64_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 blobs' values are 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(boost::array key + , boost::function& + , boost::uint64_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, int flags = 0); + + // 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 ep, entry const& e, void* userdata = 0); + +#ifndef TORRENT_NO_DEPRECATE + // 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 ``boost::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: + // + // metadata extension + // Allows peers to download the metadata (.torren files) from the swarm + // directly. Makes it possible to join a swarm with just a tracker and + // info-hash. + // + // :: + // + // #include + // ses.add_extension(&libtorrent::create_metadata_plugin); + // + // uTorrent metadata + // Same as ``metadata extension`` but compatible with uTorrent. + // + // :: + // + // #include + // ses.add_extension(&libtorrent::create_ut_metadata_plugin); + // + // uTorrent peer exchange + // Exchanges peers between clients. + // + // :: + // + // #include + // ses.add_extension(&libtorrent::create_ut_pex_plugin); + // + // smart ban plugin + // A plugin that, with a small overhead, can ban peers + // that sends bad data with very high accuracy. Should + // eliminate most problems on poisoned torrents. + // + // :: + // + // #include + // ses.add_extension(&libtorrent::create_smart_ban_plugin); + // + // + // .. _`libtorrent plugins`: libtorrent_plugins.html + void add_extension(boost::function( + torrent_handle const&, void*)> ext); + void add_extension(boost::shared_ptr ext); + +#ifndef TORRENT_NO_DEPRECATE + // GeoIP support has been removed from libtorrent internals. If you + // still need to resolve peers, please do so on the client side, using + // libgeoip directly. This was removed in libtorrent 1.1 + + // These functions expects a path to the `MaxMind ASN database`_ and + // `MaxMind GeoIP database`_ respectively. This will be used to look up + // which AS and country peers belong to. + // + // ``as_for_ip`` returns the AS number for the IP address specified. If + // the IP is not in the database or the ASN database is not loaded, 0 is + // returned. + // + // .. _`MaxMind ASN database`: http://www.maxmind.com/app/asnum + // .. _`MaxMind GeoIP database`: http://www.maxmind.com/app/geolitecountry + TORRENT_DEPRECATED + void load_asnum_db(char const* file); + TORRENT_DEPRECATED + void load_country_db(char const* file); + TORRENT_DEPRECATED + int as_for_ip(address const& addr); +#if TORRENT_USE_WSTRING + // all wstring APIs are deprecated since 0.16.11 + // instead, use the wchar -> utf8 conversion functions + // and pass in utf8 strings + TORRENT_DEPRECATED + void load_country_db(wchar_t const* file); + TORRENT_DEPRECATED + void load_asnum_db(wchar_t const* file); +#endif // TORRENT_USE_WSTRING + + // deprecated in 0.15 + // use load_state and save_state instead + TORRENT_DEPRECATED + void load_state(entry const& ses_state + , boost::uint32_t flags = 0xffffffff); + TORRENT_DEPRECATED + entry state() const; + // deprecated in 1.1 + TORRENT_DEPRECATED + void load_state(lazy_entry const& ses_state + , boost::uint32_t flags = 0xffffffff); +#endif // TORRENT_NO_DEPRECATE + + // Sets a filter that will be used to reject and accept incoming as well + // as outgoing connections based on their originating ip address. The + // default filter will allow connections to any ip address. To build a + // set of rules for which addresses are accepted and not, see ip_filter. + // + // Each time a peer is blocked because of the IP filter, a + // peer_blocked_alert is generated. ``get_ip_filter()`` Returns the + // ip_filter currently in the session. See ip_filter. + void set_ip_filter(ip_filter const& f); + ip_filter get_ip_filter() const; + + // apply port_filter ``f`` to incoming and outgoing peers. a port filter + // will reject making outgoing peer connections to certain remote ports. + // The main intention is to be able to avoid triggering certain + // anti-virus software by connecting to SMTP, FTP ports. + void set_port_filter(port_filter const& f); + +#ifndef TORRENT_NO_DEPRECATE + // deprecated in 1.1, use settings_pack::peer_fingerprint instead + TORRENT_DEPRECATED + void set_peer_id(peer_id const& pid); +#endif + + // returns the raw peer ID used by libtorrent. When anonymous mode is set + // the peer ID is randomized per peer. + peer_id id() const; + + // 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(int key); + + // built-in peer classes + enum { + global_peer_class_id, + tcp_peer_class_id, + local_peer_class_id + }; + + // ``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. If the + // port specified in settings_pack::listen_interfaces failed, libtorrent + // will try to bind to the next port, and so on. If it fails + // settings_pack::max_retry_port_bind times, it will bind to port 0 + // (meaning the OS picks the port). The only way to know which port it + // ended up binding to is to ask for it by calling ``listen_port()``. + // + // If all ports in the specified range fails to be opened for listening, + // libtorrent will try to use port 0 (which tells the operating system to + // pick a port that's free). If that still fails you may see a + // listen_failed_alert with port 0 even if you didn't ask to listen on + // it. + // + // It is possible to prevent libtorrent from binding to port 0 by passing + // in the flag ``session::no_system_port`` in the ``flags`` argument. + // + // The interface parameter can also be a hostname that will resolve to + // the device you want to listen on. If you don't specify an interface, + // libtorrent may attempt to listen on multiple interfaces (typically + // 0.0.0.0 and ::). This means that if your IPv6 interface doesn't work, + // you may still see a listen_failed_alert, even though the IPv4 port + // succeeded. + // + // The ``flags`` parameter can either be 0 or + // ``session::listen_reuse_address``, which will set the reuse address + // socket option on the listen socket(s). By default, the listen socket + // does not use reuse address. If you're running a service that needs to + // run on a specific port no matter if it's in use, set this flag. + 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:: + // + // ip_filter f; + // int my_class = ses.create_peer_class("200.1.x.x IP range"); + // f.add_rule(address_v4::from_string("200.1.1.0") + // , address_v4::from_string("200.1.255.255") + // , 1 << 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. + // + // For more information, see peer-classes_. + void set_peer_class_filter(ip_filter const& f); + + // 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_. + // TODO: add get_peer_class_type_filter() as well + void set_peer_class_type_filter(peer_class_type_filter const& f); + + // 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_. + int 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(int 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(int cid); + void set_peer_class(int cid, peer_class_info const& pci); + +#ifndef TORRENT_NO_DEPRECATE + // 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 = 0x01, + listen_no_system_port = 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 = 0 + , int flags = 0); +#endif + + // flags to be passed in to remove_torrent(). + enum options_t + { + // delete the files belonging to the torrent from disk. + // including the part-file, if there is one + delete_files = 1, + + // delete just the part-file associated with this torrent + delete_partfile = 2 + }; + + // flags to be passed in to the session constructor + enum session_flags_t + { + // this will add common extensions like ut_pex, ut_metadata, lt_tex + // smart_ban and possibly others. + add_default_plugins = 1, + + // this will start features like DHT, local service discovery, UPnP + // and NAT-PMP. + start_default_features = 2 + }; + + // ``remove_torrent()`` will close all peer connections associated with + // the torrent and tell the tracker that we've stopped participating in + // the swarm. This operation cannot fail. When it completes, you will + // receive a torrent_removed_alert. + // + // The optional second argument ``options`` can be used to delete all the + // files downloaded by this torrent. To do so, pass in the value + // ``session::delete_files``. The removal of the torrent is asynchronous, + // there is no guarantee that adding the same torrent immediately after + // it was removed will not throw a libtorrent_exception exception. Once + // the torrent is deleted, a torrent_deleted_alert is posted. + void remove_torrent(const torrent_handle& h, int options = 0); + +#ifndef TORRENT_NO_DEPRECATE + // deprecated in aio-branch + // Sets the session settings and the packet encryption settings + // respectively. See session_settings and pe_settings for more + // information on available options. + TORRENT_DEPRECATED + void set_settings(session_settings const& s); + TORRENT_DEPRECATED + session_settings settings() const; + + // deprecated in libtorrent 1.1. use settings_pack instead + TORRENT_DEPRECATED + void set_pe_settings(pe_settings const& settings); + TORRENT_DEPRECATED + pe_settings get_pe_settings() const; +#endif + + // Applies the settings specified by the settings_pack ``s``. This is an + // asynchronous operation that will return immediately and actually apply + // the settings to the main thread of libtorrent some time later. + void apply_settings(settings_pack const& s); + settings_pack get_settings() const; + +#ifndef TORRENT_NO_DEPRECATE + // ``set_i2p_proxy`` sets the i2p_ proxy, and tries to open a persistant + // connection to it. The only used fields in the proxy settings structs + // are ``hostname`` and ``port``. + // + // ``i2p_proxy`` returns the current i2p proxy in use. + // + // .. _i2p: http://www.i2p2.de + + TORRENT_DEPRECATED + void set_i2p_proxy(proxy_settings const& s); + TORRENT_DEPRECATED + proxy_settings i2p_proxy() const; + + // These functions sets and queries the proxy settings to be used for the + // session. + // + // For more information on what settings are available for proxies, see + // proxy_settings. If the session is not in anonymous mode, proxies that + // aren't working or fail, will automatically be disabled and packets + // will flow without using any proxy. If you want to enforce using a + // proxy, even when the proxy doesn't work, enable anonymous_mode in + // settings_pack. + TORRENT_DEPRECATED + void set_proxy(proxy_settings const& s); + TORRENT_DEPRECATED + proxy_settings proxy() const; + + // deprecated in 0.16 + // Get the number of uploads. + TORRENT_DEPRECATED + int num_uploads() const; + + // Get the number of connections. This number also contains the + // number of half open connections. + TORRENT_DEPRECATED + int num_connections() const; + + // deprecated in 0.15. + TORRENT_DEPRECATED + void set_peer_proxy(proxy_settings const& s); + TORRENT_DEPRECATED + void set_web_seed_proxy(proxy_settings const& s); + TORRENT_DEPRECATED + void set_tracker_proxy(proxy_settings const& s); + + TORRENT_DEPRECATED + proxy_settings peer_proxy() const; + TORRENT_DEPRECATED + proxy_settings web_seed_proxy() const; + TORRENT_DEPRECATED + proxy_settings tracker_proxy() const; + + TORRENT_DEPRECATED + void set_dht_proxy(proxy_settings const& s); + TORRENT_DEPRECATED + proxy_settings dht_proxy() const; + + // deprecated in 0.16 + TORRENT_DEPRECATED + int upload_rate_limit() const; + TORRENT_DEPRECATED + int download_rate_limit() const; + TORRENT_DEPRECATED + int local_upload_rate_limit() const; + TORRENT_DEPRECATED + int local_download_rate_limit() const; + TORRENT_DEPRECATED + int max_half_open_connections() const; + + TORRENT_DEPRECATED + void set_local_upload_rate_limit(int bytes_per_second); + TORRENT_DEPRECATED + void set_local_download_rate_limit(int bytes_per_second); + TORRENT_DEPRECATED + void set_upload_rate_limit(int bytes_per_second); + TORRENT_DEPRECATED + void set_download_rate_limit(int bytes_per_second); + TORRENT_DEPRECATED + void set_max_uploads(int limit); + TORRENT_DEPRECATED + void set_max_connections(int limit); + TORRENT_DEPRECATED + void set_max_half_open_connections(int limit); + + TORRENT_DEPRECATED + int max_connections() const; + TORRENT_DEPRECATED + int max_uploads() const; + + TORRENT_DEPRECATED + void pop_alerts(std::deque* alerts); + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + + TORRENT_DEPRECATED + std::auto_ptr pop_alert(); + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +#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 NULL. + // + // 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). + // + // 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. + void pop_alerts(std::vector* alerts); + alert* wait_for_alert(time_duration max_wait); + void set_alert_notify(boost::function const& fun); + +#ifndef TORRENT_NO_DEPRECATE + TORRENT_DEPRECATED + void set_severity_level(alert::severity_t s); + + // 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(boost::uint32_t m); + TORRENT_DEPRECATED + boost::uint32_t get_alert_mask() const; + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + + // This sets a function to be called (from within libtorrent's netowrk + // thread) every time an alert is posted. Since the function (``fun``) is + // run in libtorrent's internal thread, it may not block. + // + // The main intention with this function is to support integration with + // platform-dependent message queues or signalling systems. For instance, + // on windows, one could post a message to an HNWD or on linux, write to + // a pipe or an eventfd. + TORRENT_DEPRECATED + void set_alert_dispatch( + boost::function)> const& fun); + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + + // Starts and stops Local Service Discovery. This service will broadcast + // the infohashes 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() + enum protocol_type { udp = 1, tcp = 2 }; + + // add_port_mapping adds a port forwarding on UPnP and/or NAT-PMP, + // whichever is enabled. The return value is a handle referring to the + // port mapping that was just created. Pass it to delete_port_mapping() + // to remove it. + int add_port_mapping(protocol_type t, int external_port, int local_port); + void delete_port_mapping(int handle); + + // 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. + aux::session_impl* native_handle() const + { return m_impl; } + + private: + aux::session_impl* m_impl; + }; + +} // namespace libtorrent + +#endif // TORRENT_SESSION_HANDLE_HPP_INCLUDED diff --git a/include/libtorrent/session_settings.hpp b/include/libtorrent/session_settings.hpp new file mode 100644 index 0000000..031e526 --- /dev/null +++ b/include/libtorrent/session_settings.hpp @@ -0,0 +1,1579 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SESSION_SETTINGS_HPP_INCLUDED +#define TORRENT_SESSION_SETTINGS_HPP_INCLUDED + +#include "libtorrent/version.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/aux_/proxy_settings.hpp" + +#include +#include +#include +#include + +namespace libtorrent +{ + +#ifndef TORRENT_NO_DEPRECATE + + typedef aux::proxy_settings proxy_settings; + + // This holds most of the session-wide settings in libtorrent. Pass this + // to session::set_settings() to change the settings, initialize it from + // session::get_settings() to get the current settings. + struct TORRENT_EXPORT session_settings + { + // initializes the session_settings to the default settings. + session_settings(std::string const& user_agent = "libtorrent/" + LIBTORRENT_VERSION); + ~session_settings(); +#if __cplusplus >= 201103L + session_settings(session_settings const&) = default; + session_settings& operator=(session_settings const&) = default; +#endif + + // automatically set to the libtorrent version you're using in order to + // be forward binary compatible. This field should not be changed. + int version; + + // the client identification to the tracker. The recommended format of + // this string is: "ClientName/ClientVersion + // libtorrent/libtorrentVersion". This name will not only be used when + // making HTTP requests, but also when sending extended headers to peers + // that support that extension. + std::string user_agent; + + // the number of seconds the tracker connection will wait from when it + // sent the request until it considers the tracker to have timed-out. + // Default value is 60 seconds. + int tracker_completion_timeout; + + // 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. The default value is 20 seconds. + int tracker_receive_timeout; + + // the time 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 + // + // This is given in seconds. Default is 10 seconds. + int stop_tracker_timeout; + + // the maximum number of bytes in a tracker response. If a response size + // passes this number 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 + // megs, it will be interrupted before the entire response has been + // uncompressed (given your limit is lower than 2 megs). Default limit is + // 1 megabyte. + int tracker_maximum_response_length; + + // controls the number of seconds from a request is sent until it times + // out if no piece response is returned. + int piece_timeout; + + // the number of seconds one block (16kB) is expected to be received + // within. If it's not, the block is requested from a different peer + int 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. + int 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. + int max_allowed_in_request_queue; + + // 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. + int 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. + int whole_pieces_threshold; + + // the number of seconds to wait for any activity on the peer wire before + // closing the connection due to time out. This defaults to 120 seconds, + // since that's what's specified in the protocol specification. After + // half the time out, a keep alive message is sent. + int 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. This + // value defaults to 20 seconds. + int urlseed_timeout; + + // controls the pipelining with the web server. When using persistent + // connections to HTTP 1.1 servers, the client is allowed to send more + // requests before the first response is received. This number controls + // the number of outstanding requests to use with url-seeds. Default is + // 5. + int urlseed_pipeline_size; + + // time to wait until a new retry takes place + int 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. It is usually a good idea to find + // this limit and set the number of connections and the number of files + // limits so their sum is slightly below it. + int file_pool_size; + + // determines if connections from the same IP address as existing + // connections should be rejected or not. Multiple connections from the + // same IP address is not allowed by default, to prevent abusive behavior + // by peers. It may be useful to allow such connections in cases where + // simulations are run on the same machine, and all peers in a swarm has + // the same IP address. + bool allow_multiple_connections_per_ip; + + // the maximum times we try to connect to a peer before stop connecting + // again. If a peer succeeds, its failcounter is reset. If a peer is + // retrieved from a peer source (other than DHT) the failcount is + // decremented by one, allowing another try. + int max_failcount; + + // the number of seconds to wait to reconnect to a peer. this time is + // multiplied with the failcount. + int min_reconnect_time; + + // the number of seconds to wait after a connection attempt is initiated + // to a peer until it is considered as having timed out. The default is + // 10 seconds. 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. + int peer_connect_timeout; + +#ifndef TORRENT_NO_DEPRECATE + // deprecated, use set_peer_class_filter() instead + // if set to true, upload, download and unchoke limits + // are ignored for peers on the local network. + bool ignore_limits_on_local_network; +#endif + + // 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. + int connection_speed; + + // if this is set to true, 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. Default is + // false. + bool send_redundant_have; + + // prevents outgoing bitfields from being full. If the client is seed, a + // few bits will be set to 0, and later filled in with have-messages. + // This is an old attempt to prevent certain ISPs from stopping people + // from seeding. + bool lazy_bitfields; + + // if a peer is uninteresting and uninterested for longer than this + // number of seconds, it will be disconnected. default is 10 minutes + int inactivity_timeout; + + // 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. + int unchoke_interval; + + // the number of seconds between each *optimistic* unchoke. On this + // timer, the currently optimistically unchoked peer will change. + int optimistic_unchoke_interval; + + // the ip address passed along to trackers as the ``&ip=`` parameter. If + // left as the default (an empty string), that parameter is omitted. Most + // trackers ignore this argument. This is here for completeness for + // edge-cases where it may be useful. + std::string announce_ip; + + // the number of peers we want from each tracker request. It defines what + // is sent as the ``&num_want=`` parameter to the tracker. Stopped + // messages always send num_want=0. This setting control what to say in + // the case where we actually want peers. + int num_want; + + // specifies the number of pieces we need before we switch to rarest + // first picking. This defaults to 4, which means the 4 first pieces in + // any torrent are picked at random, the following pieces are picked in + // rarest first order. + int initial_picker_threshold; + + // the number of allowed pieces to send to choked peers that supports the + // fast extensions + int allowed_fast_set_size; + + // options for session_settings::suggest_mode. + enum suggest_mode_t + { + // the default. will not send out suggest messages. + no_piece_suggestions = 0, + + // send out suggest messages for the most recent pieces that are in + // the read cache. + suggest_read_cache = 1 + }; + + // this determines which pieces will be suggested to peers suggest read + // cache will make libtorrent suggest pieces that are fresh in the disk + // read cache, to potentially lower disk access and increase the cache + // hit ratio + // + // for options, see suggest_mode_t. + int suggest_mode; + + // the maximum number of bytes a connection may have pending in the disk + // write queue before its download rate is being throttled. This prevents + // fast downloads to slow medias to allocate more memory indefinitely. + // This should be set to at least 16 kB to not completely disrupt normal + // downloads. If it's set to 0, you will be starving the disk thread and + // nothing will be written to disk. this is a per session setting. + // + // 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. + int max_queued_disk_bytes; + +#ifndef TORRENT_NO_DEPRECATE + // not used anymore + int max_queued_disk_bytes_low_watermark; +#endif + + // 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. + int handshake_timeout; + + // 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. This is false by + // default, which means the DHT is used by default regardless of if the + // trackers fail or not. + bool use_dht_as_fallback; + + // determines whether or not the torrent's piece hashes are kept in + // memory after the torrent becomes a seed or not. If it is set to + // ``true`` the hashes are freed once the torrent is a seed (they're not + // needed anymore since the torrent won't download anything more). If + // it's set to false they are not freed. If they are freed, the + // torrent_info returned by get_torrent_info() will return an object that + // may be incomplete, that cannot be passed back to async_add_torrent() + // and add_torrent() for instance. + bool free_torrent_hashes; + + // indicates whether or not the UPnP implementation should ignore any + // broadcast response from a device whose address is not the configured + // router for this machine. i.e. it's a way to not talk to other people's + // routers by mistake. + bool upnp_ignore_nonrouters; + + // This is 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 + int send_buffer_low_watermark; + + // the upper limit of the send buffer low-watermark. + // + // if the send buffer has fewer bytes than this, we'll read another 16kB + // 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. + int send_buffer_watermark; + + // 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 indicates a factor of 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. The default is 50. + // + // 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. + int send_buffer_watermark_factor; + + // the different choking algorithms available. Set + // session_settings::choking_algorithm to one of these + enum choking_algorithm_t + { + // the traditional choker with a fixed number of unchoke slots, as + // specified by session::set_max_uploads().. + fixed_slots_choker = 0, + +#ifndef TORRENT_NO_DEPRECATE + // opens at least the number of slots as specified by + // session::set_max_uploads() but opens up more slots if the upload + // capacity is not saturated. This unchoker will work just like the + // ``fixed_slot_choker`` if there's no global upload rate limit set. + auto_expand_choker = 1, +#endif + + // 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. + rate_based_choker = 1, + + // attempts to optimize download rate by finding the reciprocation + // rate of each peer individually and prefers peers that gives the + // highest *return on investment*. It still allocates all upload + // capacity, but shuffles it around to the best peers first. For this + // choker to be efficient, you need to set a global upload rate limit + // session_settings::upload_rate_limit. For more information about + // this choker, see the paper_. + // + // .. _paper: http://bittyrant.cs.washington.edu/#papers + bittyrant_choker = 2 + }; + + // specifies which algorithm to use to determine which peers to unchoke. + // This setting replaces the deprecated settings ``auto_upload_slots`` + // and ``auto_upload_slots_rate_based``. For options, see + // choking_algorithm_t. + int choking_algorithm; + + // the different choking algorithms available when seeding. Set + // session_settings::seed_choking_algorithm to one of these + enum seed_choking_algorithm_t + { + // round-robins the peers that are unchoked when seeding. This + // distributes the upload bandwidht 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. + anti_leech + }; + + // controls the seeding unchoke behavior. For options, see + // seed_choking_algorithm_t. + int seed_choking_algorithm; + + // 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. + bool use_parole_mode; + + // 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 to the amount of physical RAM available in the + // machine divided by 8. If the amount of physical RAM cannot be + // determined, it's set to 1024 (= 16 MiB). + // + // 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``. This defaults to 16 blocks. + // Lower numbers saves memory at the expense of more heap allocations. It + // must be at least 1. + int cache_size; + + // this is the number of disk buffer blocks (16 kiB) that should be + // allocated at a time. It must be at least 1. Lower number saves memory + // at the expense of more heap allocations + // setting this to zero means 'automatic', i.e. proportional + // to the total disk cache size + int cache_buffer_chunk_size; + + // the number of seconds a write cache entry sits idle in the cache + // before it's forcefully flushed to disk. + int cache_expiry; + + // when set to true (default), the disk cache is also used to cache + // pieces read from disk. Blocks for writing pieces takes presedence. + bool use_read_cache; + bool use_write_cache; + + // 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 + bool dont_flush_write_cache; + +#ifndef TORRENT_NO_DEPRECATE + // defaults to 0. If set to something greater than 0, the disk read cache + // will not be evicted by cache misses and will explicitly be controlled + // based on the rarity of pieces. Rare pieces are more likely to be + // cached. This would typically be used together with ``suggest_mode`` + // set to ``suggest_read_cache``. The value is the number of pieces to + // keep in the read cache. If the actual read cache can't fit as many, it + // will essentially be clamped. + bool explicit_read_cache; + + // the number of seconds in between each refresh of a part of the + // explicit read cache. Torrents take turns in refreshing and this is the + // time in between each torrent refresh. Refreshing a torrent's explicit + // read cache means scanning all pieces and picking a random set of the + // rarest ones. There is an affinity to pick pieces that are already in + // the cache, so that subsequent refreshes only swaps in pieces that are + // rarer than whatever is in the cache at the time. + int explicit_cache_interval; +#endif + + // the buffer modes to use for reading and writing. Set + // session_settings::disk_io_read_mode and disk_io_write_mode to one of + // these. + enum io_buffer_mode_t + { + // This is the default and files are opened normally, with the OS + // caching reads and writes. + enable_os_cache = 0, + // This will open files in unbuffered mode for files where every read + // and write would be sector aligned. Using aligned disk offsets is a + // requirement on some operating systems. + disable_os_cache_for_aligned_files = 1, + // This opens all files in unbuffered mode (if allowed by the + // operating system). Linux and Windows, for instance, require disk + // offsets to be sector aligned, and in those cases, this option is + // the same as ``disable_os_caches_for_aligned_files``. + disable_os_cache = 2 + }; + + // determines how files are opened when they're in read only mode versus + // read and write mode. For options, see io_buffer_mode_t. + // + // One reason to disable caching is that it may help the operating system + // from growing its file cache indefinitely. Since some OSes only allow + // aligned files to be opened in unbuffered mode, It is recommended to + // make the largest file in a torrent the first file (with offset 0) or + // use pad files to align all files to piece boundaries. + int disk_io_write_mode; + int disk_io_read_mode; + + // 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 + bool coalesce_reads; + bool coalesce_writes; + + // if set to something other than (0, 0) is a range of ports used to bind + // outgoing sockets to. This may be useful for users whose router allows + // them to assign QoS classes to traffic based on its local port. 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. + // + //.. 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. + int outgoing_port; + int num_outgoing_ports; + + // determines the TOS byte set in the IP header of every packet sent to + // peers (including web seeds). The default value for this is ``0x0`` (no + // marking). One potentially useful TOS mark is ``0x20``, this represents + // the *QBone scavenger service*. For more details, see QBSS_. + // + // .. _`QBSS`: http://qbone.internet2.edu/qbss/ + char 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_dht_limit`` and ``active_tracker_limit`` limits the number of + // torrents that will be active on the DHT and their tracker. If the + // active limit is set higher than these numbers, some torrents will be + // "active" in the sense that they will accept incoming connections, but + // not announce on the DHT or their trackers. + // + // ``active_lsd_limit`` is the max number of torrents to announce to the + // local network over the local service discovery protocol. By default + // this is 80, which is no more than one announce every 5 seconds + // (assuming the default announce interval of 5 minutes). + // + // ``active_limit`` is a hard limit on the number of active torrents. + // This applies even to slow torrents. + // + // 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`` 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 also counted against these + // limits. If there are non-auto managed torrents that use up all the + // slots, no auto managed torrent will be activated. + int active_downloads; + int active_seeds; + int active_dht_limit; + int active_tracker_limit; + int active_lsd_limit; + int active_limit; + + // prefer seeding torrents when determining which torrents to give active + // slots to, the default is false which gives preference to downloading + // torrents + bool auto_manage_prefer_seeds; + + // if 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. + bool dont_count_slow_torrents; + + // the number of seconds in between recalculating which torrents to + // activate and which ones to queue + int auto_manage_interval; + + // 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 the default value for + // share ratio is 2 the default seed time ratio is 7, because that's a + // common asymmetry ratio on connections. these are specified as + // percentages + // + //.. note:: + // This is an out-dated option that doesn't make much sense. It will be + // removed in future versions of libtorrent + float share_ratio_limit; + + // the seeding time / downloading time ratio limit for considering a + // seeding torrent to have met the seed limit criteria. See queuing_. + float seed_time_ratio_limit; + + // seed time limit is specified in seconds + // + // 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_. + int seed_time_limit; + + // controls a feature where libtorrent periodically can disconnect the + // least useful peers in the hope of connecting to better ones. + // ``peer_turnover_interval`` controls the interval of this optimistic + // disconnect. It defaults to every 5 minutes, and is specified in + // seconds. + // + // ``peer_turnover`` Is the fraction of the peers that are disconnected. + // This is a float where 1.f represents all peers an 0 represents no + // peers. It defaults to 4% (i.e. 0.04f) + // + // ``peer_turnover_cutoff`` is the cut off trigger for optimistic + // unchokes. If a torrent has more than this fraction of its connection + // limit, the optimistic unchoke is triggered. This defaults to 90% (i.e. + // 0.9f). + int peer_turnover_interval; + + // the percentage of peers to disconnect every + // turnover interval (if we're at the peer limit) + // defaults to 4% + // this is specified in percent + float peer_turnover; + + // when we are connected to more than + // limit * peer_turnover_cutoff peers + // disconnect peer_turnover fraction + // of the peers. It is specified in percent + float peer_turnover_cutoff; + + // 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. This defaults to ``true``. + bool close_redundant_connections; + + // 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. + int auto_scrape_interval; + + // 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. + int auto_scrape_min_interval; + + // 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. + int max_peerlist_size; + + // the max peer list size used for torrents that are paused. This default + // to the same as ``max_peerlist_size``, but can be used to save memory + // for paused torrents, since it's not as important for them to keep a + // large peer list. + int max_paused_peerlist_size; + + // the minimum allowed announce interval for a tracker. This is specified + // in seconds, defaults to 5 minutes and is used as a sanity check on + // what is returned from a tracker. It mitigates hammering misconfigured + // trackers. + int min_announce_interval; + + // If 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. + bool prioritize_partial_pieces; + + // 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. + int auto_manage_startup; + + // if set to true, the estimated TCP/IP overhead is drained from the rate + // limiters, to avoid exceeding the limits with the total traffic + bool rate_limit_ip_overhead; + + // 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. It defaults to false, which is the same + // behavior previous versions of libtorrent has had as well. + bool announce_to_all_trackers; + + // 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. This is false by default in order to comply with the + // multi-tracker specification. + bool announce_to_all_tiers; + + // true by default. It 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 fails means that the trackers' tier is + // respected and there's no preference of one protocol over another. + bool prefer_udp_trackers; + + // 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. + bool strict_super_seeding; + + // the number of pieces to send to a peer, when seeding, before rotating + // in another peer to the unchoke set. It defaults to 3 pieces, which + // means that when seeding, any peer we've sent more than this number of + // pieces to will be unchoked in favour of a choked peer. + int seeding_piece_quota; + + // is a limit of the number of *sparse regions* in a torrent. A sparse + // region is defined as a hole of pieces we have not yet downloaded, in + // between pieces that have been downloaded. This is used as a hack for + // windows vista which has a bug where you cannot write files with more + // than a certain number of sparse regions. This limit is not hard, it + // will be exceeded. Once it's exceeded, pieces that will maintain or + // decrease the number of sparse regions are prioritized. To disable this + // functionality, set this to 0. It defaults to 0 on all platforms except + // windows. + int max_sparse_regions; + + // if lock disk cache is set to true the disk cache that's in use, will + // be locked in physical memory, preventing it from being swapped out. + bool lock_disk_cache; + + // 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. + int max_rejects; + + // specifies the buffer sizes set on peer sockets. 0 (which is the + // default) 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. + int recv_socket_buffer_size; + int send_socket_buffer_size; + + // chooses between two ways of reading back piece data from disk when its + // complete and needs to be verified against the piece hash. This happens + // if some blocks were flushed to the disk out of order. Everything that + // is flushed in order is hashed as it goes along. Optimizing for speed + // will allocate space to fit all the remaining, unhashed, part of + // the piece, reads the data into it in a single call and hashes it. This + // is the default. If ``optimizing_hashing_for_speed`` is false, a single + // block will be allocated (16 kB), and the unhashed parts of the piece + // are read, one at a time, and hashed in this single block. This is + // appropriate on systems that are memory constrained. + bool optimize_hashing_for_speed; + + // the number of milliseconds to sleep + // in between disk read operations when checking torrents. This defaults + // to 0, but 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. + int file_checks_delay_per_block; + + // the disk cache algorithms available. Set + // session_settings::disk_cache_algorithm to one of these. + enum disk_cache_algo_t + { + // This flushes the entire piece, in the write cache, that was least + // recently written to. + lru, + + // will flush the largest sequences of contiguous blocks from the + // write cache, regardless of the piece's last use time. + largest_contiguous, + + // will prioritize flushing blocks that will avoid having to read them + // back in to verify the hash of the piece once it's done. This is + // especially useful for high throughput setups, where reading from + // the disk is especially expensive. + avoid_readback + }; + + // tells the disk I/O thread which cache flush algorithm to use. + // This is specified by the disk_cache_algo_t enum. + disk_cache_algo_t disk_cache_algorithm; + + // 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. + int read_cache_line_size; + + // whenever a contiguous range of this many blocks is found in the write + // cache, it is flushed immediately + int write_cache_line_size; + + // 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(). + int optimistic_disk_retry; + + // controls if downloaded pieces are verified against the piece hashes in + // the torrent file or not. The default is false, i.e. to verify all + // downloaded data. It may be useful to turn this off for performance + // profiling and simulation scenarios. Do not disable the hash check for + // regular bittorrent clients. + bool disable_hash_checks; + + // if this is true, disk read operations are sorted by their physical + // offset on disk before issued to the operating system. This is useful + // if async I/O is not supported. It defaults to true if async I/O is not + // supported and fals otherwise. disk I/O operations are likely to be + // reordered regardless of this setting when async I/O is supported by + // the OS. + bool allow_reordered_disk_operations; + + // 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. + bool allow_i2p_mixed; + + // 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. It defaults to 10. + int max_suggest_pieces; + + // If set to true (it defaults to false), piece requests that have been + // skipped enough times when piece messages are received, will be + // considered lost. Requests are considered skipped when the returned + // piece messages are re-ordered compared to the order of the requests. + // This was an attempt to get out of dead-locks caused by BitComet peers + // silently ignoring some requests. It may cause problems at high rates, + // and high level of reordering in the uploading peer, that's why it's + // disabled by default. + bool drop_skipped_requests; + + // determines if the disk I/O should use a normal + // or low priority policy. This defaults to true, which 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. + bool low_prio_disk; + + // the time between local + // network announces for a torrent. By default, when local service + // discovery is enabled a torrent announces itself every 5 minutes. + // This interval is specified in seconds. + int local_service_announce_interval; + + // the number of seconds between announcing + // torrents to the distributed hash table (DHT). This is specified to + // be 15 minutes which is its default. + int dht_announce_interval; + + // the number of seconds libtorrent + // will keep UDP tracker connection tokens around for. This is specified + // to be 60 seconds, and defaults to that. 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. + int udp_tracker_token_expiry; + + // 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. + bool volatile_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. + bool guided_read_cache; + + // the minimum number of seconds any read cache line is kept in the + // cache. This defaults to one second but 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. + int default_cache_min_age; + + // the number of optimistic unchoke slots to use. It defaults to 0, which + // means automatic. 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. When this is set to 0, libtorrent opens up 20% of + // your allowed upload slots as optimistic unchoke slots. + int num_optimistic_unchoke_slots; + + // 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. + bool no_atime_storage; + + // the assumed reciprocation rate from peers when using the BitTyrant + // choker. This defaults to 14 kiB/s. 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. + int default_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 defaults to 20%. This only applies to the BitTyrant choker. + int increase_est_reciprocation_rate; + + // specifies how many percent the estimated reciprocation rate should be + // decreased by each unchoke interval a peer unchokes us. This default to + // 3%. This only applies to the BitTyrant choker. + int decrease_est_reciprocation_rate; + + // defaults to false. 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. + bool 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 + bool report_true_downloaded; + + // defaults to true, and controls when a block may be requested twice. If + // this is ``true``, a block may only be requested twice when there's ay + // 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. + bool strict_end_game_mode; + + // if 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. + bool broadcast_lsd; + + // these all determines if libtorrent should attempt to make outgoing + // connections of the specific type, or allow incoming connection. By + // default all of them are enabled. + bool enable_outgoing_utp; + bool enable_incoming_utp; + bool enable_outgoing_tcp; + bool enable_incoming_tcp; + + // 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 + int max_pex_peers; + + // determines if the storage, when loading resume data files, should + // verify that the file modification time with the timestamps in the + // resume data. This defaults to false, which 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. + bool ignore_resume_timestamps; + + // 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. By default, this is determined by the + // existence of any of the files. By setting this setting to true, the + // files won't be checked, but will go straight to download mode. + bool no_recheck_incomplete_resume; + + // defaults to false. When set to true, the client tries to hide its + // identity to a certain degree. The peer-ID will no longer include the + // client's fingerprint. The user-agent will be reset to an empty string. + // It will also try to not leak other identifying information, such as + // your local listen port, your IP etc. + // + // If you're using I2P, a VPN or a proxy, it might make sense to enable + // anonymous mode. + bool anonymous_mode; + + // disables any communication that's not going over a proxy. Enabling + // this requires a proxy to be configured as well, see + // ``set_proxy_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). This setting also disabled peer country lookups, since those + // are done via DNS lookups that aren't supported by proxies. + bool force_proxy; + + // 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. + int tick_interval; + + // specifies whether downloads from web seeds is reported to the + // tracker or not. Defaults to on + bool report_web_seed_downloads; + + // specifies the target share ratio for share mode torrents. This + // defaults to 3, meaning 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. + int share_mode_target; + + // sets the session-global limits of upload and download rate limits, in + // bytes per second. The local rates refer to peers on the local network. + // By default peers on the local network are not rate limited. + // + // These rate limits are only used for local peers (peers within the same + // subnet as the client itself) and it is only used when + // ``session_settings::ignore_limits_on_local_network`` is set to true + // (which it is by default). These rate limits default to unthrottled, + // but can be useful in case you want to treat local peers + // preferentially, but not quite unthrottled. + // + // A value of 0 means unlimited. + int upload_rate_limit; + int download_rate_limit; + int local_upload_rate_limit; + int local_download_rate_limit; + + // sets the rate limit on the DHT. This is specified in bytes per second + // and defaults to 4000. For busy boxes with lots of torrents that + // requires more DHT traffic, this should be raised. + int dht_upload_rate_limit; + + // 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. A value of -1 means infinite. + int unchoke_slots_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. + int half_open_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. + int connections_limit; + + // the number of extra incoming connections allowed temporarily, in order + // to support replacing peers + int connections_slack; + + // 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. The default is 50 + // milliseconds. + int utp_target_delay; + + // the number of bytes the uTP congestion window can increase at the most + // in one RTT. This defaults to 300 bytes. 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. + int utp_gain_factor; + + // the shortest allowed uTP socket timeout, specified in milliseconds. + // This defaults to 500 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. + int utp_min_timeout; + + // the number of SYN packets that are sent (and timed out) before + // giving up and closing the socket. + int utp_syn_resends; + + // the number of resent packets sent on a closed socket before giving up + int utp_fin_resends; + + // the number of times a packet is sent (and lossed or timed out) + // before giving up and closing the connection. + int utp_num_resends; + + // 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. + int utp_connect_timeout; + +#ifndef TORRENT_NO_DEPRECATE + // number of milliseconds of delaying ACKing packets the most + int utp_delayed_ack; +#endif + + // controls if the uTP socket manager is allowed to increase the socket + // buffer if a network interface with a large MTU is used (such as + // loopback or ethernet jumbo frames). This defaults to true and might + // improve uTP throughput. For RAM constrained systems, disabling this + // typically saves around 30kB in user space and probably around 400kB in + // kernel socket buffers (it adjusts the send and receive buffer size on + // the kernel socket, both for IPv4 and IPv6). + bool utp_dynamic_sock_buf; + + // controls how the congestion window is changed when a packet loss is + // experienced. It's specified as a percentage multiplier for ``cwnd``. + // By default it's set to 50 (i.e. cut in half). Do not change this value + // unless you know what you're doing. Never set it higher than 100. + int utp_loss_multiplier; + + // the options for session_settings::mixed_mode_algorithm. + enum bandwidth_mixed_algo_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 + }; + + // 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, see rate_limit_utp. + // + // see bandwidth_mixed_algo_t for options. + int mixed_mode_algorithm; + +#ifndef TORRENT_NO_DEPRECATE + // deprecated, use set_peer_class_filter() instead + // set to true if uTP connections should be rate limited + // defaults to false + bool rate_limit_utp; +#endif + + // 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. The default is 5 + // which 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 listen_on() is called again (or for the + // first time). + int listen_queue_size; + + // if 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 ther user is not double NATed, this option has no + // affect. + bool announce_double_nat; + + // 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. + int torrent_connect_boost; + + // determines if seeding (and finished) torrents should attempt to make + // outgoing connections or not. By default this is true. 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. + bool seeding_outgoing_connections; + + // if true (which is the default), libtorrent will not connect to any + // peers on privileged ports (<= 1023). This can mitigate using + // bittorrent swarms for certain DDoS attacks. + bool no_connect_privileged_ports; + + // the maximum number of alerts queued up internally. If alerts are not + // popped, the queue will eventually fill up to this level. This defaults + // to 1000. + int alert_queue_size; + + // the maximum allowed size (in bytes) to be received + // by the metadata extension, i.e. magnet links. It defaults to 1 MiB. + int max_metadata_size; + + // true by default, which 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. + bool smooth_connects; + + // defaults to false. When set to true, web connections will include a + // user-agent with every request, as opposed to just the first request in + // a connection. + bool always_send_user_agent; + + // defaults to true. It 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. + bool apply_ip_filter_to_trackers; + + // used to avoid starvation of read jobs in the disk I/O thread. By + // default, read jobs are deferred, sorted by physical disk location and + // serviced once all write jobs have been issued. In scenarios where the + // download rate is enough to saturate the disk, there's a risk the read + // jobs will never be serviced. With this setting, every *x* write job, + // issued in a row, will instead pick one read job off of the sorted + // queue, where *x* is ``read_job_every``. + int read_job_every; + + // defaults to true and 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. This gives a significant performance boost for + // seeding. + bool use_disk_read_ahead; + + // 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. + bool lock_files; + + // the number of threads to use for hash checking of pieces + // defaults to 1. If set to 0, the disk thread is used for hashing + int 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 + int 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. + int predictive_piece_announce; + + // when false, bytes off the socket is received directly into the disk + // buffer. This requires many more calls to recv(). When using a + // contiguous recv buffer, the download rate can be much higher + bool contiguous_recv_buffer; + + //#error this should not be an option, it should depend on whether or not we're seeding or downloading + + // for some aio back-ends, the number of io-threads to use + int aio_threads; + // for some aio back-ends, the max number of outstanding jobs + int aio_max; + + // the number of threads to use to call async_write_some on peer sockets. + // When seeding at extremely high speeds, using 2 or more threads here + // may make sense. Also when using SSL peer connections + int network_threads; + + // if this is set, it is interpreted as a file path to where to create an + // mmaped file to back the disk cache. this is mostly useful to introduce + // another caching layer between RAM and hard drives. Typically you would + // point this to an SSD drive. + std::string mmap_cache; + + // 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. + // + // if this is 0, outgoing SSL connections are disabled + // + // It defaults to port 4433. + int ssl_listen; + + // ``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 + // + // It defaults to 250. + // + // This setting may be useful to make libtorrent more or less aggressive + // in hitting trackers. + // + int tracker_backoff; + + // enables banning web seeds. By default, web seeds that send corrupt + // data are banned. + bool ban_web_seeds; + + // specifies the max number of bytes to receive into RAM buffers when + // downloading stuff over HTTP. Specifically when specifying a URL to a + // .torrent file when adding a torrent or when announcing to an HTTP + // tracker. The default is 2 MiB. + int max_http_recv_buffer_size; + + // enables or disables the share mode extension. This is enabled by + // default. + bool support_share_mode; + + // enables or disables the merkle tree torrent support. This is enabled + // by default. + bool support_merkle_torrents; + + // enables or disables reporting redundant bytes to the tracker. This is + // enabled by default. + bool report_redundant_bytes; + + // the version string to advertise for this client in the peer protocol + // handshake. If this is empty the user_agent is used + std::string handshake_client_version; + + // if this is true, the disk cache uses a pool allocator for disk cache + // blocks. Enabling this improves performance of the disk cache with the + // side effect that the disk cache is less likely and slower at returning + // memory to the kernel when cache pressure is low. + bool use_disk_cache_pool; + + // 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 startert. + // This logic is disabled if ``dont_count_slow_torrents`` is false. + int inactive_down_rate; + int inactive_up_rate; + }; +#endif + + // structure used to hold configuration options for the DHT + // + // The ``dht_settings`` struct used to contain a ``service_port`` member to + // control which port the DHT would listen on and send messages from. This + // field is deprecated and ignored. libtorrent always tries to open the UDP + // socket on the same port as the TCP socket. + struct TORRENT_EXPORT dht_settings + { + // initialized dht_settings to the default values + dht_settings() + : max_peers_reply(100) + , search_branching(5) +#ifndef TORRENT_NO_DEPRECATE + , service_port(0) +#endif + , max_fail_count(20) + , max_torrents(2000) + , max_dht_items(700) + , max_peers(5000) + , max_torrent_search_reply(20) + , restrict_routing_ips(true) + , restrict_search_ips(true) + , extended_routing_table(true) + , aggressive_lookups(true) + , privacy_lookups(false) + , enforce_node_id(false) + , ignore_dark_internet(true) + , block_timeout(5 * 60) + , block_ratelimit(5) + , read_only(false) + , item_lifetime(0) + {} + + // the maximum number of peers to send in a reply to ``get_peers`` + int 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 + int search_branching; + +#ifndef TORRENT_NO_DEPRECATE + // the listen port for the dht. This is a UDP port. zero means use the + // same as the tcp interface + int service_port; +#endif + + // 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; + + // 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; + + // max number of items the DHT will store + int max_dht_items; + + // the max number of peers to store per torrent (for the DHT) + int max_peers; + + // the max number of torrents to return in a torrent search query to the + // DHT + int max_torrent_search_reply; + + // 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; + + // 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; + + // 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; + + // 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; + + // 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; + + // 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; + + // ignore DHT messages from parts of the internet we wouldn't expect to + // see any traffic from + bool ignore_dark_internet; + + // 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; + + // the max number of packets per second a DHT node is allowed to send + // without getting banned. + int block_ratelimit; + + // 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; + + // the number of seconds a immutable/mutable item will be expired. + // default is 0, means never expires. + int item_lifetime; + }; + + +#ifndef TORRENT_NO_DEPRECATE + // The ``pe_settings`` structure is used to control the settings related + // to peer protocol encryption. + struct TORRENT_EXPORT pe_settings + { + // initializes the encryption settings with the default vaues + 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. + boost::uint8_t out_enc_policy; + boost::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. + boost::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_NO_DEPRECATE + +} + +#endif diff --git a/include/libtorrent/session_stats.hpp b/include/libtorrent/session_stats.hpp new file mode 100644 index 0000000..e3921ed --- /dev/null +++ b/include/libtorrent/session_stats.hpp @@ -0,0 +1,68 @@ +/* + +Copyright (c) 2012-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_SESSION_STATS_HPP_INCLUDED +#define TORRENT_SESSION_STATS_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#include + +namespace libtorrent +{ + + // describes one statistics metric from the session. For more information, + // see the session-statistics_ section. + struct TORRENT_EXPORT stats_metric + { + char const* name; + int value_index; + enum { type_counter, type_gauge }; + int 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(char const* name); + +} + +#endif + diff --git a/include/libtorrent/session_status.hpp b/include/libtorrent/session_status.hpp new file mode 100644 index 0000000..f6aa247 --- /dev/null +++ b/include/libtorrent/session_status.hpp @@ -0,0 +1,223 @@ +/* + +Copyright (c) 2006-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_SESSION_STATUS_HPP_INCLUDED +#define TORRENT_SESSION_STATUS_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include + +#ifndef TORRENT_NO_DEPRECATE +// for dht_lookup and dht_routing_bucket +#include "libtorrent/alert_types.hpp" +#endif + +namespace libtorrent +{ + +#ifndef TORRENT_NO_DEPRECATE + // 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 perfromant mechanism for stats. + struct TORRENT_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. + boost::uint64_t packet_loss; + boost::uint64_t timeout; + boost::uint64_t packets_in; + boost::uint64_t packets_out; + boost::uint64_t fast_retransmit; + boost::uint64_t packet_resend; + boost::uint64_t samples_above_target; + boost::uint64_t samples_below_target; + boost::uint64_t payload_pkts_in; + boost::uint64_t payload_pkts_out; + boost::uint64_t invalid_pkts_in; + boost::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 perfromant mechanism for stats. + struct TORRENT_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. + 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. + boost::int64_t total_download; + boost::int64_t total_upload; + + // the rate of the payload + // down- and upload only. + int payload_upload_rate; + 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. + boost::int64_t total_payload_download; + boost::int64_t total_payload_upload; + + // the estimated TCP/IP overhead in each direction. + int ip_overhead_upload_rate; + int ip_overhead_download_rate; + boost::int64_t total_ip_overhead_download; + boost::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; + boost::int64_t total_dht_download; + boost::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; + boost::int64_t total_tracker_download; + boost::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. + boost::int64_t total_redundant_bytes; + + // the number of bytes that was downloaded which later failed + // the hash-check. + boost::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 notinterested. + 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. + 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. + boost::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; + + // statistics on the uTP sockets. + utp_status utp_stats; + + // 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_NO_DEPRECATE + +} + +#endif // TORRENT_SESSION_STATUS_HPP_INCLUDED + diff --git a/include/libtorrent/settings_pack.hpp b/include/libtorrent/settings_pack.hpp new file mode 100644 index 0000000..6776de8 --- /dev/null +++ b/include/libtorrent/settings_pack.hpp @@ -0,0 +1,1690 @@ +/* + +Copyright (c) 2012-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_SETTINGS_PACK_HPP_INCLUDED +#define TORRENT_SETTINGS_PACK_HPP_INCLUDED + +#include "libtorrent/entry.hpp" +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +// 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(). +// +// 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. +// +namespace libtorrent +{ + namespace aux { struct session_impl; struct session_settings; } + + struct settings_pack; + struct bdecode_node; + + TORRENT_EXTRA_EXPORT boost::shared_ptr load_pack_from_dict(bdecode_node const& settings); + TORRENT_EXTRA_EXPORT void save_settings_to_dict(aux::session_settings const& s, entry::dictionary_type& sett); + TORRENT_EXTRA_EXPORT void apply_pack(settings_pack const* pack, aux::session_settings& sett, aux::session_impl* ses = 0); + + TORRENT_EXPORT int setting_by_name(std::string const& name); + TORRENT_EXPORT char const* name_for_setting(int s); + +#ifndef TORRENT_NO_DEPRECATE + struct session_settings; + boost::shared_ptr load_pack_from_struct(aux::session_settings const& current, session_settings const& s); + void load_struct_from_settings(aux::session_settings const& current, session_settings& ret); +#endif + + // 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. + // + // These are the available settings: + // + // .. include:: settings-ref.rst + // + struct TORRENT_EXPORT settings_pack + { + friend void apply_pack(settings_pack const* pack, aux::session_settings& sett, aux::session_impl* ses); + + void set_str(int name, std::string val); + void set_int(int name, int val); + void set_bool(int name, bool val); + bool has_val(int name) const; + void clear(); + + std::string get_str(int name) const; + int get_int(int name) const; + bool get_bool(int name) const; + + // setting names (indices) are 16 bits. The two most significant + // bits indicate what type the setting has. (string, int, bool) + enum type_bases + { + string_type_base = 0x0000, + int_type_base = 0x4000, + bool_type_base = 0x8000, + type_mask = 0xc000, + index_mask = 0x3fff + }; + + enum string_types + { + // this is the client identification to the tracker. The recommended + // format of this string is: "ClientName/ClientVersion + // libtorrent/libtorrentVersion". 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. + announce_ip, + + // ``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. + // + // Since this setting sets a hard upper limit on cache usage, it + // cannot be combined with + // ``settings_pack::contiguous_recv_buffer``, since that feature + // treats the ``cache_size`` setting as a soft (but still pretty hard) + // limit. The result of combining the two is peers being disconnected + // after failing to allocate more disk buffers. + // + // This feature requires the ``mmap`` system call, on systems that + // don't have ``mmap`` this setting is ignored. + mmap_cache, + + // 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 + handshake_client_version, + + // sets the network interface this session will use when it opens + // outgoing connections. By default, it binds outgoing connections to + // INADDR_ANY and port 0 (i.e. let the OS decide). Ths parameter must + // be a string containing one or more, comma separated, adapter names. + // Adapter names on unix systems are of the form "eth0", "eth1", + // "tun0", etc. When specifying multiple interfaces, they will be + // assigned in round-robin order. 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. + // Setting this to an empty string will disable binding of outgoing + // connections. + outgoing_interfaces, + + // a comma-separated list of IP port-pairs. These + // are the listen ports that will be opened for accepting incoming uTP + // and TCP connections. It is possible to listen on multiple + // IPs and multiple ports. Binding to port 0 will make the + // operating system pick the port. The default is "0.0.0.0:6881", which + // binds to all interfaces on port 6881. + // + // if binding fails, the listen_failed_alert is posted, potentially + // more than once. Once/if binding the listen socket(s) succeed, + // listen_succeeded_alert is posted. + // + // Each port will attempt to open both a UDP and a TCP listen socket, + // to allow accepting uTP connections as well as TCP. If using the DHT, + // this will also make the DHT use the same UDP ports. + // + // .. note:: + // The current support for opening arbitrary UDP sockets is limited. + // In this version of libtorrent, there will only ever be two UDP + // sockets, one for IPv4 and one for IPv6. + listen_interfaces, + + // when using a poxy, this is the hostname where the proxy is running + // see proxy_type. + proxy_hostname, + + // when using a proxy, these are the credentials (if any) to use whne + // 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 + // used as the peer-id + 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. This setting will contain one or more bootstrap nodes + // by default. + // + // 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 + }; + + enum bool_types + { + // determines if connections from the same IP address as existing + // connections should be rejected or not. Multiple connections from + // the same IP address is not allowed by default, to prevent abusive + // behavior by peers. It may be useful to allow such connections in + // cases where simulations are run on the same machine, and all peers + // in a swarm has the same IP address. + allow_multiple_connections_per_ip = bool_type_base, + + // 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. +#ifndef TORRENT_NO_DEPRECATE + ignore_limits_on_local_network, +#else + deprecated1, +#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. + // Default is false. + send_redundant_have, + + // 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, + + // ``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. This is false by default, which means the DHT is used + // by default 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 the configured router for this machine. i.e. + // it's a way to not talk to other people's routers by mistake. + upnp_ignore_nonrouters, + + // ``use_parole_mode`` specifies if parole mode should be used. Parole + // mode means that peers that participate in pieces that fail the hash + // check are put in a mode where they are only allowed to download + // whole pieces. If the whole piece a peer in parole mode fails the + // hash check, it is banned. If a peer participates in a piece that + // passes the hash check, it is taken out of parole mode. + use_parole_mode, + + // enable and disable caching of blocks read from disk. the purpose of + // the read cache is partly read-ahead of requests but also to avoid + // reading blocks back from the disk multiple times for popular + // pieces. + use_read_cache, +#ifndef TORRENT_NO_DEPRECATE + use_write_cache, +#else + deprecated7, +#endif + + // 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, + +#ifndef TORRENT_NO_DEPRECATE + // ``explicit_read_cache`` defaults to 0. If set to something greater + // than 0, the disk read cache will not be evicted by cache misses and + // will explicitly be controlled based on the rarity of pieces. Rare + // pieces are more likely to be cached. This would typically be used + // together with ``suggest_mode`` set to ``suggest_read_cache``. The + // value is the number of pieces to keep in the read cache. If the + // actual read cache can't fit as many, it will essentially be + // clamped. + explicit_read_cache, +#else + deprecated10, +#endif + + // allocate separate, contiguous, buffers for read and write calls. + // Only used where writev/readv cannot be used will use more RAM but + // may improve performance + coalesce_reads, + coalesce_writes, + + // prefer seeding torrents when determining which torrents to give + // active slots to, the default is false which gives 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. It + // defaults to false, which is the same behavior previous versions of + // libtorrent has had as well. + // + // ``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. This is false by + // default in order to comply with the multi-tracker specification. + announce_to_all_tiers, + announce_to_all_trackers, + + // ``prefer_udp_trackers`` is true by default. It 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 trackers' tier is respected and there's no + // preference of one protocol over another. + prefer_udp_trackers, + + // ``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, + +#ifndef TORRENT_NO_DEPRECATE + // 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, +#else + deprecated8, +#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, + + // ``low_prio_disk`` determines if the disk I/O should use a normal or + // low priority policy. This defaults to true, which 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, + + // ``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, + + // ``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, + + // ``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`` defaults to false. 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`` defaults to true, and controls when a + // block may be requested twice. If this is ``true``, a block may only + // be requested twice when there's ay 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 ``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, + + // when set to true, libtorrent will try to make outgoing utp + // connections controls whether libtorrent will accept incoming + // connections or make outgoing connections of specific type. + enable_outgoing_utp, + enable_incoming_utp, + enable_outgoing_tcp, + enable_incoming_tcp, + + // ``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. This defaults to + // false, which 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, + + // ``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. By + // default, this is determined by the existence of any of the files. + // 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`` defaults to false. When set to true, the client + // tries to hide its identity to a certain degree. The peer-ID will no + // longer include the client's fingerprint. The user-agent will be + // reset to an empty string. Trackers will only be used if they are + // using a proxy server. The listen sockets are closed, and incoming + // connections will only be accepted through a SOCKS5 or I2P proxy (if + // a peer proxy is set up and is run on the same machine as the + // tracker proxy). Since no incoming connections are accepted, + // NAT-PMP, UPnP, DHT and local peer discovery are all turned off when + // this setting is enabled. + // + // If you're using I2P, it might make sense to enable anonymous mode + // as well. + anonymous_mode, + + // specifies whether downloads from web seeds is reported to the + // tracker or not. Defaults to on. Turning it off also excludes web + // seed traffic from other stats and download rate reporting via the + // libtorrent API. + report_web_seed_downloads, + + // set to true if uTP connections should be rate limited This option + // is *DEPRECATED*, please use set_peer_class_filter() instead. +#ifndef TORRENT_NO_DEPRECATE + rate_limit_utp, +#else + deprecated2, +#endif + + // 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, + + // ``seeding_outgoing_connections`` determines if seeding (and + // finished) torrents should attempt to make outgoing connections or + // not. By default this is true. 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`` is true by default, which 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`` defaults to true. It 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, + + // ``use_disk_read_ahead`` defaults to true and 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, + + // ``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, + + // ``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, + + // when true, web seeds sending bad data will be banned + ban_web_seeds, + + // when set to false, the ``write_cache_line_size`` will apply across + // piece boundaries. this is a bad idea unless the piece picker also + // is configured to have an affinity to pick pieces belonging to the + // same write cache line as is configured in the disk cache. + allow_partial_disk_writes, + + // If 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). This setting also disabled peer + // country lookups, since those are done via DNS lookups that aren't + // supported by proxies. + force_proxy, + + // if false, prevents libtorrent to advertise share-mode support + support_share_mode, + + // if this is false, don't advertise support for the Tribler merkle + // tree piece message + support_merkle_torrents, + + // if this is true, the number of redundant bytes is sent to the + // tracker + report_redundant_bytes, + + // if this is true, libtorrent will fall back to listening on a port + // chosen by the operating system (i.e. binding to port 0). If a + // failure is preferred, set this to false. + listen_system_port_fallback, + + // ``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, + + // 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 infohashes 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, plaintext 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, + + max_bool_setting_internal + }; + + 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. Default value is 60 + // seconds. + 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, + + // the time 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 + 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 megs). + 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 (16kB) 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. This defaults to 120 seconds, since + // that's what's 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-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, + + // time to wait until a new retry of a web seed takes place + 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. It is usually a good + // idea to find this limit and set the number of connections and the + // number of files limits so their sum is slightly below it. + 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 failcounter + // 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. default is 10 minutes + 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. This defaults to 4, which + // means the 4 first 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_suggestsions`` which is the default and 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 number 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 16kB 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 + // which peers to unchoke. + // + // The options for choking algorithms are: + // + // * ``fixed_slots_choker`` is the traditional choker with a fixed + // number of unchoke slots (as specified by + // ``session::set_max_uploads()``). + // + // * ``rate_based_choker`` 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. + // + // * ``bittyrant_choker`` attempts to optimize download rate by + // finding the reciprocation rate of each peer individually and + // prefers peers that gives the highest *return on investment*. It + // still allocates all upload capacity, but shuffles it around to + // the best peers first. For this choker to be efficient, you need + // to set a global upload rate limit + // (``session::set_upload_rate_limit()``). For more information + // about this choker, see the paper_. This choker is not fully + // implemented nor tested. + // + // .. _paper: http://bittyrant.cs.washington.edu/#papers + // + // ``seed_choking_algorithm`` controls the seeding unchoke behavior. + // The available options are: + // + // * ``round_robin`` which round-robins the peers that are unchoked + // when seeding. This distributes the upload bandwidht uniformly and + // fairly. It minimizes the ability for a peer to download everything + // without redistributing it. + // + // * ``fastest_upload`` unchokes the peers we can send to the fastest. + // This might be a bit more reliable in utilizing all available + // capacity. + // + // * ``anti_leech`` 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. + choking_algorithm, + seed_choking_algorithm, + + // ``cache_size`` is the disk write and read cache. It is specified + // in units of 16 KiB blocks. Buffers that are part of a peer's send + // or receive buffer also count against this limit. Send and receive + // buffers will never be denied to be allocated, but they will cause + // the actual cached blocks to be flushed or evicted. If this is set + // to -1, the cache size is automatically set to the amount of + // physical RAM available in the machine divided by 8. If the amount + // of physical RAM cannot be determined, it's set to 1024 (= 16 MiB). + // + // 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_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. Default is 60 second. + // + // 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, + cache_buffer_chunk_size, + cache_expiry, + +#ifndef TORRENT_NO_DEPRECATE + // ``explicit_cache_interval`` is the number of seconds in between + // each refresh of a part of the explicit read cache. Torrents take + // turns in refreshing and this is the time in between each torrent + // refresh. Refreshing a torrent's explicit read cache means scanning + // all pieces and picking a random set of the rarest ones. There is an + // affinity to pick pieces that are already in the cache, so that + // subsequent refreshes only swaps in pieces that are rarer than + // whatever is in the cache at the time. + explicit_cache_interval, +#else + deprecated11, +#endif + + // determines how files are opened when they're in read only mode + // versus read and write mode. The options are: + // + // enable_os_cache + // This is the default and files are opened normally, with the OS + // caching reads and writes. + // disable_os_cache + // This opens all files in no-cache mode. This corresponds to the + // OS not letting blocks for the files linger in the cache. This + // makes sense in order to avoid the bittorrent client to + // potentially evict all other processes' cache by simply handling + // high throughput and large files. If libtorrent's read cache is + // disabled, enabling this may reduce performance. + // + // One reason to disable caching is that it may help the operating + // system from growing its file cache indefinitely. + disk_io_write_mode, + disk_io_read_mode, + + // this is the first port to use for binding outgoing connections to. + // This is useful for users that have routers that allow QoS settings + // based on local port. when binding outgoing connections to specific + // ports, ``num_outgoing_ports`` is the size of the range. It should + // be more than a few + // + // .. warning:: setting outgoing ports will limit the ability to keep + // multiple connections to the same client, even for different + // torrents. It is not recommended to change this setting. Its main + // purpose is to use as an escape hatch for cheap routers with QoS + // capability but can only classify flows based on port numbers. + // + // It is a range instead of a single port because of the problems with + // failing to reconnect to peers if a previous socket to that peer and + // port is in ``TIME_WAIT`` state. + outgoing_port, + num_outgoing_ports, + + // ``peer_tos`` determines the TOS byte set in the IP header of every + // packet sent to peers (including web seeds). The default value for + // this is ``0x0`` (no marking). One potentially useful TOS mark is + // ``0x20``, this represents the *QBone scavenger service*. For more + // details, see QBSS_. + // + // .. _`QBSS`: http://qbone.internet2.edu/qbss/ + peer_tos, + + // for auto managed torrents, these are the limits they are subject + // to. If there are too many torrents some of the auto managed ones + // will be paused until some slots free up. ``active_downloads`` and + // ``active_seeds`` controls how many active seeding and downloading + // torrents the queuing mechanism allows. The target number of active + // torrents is ``min(active_downloads + active_seeds, active_limit)``. + // ``active_downloads`` and ``active_seeds`` are upper limits on the + // number of downloading torrents and seeding torrents respectively. + // Setting the value to -1 means unlimited. + // + // For example if there are 10 seeding torrents and 10 downloading + // torrents, and ``active_downloads`` is 4 and ``active_seeds`` is 4, + // there will be 4 seeds active and 4 downloading torrents. If the + // settings are ``active_downloads`` = 2 and ``active_seeds`` = 4, + // then there will be 2 downloading torrents and 4 seeding torrents + // active. Torrents that are not auto managed are not counted against + // these limits. + // + // ``active_checking`` is the limit of number of simultaneous checking + // torrents. + // + // ``active_limit`` is a hard limit on the number of active (auto + // managed) torrents. This limit also applies to slow torrents. + // + // ``active_dht_limit`` is the max number of torrents to announce to + // the DHT. By default this is set to 88, which is no more than one + // DHT announce every 10 seconds. + // + // ``active_tracker_limit`` is the max number of torrents to announce + // to their trackers. By default this is 360, which is no more than + // one announce every 5 seconds. + // + // ``active_lsd_limit`` is the max number of torrents to announce to + // the local network over the local service discovery protocol. By + // default this is 80, which is no more than one announce every 5 + // seconds (assuming the default announce interval of 5 minutes). + // + // 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_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_downloads, + active_seeds, + active_checking, + active_dht_limit, + active_tracker_limit, + active_lsd_limit, + active_limit, + active_loaded_limit, + + // ``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 default to the same as + // ``max_peerlist_size``, but 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 misconfigured + // 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. + // It defaults to 3 pieces, which means that when seeding, any peer + // we've sent more than this number of pieces to will be unchoked in + // favour of a choked peer. + seeding_piece_quota, + + // TODO: deprecate this + // ``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, + + // ``recv_socket_buffer_size`` and ``send_socket_buffer_size`` + // specifies the buffer sizes set on peer sockets. 0 (which is the + // default) 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, + + // ``file_checks_delay_per_block`` is the number of milliseconds to + // sleep in between disk read operations when checking torrents. This + // defaults to 0, but 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, + + // ``read_cache_line_size`` is the number of blocks to read into the + // read cache when a read cache miss occurs. Setting this to 0 is + // essentially the same thing as disabling read cache. The number of + // blocks read into the read cache is always capped by the piece + // boundary. + // + // When a piece in the write cache has ``write_cache_line_size`` + // contiguous blocks in it, they will be flushed. Setting this to 1 + // effectively disables the write cache. + read_cache_line_size, + write_cache_line_size, + + // ``optimistic_disk_retry`` is the number of seconds from a disk + // write errors occur on a torrent until libtorrent will take it out + // of the upload mode, to test if the error condition has been fixed. + // + // libtorrent will only do this automatically for auto managed + // torrents. + // + // You can explicitly take a torrent out of upload only mode using + // set_upload_mode(). + optimistic_disk_retry, + + // ``max_suggest_pieces`` is the max number of suggested piece indices + // received from a peer that's remembered. If a peer floods suggest + // messages, this limit prevents libtorrent from using too much RAM. + // It defaults to 10. + max_suggest_pieces, + + // ``local_service_announce_interval`` is the time between local + // network announces for a torrent. By default, when local service + // discovery is enabled a torrent announces itself every 5 minutes. + // 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, and defaults to that. 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, + + // ``default_cache_min_age`` is the minimum number of seconds any read + // cache line is kept in the cache. This defaults to one second but + // 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, + + // ``num_optimistic_unchoke_slots`` is the number of optimistic + // unchoke slots to use. It defaults to 0, which means automatic. + // 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. When this is set to 0, libtorrent opens up 20% of your + // allowed upload slots as optimistic unchoke slots. + num_optimistic_unchoke_slots, + + // ``default_est_reciprocation_rate`` is the assumed reciprocation + // rate from peers when using the BitTyrant choker. This defaults to + // 14 kiB/s. 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 defaults to 20%. + // 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 default to 3%. This only applies + // to the BitTyrant choker. + default_est_reciprocation_rate, + increase_est_reciprocation_rate, + decrease_est_reciprocation_rate, + + // 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. This defaults to 3, meaning 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``, ``download_rate_limit``, + // ``local_upload_rate_limit`` and ``local_download_rate_limit`` sets + // the session-global limits of upload and download rate limits, in + // bytes per second. The local rates refer to peers on the local + // network. By default peers on the local network are not rate + // limited. + // + // These rate limits are only used for local peers (peers within the + // same subnet as the client itself) and it is only used when + // ``ignore_limits_on_local_network`` is set to true (which it is by + // default). These rate limits default to unthrottled, but can be + // useful in case you want to treat local peers preferentially, but + // not quite unthrottled. + // + // A value of 0 means unlimited. + upload_rate_limit, + download_rate_limit, +#ifndef TORRENT_NO_DEPRECATE + local_upload_rate_limit, + local_download_rate_limit, +#else + deprecated3, + deprecated4, +#endif + + // ``dht_upload_rate_limit`` sets the rate limit on the DHT. This is + // specified in bytes per second and defaults to 4000. For busy boxes + // with lots of torrents that requires more DHT traffic, this should + // be raised. + dht_upload_rate_limit, + + // ``unchoke_slots_limit`` is the max number of unchoked peers in the + // session. The number of unchoke slots may be ignored depending on + // what ``choking_algorithm`` is set to. + unchoke_slots_limit, + +#ifndef TORRENT_NO_DEPRECATE + // ``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, +#else + deprecated5, +#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 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. The default is 50 milliseconds. + // ``utp_gain_factor`` is the number of bytes the uTP congestion + // window can increase at the most in one RTT. This defaults to 300 + // bytes. 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. This defaults to 500 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 + // lossed 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``. By default it's set to 50 (i.e. cut in half). 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, +#ifndef TORRENT_NO_DEPRECATE + utp_delayed_ack, +#else + deprecated6, +#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. The default is 5 which 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 listen_on() is called again (or for the first time). + 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. + 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. + 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, + +#ifndef TORRENT_NO_DEPRECATE + // DEPRECTED: use aio_threads instead + + // ``hashing_threads`` is the number of threads to use for piece hash + // verification. It defaults to 1. For very high download rates, on + // machines with multiple cores, this could be incremented. Setting it + // higher than the number of CPU cores would presumably not provide + // any benefit of setting it to the number of cores. If it's set to 0, + // hashing is done in the disk thread. + hashing_threads, +#else + deprecated9, +#endif + + // the number of blocks to keep outstanding at any given time when + // checking torrents. Higher numbers give faster re-checks but uses + // more memory. Specified in number of 16 kiB blocks + checking_mem_usage, + + // if set to > 0, pieces will be announced to other peers before they + // are fully downloaded (and before they are hash checked). The + // intention is to gain 1.5 potential round trip times per downloaded + // piece. When non-zero, this indicates how many milliseconds in + // advance pieces should be announced, before they are expected to be + // completed. + predictive_piece_announce, + + // for some aio back-ends, ``aio_threads`` specifies the number of + // io-threads to use, and ``aio_max`` the max number of outstanding + // jobs. + aio_threads, + aio_max, + + // ``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, + + // ``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, + + // ``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 + 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, defaults to none. see proxy_type_t. + proxy_type, + + // the port of the proxy server + proxy_port, + + // sets the i2p_ SAM bridge port to connect to. set the hostname with + // the ``i2p_hostname`` setting. + // + // .. _i2p: http://www.i2p2.de + i2p_port, + + // this determines the max number of volatile disk cache blocks. If the + // number of volatile blocks exceed this limit, other volatile blocks + // will start to be evicted. A disk cache block is volatile if it has + // low priority, and should be one of the first blocks to be evicted + // under pressure. For instance, blocks pulled into the cache as the + // result of calculating a piece hash are volatile. These blocks don't + // represent potential interest among peers, so the value of keeping + // them in the cache is limited. + cache_size_volatile, + + max_int_setting_internal + }; + + enum settings_counts_t + { + num_string_settings = max_string_setting_internal - string_type_base, + num_bool_settings = max_bool_setting_internal - bool_type_base, + num_int_settings = max_int_setting_internal - int_type_base + }; + + enum suggest_mode_t { no_piece_suggestions = 0, suggest_read_cache = 1 }; + + enum choking_algorithm_t + { + fixed_slots_choker = 0, + rate_based_choker = 2, + bittyrant_choker = 3 + }; + + enum seed_choking_algorithm_t + { + round_robin, + fastest_upload, + anti_leech + }; + + enum io_buffer_mode_t + { + enable_os_cache = 0, +#ifndef TORRENT_NO_DEPRECATE + disable_os_cache_for_aligned_files = 2, +#else + deprecated = 1, +#endif + disable_os_cache = 2 + }; + + enum bandwidth_mixed_algo_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 + { + // 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 + { + // use only plaintext encryption + pe_plaintext = 1, + // use only rc4 encryption + pe_rc4 = 2, + // allow both + pe_both = 3 + }; + + enum proxy_type_t + { + // This is the default, no proxy server is used, 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..a2168fe --- /dev/null +++ b/include/libtorrent/sha1.hpp @@ -0,0 +1,38 @@ +/* +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" +#include + +namespace libtorrent +{ + + struct TORRENT_EXTRA_EXPORT sha_ctx + { + boost::uint32_t state[5]; + boost::uint32_t count[2]; + boost::uint8_t buffer[64]; + }; + + // we don't want these to clash with openssl's libcrypto + TORRENT_EXTRA_EXPORT void SHA1_init(sha_ctx* context); + TORRENT_EXTRA_EXPORT void SHA1_update(sha_ctx* context + , boost::uint8_t const* data + , boost::uint32_t len); + TORRENT_EXTRA_EXPORT void SHA1_final(boost::uint8_t* digest, sha_ctx* context); +} + +#endif + diff --git a/include/libtorrent/sha1_hash.hpp b/include/libtorrent/sha1_hash.hpp new file mode 100644 index 0000000..c86e9f4 --- /dev/null +++ b/include/libtorrent/sha1_hash.hpp @@ -0,0 +1,353 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SHA1_HASH_HPP_INCLUDED +#define TORRENT_SHA1_HASH_HPP_INCLUDED + +#include +#include +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/aux_/byteswap.hpp" + +#include "libtorrent/hex.hpp" // to_hex, from_hex +#if TORRENT_USE_IOSTREAM +#include +#include +#endif + +#ifdef max +#undef max +#endif + +#ifdef min +#undef min +#endif + +namespace libtorrent +{ + + // 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. + class TORRENT_EXPORT sha1_hash + { + enum { number_size = 5 }; + public: + // internal + // the number of bytes of the number + static const int size = number_size * sizeof(boost::uint32_t); + + // constructs an all-zero sha1-hash + sha1_hash() { clear(); } + + // returns an all-F sha1-hash. i.e. the maximum value + // representable by a 160 bit number (20 bytes). This is + // a static member function. + static sha1_hash max() + { + sha1_hash ret; + memset(ret.m_number, 0xff, size); + return ret; + } + + // returns an all-zero sha1-hash. i.e. the minimum value + // representable by a 160 bit number (20 bytes). This is + // a static member function. + static sha1_hash min() + { + sha1_hash ret; + memset(ret.m_number, 0, size); + return ret; + } + + // copies 20 bytes from the pointer provided, into the sha1-hash. + // The passed in string MUST be at least 20 bytes. NULL terminators + // are ignored, ``s`` is treated like a raw memory buffer. + explicit sha1_hash(char const* s) + { + if (s == 0) clear(); + else std::memcpy(m_number, s, size); + } + explicit sha1_hash(std::string const& s) + { + TORRENT_ASSERT(s.size() >= 20); + size_t sl = s.size() < size_t(size) ? s.size() : size_t(size); + std::memcpy(m_number, s.c_str(), sl); + } + void assign(std::string const& s) + { + TORRENT_ASSERT(s.size() >= 20); + size_t sl = s.size() < size_t(size) ? s.size() : size_t(size); + std::memcpy(m_number, s.c_str(), sl); + } + void assign(char const* str) { std::memcpy(m_number, str, size); } + + char const* data() const { return reinterpret_cast(&m_number[0]); } + char* data() { return reinterpret_cast(&m_number[0]); } + + // set the sha1-hash to all zeroes. + void clear() { std::memset(m_number, 0, size); } + + // return true if the sha1-hash is all zero. + bool is_all_zeros() const + { + for (int i = 0; i < number_size; ++i) + if (m_number[i] != 0) return false; + return true; + } + + // shift left ``n`` bits. + sha1_hash& operator<<=(int n) + { + TORRENT_ASSERT(n >= 0); + const int num_words = n / 32; + if (num_words >= number_size) + { + std::memset(m_number, 0, size); + return *this; + } + + if (num_words > 0) + { + std::memmove(m_number, m_number + num_words + , (number_size - num_words) * sizeof(boost::uint32_t)); + std::memset(m_number + (number_size - num_words) + , 0, num_words * sizeof(boost::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. + m_number[0] = aux::network_to_host(m_number[0]); + for (int i = 0; i < number_size - 1; ++i) + { + m_number[i] <<= n; + m_number[i+1] = aux::network_to_host(m_number[i+1]); + m_number[i] |= m_number[i+1] >> (32 - n); + m_number[i] = aux::host_to_network(m_number[i]); + } + m_number[number_size-1] <<= n; + m_number[number_size-1] = aux::host_to_network(m_number[number_size-1]); + } + return *this; + } + + // shift r ``n`` bits. + sha1_hash& operator>>=(int n) + { + TORRENT_ASSERT(n >= 0); + const int num_words = n / 32; + if (num_words >= number_size) + { + std::memset(m_number, 0, size_t(size)); + return *this; + } + if (num_words > 0) + { + std::memmove(m_number + num_words + , m_number, (number_size - num_words) * sizeof(boost::uint32_t)); + std::memset(m_number, 0, num_words * sizeof(boost::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. + m_number[number_size-1] = aux::network_to_host(m_number[number_size-1]); + + for (int i = number_size - 1; i > 0; --i) + { + m_number[i] >>= n; + m_number[i-1] = aux::network_to_host(m_number[i-1]); + m_number[i] |= (m_number[i-1] << (32 - n)) & 0xffffffff; + m_number[i] = aux::host_to_network(m_number[i]); + } + m_number[0] >>= n; + m_number[0] = aux::host_to_network(m_number[0]); + } + return *this; + } + + // standard comparison operators + bool operator==(sha1_hash const& n) const + { + return std::equal(n.m_number, n.m_number+number_size, m_number); + } + bool operator!=(sha1_hash const& n) const + { + return !std::equal(n.m_number, n.m_number+number_size, m_number); + } + bool operator<(sha1_hash const& n) const + { + for (int i = 0; i < number_size; ++i) + { + boost::uint32_t lhs = aux::network_to_host(m_number[i]); + boost::uint32_t rhs = aux::network_to_host(n.m_number[i]); + if (lhs < rhs) return true; + if (lhs > rhs) return false; + } + return false; + } + + // returns a bit-wise negated copy of the sha1-hash + sha1_hash operator~() const + { + sha1_hash ret; + for (int i = 0; i < number_size; ++i) + ret.m_number[i] = ~m_number[i]; + return ret; + } + + // returns the bit-wise XOR of the two sha1-hashes. + sha1_hash operator^(sha1_hash const& n) const + { + sha1_hash ret = *this; + ret ^= n; + return ret; + } + + // in-place bit-wise XOR with the passed in sha1_hash. + sha1_hash& operator^=(sha1_hash const& n) + { + for (int i = 0; i < number_size; ++i) + m_number[i] ^= n.m_number[i]; + return *this; + } + + // returns the bit-wise AND of the two sha1-hashes. + sha1_hash operator&(sha1_hash const& n) const + { + sha1_hash ret = *this; + ret &= n; + return ret; + } + + // in-place bit-wise AND of the passed in sha1_hash + sha1_hash& operator&=(sha1_hash const& n) + { + for (int i = 0; i < number_size; ++i) + m_number[i] &= n.m_number[i]; + return *this; + } + + // in-place bit-wise OR of the two sha1-hash. + sha1_hash& operator|=(sha1_hash const& n) + { + for (int i = 0; i < number_size; ++i) + m_number[i] |= n.m_number[i]; + return *this; + } + + // accessors for specific bytes + boost::uint8_t& operator[](int i) + { + TORRENT_ASSERT(i >= 0 && i < size); + return reinterpret_cast(m_number)[i]; + } + boost::uint8_t const& operator[](int i) const + { + TORRENT_ASSERT(i >= 0 && i < size); + return reinterpret_cast(m_number)[i]; + } + + typedef const boost::uint8_t* const_iterator; + typedef boost::uint8_t* iterator; + + // start and end iterators for the hash. The value type + // of these iterators is ``boost::uint8_t``. + const_iterator begin() const + { return reinterpret_cast(m_number); } + const_iterator end() const + { return reinterpret_cast(m_number) + size; } + iterator begin() + { return reinterpret_cast(m_number); } + iterator end() + { return reinterpret_cast(m_number) + size; } + + // return a copy of the 20 bytes representing the sha1-hash as a std::string. + // It's still a binary string with 20 binary characters. + std::string to_string() const + { + return std::string(reinterpret_cast(&m_number[0]) + , size_t(size)); + } + + private: + + boost::uint32_t m_number[number_size]; + + }; + + typedef sha1_hash peer_id; + inline std::size_t hash_value(sha1_hash const& b) + { + std::size_t ret; + std::memcpy(&ret, &b[0], sizeof(ret)); + return ret; + } + +#if TORRENT_USE_IOSTREAM + + // print a sha1_hash object to an ostream as 40 hexadecimal digits + inline std::ostream& operator<<(std::ostream& os, sha1_hash const& peer) + { + char out[41]; + to_hex(reinterpret_cast(&peer[0]), sha1_hash::size, out); + return os << out; + } + + // read 40 hexadecimal digits from an istream into a sha1_hash + inline std::istream& operator>>(std::istream& is, sha1_hash& peer) + { + char hex[40]; + is.read(hex, 40); + if (!from_hex(hex, 40, reinterpret_cast(&peer[0]))) + is.setstate(std::ios_base::failbit); + return is; + } +#endif // TORRENT_USE_IOSTREAM +} + +#endif // TORRENT_PEER_ID_HPP_INCLUDED + diff --git a/include/libtorrent/sliding_average.hpp b/include/libtorrent/sliding_average.hpp new file mode 100644 index 0000000..e87efee --- /dev/null +++ b/include/libtorrent/sliding_average.hpp @@ -0,0 +1,118 @@ +/* + +Copyright (c) 2010-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SLIDING_AVERAGE_HPP_INCLUDED +#define TORRENT_SLIDING_AVERAGE_HPP_INCLUDED + +#include +#include // for std::abs + +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 +{ + sliding_average(): m_mean(0), m_average_deviation(0), m_num_samples(0) {} + + void add_sample(int s) + { + // fixed point + s *= 64; + int deviation = 0; + + if (m_num_samples > 0) + deviation = std::abs(m_mean - s); + + if (m_num_samples < inverted_gain) + ++m_num_samples; + + m_mean += (s - m_mean) / m_num_samples; + + if (m_num_samples > 1) { + // the 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; + int m_average_deviation; + // the number of samples we have received, but no more than inverted_gain + // this is the effective inverted_gain + int m_num_samples; +}; + +struct average_accumulator +{ + average_accumulator() + : m_num_samples(0) + , m_sample_sum(0) + {} + + void add_sample(int s) + { + ++m_num_samples; + m_sample_sum += s; + } + + int mean() + { + int ret; + if (m_num_samples == 0) ret = 0; + else ret = int(m_sample_sum / m_num_samples); + // in case we don't get any more samples, at least + // let the average roll over, but only be worth a + // single sample + m_num_samples = 1; + m_sample_sum = ret; + return ret; + } + + int m_num_samples; + boost::uint64_t m_sample_sum; +}; + +} + +#endif + diff --git a/include/libtorrent/socket.hpp b/include/libtorrent/socket.hpp new file mode 100644 index 0000000..1f10677 --- /dev/null +++ b/include/libtorrent/socket.hpp @@ -0,0 +1,197 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SOCKET_HPP_INCLUDED +#define TORRENT_SOCKET_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +// if building as Objective C++, asio's template +// parameters Protocol has to be renamed to avoid +// colliding with keywords + +#ifdef __OBJC__ +#define Protocol Protocol_ +#endif + +#if defined TORRENT_WINDOWS || defined TORRENT_CYGWIN +// asio assumes that the windows error codes are defined already +#include +#endif + +#include + +#include +#include +#include +#include + +#ifdef __OBJC__ +#undef Protocol +#endif + +#if defined TORRENT_BUILD_SIMULATOR +#include "simulator/simulator.hpp" +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent +{ + +#if defined TORRENT_BUILD_SIMULATOR + using sim::asio::ip::udp; + using sim::asio::ip::tcp; + using sim::asio::async_write; + using sim::asio::async_read; + using sim::asio::null_buffers; +#else + using boost::asio::ip::tcp; + using boost::asio::ip::udp; + using boost::asio::async_write; + using boost::asio::async_read; + using boost::asio::null_buffers; +#endif + +#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 + { + 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 + { + 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 + { + traffic_class(char val): m_value(val) {} + template + int level(Protocol const&) const { return IPPROTO_IPV6; } + template + int name(Protocol const&) const { return IPV6_TCLASS; } + template + int const* data(Protocol const&) const { return &m_value; } + template + size_t size(Protocol const&) const { return sizeof(m_value); } + int m_value; + }; +#endif + + struct type_of_service + { +#ifdef _WIN32 + typedef DWORD tos_t; +#else + typedef int tos_t; +#endif + type_of_service(char val): m_value(val) {} + template + int level(Protocol const&) const { return IPPROTO_IP; } + template + int name(Protocol const&) const { return IP_TOS; } + template + tos_t const* data(Protocol const&) const { return &m_value; } + template + size_t size(Protocol const&) const { return sizeof(m_value); } + tos_t m_value; + }; + +#if defined IP_DONTFRAG || defined IP_MTU_DISCOVER || defined IP_DONTFRAGMENT +#define TORRENT_HAS_DONT_FRAGMENT +#endif + +#ifdef TORRENT_HAS_DONT_FRAGMENT + struct dont_fragment + { + dont_fragment(bool val) +#ifdef IP_PMTUDISCOVER_DO + : m_value(val ? IP_PMTUDISC_DO : IP_PMTUDISC_DONT) {} +#else + : m_value(val) {} +#endif + template + int level(Protocol const&) const { return IPPROTO_IP; } + template + int name(Protocol const&) const +#if defined IP_DONTFRAG + { return IP_DONTFRAG; } +#elif defined IP_MTU_DISCOVER + { return IP_MTU_DISCOVER; } +#elif defined IP_DONTFRAGMENT + { return IP_DONTFRAGMENT; } +#else + {} +#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; + }; +#endif // TORRENT_HAS_DONT_FRAGMENT +} + +#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..9e66468 --- /dev/null +++ b/include/libtorrent/socket_io.hpp @@ -0,0 +1,172 @@ +/* + +Copyright (c) 2009-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_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/peer_id.hpp" // for sha1_hash +#include + +namespace libtorrent +{ + TORRENT_EXTRA_EXPORT std::string print_address(address const& addr); + 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(std::string str, error_code& ec); + + TORRENT_EXTRA_EXPORT std::string address_to_bytes(address const& a); + // internal + TORRENT_EXPORT std::string endpoint_to_bytes(udp::endpoint const& ep); + TORRENT_EXTRA_EXPORT void hash_address(address const& ip, sha1_hash& h); + + namespace detail + { + template + void write_address(address const& a, OutIt& out) + { +#if TORRENT_USE_IPV6 + if (a.is_v4()) + { +#endif + write_uint32(a.to_v4().to_ulong(), out); +#if TORRENT_USE_IPV6 + } + else if (a.is_v6()) + { + typedef address_v6::bytes_type bytes_t; + bytes_t bytes = a.to_v6().to_bytes(); + for (bytes_t::iterator i = bytes.begin() + , end(bytes.end()); i != end; ++i) + write_uint8(*i, out); + } +#endif + } + + template + address read_v4_address(InIt& in) + { + unsigned long ip = read_uint32(in); + return address_v4(ip); + } + +#if TORRENT_USE_IPV6 + template + address read_v6_address(InIt& in) + { + typedef address_v6::bytes_type bytes_t; + bytes_t bytes; + for (bytes_t::iterator i = bytes.begin() + , end(bytes.end()); i != end; ++i) + *i = read_uint8(in); + return address_v6(bytes); + } +#endif + + 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); + int port = read_uint16(in); + return Endpoint(addr, port); + } + +#if TORRENT_USE_IPV6 + template + Endpoint read_v6_endpoint(InIt& in) + { + address addr = read_v6_address(in); + int port = read_uint16(in); + return Endpoint(addr, port); + } +#endif + + template + void read_endpoint_list(libtorrent::bdecode_node const& n + , std::vector& epl) + { + using namespace libtorrent; + if (n.type() != bdecode_node::list_t) return; + for (int i = 0; i < n.list_size(); ++i) + { + bdecode_node e = n.list_at(i); + if (e.type() != bdecode_node::string_t) return; + if (e.string_length() < 6) continue; + char const* in = e.string_ptr(); + if (e.string_length() == 6) + epl.push_back(read_v4_endpoint(in)); +#if TORRENT_USE_IPV6 + else if (e.string_length() == 18) + epl.push_back(read_v6_endpoint(in)); +#endif + } + } + } + + template + void read_endpoint_list(libtorrent::entry const* n, std::vector& epl) + { + using namespace libtorrent; + if (n->type() != entry::list_t) return; + entry::list_type const& contacts = n->list(); + for (entry::list_type::const_iterator i = contacts.begin() + , end(contacts.end()); i != end; ++i) + { + if (i->type() != entry::string_t) return; + std::string const& p = i->string(); + if (p.size() < 6) continue; + std::string::const_iterator in = p.begin(); + if (p.size() == 6) + epl.push_back(detail::read_v4_endpoint(in)); +#if TORRENT_USE_IPV6 + else if (p.size() == 18) + epl.push_back(detail::read_v6_endpoint(in)); +#endif + } + } + +} + +#endif + diff --git a/include/libtorrent/socket_type.hpp b/include/libtorrent/socket_type.hpp new file mode 100644 index 0000000..0b58159 --- /dev/null +++ b/include/libtorrent/socket_type.hpp @@ -0,0 +1,353 @@ +/* + +Copyright (c) 2009-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_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/utp_stream.hpp" +#include "libtorrent/io_service.hpp" +#include "libtorrent/max.hpp" +#include "libtorrent/assert.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#ifdef TORRENT_USE_OPENSSL +#include "libtorrent/ssl_stream.hpp" +#endif + +#if defined TORRENT_ASIO_DEBUGGING +#include "libtorrent/debug.hpp" +#endif + +#if defined TORRENT_OS2 && defined ioc +#undef ioc +#endif + +#if TORRENT_USE_I2P + +#define TORRENT_SOCKTYPE_I2P_FORWARD(x) \ + case socket_type_int_impl::value: \ + get()->x; break; + +#define TORRENT_SOCKTYPE_I2P_FORWARD_RET(x, def) \ + case socket_type_int_impl::value: \ + return get()->x; + +#else // TORRENT_USE_I2P + +#define TORRENT_SOCKTYPE_I2P_FORWARD(x) +#define TORRENT_SOCKTYPE_I2P_FORWARD_RET(x, def) + +#endif + +#ifdef TORRENT_USE_OPENSSL + +#define TORRENT_SOCKTYPE_SSL_FORWARD(x) \ + case socket_type_int_impl >::value: \ + get >()->x; break; \ + case socket_type_int_impl >::value: \ + get >()->x; break; \ + case socket_type_int_impl >::value: \ + get >()->x; break; \ + case socket_type_int_impl >::value: \ + get >()->x; break; + +#define TORRENT_SOCKTYPE_SSL_FORWARD_RET(x, def) \ + case socket_type_int_impl >::value: \ + return get >()->x; \ + case socket_type_int_impl >::value: \ + return get >()->x; \ + case socket_type_int_impl >::value: \ + return get >()->x; \ + case socket_type_int_impl >::value: \ + return get >()->x; + +#else + +#define TORRENT_SOCKTYPE_SSL_FORWARD(x) +#define TORRENT_SOCKTYPE_SSL_FORWARD_RET(x, def) + +#endif + +#define TORRENT_SOCKTYPE_FORWARD(x) \ + switch (m_type) { \ + case socket_type_int_impl::value: \ + get()->x; break; \ + case socket_type_int_impl::value: \ + get()->x; break; \ + case socket_type_int_impl::value: \ + get()->x; break; \ + case socket_type_int_impl::value: \ + get()->x; break; \ + TORRENT_SOCKTYPE_I2P_FORWARD(x) \ + TORRENT_SOCKTYPE_SSL_FORWARD(x) \ + default: TORRENT_ASSERT(false); \ + } + +#define TORRENT_SOCKTYPE_FORWARD_RET(x, def) \ + switch (m_type) { \ + case socket_type_int_impl::value: \ + return get()->x; \ + case socket_type_int_impl::value: \ + return get()->x; \ + case socket_type_int_impl::value: \ + return get()->x; \ + case socket_type_int_impl::value: \ + return get()->x; \ + TORRENT_SOCKTYPE_I2P_FORWARD_RET(x, def) \ + TORRENT_SOCKTYPE_SSL_FORWARD_RET(x, def) \ + default: TORRENT_ASSERT(false); return def; \ + } + +namespace libtorrent +{ + + template + struct socket_type_int_impl + { enum { value = 0 }; }; + + template <> + struct socket_type_int_impl + { enum { value = 1 }; }; + + template <> + struct socket_type_int_impl + { enum { value = 2 }; }; + + template <> + struct socket_type_int_impl + { enum { value = 3 }; }; + + template <> + struct socket_type_int_impl + { enum { value = 4 }; }; + +#if TORRENT_USE_I2P + template <> + struct socket_type_int_impl + { enum { value = 5 }; }; +#endif + +#ifdef TORRENT_USE_OPENSSL + template <> + struct socket_type_int_impl > + { enum { value = 6 }; }; + + template <> + struct socket_type_int_impl > + { enum { value = 7 }; }; + + template <> + struct socket_type_int_impl > + { enum { value = 8 }; }; + + template <> + struct socket_type_int_impl > + { enum { value = 9 }; }; +#endif + + struct TORRENT_EXTRA_EXPORT socket_type + { + typedef tcp::socket::endpoint_type endpoint_type; + typedef tcp::socket::protocol_type protocol_type; + + typedef tcp::socket::receive_buffer_size receive_buffer_size; + typedef tcp::socket::send_buffer_size send_buffer_size; + + explicit socket_type(io_service& ios): m_io_service(ios), m_type(0) {} + ~socket_type(); + + io_service& get_io_service() const; + bool is_open() const; + + char const* type_name() const; + +#ifndef BOOST_NO_EXCEPTIONS + void open(protocol_type const& p); + void close(); + endpoint_type local_endpoint() const; + endpoint_type remote_endpoint() const; + void bind(endpoint_type const& endpoint); + std::size_t available() const; +#endif + + void open(protocol_type const& p, error_code& ec); + void close(error_code& ec); + + // this is only relevant for uTP connections + void set_close_reason(boost::uint16_t code); + boost::uint16_t get_close_reason(); + + endpoint_type local_endpoint(error_code& ec) const; + endpoint_type remote_endpoint(error_code& ec) const; + void bind(endpoint_type const& endpoint, error_code& ec); + std::size_t available(error_code& ec) const; + int type() const; + + + template + std::size_t read_some(Mutable_Buffers const& buffers, error_code& ec) + { TORRENT_SOCKTYPE_FORWARD_RET(read_some(buffers, ec), 0) } + + template + void async_read_some(Mutable_Buffers const& buffers, Handler const& handler) + { TORRENT_SOCKTYPE_FORWARD(async_read_some(buffers, handler)) } + + template + std::size_t write_some(Const_Buffers const& buffers, error_code& ec) + { TORRENT_SOCKTYPE_FORWARD_RET(write_some(buffers, ec), 0) } + + template + void async_write_some(Const_Buffers const& buffers, Handler const& handler) + { TORRENT_SOCKTYPE_FORWARD(async_write_some(buffers, handler)) } + + template + void async_connect(endpoint_type const& endpoint, Handler const& handler) + { TORRENT_SOCKTYPE_FORWARD(async_connect(endpoint, handler)) } + +#ifndef BOOST_NO_EXCEPTIONS + template + void io_control(IO_Control_Command& ioc) + { TORRENT_SOCKTYPE_FORWARD(io_control(ioc)) } + + template + std::size_t read_some(Mutable_Buffers const& buffers) + { TORRENT_SOCKTYPE_FORWARD_RET(read_some(buffers), 0) } +#endif + + template + void io_control(IO_Control_Command& ioc, error_code& ec) + { TORRENT_SOCKTYPE_FORWARD(io_control(ioc, ec)) } + +#ifndef BOOST_NO_EXCEPTIONS + template + void set_option(SettableSocketOption const& opt) + { TORRENT_SOCKTYPE_FORWARD(set_option(opt)) } +#endif + + template + error_code set_option(SettableSocketOption const& opt, error_code& ec) + { TORRENT_SOCKTYPE_FORWARD_RET(set_option(opt, ec), ec) } + +#ifndef BOOST_NO_EXCEPTIONS + template + void get_option(GettableSocketOption& opt) + { TORRENT_SOCKTYPE_FORWARD(get_option(opt)) } +#endif + + template + error_code get_option(GettableSocketOption& opt, error_code& ec) + { TORRENT_SOCKTYPE_FORWARD_RET(get_option(opt, ec), ec) } + + template + void instantiate(io_service& ios, void* userdata = 0) + { + TORRENT_UNUSED(ios); + TORRENT_ASSERT(&ios == &m_io_service); + construct(socket_type_int_impl::value, userdata); + } + + template S* get() + { + if (m_type != socket_type_int_impl::value) return 0; + return reinterpret_cast(&m_data); + } + + template S const* get() const + { + if (m_type != socket_type_int_impl::value) return 0; + return reinterpret_cast(&m_data); + } + + private: + // explicitly disallow assignment, to silence msvc warning + socket_type& operator=(socket_type const&); + + void destruct(); + void construct(int type, void* userdata); + + io_service& m_io_service; + int m_type; + enum { storage_size = max9< + sizeof(tcp::socket) + , sizeof(socks5_stream) + , sizeof(http_stream) + , sizeof(utp_stream) +#if TORRENT_USE_I2P + , sizeof(i2p_stream) +#else + , 0 +#endif +#ifdef TORRENT_USE_OPENSSL + , sizeof(ssl_stream) + , sizeof(ssl_stream) + , sizeof(ssl_stream) + , sizeof(ssl_stream) +#else + , 0, 0, 0, 0 +#endif + >::value + }; + + boost::aligned_storage::type m_data; + }; + + // returns true if this socket is an SSL socket + bool is_ssl(socket_type const& s); + + // returns true if this is a uTP socket + bool is_utp(socket_type const& s); + +#if TORRENT_USE_I2P + // returns true if this is an i2p socket + bool is_i2p(socket_type const& s); +#endif + + // assuming the socket_type s is an ssl socket, make sure it + // verifies the hostname in its SSL handshake + void setup_ssl_hostname(socket_type& s, std::string const& hostname, error_code& ec); + + // properly shuts down SSL sockets. holder keeps s alive + void async_shutdown(socket_type& s, boost::shared_ptr holder); +} + +#endif + diff --git a/include/libtorrent/socket_type_fwd.hpp b/include/libtorrent/socket_type_fwd.hpp new file mode 100644 index 0000000..a2d16ad --- /dev/null +++ b/include/libtorrent/socket_type_fwd.hpp @@ -0,0 +1,42 @@ +/* + +Copyright (c) 2009-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_SOCKET_TYPE_FWD_HPP +#define TORRENT_SOCKET_TYPE_FWD_HPP + +namespace libtorrent +{ + struct socket_type; +} + +#endif + diff --git a/include/libtorrent/socks5_stream.hpp b/include/libtorrent/socks5_stream.hpp new file mode 100644 index 0000000..5be4ef0 --- /dev/null +++ b/include/libtorrent/socks5_stream.hpp @@ -0,0 +1,267 @@ +/* + +Copyright (c) 2007-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_SOCKS5_STREAM_HPP_INCLUDED +#define TORRENT_SOCKS5_STREAM_HPP_INCLUDED + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/proxy_base.hpp" +#include "libtorrent/broadcast_socket.hpp" // for is_ip_address +#include "libtorrent/assert.hpp" +#if defined TORRENT_ASIO_DEBUGGING +#include "libtorrent/debug.hpp" +#endif + +namespace libtorrent { +namespace socks_error { + + // SOCKS5 error values. If an error_code has the + // socks error category (get_socks_category()), these + // are the error values. + enum socks_error_code + { + no_error = 0, + unsupported_version, + unsupported_authentication_method, + unsupported_authentication_version, + authentication_error, + username_required, + general_failure, + command_not_supported, + no_identd, + identd_error, + + num_errors + }; + + // internal + TORRENT_EXPORT boost::system::error_code make_error_code(socks_error_code e); + +} // namespace socks_error + +// returns the error_category for SOCKS5 errors +TORRENT_EXPORT boost::system::error_category& get_socks_category(); + +class socks5_stream : public proxy_base +{ +public: + + // commands + enum { + socks5_connect = 1, + socks5_bind = 2, + socks5_udp_associate = 3 + }; + + explicit socks5_stream(io_service& io_service) + : proxy_base(io_service) + , m_version(5) + , m_command(socks5_connect) + , m_listen(0) + {} + + 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; + } + + template + void async_accept(Handler const& handler) + { + TORRENT_ASSERT(m_listen == 1); + TORRENT_ASSERT(m_command == socks5_bind); + + // to avoid unnecessary copying of the handler, + // store it in a shaed_ptr + error_code e; +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("socks5_stream::connect1"); +#endif + connect1(e, boost::make_shared(handler)); + } + + template + void async_listen(tcp::endpoint const& ep, Handler const& handler) + { + m_command = socks5_bind; + + m_remote_endpoint = ep; + + // to avoid unnecessary copying of the handler, + // store it in a shaed_ptr + boost::shared_ptr h(new handler_type(handler)); + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("socks5_stream::name_lookup"); +#endif + tcp::resolver::query q(m_hostname, to_string(m_port).elems); + m_resolver.async_resolve(q, boost::bind( + &socks5_stream::name_lookup, this, _1, _2, h)); + } + + void set_dst_name(std::string const& host) + { + // if this assert trips, set_dst_name() is called wth an IP address rather + // than a hostname. Instead, resolve the IP into an address and pass it to + // async_connect instead + TORRENT_ASSERT(!is_ip_address(host.c_str())); + 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 + +#ifndef BOOST_NO_EXCEPTIONS + endpoint_type local_endpoint() const + { + return m_local_endpoint; + } +#endif + + endpoint_type local_endpoint(error_code&) const + { + return m_local_endpoint; + } + + + // TODO: 2 add async_connect() that takes a hostname and port as well + template + void async_connect(endpoint_type const& endpoint, Handler const& handler) + { + // make sure we don't try to connect to INADDR_ANY. binding is fine, + // and using a hostname is fine on SOCKS version 5. + TORRENT_ASSERT(m_command != socks5_bind); + 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 + + // to avoid unnecessary copying of the handler, + // store it in a shaed_ptr + boost::shared_ptr h(new handler_type(handler)); + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("socks5_stream::name_lookup"); +#endif + tcp::resolver::query q(m_hostname, to_string(m_port).elems); + m_resolver.async_resolve(q, boost::bind( + &socks5_stream::name_lookup, this, _1, _2, h)); + } + +private: + + void name_lookup(error_code const& e, tcp::resolver::iterator i + , boost::shared_ptr h); + void connected(error_code const& e, boost::shared_ptr h); + void handshake1(error_code const& e, boost::shared_ptr h); + void handshake2(error_code const& e, boost::shared_ptr h); + void handshake3(error_code const& e, boost::shared_ptr h); + void handshake4(error_code const& e, boost::shared_ptr h); + void socks_connect(boost::shared_ptr h); + void connect1(error_code const& e, boost::shared_ptr h); + void connect2(error_code const& e, boost::shared_ptr h); + void connect3(error_code const& e, boost::shared_ptr h); + + // send and receive buffer + std::vector m_buffer; + // proxy authentication + std::string m_user; + std::string m_password; + std::string m_dst_name; + + // when listening via a socks proxy, this is the IP and port our listen + // socket bound to + endpoint_type m_local_endpoint; + + int m_version; + + // the socks command to send for this connection (connect, bind, + // udp associate) + int m_command; + + // set to one when we're waiting for the + // second message to accept an incoming connection + int m_listen; +}; + +} + +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/ssl_stream.hpp b/include/libtorrent/ssl_stream.hpp new file mode 100644 index 0000000..48fe111 --- /dev/null +++ b/include/libtorrent/ssl_stream.hpp @@ -0,0 +1,337 @@ +/* + +Copyright (c) 2008-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SSL_STREAM_HPP_INCLUDED +#define TORRENT_SSL_STREAM_HPP_INCLUDED + +#ifdef TORRENT_USE_OPENSSL + +#include "libtorrent/socket.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/io_service.hpp" +#include "libtorrent/aux_/openssl.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include + +// openssl seems to believe it owns +// this name in every single scope +#undef set_key + +#if defined TORRENT_BUILD_SIMULATOR +#include "simulator/simulator.hpp" +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { + + namespace ssl { +#if defined TORRENT_BUILD_SIMULATOR + using sim::asio::ssl::context; + using sim::asio::ssl::stream_base; + using sim::asio::ssl::stream; +#else + using boost::asio::ssl::context; + using boost::asio::ssl::stream_base; + using boost::asio::ssl::stream; +#endif + } + +template +class ssl_stream +{ +public: + + explicit ssl_stream(io_service& io_service, ssl::context& ctx) + : m_sock(io_service, ctx) + { + } + + typedef typename boost::asio::ssl::stream sock_type; + typedef typename sock_type::next_layer_type next_layer_type; + typedef typename Stream::lowest_layer_type lowest_layer_type; + typedef typename Stream::endpoint_type endpoint_type; + typedef typename Stream::protocol_type protocol_type; + + void set_host_name(std::string name) + { + aux::openssl_set_tlsext_hostname(m_sock.native_handle(), name.c_str()); + } + + template + void set_verify_callback(T const& fun, error_code& ec) + { m_sock.set_verify_callback(fun, ec); } + +#if BOOST_VERSION >= 104700 + SSL* native_handle() { return m_sock.native_handle(); } +#endif + + typedef boost::function handler_type; + + template + void async_connect(endpoint_type const& endpoint, Handler const& handler) + { + // the connect is split up in the following steps: + // 1. connect to peer + // 2. perform SSL client handshake + + // to avoid unnecessary copying of the handler, + // store it in a shared_ptr + boost::shared_ptr h(new handler_type(handler)); + + m_sock.next_layer().async_connect(endpoint + , boost::bind(&ssl_stream::connected, this, _1, h)); + } + + template + void async_accept_handshake(Handler const& handler) + { + // this is used for accepting SSL connections + boost::shared_ptr h(new handler_type(handler)); + m_sock.async_handshake(ssl::stream_base::server + , boost::bind(&ssl_stream::handshake, this, _1, h)); + } + + void accept_handshake(error_code& ec) + { + // this is used for accepting SSL connections + m_sock.handshake(ssl::stream_base::server, ec); + } + + template + void async_shutdown(Handler const& handler) + { + error_code ec; + m_sock.next_layer().cancel(ec); + m_sock.async_shutdown(handler); + } + + void shutdown(error_code& ec) + { + m_sock.shutdown(ec); + } + + template + void async_read_some(Mutable_Buffers const& buffers, Handler const& handler) + { + m_sock.async_read_some(buffers, handler); + } + + template + std::size_t read_some(Mutable_Buffers const& buffers, error_code& ec) + { + return m_sock.read_some(buffers, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + template + void set_option(SettableSocketOption const& opt) + { + m_sock.next_layer().set_option(opt); + } +#endif + + template + error_code set_option(SettableSocketOption const& opt, error_code& ec) + { + return m_sock.next_layer().set_option(opt, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + template + void get_option(GettableSocketOption& opt) + { + m_sock.next_layer().get_option(opt); + } +#endif + + template + error_code get_option(GettableSocketOption& opt, error_code& ec) + { + return m_sock.next_layer().get_option(opt, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + template + std::size_t read_some(Mutable_Buffers const& buffers) + { + return m_sock.read_some(buffers); + } + + template + void io_control(IO_Control_Command& ioc) + { + m_sock.next_layer().io_control(ioc); + } +#endif + + template + void io_control(IO_Control_Command& ioc, error_code& ec) + { + m_sock.next_layer().io_control(ioc, ec); + } + + template + void async_write_some(Const_Buffers const& buffers, Handler const& handler) + { + m_sock.async_write_some(buffers, handler); + } + + template + std::size_t write_some(Const_Buffers const& buffers, error_code& ec) + { + return m_sock.write_some(buffers, ec); + } + + // the SSL stream may cache 17 kiB internally, and there's no way of + // asking how large its buffer is. 17 kiB isn't very much though, so it + // seems fine to potentially over-estimate the number of bytes available. +#ifndef BOOST_NO_EXCEPTIONS + std::size_t available() const + { return 17 * 1024 + const_cast(m_sock).next_layer().available(); } +#endif + + std::size_t available(error_code& ec) const + { return 17 * 1024 + const_cast(m_sock).next_layer().available(ec); } + +#ifndef BOOST_NO_EXCEPTIONS + void bind(endpoint_type const& endpoint) + { + m_sock.next_layer().bind(endpoint); + } +#endif + + void bind(endpoint_type const& endpoint, error_code& ec) + { + m_sock.next_layer().bind(endpoint, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + void open(protocol_type const& p) + { + m_sock.next_layer().open(p); + } +#endif + + void open(protocol_type const& p, error_code& ec) + { + m_sock.next_layer().open(p, ec); + } + + bool is_open() const + { + return const_cast(m_sock).next_layer().is_open(); + } + +#ifndef BOOST_NO_EXCEPTIONS + void close() + { + m_sock.next_layer().close(); + } +#endif + + void close(error_code& ec) + { + m_sock.next_layer().close(ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + endpoint_type remote_endpoint() const + { + return const_cast(m_sock).next_layer().remote_endpoint(); + } +#endif + + endpoint_type remote_endpoint(error_code& ec) const + { + return const_cast(m_sock).next_layer().remote_endpoint(ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + endpoint_type local_endpoint() const + { + return const_cast(m_sock).next_layer().local_endpoint(); + } +#endif + + endpoint_type local_endpoint(error_code& ec) const + { + return const_cast(m_sock).next_layer().local_endpoint(ec); + } + + io_service& get_io_service() + { + return m_sock.get_io_service(); + } + + lowest_layer_type& lowest_layer() + { + return m_sock.lowest_layer(); + } + + next_layer_type& next_layer() + { + return m_sock.next_layer(); + } + +private: + + void connected(error_code const& e, boost::shared_ptr h) + { + if (e) + { + (*h)(e); + return; + } + + m_sock.async_handshake(ssl::stream_base::client + , boost::bind(&ssl_stream::handshake, this, _1, h)); + } + + void handshake(error_code const& e, boost::shared_ptr h) + { + (*h)(e); + } + + ssl::stream m_sock; +}; + +} + +#endif // TORRENT_USE_OPENSSL + +#endif + diff --git a/include/libtorrent/stack_allocator.hpp b/include/libtorrent/stack_allocator.hpp new file mode 100644 index 0000000..f12f4cb --- /dev/null +++ b/include/libtorrent/stack_allocator.hpp @@ -0,0 +1,113 @@ +/* + +Copyright (c) 2015-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_STACK_ALLOCATOR + +#include "libtorrent/assert.hpp" +#include "libtorrent/buffer.hpp" + +namespace libtorrent { namespace aux +{ + + struct stack_allocator + { + stack_allocator() {} + + int copy_string(std::string const& str) + { + int ret = int(m_storage.size()); + m_storage.resize(ret + str.length() + 1); + strcpy(&m_storage[ret], str.c_str()); + return ret; + } + + int copy_string(char const* str) + { + int ret = int(m_storage.size()); + int len = strlen(str); + m_storage.resize(ret + len + 1); + strcpy(&m_storage[ret], str); + return ret; + } + + int copy_buffer(char const* buf, int size) + { + int ret = int(m_storage.size()); + m_storage.resize(ret + size); + memcpy(&m_storage[ret], buf, size); + return ret; + } + + int allocate(int bytes) + { + int ret = int(m_storage.size()); + m_storage.resize(ret + bytes); + return ret; + } + + char* ptr(int idx) + { + TORRENT_ASSERT(idx >= 0); + TORRENT_ASSERT(idx < int(m_storage.size())); + return &m_storage[idx]; + } + + char const* ptr(int idx) const + { + TORRENT_ASSERT(idx >= 0); + TORRENT_ASSERT(idx < int(m_storage.size())); + return &m_storage[idx]; + } + + void swap(stack_allocator& rhs) + { + m_storage.swap(rhs.m_storage); + } + + void reset() + { + m_storage.clear(); + } + + private: + + // non-copyable + stack_allocator(stack_allocator const&); + stack_allocator& operator=(stack_allocator const&); + + buffer m_storage; + }; + +} } + +#endif + diff --git a/include/libtorrent/stat.hpp b/include/libtorrent/stat.hpp new file mode 100644 index 0000000..a1e2094 --- /dev/null +++ b/include/libtorrent/stat.hpp @@ -0,0 +1,289 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_STAT_HPP_INCLUDED +#define TORRENT_STAT_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include + +#include "libtorrent/invariant_check.hpp" +#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); + int rate() const { return m_5_sec_average; } + int low_pass_rate() const { return m_5_sec_average; } + + boost::int64_t total() const { return m_total_counter; } + + void offset(boost::int64_t c) + { + TORRENT_ASSERT(m_total_counter < (std::numeric_limits::max)() - c); + m_total_counter += c; + } + + int counter() const { return m_counter; } + + void clear() + { + m_counter = 0; + m_5_sec_average = 0; + m_total_counter = 0; + } + + private: + + // total counters + boost::uint64_t m_total_counter; + + // the accumulator for this second. + boost::uint32_t m_counter; + + // sliding average + boost::uint32_t m_5_sec_average; + }; + + class TORRENT_EXTRA_EXPORT stat + { + friend class invariant_access; + 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(); + } + + boost::int64_t total_upload() const + { + return m_stat[upload_payload].total() + + m_stat[upload_protocol].total() + + m_stat[upload_ip_protocol].total(); + } + + boost::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(); } + + boost::int64_t total_payload_upload() const + { return m_stat[upload_payload].total(); } + boost::int64_t total_payload_download() const + { return m_stat[download_payload].total(); } + + boost::int64_t total_protocol_upload() const + { return m_stat[upload_protocol].total(); } + boost::int64_t total_protocol_download() const + { return m_stat[download_protocol].total(); } + + boost::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(boost::int64_t downloaded, boost::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 + { + 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..e260e4d --- /dev/null +++ b/include/libtorrent/stat_cache.hpp @@ -0,0 +1,87 @@ +/* + +Copyright (c) 2012-2016, Arvid Norberg, Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_STAT_CACHE_HPP +#define TORRENT_STAT_CACHE_HPP + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/config.hpp" + +namespace libtorrent +{ + struct TORRENT_EXTRA_EXPORT stat_cache + { + stat_cache(); + ~stat_cache(); + + void init(int num_files); + + enum + { + cache_error = -1, + not_in_cache = -2, + no_exist = -3 + }; + + // returns the size of the file or one + // of the enums, noent or not_in_cache + boost::int64_t get_filesize(int i) const; + time_t get_filetime(int i) const; + + void set_cache(int i, boost::int64_t size, time_t time); + void set_noexist(int i); + void set_error(int i); + void set_dirty(int i); + + void clear(); + + private: + + struct stat_cache_t + { + stat_cache_t(boost::int64_t s, time_t t = 0): file_size(s), file_time(t) {} + boost::int64_t file_size; + time_t file_time; + }; + std::vector m_stat_cache; + }; +} + +#endif // TORRENT_STAT_CACHE_HPP + diff --git a/include/libtorrent/storage.hpp b/include/libtorrent/storage.hpp new file mode 100644 index 0000000..da894d7 --- /dev/null +++ b/include/libtorrent/storage.hpp @@ -0,0 +1,712 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_STORAGE_HPP_INCLUDE +#define TORRENT_STORAGE_HPP_INCLUDE + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/piece_picker.hpp" +#include "libtorrent/peer_request.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/file.hpp" +#include "libtorrent/disk_buffer_holder.hpp" +#include "libtorrent/thread.hpp" +#include "libtorrent/storage_defs.hpp" +#include "libtorrent/allocator.hpp" +#include "libtorrent/file_pool.hpp" // pool_file_status +#include "libtorrent/part_file.hpp" +#include "libtorrent/stat_cache.hpp" +#include "libtorrent/bdecode.hpp" +#include "libtorrent/bitfield.hpp" +#include "libtorrent/performance_counters.hpp" + +// OVERVIEW +// +// libtorrent provides a customization point for storage of data. By default, +// (``default_storage``) downloaded files are saved to disk according with the +// general conventions of bittorrent clients, mimicing the original file layout +// when the torrent was created. The libtorrent user may define a custom +// storage to store piece data in a different way. +// +// A custom storage implementation must derive from and implement the +// storage_interface. You must also provide a function that constructs the +// custom storage object and provide this function to the add_torrent() call +// via add_torrent_params. Either passed in to the constructor or by setting +// the add_torrent_params::storage field. +// +// This is an example storage implementation that stores all pieces in a +// ``std::map``, i.e. in RAM. It's not necessarily very useful in practice, but +// illustrates the basics of implementing a custom storage. +// +// .. code:: c++ +// +// struct temp_storage : storage_interface +// { +// temp_storage(file_storage const& fs) : m_files(fs) {} +// virtual bool initialize(storage_error& se) { return false; } +// virtual bool has_any_file() { return false; } +// virtual int read(char* buf, int piece, int offset, int size) +// { +// std::map >::const_iterator i = m_file_data.find(piece); +// if (i == m_file_data.end()) return 0; +// int available = i->second.size() - offset; +// if (available <= 0) return 0; +// if (available > size) available = size; +// memcpy(buf, &i->second[offset], available); +// return available; +// } +// virtual int write(const char* buf, int piece, int offset, int size) +// { +// std::vector& data = m_file_data[piece]; +// if (data.size() < offset + size) data.resize(offset + size); +// std::memcpy(&data[offset], buf, size); +// return size; +// } +// virtual bool rename_file(int file, std::string const& new_name) +// { assert(false); return false; } +// virtual bool move_storage(std::string const& save_path) { return false; } +// virtual bool verify_resume_data(bdecode_node const& rd +// , std::vector const* links +// , storage_error& error) { return false; } +// virtual bool write_resume_data(entry& rd) const { return false; } +// virtual boost::int64_t physical_offset(int piece, int offset) +// { return piece * m_files.piece_length() + offset; }; +// virtual sha1_hash hash_for_slot(int piece, partial_hash& ph, int piece_size) +// { +// int left = piece_size - ph.offset; +// assert(left >= 0); +// if (left > 0) +// { +// std::vector& data = m_file_data[piece]; +// // if there are padding files, those blocks will be considered +// // completed even though they haven't been written to the storage. +// // in this case, just extend the piece buffer to its full size +// // and fill it with zeroes. +// if (data.size() < piece_size) data.resize(piece_size, 0); +// ph.h.update(&data[ph.offset], left); +// } +// return ph.h.final(); +// } +// virtual bool release_files() { return false; } +// virtual bool delete_files() { return false; } +// +// std::map > m_file_data; +// file_storage m_files; +// }; +// +// storage_interface* temp_storage_constructor(storage_params const& params) +// { +// return new temp_storage(*params.files); +// } +namespace libtorrent +{ + class session; + struct file_pool; + struct disk_io_job; + struct disk_buffer_pool; + struct cache_status; + namespace aux { struct session_settings; } + struct cached_piece_entry; + + TORRENT_EXTRA_EXPORT int copy_bufs(file::iovec_t const* bufs, int bytes, file::iovec_t* target); + TORRENT_EXTRA_EXPORT void advance_bufs(file::iovec_t*& bufs, int bytes); + TORRENT_EXTRA_EXPORT void clear_bufs(file::iovec_t const* bufs, int num_bufs); + + // flags for async_move_storage + enum move_flags_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 + }; + + // The storage interface is a pure virtual class that can be implemented to + // customize how and where data for a torrent is stored. The default storage + // implementation uses regular files in the filesystem, mapping the files in + // the torrent in the way one would assume a torrent is saved to disk. + // Implementing your own storage interface makes it possible to store all + // data in RAM, or in some optimized order on disk (the order the pieces are + // received for instance), or saving multifile torrents in a single file in + // order to be able to take advantage of optimized disk-I/O. + // + // It is also possible to write a thin class that uses the default storage + // but modifies some particular behavior, for instance encrypting the data + // before it's written to disk, and decrypting it when it's read again. + // + // The storage interface is based on pieces. Avery read and write operation + // happens in the piece-space. Each piece fits 'piece_size' number + // of bytes. All access is done by writing and reading whole or partial + // pieces. + // + // libtorrent comes with two built-in storage implementations; + // ``default_storage`` and ``disabled_storage``. Their constructor functions + // are called default_storage_constructor() and + // ``disabled_storage_constructor`` respectively. The disabled storage does + // just what it sounds like. It throws away data that's written, and it + // reads garbage. It's useful mostly for benchmarking and profiling purpose. + // + struct TORRENT_EXPORT storage_interface + { + // hidden + storage_interface(): m_settings(0) {} + + + // This function is called when the storage is to be initialized. The + // default storage will create directories and empty files at this point. + // If ``allocate_files`` is true, it will also ``ftruncate`` all files to + // their target size. + // + // If an error occurs, ``storage_error`` should be set to reflect it. + virtual void initialize(storage_error& ec) = 0; + + // These functions should read and write the data in or to the given + // ``piece`` at the given ``offset``. It should read or write + // ``num_bufs`` buffers sequentially, where the size of each buffer is + // specified in the buffer array ``bufs``. The file::iovec_t type has the + // following members:: + // + // struct iovec_t { void* iov_base; size_t iov_len; }; + // + // These functions may be called simultaneously from multiple threads. + // Make sure they are thread safe. The ``file`` in libtorrent is thread + // safe when it can fall back to ``pread``, ``preadv`` or the windows + // equivalents. On targets where read operations cannot be thread safe + // (i.e one has to seek first and then read), only one disk thread is + // used. + // + // Every buffer in ``bufs`` can be assumed to be page aligned and be of a + // page aligned size, except for the last buffer of the torrent. The + // allocated buffer can be assumed to fit a fully page aligned number of + // bytes though. This is useful when reading and writing the last piece + // of a file in unbuffered mode. + // + // The ``offset`` is aligned to 16 kiB boundaries *most of the time*, but + // there are rare exceptions when it's not. Specifically if the read + // cache is disabled/or full and a peer requests unaligned data. Most + // clients request aligned data. + // + // The number of bytes read or written should be returned, or -1 on + // error. If there's an error, the ``storage_error`` must be filled out + // to represent the error that occurred. + virtual int readv(file::iovec_t const* bufs, int num_bufs + , int piece, int offset, int flags, storage_error& ec) = 0; + virtual int writev(file::iovec_t const* bufs, int num_bufs + , int piece, int offset, int flags, storage_error& ec) = 0; + + // This function is called when first checking (or re-checking) the + // storage for a torrent. It should return true if any of the files that + // is used in this storage exists on disk. If so, the storage will be + // checked for existing pieces before starting the download. + // + // If an error occurs, ``storage_error`` should be set to reflect it. + virtual bool has_any_file(storage_error& ec) = 0; + + // change the priorities of files. This is a fenced job and is + // guaranteed to be the only running function on this storage + // when called + virtual void set_file_priority(std::vector const& prio + , storage_error& ec) = 0; + + // This function should move all the files belonging to the storage to + // the new save_path. The default storage moves the single file or the + // directory of the torrent. + // + // Before moving the files, any open file handles may have to be closed, + // like ``release_files()``. + // + //If an error occurs, ``storage_error`` should be set to reflect it. + // + // returns one of: + // | no_error = 0 + // | fatal_disk_error = -1 + // | need_full_check = -2 + // | file_exist = -4 + virtual int move_storage(std::string const& save_path, int flags + , storage_error& ec) = 0; + + // This function should verify the resume data ``rd`` with the files + // on disk. If the resume data seems to be up-to-date, return true. If + // not, set ``error`` to a description of what mismatched and return false. + // + // The default storage may compare file sizes and time stamps of the files. + // + // If an error occurs, ``storage_error`` should be set to reflect it. + // + // This function should verify the resume data ``rd`` with the files + // on disk. If the resume data seems to be up-to-date, return true. If + // not, set ``error`` to a description of what mismatched and return false. + // + // If the ``links`` pointer is non-null, it has the same number + // of elements as there are files. Each element is either empty or contains + // the absolute path to a file identical to the corresponding file in this + // torrent. The storage must create hard links (or copy) those files. If + // any file does not exist or is inaccessible, the disk job must fail. + virtual bool verify_resume_data(bdecode_node const& rd + , std::vector const* links + , storage_error& ec) = 0; + + // This function should fill in resume data, the current state of the + // storage, in ``rd``. The default storage adds file timestamps and + // sizes. + // + // Returning ``true`` indicates an error occurred. + // + // If an error occurs, ``storage_error`` should be set to reflect it. + // + virtual void write_resume_data(entry& rd, storage_error& ec) const = 0; + + // This function should release all the file handles that it keeps open + // to files belonging to this storage. The default implementation just + // calls file_pool::release_files(). + // + // If an error occurs, ``storage_error`` should be set to reflect it. + // + virtual void release_files(storage_error& ec) = 0; + + // Rename file with index ``file`` to the thame ``new_name``. + // + // If an error occurs, ``storage_error`` should be set to reflect it. + // + virtual void rename_file(int index, std::string const& new_filename + , storage_error& ec) = 0; + + // This function should delete some or all of the storage for this torrent. + // The ``options`` parameter specifies whether to delete all files or just + // the partfile. ``options`` are set to the same value as the options + // passed to session::remove_torrent(). + // + // If an error occurs, ``storage_error`` should be set to reflect it. + // + // The ``disk_buffer_pool`` is used to allocate and free disk buffers. It + // has the following members:: + // + // struct disk_buffer_pool : boost::noncopyable + // { + // char* allocate_buffer(char const* category); + // void free_buffer(char* buf); + // + // char* allocate_buffers(int blocks, char const* category); + // void free_buffers(char* buf, int blocks); + // + // int block_size() const { return m_block_size; } + // + // void release_memory(); + // }; + // + virtual void delete_files(int options, storage_error& ec) = 0; + +#ifndef TORRENT_NO_DEPRECATE + // This function is called each time a file is completely downloaded. The + // storage implementation can perform last operations on a file. The file + // will not be opened for writing after this. + // + // ``index`` is the index of the file that completed. + // + // On windows the default storage implementation clears the sparse file + // flag on the specified file. + // + // If an error occurs, ``storage_error`` should be set to reflect it. + // + virtual void finalize_file(int, storage_error&) {} +#endif + + // called periodically (useful for deferred flushing). When returning + // false, it means no more ticks are necessary. Any disk job submitted + // will re-enable ticking. The default will always turn ticking back + // off again. + virtual bool tick() { return false; } + + // access global session_settings + aux::session_settings const& settings() const { return *m_settings; } + + // hidden + virtual ~storage_interface() {} + + // initialized in disk_io_thread::perform_async_job + aux::session_settings* m_settings; + }; + + // The default implementation of storage_interface. Behaves as a normal + // bittorrent client. It is possible to derive from this class in order to + // override some of its behavior, when implementing a custom storage. + class TORRENT_EXPORT default_storage : public storage_interface, boost::noncopyable + { + friend struct write_fileop; + friend struct read_fileop; + public: + // constructs the default_storage based on the give file_storage (fs). + // ``mapped`` is an optional argument (it may be NULL). If non-NULL it + // represents the file mapping that have been made to the torrent before + // adding it. That's where files are supposed to be saved and looked for + // on disk. ``save_path`` is the root save folder for this torrent. + // ``file_pool`` is the cache of file handles that the storage will use. + // All files it opens will ask the file_pool to open them. ``file_prio`` + // is a vector indicating the priority of files on startup. It may be + // an empty vector. Any file whose index is not represented by the vector + // (because the vector is too short) are assumed to have priority 1. + // this is used to treat files with priority 0 slightly differently. + default_storage(storage_params const& params); + + // hidden + ~default_storage(); + +#ifndef TORRENT_NO_DEPRECATE + void finalize_file(int file, storage_error& ec) TORRENT_OVERRIDE; +#endif + virtual bool has_any_file(storage_error& ec) TORRENT_OVERRIDE; + virtual void set_file_priority(std::vector const& prio + , storage_error& ec) TORRENT_OVERRIDE; + virtual void rename_file(int index, std::string const& new_filename + , storage_error& ec) TORRENT_OVERRIDE; + virtual void release_files(storage_error& ec) TORRENT_OVERRIDE; + virtual void delete_files(int options, storage_error& ec) TORRENT_OVERRIDE; + virtual void initialize(storage_error& ec) TORRENT_OVERRIDE; + virtual int move_storage(std::string const& save_path, int flags + , storage_error& ec) TORRENT_OVERRIDE; + virtual bool verify_resume_data(bdecode_node const& rd + , std::vector const* links + , storage_error& error) TORRENT_OVERRIDE; + virtual void write_resume_data(entry& rd, storage_error& ec) const TORRENT_OVERRIDE; + virtual bool tick() TORRENT_OVERRIDE; + + int readv(file::iovec_t const* bufs, int num_bufs + , int piece, int offset, int flags, storage_error& ec) TORRENT_OVERRIDE; + int writev(file::iovec_t const* bufs, int num_bufs + , int piece, int offset, int flags, storage_error& ec) TORRENT_OVERRIDE; + + // if the files in this storage are mapped, returns the mapped + // file_storage, otherwise returns the original file_storage object. + file_storage const& files() const { return m_mapped_files?*m_mapped_files:m_files; } + +#ifdef TORRENT_DISK_STATS + static bool disk_write_access_log(); + static void disk_write_access_log(bool enable); +#endif + + private: + + void delete_one_file(std::string const& p, error_code& ec); + + void need_partfile(); + + boost::scoped_ptr m_mapped_files; + file_storage const& m_files; + + // in order to avoid calling stat() on each file multiple times + // during startup, cache the results in here, and clear it all + // out once the torrent starts (to avoid getting stale results) + // each entry represents the size and timestamp of the file + mutable stat_cache m_stat_cache; + + // helper function to open a file in the file pool with the right mode + file_handle open_file(int file, int mode, storage_error& ec) const; + file_handle open_file_impl(int file, int mode, error_code& ec) const; + + std::vector m_file_priority; + std::string m_save_path; + std::string m_part_file_name; + // the file pool is typically stored in + // the session, to make all storage + // instances use the same pool + file_pool& m_pool; + + // used for skipped files + boost::scoped_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 bitfield m_file_created; + + bool m_allocate_files; + }; + + // this storage implementation does not write anything to disk + // and it pretends to read, and just leaves garbage in the buffers + // this is useful when simulating many clients on the same machine + // or when running stress tests and want to take the cost of the + // disk I/O out of the picture. This cannot be used for any kind + // of normal bittorrent operation, since it will just send garbage + // to peers and throw away all the data it downloads. It would end + // up being banned immediately + class disabled_storage TORRENT_FINAL : public storage_interface, boost::noncopyable + { + public: + virtual bool has_any_file(storage_error&) TORRENT_OVERRIDE { return false; } + virtual void set_file_priority(std::vector const& + , storage_error&) TORRENT_OVERRIDE {} + virtual void rename_file(int, std::string const&, storage_error&) TORRENT_OVERRIDE {} + virtual void release_files(storage_error&) TORRENT_OVERRIDE {} + virtual void delete_files(int, storage_error&) TORRENT_OVERRIDE {} + virtual void initialize(storage_error&) TORRENT_OVERRIDE {} + virtual int move_storage(std::string const&, int, storage_error&) TORRENT_OVERRIDE { return 0; } + + virtual int readv(file::iovec_t const* bufs, int num_bufs, int piece + , int offset, int flags, storage_error& ec) TORRENT_OVERRIDE; + virtual int writev(file::iovec_t const* bufs, int num_bufs, int piece + , int offset, int flags, storage_error& ec) TORRENT_OVERRIDE; + + virtual bool verify_resume_data(bdecode_node const& + , std::vector const* + , storage_error&) TORRENT_OVERRIDE { return false; } + virtual void write_resume_data(entry&, storage_error&) const TORRENT_OVERRIDE {} + }; + + // this storage implementation always reads zeroes, and always discards + // anything written to it + struct zero_storage TORRENT_FINAL : storage_interface + { + virtual void initialize(storage_error&) TORRENT_OVERRIDE {} + + virtual int readv(file::iovec_t const* bufs, int num_bufs + , int piece, int offset, int flags, storage_error& ec) TORRENT_OVERRIDE; + virtual int writev(file::iovec_t const* bufs, int num_bufs + , int piece, int offset, int flags, storage_error& ec) TORRENT_OVERRIDE; + + virtual bool has_any_file(storage_error&) TORRENT_OVERRIDE { return false; } + virtual void set_file_priority(std::vector const& /* prio */ + , storage_error&) TORRENT_OVERRIDE {} + virtual int move_storage(std::string const& /* save_path */ + , int /* flags */, storage_error&) TORRENT_OVERRIDE { return 0; } + virtual bool verify_resume_data(bdecode_node const& /* rd */ + , std::vector const* /* links */ + , storage_error&) TORRENT_OVERRIDE + { return false; } + virtual void write_resume_data(entry&, storage_error&) const TORRENT_OVERRIDE {} + virtual void release_files(storage_error&) TORRENT_OVERRIDE {} + virtual void rename_file(int /* index */ + , std::string const& /* new_filenamem */, storage_error&) TORRENT_OVERRIDE {} + virtual void delete_files(int, storage_error&) TORRENT_OVERRIDE {} + }; + + struct disk_io_thread; + + // implements the disk I/O job fence used by the piece_manager + // 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(); + ~disk_job_fence() + { + TORRENT_ASSERT(int(m_outstanding_jobs) == 0); + TORRENT_ASSERT(m_blocked_jobs.size() == 0); + } + + // returns one of the fence_* enums. + // if there are no outstanding jobs on the + // storage, fence_post_fence is returned, the flush job is expected + // to be discarded by the caller. + // fence_post_flush is returned if the fence job was blocked and queued, + // but the flush job should be posted (i.e. put on the job queue) + // fence_post_none if both the fence and the flush jobs were queued. + enum { fence_post_fence = 0, fence_post_flush = 1, fence_post_none = 2 }; + int raise_fence(disk_io_job* fence_job, disk_io_job* flush_job + , counters& cnt); + bool has_fence() const; + + // called whenever a job completes and is posted back to the + // main network thread. the tailqueue of jobs will have the + // backed-up jobs prepended to it in case this resulted in the + // fence being lowered. + int job_complete(disk_io_job* j, tailqueue& job_queue); + int num_outstanding_jobs() const { return m_outstanding_jobs; } + + // if there is a fence up, returns true and adds the job + // to the queue of blocked jobs + bool is_blocked(disk_io_job* j); + + // the number of blocked jobs + int num_blocked() const; + + private: + // when > 0, this storage is blocked for new async + // operations until all outstanding jobs have completed. + // at that point, the m_blocked_jobs are issued + // the count is the number of fence job currently in the queue + int m_has_fence; + + // when there's a fence up, jobs are queued up in here + // until the fence is lowered + tailqueue m_blocked_jobs; + + // the number of disk_io_job objects there are, belonging + // to this torrent, currently pending, hanging off of + // cached_piece_entry objects. This is used to determine + // when the fence can be lowered + boost::atomic m_outstanding_jobs; + + // must be held when accessing m_has_fence and + // m_blocked_jobs + mutable mutex m_mutex; + }; + + // this class keeps track of which pieces, belonging to + // a specific storage, are in the cache right now. It's + // used for quickly being able to evict all pieces for a + // specific torrent + struct TORRENT_EXTRA_EXPORT storage_piece_set + { + void add_piece(cached_piece_entry* p); + void remove_piece(cached_piece_entry* p); + bool has_piece(cached_piece_entry const* p) const; + int num_pieces() const { return int(m_cached_pieces.size()); } + boost::unordered_set const& cached_pieces() const + { return m_cached_pieces; } + private: + // these are cached pieces belonging to this storage + boost::unordered_set m_cached_pieces; + }; + + class TORRENT_EXTRA_EXPORT piece_manager + : public boost::enable_shared_from_this + , public disk_job_fence + , public storage_piece_set + , boost::noncopyable + { + friend struct disk_io_thread; + public: + + piece_manager( + storage_interface* storage_impl + , boost::shared_ptr const& torrent + , file_storage* files); + + ~piece_manager(); + + file_storage const* files() const { return &m_files; } + + enum return_t + { + // return values from check_fastresume, and move_storage + no_error = 0, + fatal_disk_error = -1, + need_full_check = -2, + disk_check_aborted = -3, + file_exist = -4 + }; + + storage_interface* get_storage_impl() { return m_storage.get(); } + + void write_resume_data(entry& rd, storage_error& ec) const; + +#ifdef TORRENT_DEBUG + void assert_torrent_refcount() const; +#endif + private: + + // if error is set and return value is 'no_error' or 'need_full_check' + // the error message indicates that the fast resume data was rejected + // if 'fatal_disk_error' is returned, the error message indicates what + // when wrong in the disk access + int check_fastresume(bdecode_node const& rd + , std::vector const* links + , storage_error& error); + + // helper functions for check_fastresume + int check_no_fastresume(storage_error& error); + int check_init_storage(storage_error& error); + +#ifdef TORRENT_DEBUG + std::string name() const { return m_files.name(); } +#endif + +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const; +#endif + file_storage const& m_files; + + boost::scoped_ptr m_storage; + + // 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 piece_manager destructs. This is because + // the torrent_info object is owned by the torrent. + boost::shared_ptr m_torrent; + }; + + // this identifies a read or write operation so that readwritev() knows + // what to do when it's actually touching the file + struct fileop + { + virtual int file_op(int file_index, boost::int64_t file_offset, int size + , file::iovec_t const* bufs, storage_error& ec) = 0; + }; + + // 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 + , file::iovec_t const* bufs, int piece, int offset, int num_bufs + , fileop& op, storage_error& ec); + +} + +#endif // TORRENT_STORAGE_HPP_INCLUDED + diff --git a/include/libtorrent/storage_defs.hpp b/include/libtorrent/storage_defs.hpp new file mode 100644 index 0000000..ed870c7 --- /dev/null +++ b/include/libtorrent/storage_defs.hpp @@ -0,0 +1,90 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_STORAGE_DEFS_HPP_INCLUDE +#define TORRENT_STORAGE_DEFS_HPP_INCLUDE + +#include "libtorrent/config.hpp" +#include +#include +#include + +namespace libtorrent +{ + struct storage_interface; + class file_storage; + struct file_pool; + class torrent_info; + + // types of storage allocation used for add_torrent_params::storage_mode. + enum storage_mode_t + { + // All pieces will be written to their final position, all files will be + // allocated in full when the torrent is first started. This is done with + // ``fallocate()`` and similar calls. This mode minimizes fragmentation. + storage_mode_allocate, + + // All pieces will be written to the place where they belong and sparse files + // will be used. This is the recommended, and default mode. + storage_mode_sparse + }; + + // see default_storage::default_storage() + struct TORRENT_EXPORT storage_params + { + storage_params(): files(NULL), mapped_files(NULL), pool(NULL) + , mode(storage_mode_sparse), priorities(NULL), info(NULL) {} + file_storage const* files; + file_storage const* mapped_files; // optional + std::string path; + file_pool* pool; + storage_mode_t mode; + std::vector const* priorities; // optional + torrent_info const* info; // optional + }; + + typedef boost::function storage_constructor_type; + + // the constructor function for the regular file storage. This is the + // default value for add_torrent_params::storage. + TORRENT_EXPORT storage_interface* default_storage_constructor(storage_params const&); + + // the constructor function for the disabled storage. This can be used for + // testing and benchmarking. It will throw away any data written to + // it and return garbage for anything read from it. + TORRENT_EXPORT storage_interface* disabled_storage_constructor(storage_params const&); + + TORRENT_EXPORT storage_interface* zero_storage_constructor(storage_params const&); +} + +#endif + diff --git a/include/libtorrent/string_util.hpp b/include/libtorrent/string_util.hpp new file mode 100644 index 0000000..e8d90d4 --- /dev/null +++ b/include/libtorrent/string_util.hpp @@ -0,0 +1,109 @@ +/* + +Copyright (c) 2012-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_STRING_UTIL_HPP_INCLUDED +#define TORRENT_STRING_UTIL_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include +#include // for boost::array + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent +{ + TORRENT_EXTRA_EXPORT bool is_alpha(char c); + + TORRENT_EXTRA_EXPORT + boost::array::digits10> + to_string(boost::int64_t n); + + // internal + inline bool is_digit(char c) + { return c >= '0' && c <= '9'; } + + 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 int split_string(char const** tags, int buf_size, char* in); + TORRENT_EXTRA_EXPORT bool string_begins_no_case(char const* s1, char const* s2); + TORRENT_EXTRA_EXPORT bool string_equal_no_case(char const* s1, char const* s2); + + TORRENT_EXTRA_EXPORT void url_random(char* begin, char* end); + + // this parses the string that's used as the liste_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 + char* allocate_string_copy(char const* str); + + // returns p + x such that the pointer is 8 bytes aligned + // x cannot be greater than 7 + void* align_pointer(void* p); + + // searches for separator in the string 'last'. the pointer last points to + // is set to point to the first character following the separator. + // returns a pointer to a null terminated string starting at last, ending + // at the separator (the string is mutated to replace the separator with + // a '\0' character). If there is no separator, but the end of the string, + // the pointer next points to is set to the last null terminator, which will + // make the following invocation return NULL, to indicate the end of the + // string. + TORRENT_EXTRA_EXPORT char* string_tokenize(char* last, char sep, char** next); + +#if TORRENT_USE_I2P + + bool is_i2p_url(std::string const& url); + +#endif +} + +#endif + diff --git a/include/libtorrent/tailqueue.hpp b/include/libtorrent/tailqueue.hpp new file mode 100644 index 0000000..ca57d36 --- /dev/null +++ b/include/libtorrent/tailqueue.hpp @@ -0,0 +1,184 @@ +/* + +Copyright (c) 2011-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_TAILQUEUE_HPP +#define TORRENT_TAILQUEUE_HPP + +#include "libtorrent/assert.hpp" + +namespace libtorrent +{ + template + struct tailqueue_node + { + tailqueue_node() : next(0) {} + 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: + tailqueue_iterator(T* cur) + : m_current(cur) {} + // the current element + T* m_current; + }; + + template +//#error boost::enable_if< is_base > > + struct TORRENT_EXTRA_EXPORT tailqueue + { + tailqueue(): m_first(NULL), m_last(NULL), 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 == 0 || m_last->next == 0); + TORRENT_ASSERT(rhs.m_last == 0 || rhs.m_last->next == 0); + + if (rhs.m_first == 0) return; + + if (m_first == 0) + { + swap(rhs); + return; + } + + m_last->next = rhs.m_first; + m_last = rhs.m_last; + m_size += rhs.m_size; + rhs.m_first = 0; + rhs.m_last = 0; + rhs.m_size = 0; + + TORRENT_ASSERT(m_last == 0 || m_last->next == 0); + } + + void prepend(tailqueue& rhs) + { + TORRENT_ASSERT(m_last == 0 || m_last->next == 0); + TORRENT_ASSERT(rhs.m_last == 0 || rhs.m_last->next == 0); + + if (rhs.m_first == 0) return; + + if (m_first == 0) + { + swap(rhs); + return; + } + + swap(rhs); + append(rhs); + } + + T* pop_front() + { + TORRENT_ASSERT(m_last == 0 || m_last->next == 0); + T* e = m_first; + m_first = m_first->next; + if (e == m_last) m_last = 0; + e->next = 0; + --m_size; + return e; + } + void push_front(T* e) + { + TORRENT_ASSERT(e->next == 0); + TORRENT_ASSERT(m_last == 0 || m_last->next == 0); + e->next = m_first; + m_first = e; + if (!m_last) m_last = e; + ++m_size; + } + void push_back(T* e) + { + TORRENT_ASSERT(e->next == 0); + TORRENT_ASSERT(m_last == 0 || m_last->next == 0); + if (m_last) m_last->next = e; + else m_first = e; + m_last = e; + e->next = 0; + ++m_size; + } + T* get_all() + { + TORRENT_ASSERT(m_last == 0 || m_last->next == 0); + T* e = m_first; + m_first = 0; + m_last = 0; + 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 { return m_size; } + bool empty() const { 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/thread.hpp b/include/libtorrent/thread.hpp new file mode 100644 index 0000000..4ef92bb --- /dev/null +++ b/include/libtorrent/thread.hpp @@ -0,0 +1,95 @@ +/* + +Copyright (c) 2009-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_THREAD_HPP_INCLUDED +#define TORRENT_THREAD_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/time.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 + +#if defined TORRENT_BEOS +#include +#endif + +#include // for auto_ptr required by asio + +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent +{ + typedef boost::asio::detail::thread thread; + typedef boost::asio::detail::mutex mutex; + typedef boost::asio::detail::event event; + + // internal + void sleep(int milliseconds); + + struct TORRENT_EXTRA_EXPORT condition_variable + { + condition_variable(); + ~condition_variable(); + void wait(mutex::scoped_lock& l); + void wait_for(mutex::scoped_lock& l, time_duration rel_time); + void notify_all(); + void notify(); + private: +#ifdef BOOST_HAS_PTHREADS + pthread_cond_t m_cond; +#elif defined TORRENT_WINDOWS || defined TORRENT_CYGWIN + HANDLE m_sem; + mutex m_mutex; + int m_num_waiters; +#elif defined TORRENT_BEOS + sem_id m_sem; + mutex m_mutex; + int m_num_waiters; +#else +#error not implemented +#endif + }; +} + +#endif + diff --git a/include/libtorrent/thread_pool.hpp b/include/libtorrent/thread_pool.hpp new file mode 100644 index 0000000..a66a782 --- /dev/null +++ b/include/libtorrent/thread_pool.hpp @@ -0,0 +1,165 @@ +/* + +Copyright (c) 2011-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_THREAD_POOL +#define TORRENT_THREAD_POOL + +#include "libtorrent/config.hpp" +#include "libtorrent/thread.hpp" +#include +#include +#include +#include +#include + +namespace libtorrent +{ + + template + struct thread_pool + { + thread_pool() : m_num_threads(0) {} + virtual ~thread_pool() {} + void stop() { set_num_threads(0, true); } + void set_num_threads(int n, bool wait = true) + { + if (n == m_num_threads) return; + + if (n > m_num_threads) + { + while (m_num_threads < n) + { + ++m_num_threads; + m_threads.push_back(boost::shared_ptr( + new thread(boost::bind(&thread_pool::thread_fun, this, int(m_num_threads)-1)))); + } + } + else + { + while (m_num_threads > n) { --m_num_threads; } + mutex::scoped_lock l(m_mutex); + m_cond.notify_all(); + l.unlock(); + if (wait) + { + for (int i = m_num_threads; i < int(m_threads.size()); ++i) + m_threads[i]->join(); + } + // this will detach the threads + m_threads.resize(m_num_threads); + } + } + + // returns true if the job was posted, and false if it was + // processed immediately + bool post_job(T& e) + { + if (m_num_threads == 0) + { + // if we don't have any worker threads + // just do the work immediately + process_job(e, false); + return false; + } + else + { + retain_job(e); + mutex::scoped_lock l(m_mutex); + m_queue.push_back(e); + // we only need to signal if the threads + // may have been put to sleep. If the size + // previous to adding the new job was > 0 + // they don't need waking up. + if (m_queue.size() == 1) + m_cond.notify(); + return true; + } + } + + protected: + + virtual void process_job(T const& j, bool post) = 0; + virtual void retain_job(T&) {} + + private: + + void thread_fun(int thread_id) + { + for (;;) + { + mutex::scoped_lock l(m_mutex); + while (m_queue.empty() && thread_id < m_num_threads) m_cond.wait(l); + + // if the number of wanted thread is decreased, + // we may stop this thread + // when we're terminating the last hasher thread (id=0), make sure + // we finish up all queud jobs first + if ((thread_id != 0 || m_queue.empty()) && thread_id >= m_num_threads) break; + + TORRENT_ASSERT(!m_queue.empty()); + T e = m_queue.front(); + m_queue.pop_front(); + l.unlock(); + + process_job(e, true); + } + +#ifdef TORRENT_DEBUG + if (thread_id == 0) + { + // when we're terminating the last hasher thread, make sure + // there are no more scheduled jobs + mutex::scoped_lock l(m_mutex); + TORRENT_ASSERT(m_queue.empty()); + } +#endif + } + + // the mutex only protects m_cond and m_queue + // all other members are only used from a single + // thread (the user of this class, i.e. the disk + // thread). + mutex m_mutex; + condition_variable m_cond; + std::deque m_queue; + + std::vector > m_threads; + // this is a counter which is atomically incremented + // by each thread as it's started up, in order to + // assign a unique id to each thread + boost::atomic m_num_threads; + }; + +} + +#endif // TORRENT_THREAD_POOL + diff --git a/include/libtorrent/time.hpp b/include/libtorrent/time.hpp new file mode 100644 index 0000000..a63cefd --- /dev/null +++ b/include/libtorrent/time.hpp @@ -0,0 +1,122 @@ +/* + +Copyright (c) 2007-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_TIME_HPP_INCLUDED +#define TORRENT_TIME_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include + +#if defined BOOST_ASIO_HAS_STD_CHRONO +#include +#else +#include +#endif + +#if defined TORRENT_BUILD_SIMULATOR +#include "simulator/simulator.hpp" +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { + +#if defined TORRENT_BUILD_SIMULATOR + typedef sim::chrono::high_resolution_clock clock_type; +#elif defined BOOST_ASIO_HAS_STD_CHRONO + typedef std::chrono::high_resolution_clock clock_type; +#else + typedef boost::chrono::high_resolution_clock clock_type; +#endif + + typedef clock_type::time_point time_point; + typedef clock_type::duration time_duration; + +#if defined BOOST_ASIO_HAS_STD_CHRONO + using std::chrono::seconds; + using std::chrono::milliseconds; + using std::chrono::microseconds; + using std::chrono::minutes; + using std::chrono::hours; + using std::chrono::duration_cast; +#else + using boost::chrono::seconds; + using boost::chrono::milliseconds; + using boost::chrono::microseconds; + using boost::chrono::minutes; + using boost::chrono::hours; + using boost::chrono::duration_cast; +#endif + + // internal + inline time_point min_time() { return (time_point::min)(); } + + // internal + inline time_point max_time() { return (time_point::max)(); } + + template + boost::int64_t total_seconds(T td) + { return duration_cast(td).count(); } + + template + boost::int64_t total_milliseconds(T td) + { return duration_cast(td).count(); } + + template + boost::int64_t total_microseconds(T td) + { return duration_cast(td).count(); } + +#ifndef TORRENT_NO_DEPRECATE + + TORRENT_DEPRECATED + time_point time_now(); + + TORRENT_DEPRECATED + time_point time_now_hires(); + + inline time_point time_now() + { return clock_type::now(); } + + inline time_point time_now_hires() + { return clock_type::now(); } + + typedef time_point ptime; + +#endif + +} + +#endif // TORRENT_TIME_HPP_INCLUDED + diff --git a/include/libtorrent/timestamp_history.hpp b/include/libtorrent/timestamp_history.hpp new file mode 100644 index 0000000..d0961a0 --- /dev/null +++ b/include/libtorrent/timestamp_history.hpp @@ -0,0 +1,87 @@ +/* + +Copyright (c) 2009-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 TIMESTAMP_HISTORY_HPP +#define TIMESTAMP_HISTORY_HPP + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" + +namespace libtorrent { + +// timestamp history keeps a history of the lowest timestamps we've +// seen in the last 20 minutes +struct TORRENT_EXTRA_EXPORT timestamp_history +{ + enum { history_size = 20 }; + + timestamp_history() : m_base(0), m_index(0), m_num_samples(not_initialized) {} + 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 + boost::uint32_t add_sample(boost::uint32_t sample, bool step); + boost::uint32_t base() const { TORRENT_ASSERT(initialized()); return m_base; } + void adjust_base(int change); + +private: + + // this is a circular buffer + boost::uint32_t m_history[history_size]; + + // this is the lowest sample seen in the + // last 'history_size' minutes + boost::uint32_t m_base; + + // and this is the index we're currently at + // in the circular buffer + boost::uint16_t m_index; + + enum { 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 + boost::uint16_t m_num_samples; +}; + +} + +#endif + diff --git a/include/libtorrent/tommath.h b/include/libtorrent/tommath.h new file mode 100644 index 0000000..e1d58ec --- /dev/null +++ b/include/libtorrent/tommath.h @@ -0,0 +1,575 @@ +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://math.libtomcrypt.com + */ +#ifndef BN_H_ +#define BN_H_ + +#include +#include +#include + +#include "libtorrent/export.hpp" + +#ifdef _MSC_VER +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; +typedef unsigned __int64 uint64_t; +#else +#include +#endif + +#include "libtorrent/tommath_class.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* detect 64-bit mode if possible */ +#if defined(__x86_64__) + #if !(defined(MP_32BIT) || defined(MP_16BIT) || defined(MP_8BIT)) + #define MP_64BIT + #endif +#endif + +/* some default configurations. + * + * A "mp_digit" must be able to hold DIGIT_BIT + 1 bits + * A "mp_word" must be able to hold 2*DIGIT_BIT + 1 bits + * + * At the very least a mp_digit must be able to hold 7 bits + * [any size beyond that is ok provided it doesn't overflow the data type] + */ +#ifdef MP_8BIT + typedef uint8_t mp_digit; + typedef uint16_t mp_word; +#define MP_SIZEOF_MP_DIGIT 1 +#ifdef DIGIT_BIT +#error You must not define DIGIT_BIT when using MP_8BIT +#endif +#elif defined(MP_16BIT) + typedef uint16_t mp_digit; + typedef uint32_t mp_word; +#define MP_SIZEOF_MP_DIGIT 2 +#ifdef DIGIT_BIT +#error You must not define DIGIT_BIT when using MP_16BIT +#endif +#elif defined(MP_64BIT) + /* for GCC only on supported platforms */ + typedef uint64_t mp_digit; +#if defined(_WIN32) + typedef unsigned __int128 mp_word; +#elif defined(__GNUC__) + typedef unsigned long mp_word __attribute__ ((mode(TI))); +#else + /* it seems you have a problem + * but we assume you can somewhere define your own uint128_t */ + typedef uint128_t mp_word; +#endif + + #define DIGIT_BIT 60 +#else + /* this is the default case, 28-bit digits */ + + /* this is to make porting into LibTomCrypt easier :-) */ + typedef uint32_t mp_digit; + typedef uint64_t mp_word; + +#ifdef MP_31BIT + /* this is an extension that uses 31-bit digits */ + #define DIGIT_BIT 31 +#else + /* default case is 28-bit digits, defines MP_28BIT as a handy macro to test */ + #define DIGIT_BIT 28 + #define MP_28BIT +#endif +#endif + +/* otherwise the bits per digit is calculated automatically from the size of a mp_digit */ +#ifndef DIGIT_BIT + #define DIGIT_BIT (((CHAR_BIT * MP_SIZEOF_MP_DIGIT) - 1)) /* bits per digit */ + typedef uint_least32_t mp_min_u32; +#else + typedef mp_digit mp_min_u32; +#endif + +/* platforms that can use a better rand function */ +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) + #define MP_USE_ALT_RAND 1 +#endif + +/* use arc4random on platforms that support it */ +#ifdef MP_USE_ALT_RAND + #define MP_GEN_RANDOM() arc4random() +#else + #define MP_GEN_RANDOM() rand() +#endif + +#define MP_DIGIT_BIT DIGIT_BIT +#define MP_MASK ((((mp_digit)1)<<((mp_digit)DIGIT_BIT))-((mp_digit)1)) +#define MP_DIGIT_MAX MP_MASK + +/* equalities */ +#define MP_LT -1 /* less than */ +#define MP_EQ 0 /* equal to */ +#define MP_GT 1 /* greater than */ + +#define MP_ZPOS 0 /* positive integer */ +#define MP_NEG 1 /* negative */ + +#define MP_OKAY 0 /* ok result */ +#define MP_MEM -2 /* out of mem */ +#define MP_VAL -3 /* invalid input */ +#define MP_RANGE MP_VAL + +#define MP_YES 1 /* yes response */ +#define MP_NO 0 /* no response */ + +/* Primality generation flags */ +#define LTM_PRIME_BBS 0x0001 /* BBS style prime */ +#define LTM_PRIME_SAFE 0x0002 /* Safe prime (p-1)/2 == prime */ +#define LTM_PRIME_2MSB_ON 0x0008 /* force 2nd MSB to 1 */ + +typedef int mp_err; + +/* you'll have to tune these... */ +extern int KARATSUBA_MUL_CUTOFF, + KARATSUBA_SQR_CUTOFF, + TOOM_MUL_CUTOFF, + TOOM_SQR_CUTOFF; + +/* define this to use lower memory usage routines (exptmods mostly) */ +/* #define MP_LOW_MEM */ + +/* default precision */ +#ifndef MP_PREC + #ifndef MP_LOW_MEM + #define MP_PREC 32 /* default digits of precision */ + #else + #define MP_PREC 8 /* default digits of precision */ + #endif +#endif + +/* size of comba arrays, should be at least 2 * 2**(BITS_PER_WORD - BITS_PER_DIGIT*2) */ +#define MP_WARRAY (1 << (((sizeof(mp_word) * CHAR_BIT) - (2 * DIGIT_BIT)) + 1)) + +/* the infamous mp_int structure */ +typedef struct { + int used, alloc, sign; + mp_digit *dp; +} mp_int; + +/* callback for mp_prime_random, should fill dst with random bytes and return how many read [upto len] */ +typedef int ltm_prime_callback(unsigned char *dst, int len, void *dat); + + +#define USED(m) ((m)->used) +#define DIGIT(m,k) ((m)->dp[(k)]) +#define SIGN(m) ((m)->sign) + +/* error code to char* string */ +const char *mp_error_to_string(int code); + +/* ---> init and deinit bignum functions <--- */ +/* init a bignum */ +TORRENT_EXTRA_EXPORT int mp_init(mp_int *a); + +/* free a bignum */ +TORRENT_EXTRA_EXPORT void mp_clear(mp_int *a); + +/* init a null terminated series of arguments */ +int mp_init_multi(mp_int *mp, ...); + +/* clear a null terminated series of arguments */ +void mp_clear_multi(mp_int *mp, ...); + +/* exchange two ints */ +void mp_exch(mp_int *a, mp_int *b); + +/* shrink ram required for a bignum */ +int mp_shrink(mp_int *a); + +/* grow an int to a given size */ +int mp_grow(mp_int *a, int size); + +/* init to a given number of digits */ +int mp_init_size(mp_int *a, int size); + +/* ---> Basic Manipulations <--- */ +#define mp_iszero(a) (((a)->used == 0) ? MP_YES : MP_NO) +#define mp_iseven(a) ((((a)->used > 0) && (((a)->dp[0] & 1u) == 0u)) ? MP_YES : MP_NO) +#define mp_isodd(a) ((((a)->used > 0) && (((a)->dp[0] & 1u) == 1u)) ? MP_YES : MP_NO) +#define mp_isneg(a) (((a)->sign != MP_ZPOS) ? MP_YES : MP_NO) + +/* set to zero */ +void mp_zero(mp_int *a); + +/* set to a digit */ +void mp_set(mp_int *a, mp_digit b); + +/* set a 32-bit const */ +int mp_set_int(mp_int *a, unsigned long b); + +/* set a platform dependent unsigned long value */ +int mp_set_long(mp_int *a, unsigned long b); + +/* set a platform dependent unsigned long long value */ +int mp_set_long_long(mp_int *a, unsigned long long b); + +/* get a 32-bit value */ +unsigned long mp_get_int(mp_int * a); + +/* get a platform dependent unsigned long value */ +unsigned long mp_get_long(mp_int * a); + +/* get a platform dependent unsigned long long value */ +unsigned long long mp_get_long_long(mp_int * a); + +/* initialize and set a digit */ +int mp_init_set (mp_int * a, mp_digit b); + +/* initialize and set 32-bit value */ +int mp_init_set_int (mp_int * a, unsigned long b); + +/* copy, b = a */ +int mp_copy(mp_int *a, mp_int *b); + +/* inits and copies, a = b */ +int mp_init_copy(mp_int *a, mp_int *b); + +/* trim unused digits */ +void mp_clamp(mp_int *a); + +/* import binary data */ +int mp_import(mp_int* rop, size_t count, int order, size_t size, int endian, size_t nails, const void* op); + +/* export binary data */ +int mp_export(void* rop, size_t* countp, int order, size_t size, int endian, size_t nails, mp_int* op); + +/* ---> digit manipulation <--- */ + +/* right shift by "b" digits */ +void mp_rshd(mp_int *a, int b); + +/* left shift by "b" digits */ +int mp_lshd(mp_int *a, int b); + +/* c = a / 2**b, implemented as c = a >> b */ +int mp_div_2d(mp_int *a, int b, mp_int *c, mp_int *d); + +/* b = a/2 */ +int mp_div_2(mp_int *a, mp_int *b); + +/* c = a * 2**b, implemented as c = a << b */ +int mp_mul_2d(mp_int *a, int b, mp_int *c); + +/* b = a*2 */ +int mp_mul_2(mp_int *a, mp_int *b); + +/* c = a mod 2**b */ +int mp_mod_2d(mp_int *a, int b, mp_int *c); + +/* computes a = 2**b */ +int mp_2expt(mp_int *a, int b); + +/* Counts the number of lsbs which are zero before the first zero bit */ +int mp_cnt_lsb(mp_int *a); + +/* I Love Earth! */ + +/* makes a pseudo-random int of a given size */ +int mp_rand(mp_int *a, int digits); + +/* ---> binary operations <--- */ +/* c = a XOR b */ +int mp_xor(mp_int *a, mp_int *b, mp_int *c); + +/* c = a OR b */ +int mp_or(mp_int *a, mp_int *b, mp_int *c); + +/* c = a AND b */ +int mp_and(mp_int *a, mp_int *b, mp_int *c); + +/* ---> Basic arithmetic <--- */ + +/* b = -a */ +int mp_neg(mp_int *a, mp_int *b); + +/* b = |a| */ +int mp_abs(mp_int *a, mp_int *b); + +/* compare a to b */ +int mp_cmp(mp_int *a, mp_int *b); + +/* compare |a| to |b| */ +int mp_cmp_mag(mp_int *a, mp_int *b); + +/* c = a + b */ +int mp_add(mp_int *a, mp_int *b, mp_int *c); + +/* c = a - b */ +int mp_sub(mp_int *a, mp_int *b, mp_int *c); + +/* c = a * b */ +int mp_mul(mp_int *a, mp_int *b, mp_int *c); + +/* b = a*a */ +int mp_sqr(mp_int *a, mp_int *b); + +/* a/b => cb + d == a */ +int mp_div(mp_int *a, mp_int *b, mp_int *c, mp_int *d); + +/* c = a mod b, 0 <= c < b */ +int mp_mod(mp_int *a, mp_int *b, mp_int *c); + +/* ---> single digit functions <--- */ + +/* compare against a single digit */ +int mp_cmp_d(mp_int *a, mp_digit b); + +/* c = a + b */ +int mp_add_d(mp_int *a, mp_digit b, mp_int *c); + +/* c = a - b */ +int mp_sub_d(mp_int *a, mp_digit b, mp_int *c); + +/* c = a * b */ +int mp_mul_d(mp_int *a, mp_digit b, mp_int *c); + +/* a/b => cb + d == a */ +int mp_div_d(mp_int *a, mp_digit b, mp_int *c, mp_digit *d); + +/* a/3 => 3c + d == a */ +int mp_div_3(mp_int *a, mp_int *c, mp_digit *d); + +/* c = a**b */ +int mp_expt_d(mp_int *a, mp_digit b, mp_int *c); +int mp_expt_d_ex (mp_int * a, mp_digit b, mp_int * c, int fast); + +/* c = a mod b, 0 <= c < b */ +int mp_mod_d(mp_int *a, mp_digit b, mp_digit *c); + +/* ---> number theory <--- */ + +/* d = a + b (mod c) */ +int mp_addmod(mp_int *a, mp_int *b, mp_int *c, mp_int *d); + +/* d = a - b (mod c) */ +int mp_submod(mp_int *a, mp_int *b, mp_int *c, mp_int *d); + +/* d = a * b (mod c) */ +int mp_mulmod(mp_int *a, mp_int *b, mp_int *c, mp_int *d); + +/* c = a * a (mod b) */ +int mp_sqrmod(mp_int *a, mp_int *b, mp_int *c); + +/* c = 1/a (mod b) */ +int mp_invmod(mp_int *a, mp_int *b, mp_int *c); + +/* c = (a, b) */ +int mp_gcd(mp_int *a, mp_int *b, mp_int *c); + +/* produces value such that U1*a + U2*b = U3 */ +int mp_exteuclid(mp_int *a, mp_int *b, mp_int *U1, mp_int *U2, mp_int *U3); + +/* c = [a, b] or (a*b)/(a, b) */ +int mp_lcm(mp_int *a, mp_int *b, mp_int *c); + +/* finds one of the b'th root of a, such that |c|**b <= |a| + * + * returns error if a < 0 and b is even + */ +int mp_n_root(mp_int *a, mp_digit b, mp_int *c); +int mp_n_root_ex (mp_int * a, mp_digit b, mp_int * c, int fast); + +/* special sqrt algo */ +int mp_sqrt(mp_int *arg, mp_int *ret); + +/* special sqrt (mod prime) */ +int mp_sqrtmod_prime(mp_int *arg, mp_int *prime, mp_int *ret); + +/* is number a square? */ +int mp_is_square(mp_int *arg, int *ret); + +/* computes the jacobi c = (a | n) (or Legendre if b is prime) */ +int mp_jacobi(mp_int *a, mp_int *n, int *c); + +/* used to setup the Barrett reduction for a given modulus b */ +int mp_reduce_setup(mp_int *a, mp_int *b); + +/* Barrett Reduction, computes a (mod b) with a precomputed value c + * + * Assumes that 0 < a <= b*b, note if 0 > a > -(b*b) then you can merely + * compute the reduction as -1 * mp_reduce(mp_abs(a)) [pseudo code]. + */ +int mp_reduce(mp_int *a, mp_int *b, mp_int *c); + +/* setups the montgomery reduction */ +int mp_montgomery_setup(mp_int *a, mp_digit *mp); + +/* computes a = B**n mod b without division or multiplication useful for + * normalizing numbers in a Montgomery system. + */ +int mp_montgomery_calc_normalization(mp_int *a, mp_int *b); + +/* computes x/R == x (mod N) via Montgomery Reduction */ +int mp_montgomery_reduce(mp_int *a, mp_int *m, mp_digit mp); + +/* returns 1 if a is a valid DR modulus */ +int mp_dr_is_modulus(mp_int *a); + +/* sets the value of "d" required for mp_dr_reduce */ +void mp_dr_setup(mp_int *a, mp_digit *d); + +/* reduces a modulo b using the Diminished Radix method */ +int mp_dr_reduce(mp_int *a, mp_int *b, mp_digit mp); + +/* returns true if a can be reduced with mp_reduce_2k */ +int mp_reduce_is_2k(mp_int *a); + +/* determines k value for 2k reduction */ +int mp_reduce_2k_setup(mp_int *a, mp_digit *d); + +/* reduces a modulo b where b is of the form 2**p - k [0 <= a] */ +int mp_reduce_2k(mp_int *a, mp_int *n, mp_digit d); + +/* returns true if a can be reduced with mp_reduce_2k_l */ +int mp_reduce_is_2k_l(mp_int *a); + +/* determines k value for 2k reduction */ +int mp_reduce_2k_setup_l(mp_int *a, mp_int *d); + +/* reduces a modulo b where b is of the form 2**p - k [0 <= a] */ +int mp_reduce_2k_l(mp_int *a, mp_int *n, mp_int *d); + +/* d = a**b (mod c) */ +int mp_exptmod(mp_int *a, mp_int *b, mp_int *c, mp_int *d); + +/* ---> Primes <--- */ + +/* number of primes */ +#ifdef MP_8BIT + #define PRIME_SIZE 31 +#else + #define PRIME_SIZE 256 +#endif + +/* table of first PRIME_SIZE primes */ +extern const mp_digit ltm_prime_tab[PRIME_SIZE]; + +/* result=1 if a is divisible by one of the first PRIME_SIZE primes */ +int mp_prime_is_divisible(mp_int *a, int *result); + +/* performs one Fermat test of "a" using base "b". + * Sets result to 0 if composite or 1 if probable prime + */ +int mp_prime_fermat(mp_int *a, mp_int *b, int *result); + +/* performs one Miller-Rabin test of "a" using base "b". + * Sets result to 0 if composite or 1 if probable prime + */ +int mp_prime_miller_rabin(mp_int *a, mp_int *b, int *result); + +/* This gives [for a given bit size] the number of trials required + * such that Miller-Rabin gives a prob of failure lower than 2^-96 + */ +int mp_prime_rabin_miller_trials(int size); + +/* performs t rounds of Miller-Rabin on "a" using the first + * t prime bases. Also performs an initial sieve of trial + * division. Determines if "a" is prime with probability + * of error no more than (1/4)**t. + * + * Sets result to 1 if probably prime, 0 otherwise + */ +int mp_prime_is_prime(mp_int *a, int t, int *result); + +/* finds the next prime after the number "a" using "t" trials + * of Miller-Rabin. + * + * bbs_style = 1 means the prime must be congruent to 3 mod 4 + */ +int mp_prime_next_prime(mp_int *a, int t, int bbs_style); + +/* makes a truly random prime of a given size (bytes), + * call with bbs = 1 if you want it to be congruent to 3 mod 4 + * + * You have to supply a callback which fills in a buffer with random bytes. "dat" is a parameter you can + * have passed to the callback (e.g. a state or something). This function doesn't use "dat" itself + * so it can be NULL + * + * The prime generated will be larger than 2^(8*size). + */ +#define mp_prime_random(a, t, size, bbs, cb, dat) mp_prime_random_ex(a, t, ((size) * 8) + 1, (bbs==1)?LTM_PRIME_BBS:0, cb, dat) + +/* makes a truly random prime of a given size (bits), + * + * Flags are as follows: + * + * LTM_PRIME_BBS - make prime congruent to 3 mod 4 + * LTM_PRIME_SAFE - make sure (p-1)/2 is prime as well (implies LTM_PRIME_BBS) + * LTM_PRIME_2MSB_ON - make the 2nd highest bit one + * + * You have to supply a callback which fills in a buffer with random bytes. "dat" is a parameter you can + * have passed to the callback (e.g. a state or something). This function doesn't use "dat" itself + * so it can be NULL + * + */ +int mp_prime_random_ex(mp_int *a, int t, int size, int flags, ltm_prime_callback cb, void *dat); + +/* ---> radix conversion <--- */ +int mp_count_bits(mp_int *a); + +TORRENT_EXTRA_EXPORT int mp_unsigned_bin_size(mp_int *a); +TORRENT_EXTRA_EXPORT int mp_read_unsigned_bin(mp_int *a, const unsigned char *b, int c); +int mp_to_unsigned_bin(mp_int *a, unsigned char *b); +int mp_to_unsigned_bin_n (mp_int * a, unsigned char *b, unsigned long *outlen); + +int mp_signed_bin_size(mp_int *a); +int mp_read_signed_bin(mp_int *a, const unsigned char *b, int c); +int mp_to_signed_bin(mp_int *a, unsigned char *b); +int mp_to_signed_bin_n (mp_int * a, unsigned char *b, unsigned long *outlen); + +int mp_read_radix(mp_int *a, const char *str, int radix); +int mp_toradix(mp_int *a, char *str, int radix); +int mp_toradix_n(mp_int * a, char *str, int radix, int maxlen); +int mp_radix_size(mp_int *a, int radix, int *size); + +#ifndef LTM_NO_FILE +int mp_fread(mp_int *a, int radix, FILE *stream); +int mp_fwrite(mp_int *a, int radix, FILE *stream); +#endif + +#define mp_read_raw(mp, str, len) mp_read_signed_bin((mp), (str), (len)) +#define mp_raw_size(mp) mp_signed_bin_size(mp) +#define mp_toraw(mp, str) mp_to_signed_bin((mp), (str)) +#define mp_read_mag(mp, str, len) mp_read_unsigned_bin((mp), (str), (len)) +#define mp_mag_size(mp) mp_unsigned_bin_size(mp) +#define mp_tomag(mp, str) mp_to_unsigned_bin((mp), (str)) + +#define mp_tobinary(M, S) mp_toradix((M), (S), 2) +#define mp_tooctal(M, S) mp_toradix((M), (S), 8) +#define mp_todecimal(M, S) mp_toradix((M), (S), 10) +#define mp_tohex(M, S) mp_toradix((M), (S), 16) + +#ifdef __cplusplus + } +#endif + +#endif + + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ diff --git a/include/libtorrent/tommath_class.h b/include/libtorrent/tommath_class.h new file mode 100644 index 0000000..becaa0a --- /dev/null +++ b/include/libtorrent/tommath_class.h @@ -0,0 +1,1057 @@ +#if !(defined(LTM1) && defined(LTM2) && defined(LTM3)) +#if defined(LTM2) +#define LTM3 +#endif +#if defined(LTM1) +#define LTM2 +#endif +#define LTM1 + +#if defined(LTM_ALL) +#define BN_ERROR_C +#define BN_FAST_MP_INVMOD_C +#define BN_FAST_MP_MONTGOMERY_REDUCE_C +#define BN_FAST_S_MP_MUL_DIGS_C +#define BN_FAST_S_MP_MUL_HIGH_DIGS_C +#define BN_FAST_S_MP_SQR_C +#define BN_MP_2EXPT_C +#define BN_MP_ABS_C +#define BN_MP_ADD_C +#define BN_MP_ADD_D_C +#define BN_MP_ADDMOD_C +#define BN_MP_AND_C +#define BN_MP_CLAMP_C +#define BN_MP_CLEAR_C +#define BN_MP_CLEAR_MULTI_C +#define BN_MP_CMP_C +#define BN_MP_CMP_D_C +#define BN_MP_CMP_MAG_C +#define BN_MP_CNT_LSB_C +#define BN_MP_COPY_C +#define BN_MP_COUNT_BITS_C +#define BN_MP_DIV_C +#define BN_MP_DIV_2_C +#define BN_MP_DIV_2D_C +#define BN_MP_DIV_3_C +#define BN_MP_DIV_D_C +#define BN_MP_DR_IS_MODULUS_C +#define BN_MP_DR_REDUCE_C +#define BN_MP_DR_SETUP_C +#define BN_MP_EXCH_C +#define BN_MP_EXPORT_C +#define BN_MP_EXPT_D_C +#define BN_MP_EXPT_D_EX_C +#define BN_MP_EXPTMOD_C +#define BN_MP_EXPTMOD_FAST_C +#define BN_MP_EXTEUCLID_C +#define BN_MP_FREAD_C +#define BN_MP_FWRITE_C +#define BN_MP_GCD_C +#define BN_MP_GET_INT_C +#define BN_MP_GET_LONG_C +#define BN_MP_GET_LONG_LONG_C +#define BN_MP_GROW_C +#define BN_MP_IMPORT_C +#define BN_MP_INIT_C +#define BN_MP_INIT_COPY_C +#define BN_MP_INIT_MULTI_C +#define BN_MP_INIT_SET_C +#define BN_MP_INIT_SET_INT_C +#define BN_MP_INIT_SIZE_C +#define BN_MP_INVMOD_C +#define BN_MP_INVMOD_SLOW_C +#define BN_MP_IS_SQUARE_C +#define BN_MP_JACOBI_C +#define BN_MP_KARATSUBA_MUL_C +#define BN_MP_KARATSUBA_SQR_C +#define BN_MP_LCM_C +#define BN_MP_LSHD_C +#define BN_MP_MOD_C +#define BN_MP_MOD_2D_C +#define BN_MP_MOD_D_C +#define BN_MP_MONTGOMERY_CALC_NORMALIZATION_C +#define BN_MP_MONTGOMERY_REDUCE_C +#define BN_MP_MONTGOMERY_SETUP_C +#define BN_MP_MUL_C +#define BN_MP_MUL_2_C +#define BN_MP_MUL_2D_C +#define BN_MP_MUL_D_C +#define BN_MP_MULMOD_C +#define BN_MP_N_ROOT_C +#define BN_MP_N_ROOT_EX_C +#define BN_MP_NEG_C +#define BN_MP_OR_C +#define BN_MP_PRIME_FERMAT_C +#define BN_MP_PRIME_IS_DIVISIBLE_C +#define BN_MP_PRIME_IS_PRIME_C +#define BN_MP_PRIME_MILLER_RABIN_C +#define BN_MP_PRIME_NEXT_PRIME_C +#define BN_MP_PRIME_RABIN_MILLER_TRIALS_C +#define BN_MP_PRIME_RANDOM_EX_C +#define BN_MP_RADIX_SIZE_C +#define BN_MP_RADIX_SMAP_C +#define BN_MP_RAND_C +#define BN_MP_READ_RADIX_C +#define BN_MP_READ_SIGNED_BIN_C +#define BN_MP_READ_UNSIGNED_BIN_C +#define BN_MP_REDUCE_C +#define BN_MP_REDUCE_2K_C +#define BN_MP_REDUCE_2K_L_C +#define BN_MP_REDUCE_2K_SETUP_C +#define BN_MP_REDUCE_2K_SETUP_L_C +#define BN_MP_REDUCE_IS_2K_C +#define BN_MP_REDUCE_IS_2K_L_C +#define BN_MP_REDUCE_SETUP_C +#define BN_MP_RSHD_C +#define BN_MP_SET_C +#define BN_MP_SET_INT_C +#define BN_MP_SET_LONG_C +#define BN_MP_SET_LONG_LONG_C +#define BN_MP_SHRINK_C +#define BN_MP_SIGNED_BIN_SIZE_C +#define BN_MP_SQR_C +#define BN_MP_SQRMOD_C +#define BN_MP_SQRT_C +#define BN_MP_SQRTMOD_PRIME_C +#define BN_MP_SUB_C +#define BN_MP_SUB_D_C +#define BN_MP_SUBMOD_C +#define BN_MP_TO_SIGNED_BIN_C +#define BN_MP_TO_SIGNED_BIN_N_C +#define BN_MP_TO_UNSIGNED_BIN_C +#define BN_MP_TO_UNSIGNED_BIN_N_C +#define BN_MP_TOOM_MUL_C +#define BN_MP_TOOM_SQR_C +#define BN_MP_TORADIX_C +#define BN_MP_TORADIX_N_C +#define BN_MP_UNSIGNED_BIN_SIZE_C +#define BN_MP_XOR_C +#define BN_MP_ZERO_C +#define BN_PRIME_TAB_C +#define BN_REVERSE_C +#define BN_S_MP_ADD_C +#define BN_S_MP_EXPTMOD_C +#define BN_S_MP_MUL_DIGS_C +#define BN_S_MP_MUL_HIGH_DIGS_C +#define BN_S_MP_SQR_C +#define BN_S_MP_SUB_C +#define BNCORE_C +#endif + +#if defined(BN_ERROR_C) + #define BN_MP_ERROR_TO_STRING_C +#endif + +#if defined(BN_FAST_MP_INVMOD_C) + #define BN_MP_ISEVEN_C + #define BN_MP_INIT_MULTI_C + #define BN_MP_COPY_C + #define BN_MP_MOD_C + #define BN_MP_SET_C + #define BN_MP_DIV_2_C + #define BN_MP_ISODD_C + #define BN_MP_SUB_C + #define BN_MP_CMP_C + #define BN_MP_ISZERO_C + #define BN_MP_CMP_D_C + #define BN_MP_ADD_C + #define BN_MP_EXCH_C + #define BN_MP_CLEAR_MULTI_C +#endif + +#if defined(BN_FAST_MP_MONTGOMERY_REDUCE_C) + #define BN_MP_GROW_C + #define BN_MP_RSHD_C + #define BN_MP_CLAMP_C + #define BN_MP_CMP_MAG_C + #define BN_S_MP_SUB_C +#endif + +#if defined(BN_FAST_S_MP_MUL_DIGS_C) + #define BN_MP_GROW_C + #define BN_MP_CLAMP_C +#endif + +#if defined(BN_FAST_S_MP_MUL_HIGH_DIGS_C) + #define BN_MP_GROW_C + #define BN_MP_CLAMP_C +#endif + +#if defined(BN_FAST_S_MP_SQR_C) + #define BN_MP_GROW_C + #define BN_MP_CLAMP_C +#endif + +#if defined(BN_MP_2EXPT_C) + #define BN_MP_ZERO_C + #define BN_MP_GROW_C +#endif + +#if defined(BN_MP_ABS_C) + #define BN_MP_COPY_C +#endif + +#if defined(BN_MP_ADD_C) + #define BN_S_MP_ADD_C + #define BN_MP_CMP_MAG_C + #define BN_S_MP_SUB_C +#endif + +#if defined(BN_MP_ADD_D_C) + #define BN_MP_GROW_C + #define BN_MP_SUB_D_C + #define BN_MP_CLAMP_C +#endif + +#if defined(BN_MP_ADDMOD_C) + #define BN_MP_INIT_C + #define BN_MP_ADD_C + #define BN_MP_CLEAR_C + #define BN_MP_MOD_C +#endif + +#if defined(BN_MP_AND_C) + #define BN_MP_INIT_COPY_C + #define BN_MP_CLAMP_C + #define BN_MP_EXCH_C + #define BN_MP_CLEAR_C +#endif + +#if defined(BN_MP_CLAMP_C) +#endif + +#if defined(BN_MP_CLEAR_C) +#endif + +#if defined(BN_MP_CLEAR_MULTI_C) + #define BN_MP_CLEAR_C +#endif + +#if defined(BN_MP_CMP_C) + #define BN_MP_CMP_MAG_C +#endif + +#if defined(BN_MP_CMP_D_C) +#endif + +#if defined(BN_MP_CMP_MAG_C) +#endif + +#if defined(BN_MP_CNT_LSB_C) + #define BN_MP_ISZERO_C +#endif + +#if defined(BN_MP_COPY_C) + #define BN_MP_GROW_C +#endif + +#if defined(BN_MP_COUNT_BITS_C) +#endif + +#if defined(BN_MP_DIV_C) + #define BN_MP_ISZERO_C + #define BN_MP_CMP_MAG_C + #define BN_MP_COPY_C + #define BN_MP_ZERO_C + #define BN_MP_INIT_MULTI_C + #define BN_MP_SET_C + #define BN_MP_COUNT_BITS_C + #define BN_MP_ABS_C + #define BN_MP_MUL_2D_C + #define BN_MP_CMP_C + #define BN_MP_SUB_C + #define BN_MP_ADD_C + #define BN_MP_DIV_2D_C + #define BN_MP_EXCH_C + #define BN_MP_CLEAR_MULTI_C + #define BN_MP_INIT_SIZE_C + #define BN_MP_INIT_C + #define BN_MP_INIT_COPY_C + #define BN_MP_LSHD_C + #define BN_MP_RSHD_C + #define BN_MP_MUL_D_C + #define BN_MP_CLAMP_C + #define BN_MP_CLEAR_C +#endif + +#if defined(BN_MP_DIV_2_C) + #define BN_MP_GROW_C + #define BN_MP_CLAMP_C +#endif + +#if defined(BN_MP_DIV_2D_C) + #define BN_MP_COPY_C + #define BN_MP_ZERO_C + #define BN_MP_INIT_C + #define BN_MP_MOD_2D_C + #define BN_MP_CLEAR_C + #define BN_MP_RSHD_C + #define BN_MP_CLAMP_C + #define BN_MP_EXCH_C +#endif + +#if defined(BN_MP_DIV_3_C) + #define BN_MP_INIT_SIZE_C + #define BN_MP_CLAMP_C + #define BN_MP_EXCH_C + #define BN_MP_CLEAR_C +#endif + +#if defined(BN_MP_DIV_D_C) + #define BN_MP_ISZERO_C + #define BN_MP_COPY_C + #define BN_MP_DIV_2D_C + #define BN_MP_DIV_3_C + #define BN_MP_INIT_SIZE_C + #define BN_MP_CLAMP_C + #define BN_MP_EXCH_C + #define BN_MP_CLEAR_C +#endif + +#if defined(BN_MP_DR_IS_MODULUS_C) +#endif + +#if defined(BN_MP_DR_REDUCE_C) + #define BN_MP_GROW_C + #define BN_MP_CLAMP_C + #define BN_MP_CMP_MAG_C + #define BN_S_MP_SUB_C +#endif + +#if defined(BN_MP_DR_SETUP_C) +#endif + +#if defined(BN_MP_EXCH_C) +#endif + +#if defined(BN_MP_EXPORT_C) + #define BN_MP_INIT_COPY_C + #define BN_MP_COUNT_BITS_C + #define BN_MP_DIV_2D_C + #define BN_MP_CLEAR_C +#endif + +#if defined(BN_MP_EXPT_D_C) + #define BN_MP_EXPT_D_EX_C +#endif + +#if defined(BN_MP_EXPT_D_EX_C) + #define BN_MP_INIT_COPY_C + #define BN_MP_SET_C + #define BN_MP_MUL_C + #define BN_MP_CLEAR_C + #define BN_MP_SQR_C +#endif + +#if defined(BN_MP_EXPTMOD_C) + #define BN_MP_INIT_C + #define BN_MP_INVMOD_C + #define BN_MP_CLEAR_C + #define BN_MP_ABS_C + #define BN_MP_CLEAR_MULTI_C + #define BN_MP_REDUCE_IS_2K_L_C + #define BN_S_MP_EXPTMOD_C + #define BN_MP_DR_IS_MODULUS_C + #define BN_MP_REDUCE_IS_2K_C + #define BN_MP_ISODD_C + #define BN_MP_EXPTMOD_FAST_C +#endif + +#if defined(BN_MP_EXPTMOD_FAST_C) + #define BN_MP_COUNT_BITS_C + #define BN_MP_INIT_C + #define BN_MP_CLEAR_C + #define BN_MP_MONTGOMERY_SETUP_C + #define BN_FAST_MP_MONTGOMERY_REDUCE_C + #define BN_MP_MONTGOMERY_REDUCE_C + #define BN_MP_DR_SETUP_C + #define BN_MP_DR_REDUCE_C + #define BN_MP_REDUCE_2K_SETUP_C + #define BN_MP_REDUCE_2K_C + #define BN_MP_MONTGOMERY_CALC_NORMALIZATION_C + #define BN_MP_MULMOD_C + #define BN_MP_SET_C + #define BN_MP_MOD_C + #define BN_MP_COPY_C + #define BN_MP_SQR_C + #define BN_MP_MUL_C + #define BN_MP_EXCH_C +#endif + +#if defined(BN_MP_EXTEUCLID_C) + #define BN_MP_INIT_MULTI_C + #define BN_MP_SET_C + #define BN_MP_COPY_C + #define BN_MP_ISZERO_C + #define BN_MP_DIV_C + #define BN_MP_MUL_C + #define BN_MP_SUB_C + #define BN_MP_NEG_C + #define BN_MP_EXCH_C + #define BN_MP_CLEAR_MULTI_C +#endif + +#if defined(BN_MP_FREAD_C) + #define BN_MP_ZERO_C + #define BN_MP_S_RMAP_C + #define BN_MP_MUL_D_C + #define BN_MP_ADD_D_C + #define BN_MP_CMP_D_C +#endif + +#if defined(BN_MP_FWRITE_C) + #define BN_MP_RADIX_SIZE_C + #define BN_MP_TORADIX_C +#endif + +#if defined(BN_MP_GCD_C) + #define BN_MP_ISZERO_C + #define BN_MP_ABS_C + #define BN_MP_INIT_COPY_C + #define BN_MP_CNT_LSB_C + #define BN_MP_DIV_2D_C + #define BN_MP_CMP_MAG_C + #define BN_MP_EXCH_C + #define BN_S_MP_SUB_C + #define BN_MP_MUL_2D_C + #define BN_MP_CLEAR_C +#endif + +#if defined(BN_MP_GET_INT_C) +#endif + +#if defined(BN_MP_GET_LONG_C) +#endif + +#if defined(BN_MP_GET_LONG_LONG_C) +#endif + +#if defined(BN_MP_GROW_C) +#endif + +#if defined(BN_MP_IMPORT_C) + #define BN_MP_ZERO_C + #define BN_MP_MUL_2D_C + #define BN_MP_CLAMP_C +#endif + +#if defined(BN_MP_INIT_C) +#endif + +#if defined(BN_MP_INIT_COPY_C) + #define BN_MP_INIT_SIZE_C + #define BN_MP_COPY_C +#endif + +#if defined(BN_MP_INIT_MULTI_C) + #define BN_MP_ERR_C + #define BN_MP_INIT_C + #define BN_MP_CLEAR_C +#endif + +#if defined(BN_MP_INIT_SET_C) + #define BN_MP_INIT_C + #define BN_MP_SET_C +#endif + +#if defined(BN_MP_INIT_SET_INT_C) + #define BN_MP_INIT_C + #define BN_MP_SET_INT_C +#endif + +#if defined(BN_MP_INIT_SIZE_C) + #define BN_MP_INIT_C +#endif + +#if defined(BN_MP_INVMOD_C) + #define BN_MP_ISZERO_C + #define BN_MP_ISODD_C + #define BN_FAST_MP_INVMOD_C + #define BN_MP_INVMOD_SLOW_C +#endif + +#if defined(BN_MP_INVMOD_SLOW_C) + #define BN_MP_ISZERO_C + #define BN_MP_INIT_MULTI_C + #define BN_MP_MOD_C + #define BN_MP_COPY_C + #define BN_MP_ISEVEN_C + #define BN_MP_SET_C + #define BN_MP_DIV_2_C + #define BN_MP_ISODD_C + #define BN_MP_ADD_C + #define BN_MP_SUB_C + #define BN_MP_CMP_C + #define BN_MP_CMP_D_C + #define BN_MP_CMP_MAG_C + #define BN_MP_EXCH_C + #define BN_MP_CLEAR_MULTI_C +#endif + +#if defined(BN_MP_IS_SQUARE_C) + #define BN_MP_MOD_D_C + #define BN_MP_INIT_SET_INT_C + #define BN_MP_MOD_C + #define BN_MP_GET_INT_C + #define BN_MP_SQRT_C + #define BN_MP_SQR_C + #define BN_MP_CMP_MAG_C + #define BN_MP_CLEAR_C +#endif + +#if defined(BN_MP_JACOBI_C) + #define BN_MP_CMP_D_C + #define BN_MP_ISZERO_C + #define BN_MP_INIT_COPY_C + #define BN_MP_CNT_LSB_C + #define BN_MP_DIV_2D_C + #define BN_MP_MOD_C + #define BN_MP_CLEAR_C +#endif + +#if defined(BN_MP_KARATSUBA_MUL_C) + #define BN_MP_MUL_C + #define BN_MP_INIT_SIZE_C + #define BN_MP_CLAMP_C + #define BN_S_MP_ADD_C + #define BN_MP_ADD_C + #define BN_S_MP_SUB_C + #define BN_MP_LSHD_C + #define BN_MP_CLEAR_C +#endif + +#if defined(BN_MP_KARATSUBA_SQR_C) + #define BN_MP_INIT_SIZE_C + #define BN_MP_CLAMP_C + #define BN_MP_SQR_C + #define BN_S_MP_ADD_C + #define BN_S_MP_SUB_C + #define BN_MP_LSHD_C + #define BN_MP_ADD_C + #define BN_MP_CLEAR_C +#endif + +#if defined(BN_MP_LCM_C) + #define BN_MP_INIT_MULTI_C + #define BN_MP_GCD_C + #define BN_MP_CMP_MAG_C + #define BN_MP_DIV_C + #define BN_MP_MUL_C + #define BN_MP_CLEAR_MULTI_C +#endif + +#if defined(BN_MP_LSHD_C) + #define BN_MP_GROW_C + #define BN_MP_RSHD_C +#endif + +#if defined(BN_MP_MOD_C) + #define BN_MP_INIT_C + #define BN_MP_DIV_C + #define BN_MP_CLEAR_C + #define BN_MP_ISZERO_C + #define BN_MP_EXCH_C + #define BN_MP_ADD_C +#endif + +#if defined(BN_MP_MOD_2D_C) + #define BN_MP_ZERO_C + #define BN_MP_COPY_C + #define BN_MP_CLAMP_C +#endif + +#if defined(BN_MP_MOD_D_C) + #define BN_MP_DIV_D_C +#endif + +#if defined(BN_MP_MONTGOMERY_CALC_NORMALIZATION_C) + #define BN_MP_COUNT_BITS_C + #define BN_MP_2EXPT_C + #define BN_MP_SET_C + #define BN_MP_MUL_2_C + #define BN_MP_CMP_MAG_C + #define BN_S_MP_SUB_C +#endif + +#if defined(BN_MP_MONTGOMERY_REDUCE_C) + #define BN_FAST_MP_MONTGOMERY_REDUCE_C + #define BN_MP_GROW_C + #define BN_MP_CLAMP_C + #define BN_MP_RSHD_C + #define BN_MP_CMP_MAG_C + #define BN_S_MP_SUB_C +#endif + +#if defined(BN_MP_MONTGOMERY_SETUP_C) +#endif + +#if defined(BN_MP_MUL_C) + #define BN_MP_TOOM_MUL_C + #define BN_MP_KARATSUBA_MUL_C + #define BN_FAST_S_MP_MUL_DIGS_C + #define BN_S_MP_MUL_C + #define BN_S_MP_MUL_DIGS_C +#endif + +#if defined(BN_MP_MUL_2_C) + #define BN_MP_GROW_C +#endif + +#if defined(BN_MP_MUL_2D_C) + #define BN_MP_COPY_C + #define BN_MP_GROW_C + #define BN_MP_LSHD_C + #define BN_MP_CLAMP_C +#endif + +#if defined(BN_MP_MUL_D_C) + #define BN_MP_GROW_C + #define BN_MP_CLAMP_C +#endif + +#if defined(BN_MP_MULMOD_C) + #define BN_MP_INIT_C + #define BN_MP_MUL_C + #define BN_MP_CLEAR_C + #define BN_MP_MOD_C +#endif + +#if defined(BN_MP_N_ROOT_C) + #define BN_MP_N_ROOT_EX_C +#endif + +#if defined(BN_MP_N_ROOT_EX_C) + #define BN_MP_INIT_C + #define BN_MP_SET_C + #define BN_MP_COPY_C + #define BN_MP_EXPT_D_EX_C + #define BN_MP_MUL_C + #define BN_MP_SUB_C + #define BN_MP_MUL_D_C + #define BN_MP_DIV_C + #define BN_MP_CMP_C + #define BN_MP_SUB_D_C + #define BN_MP_EXCH_C + #define BN_MP_CLEAR_C +#endif + +#if defined(BN_MP_NEG_C) + #define BN_MP_COPY_C + #define BN_MP_ISZERO_C +#endif + +#if defined(BN_MP_OR_C) + #define BN_MP_INIT_COPY_C + #define BN_MP_CLAMP_C + #define BN_MP_EXCH_C + #define BN_MP_CLEAR_C +#endif + +#if defined(BN_MP_PRIME_FERMAT_C) + #define BN_MP_CMP_D_C + #define BN_MP_INIT_C + #define BN_MP_EXPTMOD_C + #define BN_MP_CMP_C + #define BN_MP_CLEAR_C +#endif + +#if defined(BN_MP_PRIME_IS_DIVISIBLE_C) + #define BN_MP_MOD_D_C +#endif + +#if defined(BN_MP_PRIME_IS_PRIME_C) + #define BN_MP_CMP_D_C + #define BN_MP_PRIME_IS_DIVISIBLE_C + #define BN_MP_INIT_C + #define BN_MP_SET_C + #define BN_MP_PRIME_MILLER_RABIN_C + #define BN_MP_CLEAR_C +#endif + +#if defined(BN_MP_PRIME_MILLER_RABIN_C) + #define BN_MP_CMP_D_C + #define BN_MP_INIT_COPY_C + #define BN_MP_SUB_D_C + #define BN_MP_CNT_LSB_C + #define BN_MP_DIV_2D_C + #define BN_MP_EXPTMOD_C + #define BN_MP_CMP_C + #define BN_MP_SQRMOD_C + #define BN_MP_CLEAR_C +#endif + +#if defined(BN_MP_PRIME_NEXT_PRIME_C) + #define BN_MP_CMP_D_C + #define BN_MP_SET_C + #define BN_MP_SUB_D_C + #define BN_MP_ISEVEN_C + #define BN_MP_MOD_D_C + #define BN_MP_INIT_C + #define BN_MP_ADD_D_C + #define BN_MP_PRIME_MILLER_RABIN_C + #define BN_MP_CLEAR_C +#endif + +#if defined(BN_MP_PRIME_RABIN_MILLER_TRIALS_C) +#endif + +#if defined(BN_MP_PRIME_RANDOM_EX_C) + #define BN_MP_READ_UNSIGNED_BIN_C + #define BN_MP_PRIME_IS_PRIME_C + #define BN_MP_SUB_D_C + #define BN_MP_DIV_2_C + #define BN_MP_MUL_2_C + #define BN_MP_ADD_D_C +#endif + +#if defined(BN_MP_RADIX_SIZE_C) + #define BN_MP_ISZERO_C + #define BN_MP_COUNT_BITS_C + #define BN_MP_INIT_COPY_C + #define BN_MP_DIV_D_C + #define BN_MP_CLEAR_C +#endif + +#if defined(BN_MP_RADIX_SMAP_C) + #define BN_MP_S_RMAP_C +#endif + +#if defined(BN_MP_RAND_C) + #define BN_MP_ZERO_C + #define BN_MP_ADD_D_C + #define BN_MP_LSHD_C +#endif + +#if defined(BN_MP_READ_RADIX_C) + #define BN_MP_ZERO_C + #define BN_MP_S_RMAP_C + #define BN_MP_MUL_D_C + #define BN_MP_ADD_D_C + #define BN_MP_ISZERO_C +#endif + +#if defined(BN_MP_READ_SIGNED_BIN_C) + #define BN_MP_READ_UNSIGNED_BIN_C +#endif + +#if defined(BN_MP_READ_UNSIGNED_BIN_C) + #define BN_MP_GROW_C + #define BN_MP_ZERO_C + #define BN_MP_MUL_2D_C + #define BN_MP_CLAMP_C +#endif + +#if defined(BN_MP_REDUCE_C) + #define BN_MP_REDUCE_SETUP_C + #define BN_MP_INIT_COPY_C + #define BN_MP_RSHD_C + #define BN_MP_MUL_C + #define BN_S_MP_MUL_HIGH_DIGS_C + #define BN_FAST_S_MP_MUL_HIGH_DIGS_C + #define BN_MP_MOD_2D_C + #define BN_S_MP_MUL_DIGS_C + #define BN_MP_SUB_C + #define BN_MP_CMP_D_C + #define BN_MP_SET_C + #define BN_MP_LSHD_C + #define BN_MP_ADD_C + #define BN_MP_CMP_C + #define BN_S_MP_SUB_C + #define BN_MP_CLEAR_C +#endif + +#if defined(BN_MP_REDUCE_2K_C) + #define BN_MP_INIT_C + #define BN_MP_COUNT_BITS_C + #define BN_MP_DIV_2D_C + #define BN_MP_MUL_D_C + #define BN_S_MP_ADD_C + #define BN_MP_CMP_MAG_C + #define BN_S_MP_SUB_C + #define BN_MP_CLEAR_C +#endif + +#if defined(BN_MP_REDUCE_2K_L_C) + #define BN_MP_INIT_C + #define BN_MP_COUNT_BITS_C + #define BN_MP_DIV_2D_C + #define BN_MP_MUL_C + #define BN_S_MP_ADD_C + #define BN_MP_CMP_MAG_C + #define BN_S_MP_SUB_C + #define BN_MP_CLEAR_C +#endif + +#if defined(BN_MP_REDUCE_2K_SETUP_C) + #define BN_MP_INIT_C + #define BN_MP_COUNT_BITS_C + #define BN_MP_2EXPT_C + #define BN_MP_CLEAR_C + #define BN_S_MP_SUB_C +#endif + +#if defined(BN_MP_REDUCE_2K_SETUP_L_C) + #define BN_MP_INIT_C + #define BN_MP_2EXPT_C + #define BN_MP_COUNT_BITS_C + #define BN_S_MP_SUB_C + #define BN_MP_CLEAR_C +#endif + +#if defined(BN_MP_REDUCE_IS_2K_C) + #define BN_MP_REDUCE_2K_C + #define BN_MP_COUNT_BITS_C +#endif + +#if defined(BN_MP_REDUCE_IS_2K_L_C) +#endif + +#if defined(BN_MP_REDUCE_SETUP_C) + #define BN_MP_2EXPT_C + #define BN_MP_DIV_C +#endif + +#if defined(BN_MP_RSHD_C) + #define BN_MP_ZERO_C +#endif + +#if defined(BN_MP_SET_C) + #define BN_MP_ZERO_C +#endif + +#if defined(BN_MP_SET_INT_C) + #define BN_MP_ZERO_C + #define BN_MP_MUL_2D_C + #define BN_MP_CLAMP_C +#endif + +#if defined(BN_MP_SET_LONG_C) +#endif + +#if defined(BN_MP_SET_LONG_LONG_C) +#endif + +#if defined(BN_MP_SHRINK_C) +#endif + +#if defined(BN_MP_SIGNED_BIN_SIZE_C) + #define BN_MP_UNSIGNED_BIN_SIZE_C +#endif + +#if defined(BN_MP_SQR_C) + #define BN_MP_TOOM_SQR_C + #define BN_MP_KARATSUBA_SQR_C + #define BN_FAST_S_MP_SQR_C + #define BN_S_MP_SQR_C +#endif + +#if defined(BN_MP_SQRMOD_C) + #define BN_MP_INIT_C + #define BN_MP_SQR_C + #define BN_MP_CLEAR_C + #define BN_MP_MOD_C +#endif + +#if defined(BN_MP_SQRT_C) + #define BN_MP_N_ROOT_C + #define BN_MP_ISZERO_C + #define BN_MP_ZERO_C + #define BN_MP_INIT_COPY_C + #define BN_MP_RSHD_C + #define BN_MP_DIV_C + #define BN_MP_ADD_C + #define BN_MP_DIV_2_C + #define BN_MP_CMP_MAG_C + #define BN_MP_EXCH_C + #define BN_MP_CLEAR_C +#endif + +#if defined(BN_MP_SQRTMOD_PRIME_C) + #define BN_MP_CMP_D_C + #define BN_MP_ZERO_C + #define BN_MP_JACOBI_C + #define BN_MP_INIT_MULTI_C + #define BN_MP_MOD_D_C + #define BN_MP_ADD_D_C + #define BN_MP_DIV_2_C + #define BN_MP_EXPTMOD_C + #define BN_MP_COPY_C + #define BN_MP_SUB_D_C + #define BN_MP_ISEVEN_C + #define BN_MP_SET_INT_C + #define BN_MP_SQRMOD_C + #define BN_MP_MULMOD_C + #define BN_MP_SET_C + #define BN_MP_CLEAR_MULTI_C +#endif + +#if defined(BN_MP_SUB_C) + #define BN_S_MP_ADD_C + #define BN_MP_CMP_MAG_C + #define BN_S_MP_SUB_C +#endif + +#if defined(BN_MP_SUB_D_C) + #define BN_MP_GROW_C + #define BN_MP_ADD_D_C + #define BN_MP_CLAMP_C +#endif + +#if defined(BN_MP_SUBMOD_C) + #define BN_MP_INIT_C + #define BN_MP_SUB_C + #define BN_MP_CLEAR_C + #define BN_MP_MOD_C +#endif + +#if defined(BN_MP_TO_SIGNED_BIN_C) + #define BN_MP_TO_UNSIGNED_BIN_C +#endif + +#if defined(BN_MP_TO_SIGNED_BIN_N_C) + #define BN_MP_SIGNED_BIN_SIZE_C + #define BN_MP_TO_SIGNED_BIN_C +#endif + +#if defined(BN_MP_TO_UNSIGNED_BIN_C) + #define BN_MP_INIT_COPY_C + #define BN_MP_ISZERO_C + #define BN_MP_DIV_2D_C + #define BN_MP_CLEAR_C +#endif + +#if defined(BN_MP_TO_UNSIGNED_BIN_N_C) + #define BN_MP_UNSIGNED_BIN_SIZE_C + #define BN_MP_TO_UNSIGNED_BIN_C +#endif + +#if defined(BN_MP_TOOM_MUL_C) + #define BN_MP_INIT_MULTI_C + #define BN_MP_MOD_2D_C + #define BN_MP_COPY_C + #define BN_MP_RSHD_C + #define BN_MP_MUL_C + #define BN_MP_MUL_2_C + #define BN_MP_ADD_C + #define BN_MP_SUB_C + #define BN_MP_DIV_2_C + #define BN_MP_MUL_2D_C + #define BN_MP_MUL_D_C + #define BN_MP_DIV_3_C + #define BN_MP_LSHD_C + #define BN_MP_CLEAR_MULTI_C +#endif + +#if defined(BN_MP_TOOM_SQR_C) + #define BN_MP_INIT_MULTI_C + #define BN_MP_MOD_2D_C + #define BN_MP_COPY_C + #define BN_MP_RSHD_C + #define BN_MP_SQR_C + #define BN_MP_MUL_2_C + #define BN_MP_ADD_C + #define BN_MP_SUB_C + #define BN_MP_DIV_2_C + #define BN_MP_MUL_2D_C + #define BN_MP_MUL_D_C + #define BN_MP_DIV_3_C + #define BN_MP_LSHD_C + #define BN_MP_CLEAR_MULTI_C +#endif + +#if defined(BN_MP_TORADIX_C) + #define BN_MP_ISZERO_C + #define BN_MP_INIT_COPY_C + #define BN_MP_DIV_D_C + #define BN_MP_CLEAR_C + #define BN_MP_S_RMAP_C +#endif + +#if defined(BN_MP_TORADIX_N_C) + #define BN_MP_ISZERO_C + #define BN_MP_INIT_COPY_C + #define BN_MP_DIV_D_C + #define BN_MP_CLEAR_C + #define BN_MP_S_RMAP_C +#endif + +#if defined(BN_MP_UNSIGNED_BIN_SIZE_C) + #define BN_MP_COUNT_BITS_C +#endif + +#if defined(BN_MP_XOR_C) + #define BN_MP_INIT_COPY_C + #define BN_MP_CLAMP_C + #define BN_MP_EXCH_C + #define BN_MP_CLEAR_C +#endif + +#if defined(BN_MP_ZERO_C) +#endif + +#if defined(BN_PRIME_TAB_C) +#endif + +#if defined(BN_REVERSE_C) +#endif + +#if defined(BN_S_MP_ADD_C) + #define BN_MP_GROW_C + #define BN_MP_CLAMP_C +#endif + +#if defined(BN_S_MP_EXPTMOD_C) + #define BN_MP_COUNT_BITS_C + #define BN_MP_INIT_C + #define BN_MP_CLEAR_C + #define BN_MP_REDUCE_SETUP_C + #define BN_MP_REDUCE_C + #define BN_MP_REDUCE_2K_SETUP_L_C + #define BN_MP_REDUCE_2K_L_C + #define BN_MP_MOD_C + #define BN_MP_COPY_C + #define BN_MP_SQR_C + #define BN_MP_MUL_C + #define BN_MP_SET_C + #define BN_MP_EXCH_C +#endif + +#if defined(BN_S_MP_MUL_DIGS_C) + #define BN_FAST_S_MP_MUL_DIGS_C + #define BN_MP_INIT_SIZE_C + #define BN_MP_CLAMP_C + #define BN_MP_EXCH_C + #define BN_MP_CLEAR_C +#endif + +#if defined(BN_S_MP_MUL_HIGH_DIGS_C) + #define BN_FAST_S_MP_MUL_HIGH_DIGS_C + #define BN_MP_INIT_SIZE_C + #define BN_MP_CLAMP_C + #define BN_MP_EXCH_C + #define BN_MP_CLEAR_C +#endif + +#if defined(BN_S_MP_SQR_C) + #define BN_MP_INIT_SIZE_C + #define BN_MP_CLAMP_C + #define BN_MP_EXCH_C + #define BN_MP_CLEAR_C +#endif + +#if defined(BN_S_MP_SUB_C) + #define BN_MP_GROW_C + #define BN_MP_CLAMP_C +#endif + +#if defined(BNCORE_C) +#endif + +#ifdef LTM3 +#define LTM_LAST +#endif +#include "libtorrent/tommath_superclass.h" +#include "libtorrent/tommath_class.h" +#else +#define LTM_LAST +#endif diff --git a/include/libtorrent/tommath_private.h b/include/libtorrent/tommath_private.h new file mode 100755 index 0000000..d8f5994 --- /dev/null +++ b/include/libtorrent/tommath_private.h @@ -0,0 +1,119 @@ +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://math.libtomcrypt.com + */ +#ifndef TOMMATH_PRIV_H_ +#define TOMMATH_PRIV_H_ + +#include "libtorrent/tommath.h" +#include + +#define MIN(x,y) (((x) < (y)) ? (x) : (y)) + +#define MAX(x,y) (((x) > (y)) ? (x) : (y)) + +#ifdef __cplusplus +extern "C" { + +/* C++ compilers don't like assigning void * to mp_digit * */ +#define OPT_CAST(x) (x *) + +#else + +/* C on the other hand doesn't care */ +#define OPT_CAST(x) + +#endif + +/* define heap macros */ +#ifndef XMALLOC + /* default to libc stuff */ + #define XMALLOC malloc + #define XFREE free + #define XREALLOC realloc + #define XCALLOC calloc +#else + /* prototypes for our heap functions */ + extern void *XMALLOC(size_t n); + extern void *XREALLOC(void *p, size_t n); + extern void *XCALLOC(size_t n, size_t s); + extern void XFREE(void *p); +#endif + +/* lowlevel functions, do not call! */ +int s_mp_add(mp_int *a, mp_int *b, mp_int *c); +int s_mp_sub(mp_int *a, mp_int *b, mp_int *c); +#define s_mp_mul(a, b, c) s_mp_mul_digs(a, b, c, (a)->used + (b)->used + 1) +int fast_s_mp_mul_digs(mp_int *a, mp_int *b, mp_int *c, int digs); +int s_mp_mul_digs(mp_int *a, mp_int *b, mp_int *c, int digs); +int fast_s_mp_mul_high_digs(mp_int *a, mp_int *b, mp_int *c, int digs); +int s_mp_mul_high_digs(mp_int *a, mp_int *b, mp_int *c, int digs); +int fast_s_mp_sqr(mp_int *a, mp_int *b); +int s_mp_sqr(mp_int *a, mp_int *b); +int mp_karatsuba_mul(mp_int *a, mp_int *b, mp_int *c); +int mp_toom_mul(mp_int *a, mp_int *b, mp_int *c); +int mp_karatsuba_sqr(mp_int *a, mp_int *b); +int mp_toom_sqr(mp_int *a, mp_int *b); +int fast_mp_invmod(mp_int *a, mp_int *b, mp_int *c); +int mp_invmod_slow (mp_int * a, mp_int * b, mp_int * c); +int fast_mp_montgomery_reduce(mp_int *x, mp_int *n, mp_digit rho); +int mp_exptmod_fast(mp_int *G, mp_int *X, mp_int *P, mp_int *Y, int redmode); +int s_mp_exptmod (mp_int * G, mp_int * X, mp_int * P, mp_int * Y, int redmode); +void bn_reverse(unsigned char *s, int len); + +extern const char *mp_s_rmap; + +/* Fancy macro to set an MPI from another type. + * There are several things assumed: + * x is the counter and unsigned + * a is the pointer to the MPI + * b is the original value that should be set in the MPI. + */ +#define MP_SET_XLONG(func_name, type) \ +int func_name (mp_int * a, type b) \ +{ \ + unsigned int x; \ + int res; \ + \ + mp_zero (a); \ + \ + /* set four bits at a time */ \ + for (x = 0; x < (sizeof(type) * 2u); x++) { \ + /* shift the number up four bits */ \ + if ((res = mp_mul_2d (a, 4, a)) != MP_OKAY) { \ + return res; \ + } \ + \ + /* OR in the top four bits of the source */ \ + a->dp[0] |= (b >> ((sizeof(type) * 8u) - 4u)) & 15u; \ + \ + /* shift the source up to the next four bits */ \ + b <<= 4; \ + \ + /* ensure that digits are not clamped off */ \ + a->used += 1; \ + } \ + mp_clamp (a); \ + return MP_OKAY; \ +} + +#ifdef __cplusplus + } +#endif + +#endif + + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ diff --git a/include/libtorrent/tommath_superclass.h b/include/libtorrent/tommath_superclass.h new file mode 100644 index 0000000..1b26841 --- /dev/null +++ b/include/libtorrent/tommath_superclass.h @@ -0,0 +1,76 @@ +/* super class file for PK algos */ + +/* default ... include all MPI */ +#define LTM_ALL + +/* RSA only (does not support DH/DSA/ECC) */ +/* #define SC_RSA_1 */ + +/* For reference.... On an Athlon64 optimizing for speed... + + LTM's mpi.o with all functions [striped] is 142KiB in size. + +*/ + +/* Works for RSA only, mpi.o is 68KiB */ +#ifdef SC_RSA_1 + #define BN_MP_SHRINK_C + #define BN_MP_LCM_C + #define BN_MP_PRIME_RANDOM_EX_C + #define BN_MP_INVMOD_C + #define BN_MP_GCD_C + #define BN_MP_MOD_C + #define BN_MP_MULMOD_C + #define BN_MP_ADDMOD_C + #define BN_MP_EXPTMOD_C + #define BN_MP_SET_INT_C + #define BN_MP_INIT_MULTI_C + #define BN_MP_CLEAR_MULTI_C + #define BN_MP_UNSIGNED_BIN_SIZE_C + #define BN_MP_TO_UNSIGNED_BIN_C + #define BN_MP_MOD_D_C + #define BN_MP_PRIME_RABIN_MILLER_TRIALS_C + #define BN_REVERSE_C + #define BN_PRIME_TAB_C + + /* other modifiers */ + #define BN_MP_DIV_SMALL /* Slower division, not critical */ + + /* here we are on the last pass so we turn things off. The functions classes are still there + * but we remove them specifically from the build. This also invokes tweaks in functions + * like removing support for even moduli, etc... + */ +#ifdef LTM_LAST + #undef BN_MP_TOOM_MUL_C + #undef BN_MP_TOOM_SQR_C + #undef BN_MP_KARATSUBA_MUL_C + #undef BN_MP_KARATSUBA_SQR_C + #undef BN_MP_REDUCE_C + #undef BN_MP_REDUCE_SETUP_C + #undef BN_MP_DR_IS_MODULUS_C + #undef BN_MP_DR_SETUP_C + #undef BN_MP_DR_REDUCE_C + #undef BN_MP_REDUCE_IS_2K_C + #undef BN_MP_REDUCE_2K_SETUP_C + #undef BN_MP_REDUCE_2K_C + #undef BN_S_MP_EXPTMOD_C + #undef BN_MP_DIV_3_C + #undef BN_S_MP_MUL_HIGH_DIGS_C + #undef BN_FAST_S_MP_MUL_HIGH_DIGS_C + #undef BN_FAST_MP_INVMOD_C + + /* To safely undefine these you have to make sure your RSA key won't exceed the Comba threshold + * which is roughly 255 digits [7140 bits for 32-bit machines, 15300 bits for 64-bit machines] + * which means roughly speaking you can handle upto 2536-bit RSA keys with these defined without + * trouble. + */ + #undef BN_S_MP_MUL_DIGS_C + #undef BN_S_MP_SQR_C + #undef BN_MP_MONTGOMERY_REDUCE_C +#endif + +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ diff --git a/include/libtorrent/torrent.hpp b/include/libtorrent/torrent.hpp new file mode 100644 index 0000000..0e168dd --- /dev/null +++ b/include/libtorrent/torrent.hpp @@ -0,0 +1,1750 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_TORRENT_HPP_INCLUDE +#define TORRENT_TORRENT_HPP_INCLUDE + +#include +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/peer_list.hpp" +#include "libtorrent/tracker_manager.hpp" +#include "libtorrent/stat.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/piece_picker.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/bandwidth_limit.hpp" +#include "libtorrent/bandwidth_queue_entry.hpp" +#include "libtorrent/storage_defs.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/bitfield.hpp" +#include "libtorrent/aux_/session_interface.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/peer_class_set.hpp" +#include "libtorrent/link.hpp" +#include "libtorrent/vector_utils.hpp" +#include "libtorrent/linked_list.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/aux_/file_progress.hpp" + +#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; + + class piece_manager; + struct torrent_plugin; + struct bitfield; + struct announce_entry; + struct tracker_request; + struct add_torrent_params; + struct storage_interface; + class bt_peer_connection; + struct listen_socket_t; + + + namespace aux + { + struct piece_checker_data; + } + + struct resume_data_t + { + std::vector buf; + bdecode_node node; + }; + + 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 + int flags; + // how many peers it's been requested from + int peers; + // the piece index + int 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; } + }; + + // this is the internal representation of web seeds + struct web_seed_t : web_seed_entry + { + 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_point retry; + + // 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; + + // 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; + + // this indicates whether or not we're resolving the + // hostname of this URL + bool resolving; + + // 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; + + // 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; + std::vector restart_piece; + }; + + struct TORRENT_EXTRA_EXPORT torrent_hot_members + { + torrent_hot_members(aux::session_interface& ses + , add_torrent_params const& p, int block_size); + + 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. + boost::scoped_ptr m_picker; + + // TODO: make this a raw pointer. perhaps keep the shared_ptr + // around further down the object to maintain an owner + boost::shared_ptr m_torrent_file; + + // a back reference to the session + // this torrent belongs to. + aux::session_interface& m_ses; + + // this vector is sorted at all times, by the pointer value. + // use sorted_insert() and sorted_find() on it. The GNU STL + // implementation on Darwin uses significantly less memory to + // represent a vector than a set, and this set is typically + // relatively small, and it's cheap to copy pointers. + std::vector m_connections; + + // the scrape data from the tracker response, this + // is optional and may be 0xffffff + boost::uint32_t m_complete:24; + + // set to true when this torrent may not download anything + bool m_upload_mode:1; + + // this is set to false as long as the connections + // of this torrent hasn't been initialized. If we + // have metadata from the start, connections are + // initialized immediately, if we didn't have metadata, + // they are initialized right after files_checked(). + // valid_resume_data() will return false as long as + // the connections aren't initialized, to avoid + // them from altering the piece-picker before it + // has been initialized with files_checked(). + bool m_connections_initialized:1; + + // is set to true when the torrent has + // been aborted. + bool m_abort:1; + + // is true if this torrent has allows having peers + bool m_allow_peers:1; + + // this is set when the torrent is in share-mode + bool m_share_mode:1; + + // 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 contans + // 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_allow_peers is also false. + bool m_graceful_pause_mode:1; + + // state subscription. If set, a pointer to this torrent + // will be added to the m_state_updates set in session_impl + // whenever this torrent's state changes (any state). + bool m_state_subscription:1; + + // the maximum number of connections for this torrent + boost::uint32_t m_max_connections:24; + + // the size of a request block + // each piece is divided into these + // blocks when requested. The block size is + // 1 << m_block_size_shift + boost::uint32_t m_block_size_shift:5; + + // the state of this torrent (queued, checking, downloading, etc.) + boost::uint32_t m_state:3; + + boost::scoped_ptr m_peer_list; + }; + + // a torrent is a class that holds information + // for a specific download. It updates itself against + // the tracker + class TORRENT_EXTRA_EXPORT torrent + : private single_threaded + , private torrent_hot_members + , public request_callback + , public peer_class_set + , public boost::enable_shared_from_this + , public list_node // used for torrent activity LRU + { + public: + + torrent(aux::session_interface& ses, int block_size + , int seq, add_torrent_params const& p + , sha1_hash const& info_hash); + ~torrent(); + + // This may be called from multiple threads + sha1_hash const& info_hash() const { return m_info_hash; } + + bool is_deleted() const { return m_deleted; } + + // starts the announce timer + void start(add_torrent_params const& p); + + void start_download_url(); + + // returns which stats gauge this torrent currently + // has incremented. + int current_stats_state() const; + +#ifndef TORRENT_DISABLE_EXTENSIONS + void add_extension(boost::shared_ptr); + void remove_extension(boost::shared_ptr); + void add_extension(boost::function(torrent_handle const&, void*)> const& ext + , void* userdata); + void notify_extension_add_peer(tcp::endpoint const& ip, int src, int 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(); + + // called every time we actually need the torrent_info + // object to be fully loaded. If it isn't, this triggers + // loading it from disk + // the return value indicates success. If it failed to + // load, the torrent will be set to an error state and + // return false + bool need_loaded(); + + // unload the torrent file to save memory + void unload(); + // returns true if parsed successfully + bool load(std::vector& buffer); + + // pinned torrents may not be unloaded + bool is_pinned() const { return m_pinned; } + void set_pinned(bool p); + bool is_loaded() const { return m_torrent_file->is_loaded(); } + bool should_be_loaded() const { return m_should_be_loaded; } + + // 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(sha1_hash const& pid); + + void on_resume_data_checked(disk_io_job const* j); + void on_force_recheck(disk_io_job const* j); + void on_piece_hashed(disk_io_job const* j); + void files_checked(); + void start_checking(); + + void start_announcing(); + void stop_announcing(); + + void send_share_mode(); + void send_upload_only(); + + void set_share_mode(bool s); + bool share_mode() const { return m_share_mode; } + + // TOOD: make graceful pause also finish all sending blocks + // before disconnecting + bool graceful_pause() const { return m_graceful_pause_mode; } + + 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; + + enum flags_t { overwrite_existing = 1 }; + void add_piece(int piece, char const* data, int flags = 0); + void on_disk_write_complete(disk_io_job const* j + , peer_request p); + void on_disk_cache_complete(disk_io_job const* j); + void on_disk_tick_done(disk_io_job const* j); + + void schedule_storage_tick(); + + void set_progress_ppm(int p) { m_progress_ppm = p; } + struct read_piece_struct + { + boost::shared_array piece_data; + int blocks_left; + bool fail; + error_code error; + }; + void read_piece(int piece); + void on_disk_read_complete(disk_io_job const* j, peer_request r + , boost::shared_ptr rp); + + storage_mode_t storage_mode() const; + storage_interface* get_storage(); + + // 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 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(int p); + int queue_position() const { return m_sequence_number; } + // used internally + void set_queue_position_impl(int p) { m_sequence_number = p; } + + 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::int64_t bytes_left() const; + int block_bytes_wanted(piece_block const& p) const; + void bytes_done(torrent_status& st, bool accurate) const; + boost::int64_t quantized_bytes_done() 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(boost::shared_ptr ipf); + void port_filter_updated(); + ip_filter const* get_ip_filter() { return m_ip_filter.get(); } + + std::string resolve_filename(int file) const; + void handle_disk_error(disk_io_job const* j, peer_connection* c = 0); + void clear_error(); + + void set_error(error_code const& ec, int file); + bool has_error() const { return !!m_error; } + error_code error() const { return m_error; } + + void flush_cache(); + void pause(bool graceful = false); + void resume(); + + enum pause_flags_t + { + flag_graceful_pause = 1, + flag_clear_disk_cache = 2 + }; + void set_allow_peers(bool b, int flags = flag_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); + + int started() const { return m_started; } + void step_session_time(int seconds); + void do_pause(bool clear_disk_cache = true); + void do_resume(); + + int finished_time() const; + int active_time() const; + int seeding_time() const; + + bool is_paused() const; + bool allows_peers() const { return m_allow_peers; } + bool is_torrent_paused() const { return !m_allow_peers || m_graceful_pause_mode; } + void force_recheck(); + void save_resume_data(int flags); + bool do_async_save_resume_data(); + + bool need_save_resume_data() const + { + // save resume data every 15 minutes regardless, just to + // keep stats up to date + return m_need_save_resume_data || m_ses.session_time() - m_last_saved_resume > 15 * 60; + } + + 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(int options); + void peers_erased(std::vector const& peers); + + // ============ start deprecation ============= + void filter_piece(int index, bool filter); + void filter_pieces(std::vector const& bitmask); + bool is_piece_filtered(int index) const; + void filtered_pieces(std::vector& bitmask) const; + void filter_files(std::vector const& files); +#if !TORRENT_NO_FPU + void file_progress(std::vector& fp); +#endif + // ============ end deprecation ============= + + void piece_availability(std::vector& avail) const; + + void set_piece_priority(int index, int priority); + int piece_priority(int index) const; + + void prioritize_pieces(std::vector const& pieces); + void prioritize_piece_list(std::vector > const& pieces); + void piece_priorities(std::vector*) const; + + void set_file_priority(int index, int priority); + int file_priority(int index) const; + + void on_file_priority(); + void prioritize_files(std::vector const& files); + void file_priorities(std::vector*) const; + + void cancel_non_critical(); + void set_piece_deadline(int piece, int t, int flags); + void reset_piece_deadline(int piece); + void clear_time_critical(); + void update_piece_priorities(); + + void status(torrent_status* st, boost::uint32_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(std::vector& fp, int flags = 0); + +#ifndef TORRENT_NO_DEPRECATE + void use_interface(std::string net_interface); +#endif + + void connect_to_url_seed(std::list::iterator url); + bool connect_to_peer(torrent_peer* peerinfo, bool ignore_limit = false); + + int priority() const { return m_priority; } + void set_priority(int prio) + { + TORRENT_ASSERT(prio <= 255 && prio >= 0); + if (prio > 255) prio = 255; + else if (prio < 0) prio = 0; + m_priority = prio; + state_updated(); + } + +#ifndef TORRENT_NO_DEPRECATE + // deprecated in 1.1 + +#ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES + void resolve_countries(bool r); + bool resolving_countries() const; + + void resolve_peer_country(boost::shared_ptr const& p) const; + void on_country_lookup(error_code const& error + , std::vector
    const& host_list + , boost::shared_ptr p) const; +#endif +#endif // TORRENT_NO_DEPRECATE + +// -------------------------------------------- + // 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 peer_class_t(m_peer_class); } + + void set_max_uploads(int limit, bool state_update = true); + int max_uploads() const { return m_max_uploads; } + void set_max_connections(int limit, bool state_update = true); + int max_connections() const { return m_max_connections; } + +// -------------------------------------------- + // PEER MANAGEMENT + + // add or remove a url that will be attempted for + // finding the file(s) in this torrent. + void add_web_seed(std::string const& url, web_seed_t::type_t type); + void add_web_seed(std::string const& url, web_seed_t::type_t type + , std::string const& auth, web_seed_t::headers_t const& extra_headers); + + 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, int retry = 0); + + void remove_web_seed(peer_connection* p, error_code const& ec + , operation_t op, int error = 0); + + 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(); + void trigger_optimistic_unchoke(); + + // 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(peer_connection* p); + + // 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, int source, int flags = 0); + bool ban_peer(torrent_peer* tp); + void update_peer_port(int port, torrent_peer* p, int 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()); } + int num_seeds() const; + int num_downloaders() const; + + typedef std::vector::iterator peer_iterator; + typedef std::vector::const_iterator const_peer_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(); } + + void get_full_peer_list(std::vector& v) const; + void get_peer_info(std::vector& v); + void get_download_queue(std::vector* queue) const; + +#ifndef TORRENT_NO_DEPRECATE + void refresh_explicit_cache(int cache_size); +#endif + + void add_suggest_piece(int piece); + void update_suggest_piece(int index, int change); + void update_auto_sequential(); + + void refresh_suggest_pieces(); + void do_refresh_suggest_pieces(); + +// -------------------------------------------- + // 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 occured + virtual void tracker_response( + tracker_request const& r + , address const& tracker_ip + , std::list
    const& ip_list + , struct tracker_response const& resp) TORRENT_OVERRIDE; + virtual void tracker_request_error(tracker_request const& r + , int response_code, error_code const& ec, const std::string& msg + , int retry_interval) TORRENT_OVERRIDE; + virtual void tracker_warning(tracker_request const& req + , std::string const& msg) TORRENT_OVERRIDE; + virtual void tracker_scrape_response(tracker_request const& req + , int complete, int incomplete, int downloaded, int downloaders) TORRENT_OVERRIDE; + + void update_scrape_state(); + + // 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; + + // generate the tracker key for this torrent. + // The key is passed to http trackers as ``&key=``. + boost::uint32_t tracker_key() const; + + // if we need a connect boost, connect some peers + // immediately + void do_connect_boost(); + + // returns the absolute time when the next tracker + // announce will take place. + time_point next_announce() const; + + // forcefully sets next_announce to the current time + void force_tracker_request(time_point, int tracker_idx); + void scrape_tracker(int idx, bool user_triggered); + void announce_with_tracker(boost::uint8_t e + = tracker_request::none + , address const& bind_interface = address_v4::any()); + int seconds_since_last_scrape() const + { + return m_last_scrape == (std::numeric_limits::min)() + ? -1 : int(m_ses.session_time() - m_last_scrape); + } + +#ifndef TORRENT_DISABLE_DHT + void dht_announce(); +#endif + +#ifndef TORRENT_NO_DEPRECATE + // sets the username and password that will be sent to + // the tracker + void set_tracker_login(std::string const& name, std::string const& pw); +#endif + + announce_entry* find_tracker(tracker_request const& r); + +// -------------------------------------------- + // PIECE MANAGEMENT + + void recalc_share_mode(); + + struct suggest_piece_t + { + int piece_index; + int num_peers; + bool operator<(suggest_piece_t const& p) const { return num_peers < p.num_peers; } + }; + + std::vector const& get_suggested_pieces() const + { return m_suggested_pieces; } + + bool super_seeding() const + { + // we're not super seeding if we're not a seed + return m_super_seeding && is_seed(); + } + + void super_seeding(bool on); + int get_piece_to_super_seed(bitfield const&); + + // returns true if we have downloaded the given piece + bool have_piece(int 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 has_piece_passed(int index) const + { + if (!valid_metadata()) return false; + if (index < 0 || index >= torrent_file().num_pieces()) return false; + if (!has_picker()) return m_have_all; + return m_picker->has_piece_passed(index); + } + + // 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(int index) const + { + return std::binary_search(m_predictive_pieces.begin(), m_predictive_pieces.end(), index); + } + + private: + + // called when we learn that we have a piece + // only once per piece + void we_have(int index); + + public: + + int num_have() const + { + // pretend we have every piece when in seed mode + if (m_seed_mode) { + return m_torrent_file->num_pieces(); + } + + return has_picker() + ? m_picker->num_have() + : m_have_all ? m_torrent_file->num_pieces() : 0; + } + + // the number of pieces that have passed + // hash check, but aren't necessarily + // flushed to disk yet + int num_passed() const + { + return has_picker() + ? m_picker->num_passed() + : m_have_all ? m_torrent_file->num_pieces() : 0; + } + + // when we get a have message, this is called for that piece + void peer_has(int index, peer_connection const* peer); + + // when we get a bitfield message, this is called for that piece + void peer_has(bitfield const& bits, peer_connection const* peer); + + void peer_has_all(peer_connection const* peer); + + void peer_lost(int index, peer_connection const* peer); + void peer_lost(bitfield const& bits, peer_connection const* peer); + + int block_size() const { TORRENT_ASSERT(m_block_size_shift > 0); return 1 << m_block_size_shift; } + 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 wheh the torrent has completed + // the download. It will post an event, disconnect + // all seeds and let the tracker know we're finished. + void completed(); + +#if TORRENT_USE_I2P + void on_i2p_resolve(error_code const& ec, char const* dest); + bool is_i2p() const { return m_torrent_file && m_torrent_file->is_i2p(); } +#endif + + // this is the asio callback that is called when a name + // lookup for a PEER is completed. + void on_peer_name_lookup(error_code const& e + , std::vector
    const& addrs + , int port); + + // this is the asio callback that is called when a name + // lookup for a WEB SEED is completed. + void on_name_lookup(error_code const& e + , std::vector
    const& addrs + , int port + , std::list::iterator web); + + void connect_web_seed(std::list::iterator web, tcp::endpoint a); + + // this is the asio callback that is called when a name + // lookup for a proxy for a web seed is completed. + void on_proxy_name_lookup(error_code const& e + , std::vector
    const& addrs + , std::list::iterator web, int port); + + // re-evaluates whether this torrent should be considered inactive or not + void on_inactivity_tick(error_code const& ec); + + + // calculate the instantaneous inactive state (the externally facing + // inactive state is not instantaneous, but low-pass filtered) + bool is_inactive_internal() const; + + // remove a web seed, or schedule it for removal in case there + // are outstanding operations on it + void remove_web_seed(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(int piece); + void on_piece_verified(disk_io_job const* j); + + // 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(int index); + + // piece_failed is called when a piece fails the hash check + void piece_failed(int index); + + // this is the handler for hash failure piece synchronization + // i.e. resetting the piece + void on_piece_sync(disk_io_job const* j); + + // this is the handler for write failure piece synchronization + void on_piece_fail_sync(disk_io_job const* j, piece_block b); + + enum wasted_reason_t + { + piece_timed_out, piece_cancelled, piece_unknown, piece_seed + , piece_end_game, piece_closing + , waste_reason_max + }; + void add_redundant_bytes(int b, wasted_reason_t reason); + void add_failed_bytes(int b); + + // this is true if we have all the pieces, but not necessarily flushed them to disk + bool is_seed() const; + + // this is true if we have all the pieces that we want + // the pieces don't necessarily need to be flushed to disk + bool is_finished() const; + + bool is_inactive() const; + + std::string save_path() const; + alert_manager& alerts() const; + piece_picker& picker() + { + TORRENT_ASSERT(m_picker.get()); + return *m_picker; + } + piece_picker const& picker() const + { + TORRENT_ASSERT(m_picker.get()); + return *m_picker; + } + void need_picker(); + bool has_picker() const + { + return m_picker.get() != 0; + } + + 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; } + + piece_manager& storage(); + bool has_storage() const { return m_storage.get() != NULL; } + + torrent_info const& torrent_file() const + { return *m_torrent_file; } + + boost::shared_ptr get_torrent_copy(); + + std::string const& uuid() const { return m_uuid; } + void set_uuid(std::string const& s) { m_uuid = s; } + std::string const& url() const { return m_url; } + void set_url(std::string const& s) { m_url = s; } + std::string const& source_feed_url() const { return m_source_feed_url; } + void set_source_feed_url(std::string const& s) { m_source_feed_url = s; } + + std::vector const& trackers() const + { return m_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(entry& rd) const; + void read_resume_data(bdecode_node const& rd); + + void seen_complete() { m_last_seen_complete = time(0); } + int time_since_complete() const { return int(time(0) - m_last_seen_complete); } + time_t last_seen_complete() const { return m_last_seen_complete; } + + // LOGGING +#ifndef TORRENT_DISABLE_LOGGING + virtual void debug_log(const char* fmt, ...) const TORRENT_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, int flags); + + // renames the file with the given index to the new name + // the name may include a directory path + // returns false on failure + void rename_file(int index, std::string const& 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; } + bool valid_storage() const + { return m_storage.get() != NULL; } + + // 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(char const* metadata_buf, int metadata_size); + + void on_torrent_download(error_code const& ec, http_parser const& parser + , char const* data, int size); + + int sequence_number() const { return m_sequence_number; } + + bool seed_mode() const { return m_seed_mode; } + void leave_seed_mode(bool skip_checking); + + bool all_verified() const + { return int(m_num_verified) == m_torrent_file->num_pieces(); } + bool verifying_piece(int piece) const + { + TORRENT_ASSERT(piece < int(m_verifying.size())); + TORRENT_ASSERT(piece >= 0); + return m_verifying.get_bit(piece); + } + void verifying(int piece) + { + TORRENT_ASSERT(piece < int(m_verifying.size())); + TORRENT_ASSERT(piece >= 0); + TORRENT_ASSERT(m_verifying.get_bit(piece) == false); + m_verifying.set_bit(piece); + } + bool verified_piece(int piece) const + { + TORRENT_ASSERT(piece < int(m_verified.size())); + TORRENT_ASSERT(piece >= 0); + return m_verified.get_bit(piece); + } + void verified(int piece); + + bool add_merkle_nodes(std::map const& n, int piece); + + // this is called once periodically for torrents + // that are not private + void lsd_announce(); + + void update_last_upload() { m_last_upload = m_ses.session_time(); } + + void set_apply_ip_filter(bool b); + bool apply_ip_filter() const { return m_apply_ip_filter; } + + 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(int index, int milliseconds); + + 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 dec_refcount(char const* purpose); + void inc_refcount(char const* purpose); + int refcount() const { return m_refcount; } + + void inc_num_connecting() + { ++m_num_connecting; } + void dec_num_connecting() + { + TORRENT_ASSERT(m_num_connecting > 0); + --m_num_connecting; + } + + bool is_ssl_torrent() const { return m_ssl_torrent; } +#ifdef TORRENT_USE_OPENSSL + void set_ssl_cert(std::string const& certificate + , std::string const& private_key + , std::string const& dh_params + , std::string const& passphrase); + void set_ssl_cert_buffer(std::string const& certificate + , std::string const& private_key + , std::string const& dh_params); + boost::asio::ssl::context* ssl_ctx() const { return m_ssl_ctx.get(); } +#endif + + int num_time_critical_pieces() const + { return int(m_time_critical_pieces.size()); } + + private: + + 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(int list, bool in); + + void on_files_deleted(disk_io_job const* j); + void on_torrent_paused(disk_io_job const* j); + void on_storage_moved(disk_io_job const* j); + void on_save_resume_data(disk_io_job const* j); + void on_file_renamed(disk_io_job const* j); + void on_cache_flushed(disk_io_job const* j); + + // 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; + + void refresh_explicit_cache_impl(disk_io_job const* j, int cache_size); + + int prioritize_tracker(int tracker_index); + int deprioritize_tracker(int tracker_index); + + bool request_bandwidth_from_session(int channel) const; + + void update_peer_interest(bool was_finished); + void prioritize_udp_trackers(); + + void update_tracker_timer(time_point now); + + static void on_tracker_announce_disp(boost::weak_ptr p + , error_code const& e); + + void on_tracker_announce(); + +#ifndef TORRENT_DISABLE_DHT + static void on_dht_announce_response_disp(boost::weak_ptr t + , std::vector const& peers); + void on_dht_announce_response(std::vector const& peers); + bool should_announce_dht() const; +#endif + + void remove_time_critical_piece(int piece, bool finished = false); + void remove_time_critical_pieces(std::vector const& priority); + void request_time_critical_pieces(); + + void need_peer_list(); + + boost::shared_ptr m_ip_filter; + + // all time totals of uploaded and downloaded payload + // stored in resume data + boost::int64_t m_total_uploaded; + boost::int64_t m_total_downloaded; + + // if this pointer is 0, the torrent is in + // a state where the metadata hasn't been + // received yet, or during shutdown. + // the piece_manager keeps the torrent object + // alive by holding a shared_ptr to it and + // the torrent keeps the piece manager alive + // with this shared_ptr. This cycle is + // broken when torrent::abort() is called + // Then the torrent releases the piece_manager + // and when the piece_manager is complete with all + // outstanding disk io jobs (that keeps + // the piece_manager alive) it will destruct + // and release the torrent file. The reason for + // this is that the torrent_info is used by + // the piece_manager, and stored in the + // torrent, so the torrent cannot destruct + // before the piece_manager. + boost::shared_ptr m_storage; + +#ifdef TORRENT_USE_OPENSSL + boost::shared_ptr m_ssl_ctx; + +#if BOOST_VERSION >= 104700 + bool verify_peer_cert(bool preverified, boost::asio::ssl::verify_context& ctx); +#endif + + void init_ssl(std::string const& 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 iteratores 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 + typedef std::list > extension_list_t; + extension_list_t 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 1. + // TODO: this wastes 5 bits per file + std::vector m_file_priority; + + // this object is used to track download progress of individual files + aux::file_progress m_file_progress; + + // these are the pieces we're currently + // suggesting to peers. + std::vector m_suggested_pieces; + + std::vector m_trackers; + // this is an index into m_trackers + + // this list is sorted by time_critical_piece::deadline + std::vector m_time_critical_pieces; + + std::string m_trackerid; +#ifndef TORRENT_NO_DEPRECATE + // deprecated in 1.1 + std::string m_username; + std::string m_password; +#endif + + std::string m_save_path; + + // if we don't have the metadata, this is a url to + // the torrent file + std::string m_url; + + // if this was added from an RSS feed, this is the unique + // identifier in the feed. + std::string m_uuid; + + // if this torrent was added by an RSS feed, this is the + // URL to that feed + std::string m_source_feed_url; + + // this is used as temporary storage while downloading + // the .torrent file from m_url +// std::vector m_torrent_file_buf; + + // 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. + std::vector m_predictive_pieces; + + // 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) + + // TODO: These two bitfields should probably be coalesced into one + bitfield m_verified; + // this means there is an outstanding, async, operation + // to verify each piece that has a 1 + bitfield m_verifying; + + // set if there's an error on this torrent + error_code m_error; + + // used if there is any resume data + boost::scoped_ptr m_resume_data; + + // 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 + boost::scoped_ptr m_name; + + storage_constructor_type m_storage_constructor; + + // the posix time this torrent was added and when + // it was completed. If the torrent isn't yet + // completed, m_completed_time is 0 + time_t m_added_time; + time_t m_completed_time; + + // this was the last time _we_ saw a seed in this swarm + time_t m_last_seen_complete; + + // this is the time last any of our peers saw a seed + // in this swarm + time_t m_swarm_last_seen_complete; + + // keep a copy if the info-hash here, so it can be accessed from multiple + // threads, and be cheap to access from the client + sha1_hash m_info_hash; + + public: + // these are the lists this torrent belongs to. For more + // details about each list, see session_impl.hpp. Each list + // represents a group this torrent belongs to and makes it + // efficient to enumerate only torrents belonging to a specific + // group. Such as torrents that want peer connections or want + // to be ticked etc. + + // TODO: 3 factor out the links (as well as update_list() to a separate + // class that torrent can inherit) + link m_links[aux::session_interface::num_torrent_lists]; + + private: + + // m_num_verified = m_verified.count() + boost::uint32_t m_num_verified; + + // this timestamp is kept in session-time, to + // make it fit in 16 bits + boost::uint16_t m_last_saved_resume; + + // if this torrent is running, this was the time + // when it was started. This is used to have a + // bias towards keeping seeding torrents that + // recently was started, to avoid oscillation + // this is specified at a second granularity + // in session-time. see session_impl for details. + // the reference point is stepped forward every 4 + // hours to keep the timestamps fit in 16 bits + boost::uint16_t m_started; + + // if we're a seed, this is the session time + // timestamp of when we became one + boost::uint16_t m_became_seed; + + // if we're finished, this is the session time + // timestamp of when we finished + boost::uint16_t m_became_finished; + + // when checking, this is the first piece we have not + // issued a hash job for + int m_checking_piece; + + // the number of pieces we completed the check of + int m_num_checked_pieces; + + // the number of async. operations that need this torrent + // loaded in RAM. having a refcount > 0 prevents it from + // being unloaded. + int m_refcount; + + // if the error ocurred on a file, this is the index of that file + // there are a few special cases, when this is negative. See + // set_error() + int m_error_file; + + // the average time it takes to download one time critical piece + boost::uint32_t m_average_piece_time; + + // the average piece download time deviation + boost::uint32_t m_piece_time_deviation; + + // the number of bytes that has been + // downloaded that failed the hash-test + boost::uint32_t m_total_failed_bytes; + boost::uint32_t m_total_redundant_bytes; + + // the sequence number for this torrent, this is a + // monotonically increasing number for each added torrent + int m_sequence_number; + + // for torrents who have a bandwidth limit, this is != 0 + // and refers to a peer_class in the session. + boost::uint16_t m_peer_class; + + // 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) + boost::uint16_t m_num_connecting; + + // ============================== + // The following members are specifically + // ordered to make the 24 bit members + // properly 32 bit aligned by inserting + // 8 bits after each one + // ============================== + + // the session time timestamp of when we entered upload mode + // if we're currently in upload-mode + boost::uint16_t m_upload_mode_time; + + // true when this torrent should anncounce to + // trackers + bool m_announce_to_trackers:1; + + // true when this torrent should anncounce 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 while the tracker deadline timer + // is in use. i.e. one or more trackers are waiting + // for a reannounce + bool m_waiting_tracker:1; + +// ---- + + // 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. + unsigned int m_active_time:24; + + // the index to the last tracker that worked + boost::int8_t m_last_working_tracker; + +// ---- + + // total time we've been finished with this torrent. + // does not count when the torrent is stopped or paused. + unsigned int m_finished_time:24; + + // 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; + + // if this is true, we're currently super seeding this + // torrent. + bool m_super_seeding:1; + + // this is set when we don't want to load seed_mode, + // paused or auto_managed from the resume data + const bool m_override_resume_data:1; + +#ifndef TORRENT_NO_DEPRECATE +#ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES + // this is true while there is a country + // resolution in progress. To avoid flodding + // the DNS request queue, only one ip is resolved + // at a time. + mutable bool m_resolving_country:1; + + // this is true if the user has enabled + // country resolution in this torrent + bool m_resolve_countries:1; +#endif +#endif + + // set to false when saving resume data. Set to true + // whenever something is downloaded + bool m_need_save_resume_data: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. + unsigned int m_seeding_time:24; + +// ---- + + // the maximum number of uploads for this torrent + unsigned int m_max_uploads:24; + + // these are the flags sent in on a call to save_resume_data + // we need to save them to check them in write_resume_data + boost::uint8_t m_save_resume_flags; + +// ---- + + // the number of unchoked peers in this torrent + unsigned int m_num_uploads:24; + + // when this is set, second_tick will perform the actual + // work of refreshing the suggest pieces + bool m_need_suggest_pieces_refresh:1; + + // this is set to true when the torrent starts up + // The first tracker response, when this is true, + // will attempt to connect to a bunch of peers immediately + // and set this to false. We only do this once to get + // the torrent kick-started + bool m_need_connect_boost:1; + + // rotating sequence number for LSD announces sent out. + // used to only use IP broadcast for every 8th lsd announce + boost::uint8_t m_lsd_seq:3; + + // this is set to true if the torrent was started without + // metadata. It is used to save metadata in the resume file + // by default for such torrents. It does not necessarily + // have to be a magnet link. + bool m_magnet_link:1; + + // set to true if the session IP filter applies to this + // torrent or not. Defaults to true. + bool m_apply_ip_filter:1; + + // if set to true, add tracker URLs loaded from resume + // data into this torrent instead of replacing them + bool m_merge_resume_trackers:1; + +// ---- + + // the number of bytes of padding files + boost::uint32_t m_padding:24; + + // this is the priority of the torrent. The higher + // the value is, the more bandwidth is assigned to + // the torrent's peers + boost::uint32_t m_priority:8; + +// ---- + + // the scrape data from the tracker response, this + // is optional and may be 0xffffff + boost::uint32_t m_incomplete:24; + + + // true when the torrent should announce to + // the DHT + bool m_announce_to_dht:1; + + // in state_updates list. When adding a torrent to the + // session_impl's m_state_update list, this bit is set + // to never add the same torrent twice + bool m_in_state_updates:1; + + // these represent whether or not this torrent is counted + // in the total counters of active seeds and downloads + // in the session. + bool m_is_active_download:1; + bool m_is_active_finished: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; + + // pinned torrents are locked in RAM and won't be unloaded + // in favor of more active torrents. When the torrent is added, + // the user may choose to initialize this to 1, in which case + // it will never be unloaded from RAM + bool m_pinned:1; + + // when this is false, we should unload the torrent as soon + // as the no other async. job needs the torrent loaded + bool m_should_be_loaded:1; + +// ---- + + // the timestamp of the last piece passed for this torrent specified in + // session_time. This is signed because it must be able to represent time + // before the session started + boost::int16_t m_last_download; + + // the number of peer connections to seeds. This should be the same as + // counting the peer connections that say true for is_seed() + boost::uint16_t m_num_seeds; + + // the timestamp of the last byte uploaded from this torrent specified in + // session_time. This is signed because it must be able to represent time + // before the session started. + boost::int16_t m_last_upload; + + // this is a second count-down to when we should tick the + // storage for this torrent. Ticking the storage is used + // to periodically flush the partfile metadata and possibly + // other deferred flushing. Any disk operation starts this + // counter (unless it's already counting down). 0 means no + // ticking is needed. + boost::uint8_t m_storage_tick; + +// ---- + + // 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; + + enum { no_gauge_state = 0xf }; + // the current stats gauge this torrent counts against + boost::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 + unsigned int m_downloaded:24; + + // 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 + boost::int16_t m_last_scrape; + +// ---- + + // progress parts per million (the number of + // millionths of completeness) + unsigned int m_progress_ppm:20; + + // 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; + + // if this is set, accept the save path saved in the resume data, if + // present + bool m_use_resume_save_path:1; + + // if set to true, add web seed URLs loaded from resume + // data into this torrent instead of replacing the ones from the .torrent + // file + bool m_merge_resume_http_seeds:1; + + // 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; + +#if TORRENT_USE_ASSERTS + public: + // set to false until we've loaded resume data + bool m_resume_data_loaded; + + // set to true when torrent is start()ed. It may only be started once + bool m_was_started; +#endif + }; + + struct torrent_ref_holder + { + torrent_ref_holder(torrent* t, char const* p) + : m_torrent(t) + , m_purpose(p) + { + if (m_torrent) m_torrent->inc_refcount(m_purpose); + } + + ~torrent_ref_holder() + { + if (m_torrent) m_torrent->dec_refcount(m_purpose); + } + torrent* m_torrent; + char const* m_purpose; + }; + +} + +#endif // TORRENT_TORRENT_HPP_INCLUDED + diff --git a/include/libtorrent/torrent_handle.hpp b/include/libtorrent/torrent_handle.hpp new file mode 100644 index 0000000..63e5343 --- /dev/null +++ b/include/libtorrent/torrent_handle.hpp @@ -0,0 +1,1308 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_TORRENT_HANDLE_HPP_INCLUDED +#define TORRENT_TORRENT_HANDLE_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include + +#include +#include +#include +#include + +#ifndef TORRENT_NO_DEPRECATE +// for deprecated force_reannounce +#include +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/address.hpp" +#include "libtorrent/socket.hpp" // tcp::endpoint + +namespace libtorrent +{ + namespace aux + { + struct session_impl; + } + + class entry; + struct pool_file_status; + struct announce_entry; + class torrent_info; + struct torrent_plugin; + struct peer_info; + struct peer_list_entry; + struct torrent_status; + struct torrent_handle; + class sha1_hash; + struct storage_interface; + class torrent; + + // allows torrent_handle to be used in unordered_map and unordered_set. + TORRENT_EXPORT std::size_t hash_value(torrent_status const& ts); + +#ifndef BOOST_NO_EXCEPTIONS + void throw_invalid_handle() TORRENT_NO_RETURN; +#endif + + // 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: + TORRENT_UNION addr_t + { + address_v4::bytes_type v4; +#if TORRENT_USE_IPV6 + address_v6::bytes_type v6; +#endif + } addr; + + boost::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) + { +#if TORRENT_USE_IPV6 + is_v6_addr = ep.address().is_v6(); + if (is_v6_addr) + addr.v6 = ep.address().to_v6().to_bytes(); + else +#endif + addr.v4 = ep.address().to_v4().to_bytes(); + port = ep.port(); + } + tcp::endpoint peer() const + { +#if TORRENT_USE_IPV6 + if (is_v6_addr) + return tcp::endpoint(address_v6(addr.v6), port); + else +#endif + return tcp::endpoint(address_v4(addr.v4), port); + } + + // the number of bytes that have been received for this block + unsigned bytes_progress:15; + + // the total number of bytes in this block. + unsigned block_size:15; + + // the state this block is in (see block_state_t) + unsigned state:2; + + // the number of peers that is currently requesting this block. Typically + // this is 0 or 1, but at the end of the torrent blocks may be requested + // by more peers in parallel to speed things up. + unsigned num_peers:14; + private: +#if TORRENT_USE_IPV6 + // the type of the addr union + unsigned is_v6_addr:1; +#endif + }; + + // This class holds information about pieces that have outstanding requests + // or outstanding writes + struct TORRENT_EXPORT partial_piece_info + { + // 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. + int piece_index; + + // the number of blocks in this piece + int blocks_in_piece; + + // the number of blocks that are in the finished state + int finished; + + // the number of blocks that are in the writing state + int writing; + + // the number of blocks that are in the requested state + int requested; + + // this is an array of ``blocks_in_piece`` number of + // items. One for each block in the piece. + // + // .. warning:: This is a pointer that points to an array + // that's owned by the session object. The next time + // get_download_queue() is called, it will be invalidated. + block_info* blocks; + + // the speed classes. These may be used by the piece picker to + // coalesce requests of similar download rates + enum state_t { none, slow, medium, fast }; + + // the download speed class this piece falls into. + // this is used internally to cluster peers of the same + // speed class together when requesting blocks. + // + // set to either ``fast``, ``medium``, ``slow`` or ``none``. It tells + // which download rate category the peers downloading this piece falls + // into. ``none`` means that no peer is currently downloading any part of + // the piece. Peers prefer picking pieces from the same category as + // themselves. The reason for this is to keep the number of partially + // downloaded pieces down. Pieces set to ``none`` can be converted into + // any of ``fast``, ``medium`` or ``slow`` as soon as a peer want to + // download from it. + state_t piece_state; + }; + + // for boost::hash (and to support using this type in unordered_map etc.) + 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 libtorrent_exception + // 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 + { + // TODO: 3 consider replacing all the setters and getters for pause, + // resume, stop-when-ready, share-mode, upload-mode, super-seeding, + // apply-ip-filter, resolve-countries, pinned, sequential-download, + // seed-mode + // with just set_flags() and clear_flags() using the flags from + // add_torrent_params. Perhaps those flags should have a more generic + // name. + friend class invariant_access; + friend struct aux::session_impl; + friend class session; + friend struct session_handle; + friend struct feed; + friend class torrent; + friend 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() {} + + torrent_handle(torrent_handle const& t) + { if (!t.m_torrent.expired()) m_torrent = t.m_torrent; } + +#if __cplusplus >= 201103L + torrent_handle& operator=(torrent_handle const&) = default; +#endif + + // flags for add_piece(). + enum flags_t { overwrite_existing = 1 }; + + // 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. + void add_piece(int piece, char const* data, int flags = 0) 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(int piece) const; + + // Returns true if this piece has been completely downloaded, and false + // otherwise. + bool have_piece(int piece) const; + + // internal + void get_full_peer_list(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 + // libtorrent_exception exception. Each entry in the vector contains + // information about that particular peer. See peer_info. + void get_peer_info(std::vector& v) const; + + // flags to pass in to status() to specify which properties of the + // torrent to query for. By default all flags are set. + enum status_flags_t + { + // calculates ``distributed_copies``, ``distributed_full_copies`` and + // ``distributed_fraction``. + query_distributed_copies = 1, + // includes partial downloaded blocks in ``total_done`` and + // ``total_wanted_done``. + query_accurate_download_counters = 2, + // includes ``last_seen_complete``. + query_last_seen_complete = 4, + // includes ``pieces``. + query_pieces = 8, + // includes ``verified_pieces`` (only applies to torrents in *seed + // mode*). + query_verified_pieces = 16, + // includes ``torrent_file``, which is all the static information from + // the .torrent file. + query_torrent_file = 32, + // 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. + query_name = 64, + // includes ``save_path``, the path to the directory the files of the + // torrent are saved to. + query_save_path = 128 + }; + + // ``status()`` will return a structure with information about the status + // of this torrent. If the torrent_handle is invalid, it will throw + // libtorrent_exception 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 the status_flags_t enum. + torrent_status status(boost::uint32_t flags = 0xffffffff) const; + + // ``get_download_queue()`` takes a non-const reference to a vector which + // it will fill with information about pieces that are partially + // downloaded or not downloaded at all but partially requested. See + // partial_piece_info for the fields in the returned vector. + void get_download_queue(std::vector& queue) const; + + // flags for set_piece_deadline(). + enum deadline_flags { alert_when_available = 1 }; + + // 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. + // + // The ``flags`` parameter can be 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. + // + // 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 do the same thing 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(int index, int deadline, int flags = 0) const; + void reset_piece_deadline(int index) const; + void clear_piece_deadlines() const; + + // 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 prioritization where + // every interested peer on a high priority torrent will be unchoked + // before any other, lower priority, torrents have any peers unchoked. + void set_priority(int prio) const; + +#ifndef TORRENT_NO_DEPRECATE +#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 +#endif + + // flags to be passed in file_progress(). + enum file_progress_flags_t + { + // only calculate file progress at piece granularity. This makes + // the file_progress() call cheaper and also only takes bytes that + // have passed the hash check into account, so progress cannot + // regress in this mode. + piece_granularity = 1 + }; + + // This function fills in the supplied vector with the the number of + // bytes downloaded of each file in this torrent. The progress values are + // ordered the same as the files in the torrent_info. This operation is + // not very cheap. Its complexity is *O(n + mj)*. Where *n* is the number + // of files, *m* is the number of downloading pieces and *j* is the + // number of blocks in a piece. + // + // The ``flags`` parameter can be used to specify the granularity of the + // file progress. If left at the default value of 0, the progress will be + // as accurate as possible, but also more expensive to calculate. If + // ``torrent_handle::piece_granularity`` is specified, the progress will + // be specified in piece granularity. i.e. only pieces that have been + // fully downloaded and passed the hash check count. When specifying + // piece granularity, the operation is a lot cheaper, since libtorrent + // already keeps track of this internally and no calculation is required. + void file_progress(std::vector& progress, int flags = 0) const; + + // This function fills in the passed in vector with status about files + // that are open for this torrent. Any file that is not open in this + // torrent, 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 pool_file_status. + void file_status(std::vector& 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( + boost::function(torrent_handle const&, void*)> const& ext + , void* userdata = 0); + + // ``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(char const* metadata, int size) const; + + // Returns true if this handle refers to a valid torrent and false if it + // hasn't been initialized or if the torrent it refers to has been + // aborted. Note that a handle may become invalid after it has been added + // to the session. Usually this is because the storage for the torrent is + // somehow invalid or if the filenames are not allowed (and hence cannot + // be opened/created) on your filesystem. If such an error occurs, a + // file_error_alert is generated and all handles that refers to that + // torrent will become invalid. + bool is_valid() const; + + // flags for torrent_session::pause() + enum pause_flags_t { graceful_pause = 1 }; + + // ``pause()``, and ``resume()`` will disconnect all peers and reconnect + // all peers respectively. When a torrent is paused, it will however + // remember all share ratios to all peers and remember all potential (not + // connected) peers. Torrents may be paused automatically if there is a + // file error (e.g. disk full) or something similar. See + // file_error_alert. + // + // To know if a torrent is paused or not, call + // ``torrent_handle::status()`` and inspect ``torrent_status::paused``. + // + // The ``flags`` argument to pause can be set to + // ``torrent_handle::graceful_pause`` which 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. + // + // .. 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 automanaged first. Torrents are auto-managed by default when added + // to the session. For more information, see queuing_. + // + void pause(int flags = 0) const; + void resume() const; + + // set or clear the stop-when-ready flag. 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 auto_manage(false) and pause() were called on the torrent. + // + // Note that the torrent may transition into a downloading state while + // calling this function, 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 function is used to set it 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. + void stop_when_ready(bool b) const; + + // Explicitly sets the upload mode of the torrent. In upload mode, the + // torrent will not request any pieces. If the torrent is auto managed, + // it will automatically be taken out of upload mode periodically (see + // ``settings_pack::optimistic_disk_retry``). Torrents are + // automatically put in upload mode whenever they encounter a disk write + // error. + // + // ``m`` should be true to enter upload mode, and false to leave it. + // + // To test if a torrent is in upload mode, call + // ``torrent_handle::status()`` and inspect + // ``torrent_status::upload_mode``. + void set_upload_mode(bool b) const; + + // Enable or disable share mode for this torrent. When in share mode, the + // torrent will not necessarily be downloaded, especially not the whole + // of it. Only parts that are likely to be distributed to more than 2 + // other peers are downloaded, and only if the previous prediction was + // correct. + void set_share_mode(bool b) 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; + + // Set to true to apply the session global IP filter to this torrent + // (which is the default). Set to false to make this torrent ignore the + // IP filter. + void apply_ip_filter(bool b) 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. + void force_recheck() const; + + // flags used in the save_resume_data call to control additional + // actions or fields to save. + enum save_resume_flags_t + { + // 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. + flush_disk_cache = 1, + + // 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). + save_info_dict = 2, + + // 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. + only_if_modified = 4 + }; + + // ``save_resume_data()`` asks libtorrent to generate fast-resume data for + // this torrent. + // + // The ``flags`` argument is a bitmask of flags ORed together. see + // save_resume_flags_t + // + // 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! + // + //.. warning:: + // The resume data contains the modification timestamps for all files. + // If one file has been modified when the torrent is added again, the + // will be rechecked. When shutting down, make sure to flush the disk + // cache before saving the resume data. This will make sure that the + // file timestamps are up to date and won't be modified after saving + // the resume data. The recommended way to do this is to pause the + // torrent, which will flush the cache and disconnect all peers. + // + //.. 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 i : handles) + // { + // torrent_handle& h = *i; + // if (!h.is_valid()) continue; + // torrent_status s = h.status(); + // if (!s.has_metadata) continue; + // if (!s.need_save_resume_data()) continue; + // + // h.save_resume_data(); + // ++outstanding_resume_data; + // } + // + // while (outstanding_resume_data > 0) + // { + // alert const* a = ses.wait_for_alert(seconds(10)); + // + // // if we don't get an alert within 10 seconds, abort + // if (a == 0) break; + // + // std::vector alerts; + // ses.pop_alerts(&alerts); + // + // for (alert* i : alerts) + // { + // if (alert_cast(a)) + // { + // process_alert(a); + // --outstanding_resume_data; + // continue; + // } + // + // save_resume_data_alert const* rd = alert_cast(a); + // if (rd == 0) + // { + // process_alert(a); + // 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); + // out.unsetf(std::ios_base::skipws); + // bencode(std::ostream_iterator(out), *rd->resume_data); + // --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(int flags = 0) 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 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; + + // changes whether the torrent is auto managed or not. For more info, + // see queuing_. + void auto_managed(bool m) 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. + int queue_position() const; + void queue_position_up() const; + void queue_position_down() const; + void queue_position_top() const; + void queue_position_bottom() const; + +#ifndef TORRENT_NO_DEPRECATE + // deprecated in 1.1 + + // Sets or gets the flag that determines if countries should be resolved + // for the peers of this torrent. It defaults to false. If it is set to + // true, the peer_info structure for the peers in this torrent will have + // their ``country`` member set. See peer_info for more information on + // how to interpret this field. + TORRENT_DEPRECATED + void resolve_countries(bool r); + TORRENT_DEPRECATED + bool resolve_countries() const; +#endif + + // 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. This + // overload is only available when libtorrent is built against boost + // 1.54 or later. + // + // ``cert`` is a path to the (signed) certificate in .pem format + // corresponding to this torrent. + // + // ``private_key`` is a path to the private key for the specified + // certificate. This must be in .pem format. + // + // ``dh_params`` is a path to the Diffie-Hellman parameter file, which + // needs to be in .pem format. You can generate this file using the + // openssl command like this: ``openssl dhparam -outform PEM -out + // dhparams.pem 512``. + // + // ``passphrase`` may be specified if the private key is encrypted and + // requires a passphrase to be decrypted. + // + // Note that when a torrent first starts up, and it needs a certificate, + // it will suspend connecting to any peers until it has one. It's + // typically desirable to resume the torrent after setting the ssl + // certificate. + // + // If you receive a torrent_need_cert_alert, you need to call this to + // provide a valid cert. If you don't have a cert you won't be allowed to + // connect to any peers. + void set_ssl_certificate(std::string const& certificate + , std::string const& private_key + , std::string const& dh_params + , std::string const& passphrase = ""); + void set_ssl_certificate_buffer(std::string const& certificate + , std::string const& private_key + , std::string const& dh_params); + + // Returns the storage implementation for this torrent. This depends on the + // storage constructor function that was passed to add_torrent. + storage_interface* get_storage_impl() const; + + // Returns a pointer to the torrent_info object associated with this + // torrent. The torrent_info object may be a copy of the internal object. + // If the torrent doesn't have metadata, the pointer will not be + // initialized (i.e. a NULL pointer). The torrent may be in a state + // without metadata only if it was started without a .torrent file, e.g. + // by using the libtorrent extension of just supplying a tracker and + // info-hash. + boost::shared_ptr torrent_file() const; + +#ifndef TORRENT_NO_DEPRECATE + + // ================ start deprecation ============ + + // deprecated in 1.0 + // use status() instead (with query_save_path) + TORRENT_DEPRECATED + std::string save_path() const; + + // deprecated in 1.0 + // use status() instead (with query_name) + // returns the name of this torrent, in case it doesn't + // have metadata it returns the name assigned to it + // when it was added. + TORRENT_DEPRECATED + std::string name() const; + + // use torrent_file() instead + TORRENT_DEPRECATED + const torrent_info& get_torrent_info() const; + + // deprecated in 0.16, feature will be removed + TORRENT_DEPRECATED + int get_peer_upload_limit(tcp::endpoint ip) const; + TORRENT_DEPRECATED + int get_peer_download_limit(tcp::endpoint ip) const; + TORRENT_DEPRECATED + void set_peer_upload_limit(tcp::endpoint ip, int limit) const; + TORRENT_DEPRECATED + void set_peer_download_limit(tcp::endpoint ip, int limit) const; + + // deprecated in 0.16, feature will be removed + TORRENT_DEPRECATED + void set_ratio(float up_down_ratio) const; + + // deprecated in 0.16. use status() instead + 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.13 + // all these are deprecated, use piece + // priority functions instead + // marks the piece with the given index as filtered + // it will not be downloaded + TORRENT_DEPRECATED + void filter_piece(int index, bool filter) const; + TORRENT_DEPRECATED + void filter_pieces(std::vector const& pieces) const; + TORRENT_DEPRECATED + bool is_piece_filtered(int index) const; + TORRENT_DEPRECATED + std::vector filtered_pieces() const; + // marks the file with the given index as filtered + // it will not be downloaded + TORRENT_DEPRECATED + void filter_files(std::vector const& files) 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; + // ================ end deprecation ============ +#endif + + // ``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. + void use_interface(const char* net_interface) 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. + 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 priority-7 piece will + // be attempted to be picked before a priority 6 piece and so on. + // + // Piece priorities can not be changed for torrents that have not + // downloaded the metadata yet. For instance, magnet links and torrents + // added by URL 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. + // + // ``piece_priorities`` returns a vector with one element for each piece + // in the torrent. Each element is the current priority of that piece. + void piece_priority(int index, int priority) const; + int piece_priority(int index) const; + void prioritize_pieces(std::vector const& pieces) const; + void prioritize_pieces(std::vector > const& pieces) const; + std::vector piece_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. + // + // ``file_priorities()`` returns a vector with the priorities of all + // files. + // + // The priority values are the same as for piece_priority(). + // + // 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. + void file_priority(int index, int priority) const; + int file_priority(int index) const; + void prioritize_files(std::vector const& files) const; + std::vector file_priorities() 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. + // + // ``force_dht_announce`` will announce the torrent to the DHT + // immediately. + void force_reannounce(int seconds = 0, int tracker_index = -1) const; + void force_dht_announce() const; + +#ifndef TORRENT_NO_DEPRECATE + // 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. + void set_upload_limit(int limit) const; + int upload_limit() const; + void set_download_limit(int limit) const; + int download_limit() const; + + // A pinned torrent may not be unloaded by libtorrent. When the dynamic + // loading and unloading of torrents is enabled (by setting a load + // function on the session), this can be used to exempt certain torrents + // from the unloading logic. + // + // Magnet links, and other torrents that start out without having + // metadata are pinned automatically. This is to give the client a chance + // to get the metadata and save it before it's unloaded. In this case, it + // may be useful to un-pin the torrent once its metadata has been saved + // to disk. + // + // For more information about dynamically loading and unloading torrents, + // see dynamic-loading-of-torrent-files_. + // + void set_pinned(bool p) const; + + // ``set_sequential_download()`` enables or disables *sequential + // download*. When enabled, the piece picker will pick pieces in sequence + // instead of rarest first. In this mode, piece priorities are ignored, + // with the exception of priority 7, which are still preferred over the + // sequential piece order. + // + // Enabling sequential download will affect the piece distribution + // negatively in the swarm. It should be used sparingly. + void set_sequential_download(bool sd) 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 libtorrent_exception. The second (optional) + // argument will be bitwised 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. + // + // ``flags`` are the same flags that are passed along with the ``ut_pex`` extension. + // + // ==== ========================================== + // 0x01 peer supports encryption. + // + // 0x02 peer is a seed + // + // 0x04 supports uTP. If this is not set, the peer will only be contacted + // over TCP. + // + // 0x08 supports holepunching 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 + // ==== ========================================== + void connect_peer(tcp::endpoint const& adr, int source = 0 + , int flags = 0x1 + 0x4 + 0x8) 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. + 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; + +#ifndef TORRENT_NO_DEPRECATE + // 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 = 0 + // * fail_if_exist = 1 + // * dont_replace = 2 + // + // ``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 takes the existing file in the target + // directory, if there is one. The source files will still be removed in + // that case. + // + // Files that have been renamed to have absolute pahts are not moved by + // this function. Keep in mind that files that don't belong to the + // torrent but are stored in the torrent's directory may be moved as + // well. This goes for files that have been renamed to absolute paths + // that still end up inside the save path. + void move_storage(std::string const& save_path, int flags = 0) 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. + void rename_file(int index, std::string const& new_name) const; + +#ifndef TORRENT_NO_DEPRECATE +#if TORRENT_USE_WSTRING + // all wstring APIs are deprecated since 0.16.11 + // instead, use the wchar -> utf8 conversion functions + // and pass in utf8 strings + TORRENT_DEPRECATED + void move_storage(std::wstring const& save_path, int flags = 0) const; + TORRENT_DEPRECATED + void rename_file(int index, std::wstring const& new_name) const; +#endif // TORRENT_USE_WSTRING +#endif // TORRENT_NO_DEPRECATE + + // Enables or disabled super seeding/initial seeding for this torrent. + // The torrent needs to be a seed for this to take effect. + void super_seeding(bool on) const; + + // ``info_hash()`` returns the info-hash of the torrent. If this handle + // is to a torrent that hasn't loaded yet (for instance by being added) + // by a URL, the returned value is undefined. + sha1_hash info_hash() const; + + // comparison operators. The order of the torrents is unspecified + // but stable. + bool operator==(const torrent_handle& h) const + { return m_torrent.lock() == h.m_torrent.lock(); } + bool operator!=(const torrent_handle& h) const + { return m_torrent.lock() != h.m_torrent.lock(); } + bool operator<(const torrent_handle& h) const + { return m_torrent.lock() < h.m_torrent.lock(); } + + boost::uint32_t id() const + { + uintptr_t ret = reinterpret_cast(m_torrent.lock().get()); + // a torrent object is about 1024 bytes, so + // it's safe to shift 11 bits + return boost::uint32_t(ret >> 11); + } + + // This function is intended only for use by plugins and the alert + // dispatch function. This type does not have a stable API and should + // be relied on as little as possible. + boost::shared_ptr native_handle() const; + + private: + + torrent_handle(boost::weak_ptr const& t) + { if (!t.expired()) m_torrent = t; } + + boost::weak_ptr m_torrent; + + }; + +} + +#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..03c9a8b --- /dev/null +++ b/include/libtorrent/torrent_info.hpp @@ -0,0 +1,641 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_TORRENT_INFO_HPP_INCLUDED +#define TORRENT_TORRENT_INFO_HPP_INCLUDED + +#include +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/config.hpp" +#include "libtorrent/bdecode.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/copy_ptr.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/file_storage.hpp" + +namespace libtorrent +{ + class peer_connection; + class entry; + struct announce_entry; + struct lazy_entry; + + namespace aux { struct session_settings; } + + // internal, exposed for the unit test + TORRENT_EXTRA_EXPORT void sanitize_append_path_element(std::string& path + , char const* element, int element_len); + 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 }; + + typedef std::vector > headers_t; + + web_seed_entry(std::string const& url_, type_t type_ + , std::string const& auth_ = std::string() + , headers_t const& extra_headers_ = headers_t()); + + // URL and type comparison + bool operator==(web_seed_entry const& e) const + { return url == e.url && type == e.type; } + + // 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) + boost::uint8_t type; + }; + +#ifndef BOOST_NO_EXCEPTIONS + // for backwards compatibility with 0.14 + typedef libtorrent_exception invalid_torrent_file; +#endif + + // TODO: there may be some opportunities to optimize the size if torrent_info. + // specifically to turn some std::string and std::vector into pointers + 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 ``flags`` argument is currently unused. +#ifndef BOOST_NO_EXCEPTIONS + torrent_info(bdecode_node const& torrent_file, int flags = 0); + torrent_info(char const* buffer, int size, int flags = 0); + torrent_info(std::string const& filename, int flags = 0); +#endif // BOOST_NO_EXCEPTIONS + torrent_info(torrent_info const& t); + torrent_info(sha1_hash const& info_hash, int flags = 0); + torrent_info(bdecode_node const& torrent_file, error_code& ec, int flags = 0); + torrent_info(char const* buffer, int size, error_code& ec, int flags = 0); + torrent_info(std::string const& filename, error_code& ec, int flags = 0); +#ifndef TORRENT_NO_DEPRECATE + TORRENT_DEPRECATED + torrent_info(lazy_entry const& torrent_file, int flags = 0); + TORRENT_DEPRECATED + torrent_info(lazy_entry const& torrent_file, error_code& ec + , int flags = 0); +#if TORRENT_USE_WSTRING + // all wstring APIs are deprecated since 0.16.11 instead, use the wchar + // -> utf8 conversion functions and pass in utf8 strings + TORRENT_DEPRECATED + torrent_info(std::wstring const& filename, error_code& ec + , int flags = 0); + TORRENT_DEPRECATED + torrent_info(std::wstring const& filename, int flags = 0); +#endif // TORRENT_USE_WSTRING +#endif // TORRENT_NO_DEPRECATE + + // frees all storage associated with this torrent_info object + ~torrent_info(); + + // The file_storage object contains the information on how to map the + // pieces to files. It is separated from the torrent_info object because + // when creating torrents a storage object needs to be created without + // having a torrent file. When renaming files in a storage, the storage + // needs to make its own copy of the file_storage in order to make its + // mapping differ from the one in the torrent file. + // + // ``orig_files()`` returns the original (unmodified) file storage for + // this torrent. This is used by the web server connection, which needs + // to request files with the original names. Filename may be changed using + // ``torrent_info::rename_file()``. + // + // For more information on the file_storage object, see the separate + // document on how to create torrents. + file_storage const& files() const { return m_files; } + file_storage const& orig_files() const + { + TORRENT_ASSERT(is_loaded()); + return m_orig_files ? *m_orig_files : m_files; + } + + // Renames a 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 multifile + // 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(int index, std::string const& new_filename) + { + TORRENT_ASSERT(is_loaded()); + copy_on_write(); + m_files.rename_file(index, new_filename); + } +#ifndef TORRENT_NO_DEPRECATE +#if TORRENT_USE_WSTRING + // all wstring APIs are deprecated since 0.16.11 + // instead, use the wchar -> utf8 conversion functions + // and pass in utf8 strings + TORRENT_DEPRECATED + void rename_file(int index, std::wstring const& new_filename); +#endif // TORRENT_USE_WSTRING +#endif // TORRENT_NO_DEPRECATE + + // Remaps the file storage to a new file layout. This can be used to, for + // instance, download all data in a torrent to a single file, or to a + // number of fixed size sector aligned files, regardless of the number + // and sizes of the files in the torrent. + // + // The new specified ``file_storage`` must have the exact same size as + // the current one. + void remap_files(file_storage const& f); + + // ``add_tracker()`` adds a tracker to the announce-list. The ``tier`` + // determines the order in which the trackers are to be tried. + // + // The ``trackers()`` function will return a sorted vector of + // ``announce_entry``. Each announce entry contains a string, which is + // the tracker url, and a tier index. The tier index is the high-level + // priority. No matter which trackers that works or not, the ones with + // lower tier will always be tried before the one with higher tier + // number. For more information, see announce_entry_. + void add_tracker(std::string const& url, int tier = 0); + std::vector const& trackers() const { return m_urls; } + + // These two functions are related to BEP38_ (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. + // + // .. _BEP38: http://www.bittorrent.org/beps/bep_0038.html + std::vector similar_torrents() const; + std::vector collections() const; + +#ifndef TORRENT_NO_DEPRECATE + // deprecated in 0.16. Use web_seeds() instead + TORRENT_DEPRECATED + std::vector url_seeds() const; + TORRENT_DEPRECATED + std::vector http_seeds() const; + + // deprecated in 1.1 + TORRENT_DEPRECATED + bool parse_info_section(lazy_entry const& e, error_code& ec + , int flags); +#endif // TORRENT_NO_DEPRECATE + + // ``web_seeds()`` returns all url seeds and http seeds in the torrent. + // Each entry is a ``web_seed_entry`` and may refer to either a url seed + // or http seed. + // + // ``add_url_seed()`` and ``add_http_seed()`` adds one url to the list of + // url/http seeds. Currently, the only transport protocol supported for + // the url is http. + // + // ``set_web_seeds()`` replaces all web seeds with the ones specified in + // the ``seeds`` vector. + // + // The ``extern_auth`` argument can be used for other authorization + // schemes than basic HTTP authorization. If set, it will override any + // username and password found in the URL itself. The string will be sent + // as the HTTP authorization header's value (without specifying "Basic"). + // + // The ``extra_headers`` argument defaults to an empty list, but can be + // used to insert custom HTTP headers in the requests to a specific web + // seed. + // + // See http-seeding_ for more information. + void add_url_seed(std::string const& url + , std::string const& extern_auth = std::string() + , web_seed_entry::headers_t const& extra_headers = web_seed_entry::headers_t()); + void add_http_seed(std::string const& url + , std::string const& extern_auth = std::string() + , web_seed_entry::headers_t const& extra_headers = web_seed_entry::headers_t()); + std::vector const& web_seeds() const { return m_web_seeds; } + void set_web_seeds(std::vector seeds); + + // ``total_size()``, ``piece_length()`` and ``num_pieces()`` returns the + // total number of bytes the torrent-file represents (all the files in + // it), the number of byte for each piece and the total number of pieces, + // respectively. The difference between ``piece_size()`` and + // ``piece_length()`` is that ``piece_size()`` takes the piece index as + // argument and gives you the exact size of that piece. It will always be + // the same as ``piece_length()`` except in the case of the last piece, + // which may be smaller. + boost::int64_t total_size() const { return m_files.total_size(); } + int piece_length() const { return m_files.piece_length(); } + int num_pieces() const { return m_files.num_pieces(); } + + // returns the info-hash of the torrent + const sha1_hash& info_hash() const { return m_info_hash; } + +#ifndef TORRENT_NO_DEPRECATE + // deprecated in 1.0. Use the variants that take an index instead + // internal_file_entry is no longer exposed in the API + typedef file_storage::iterator file_iterator; + typedef file_storage::reverse_iterator reverse_file_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(boost::int64_t offset) const + { return m_files.file_at_offset_deprecated(offset); } + + TORRENT_DEPRECATED + file_entry file_at(int index) const { return m_files.at_deprecated(index); } +#endif // TORRENT_NO_DEPRECATE + + // If you need index-access to files you can use the ``num_files()`` and + // ``file_path()`` et.al. 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(int piece, boost::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(int file, boost::int64_t offset, int size) const + { + TORRENT_ASSERT(is_loaded()); + return m_files.map_file(file, offset, size); + } + + // load and unload this torrent info + void load(char const* buffer, int size, error_code& ec); + void unload(); + +#ifndef TORRENT_NO_DEPRECATE +// ------- start deprecation ------- +// these functions will be removed in a future version + TORRENT_DEPRECATED + torrent_info(entry const& torrent_file); + TORRENT_DEPRECATED + void print(std::ostream& os) const; +// ------- 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 the public certificate in x509 format. + std::string 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., it should not be + // distributed on the trackerless network (the kademlia DHT). + bool priv() const { return m_private; } + + // returns true if this is an i2p torrent. This is determined by whether + // or not it has a tracker whose URL domain name ends with ".i2p". i2p + // torrents disable the DHT and local peer discovery as well as talking + // to peers over anything other than the i2p network. + bool is_i2p() const { return m_i2p; } + + // ``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 null-terminated. + int piece_size(int index) const { return m_files.piece_size(index); } + sha1_hash hash_for_piece(int index) const; + char const* hash_for_piece_ptr(int index) const + { + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < m_files.num_pieces()); + TORRENT_ASSERT(is_loaded()); + if (is_merkle_torrent()) + { + TORRENT_ASSERT(index < int(m_merkle_tree.size() - m_merkle_first_leaf)); + return m_merkle_tree[m_merkle_first_leaf + index].data(); + } + else + { + TORRENT_ASSERT(m_piece_hashes); + TORRENT_ASSERT(m_piece_hashes >= m_info_section.get()); + TORRENT_ASSERT(m_piece_hashes < m_info_section.get() + m_info_section_size); + TORRENT_ASSERT(index < int(m_info_section_size / 20)); + return &m_piece_hashes[index*20]; + } + } + + bool is_loaded() const { return m_piece_hashes || !m_merkle_tree.empty(); } + + // ``merkle_tree()`` returns a reference to the merkle tree for this + // torrent, if any. + // + // ``set_merkle_tree()`` moves the passed in merkle tree into the + // torrent_info object. i.e. ``h`` will not be identical after the call. + // You need to set the merkle tree for a torrent that you've just created + // (as a merkle torrent). The merkle tree is retrieved from the + // ``create_torrent::merkle_tree()`` function, and need to be saved + // separately from the torrent file itself. Once it's added to + // libtorrent, the merkle tree will be persisted in the resume data. + std::vector const& merkle_tree() const { return m_merkle_tree; } + void set_merkle_tree(std::vector& h) + { TORRENT_ASSERT(h.size() == m_merkle_tree.size() ); m_merkle_tree.swap(h); } + + // ``name()`` returns the name of the torrent. + // + // ``comment()`` returns the comment associated with the torrent. If + // there's no comment, it will return an empty 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, the + // optional object will be uninitialized. + // + // Both the name and the comment is UTF-8 encoded strings. + // + // ``creator()`` returns the creator string in the torrent. If there is + // no creator string it will return an empty string. + // + // .. _`posix time`: http://www.opengroup.org/onlinepubs/009695399/functions/time.html + const std::string& name() const { return m_files.name(); } + boost::optional creation_date() const; + const std::string& creator() const + { return m_created_by; } + const std::string& comment() const + { return m_comment; } + + // dht nodes to add to the routing table/bootstrap from + typedef std::vector > nodes_t; + + // If this torrent contains any DHT nodes, they are put in this vector in + // their original form (host name and port number). + nodes_t 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). ``flags`` are + // currently unused. + bool parse_info_section(bdecode_node const& e, error_code& ec, int flags); + + // 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 NULL. + bdecode_node info(char const* key) const; + + // swap the content of this and ``ti```. + void swap(torrent_info& ti); + + // ``metadata()`` returns a the raw info section of the torrent file. The size + // of the metadata is returned by ``metadata_size()``. + int metadata_size() const { return m_info_section_size; } + boost::shared_array metadata() const + { return m_info_section; } + + // internal + bool add_merkle_nodes(std::map const& subtree + , int piece); + std::map build_merkle_list(int piece) const; + + // returns whether or not this is a merkle torrent. + // see BEP30__. + // + // __ http://bittorrent.org/beps/bep_0030.html + bool is_merkle_torrent() const { return !m_merkle_tree.empty(); } + + bool parse_torrent_file(bdecode_node const& libtorrent, error_code& ec, int flags); + + // if we're logging member offsets, we need access to them + private: + + void resolve_duplicate_filenames(); + + // the slow path, in case we detect/suspect a name collision + void resolve_duplicate_filenames_slow(); + +#if TORRENT_USE_INVARIANT_CHECKS + friend class invariant_access; + void check_invariant() const; +#endif + + // not assignable + torrent_info const& operator=(torrent_info const&); + + void copy_on_write(); + + file_storage m_files; + + // if m_files is modified, it is first copied into + // m_orig_files so that the original name and + // filenames are preserved. + copy_ptr m_orig_files; + + // the urls to the trackers + std::vector m_urls; + std::vector m_web_seeds; + nodes_t m_nodes; + + // the info-hashes (20 bytes each) in the "similar" key. The pointers + // point directly into the info_section. When copied, these pointers must + // be corrected to point into the copied-to buffer + std::vector m_similar_torrents; + + // these are similar torrents from outside of the info-dict. We can't + // have non-owning pointers to those, as we only keep the info-dict + // around. + std::vector m_owned_similar_torrents; + + // these or strings of the "collections" key from the torrent file. The + // pointers point directly into the info_section buffer and when copied, + // these pointers must be corrected to point into the new buffer. The + // int is the length of the string. Strings are not NULL-terminated. + std::vector > m_collections; + + // these are the collections from outside of the info-dict. These are + // owning strings, since we only keep the info-section around, these + // cannot be pointers into that buffer. + std::vector m_owned_collections; + + // if this is a merkle torrent, this is the merkle + // tree. It has space for merkle_num_nodes(merkle_num_leafs(num_pieces)) + // hashes + std::vector m_merkle_tree; + + // this is a copy of the info section from the torrent. + // it use maintained in this flat format in order to + // make it available through the metadata extension + boost::shared_array m_info_section; + + // this is a pointer into the m_info_section buffer + // pointing to the first byte of the first sha-1 hash + char const* m_piece_hashes; + + // 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 + time_t m_creation_date; + + // the hash that identifies this torrent + sha1_hash m_info_hash; + + // the number of bytes in m_info_section + boost::uint32_t m_info_section_size; + + // the index to the first leaf. This is where the hash for the + // first piece is stored + boost::uint32_t m_merkle_first_leaf:24; + + // 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 multifile 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., is should not + // be announced on the dht + bool m_private:1; + + // 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) + bool m_i2p:1; + }; + +} + +#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..901e360 --- /dev/null +++ b/include/libtorrent/torrent_peer.hpp @@ -0,0 +1,277 @@ +/* + +Copyright (c) 2012-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_TORRENT_PEER_HPP_INCLUDED +#define TORRENT_TORRENT_PEER_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/socket.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 boost::uint32_t peer_priority( + tcp::endpoint e1, tcp::endpoint e2); + + struct TORRENT_EXTRA_EXPORT torrent_peer + { + torrent_peer(boost::uint16_t port, bool connectable, int src); + + boost::uint64_t total_download() const; + boost::uint64_t total_upload() const; + + boost::uint32_t rank(external_ip const& external, int external_port) const; + + libtorrent::address address() const; + char const* dest() const; + + tcp::endpoint ip() const { return tcp::endpoint(address(), port); } + +#ifndef TORRENT_DISABLE_LOGGING + std::string to_string() const; +#endif + + // this is the accumulated amount of + // uploaded and downloaded data to this + // torrent_peer. It only accounts for what was + // shared during the last connection to + // this torrent_peer. i.e. These are only updated + // when the connection is closed. For the + // total amount of upload and download + // we'll have to add these figures with the + // statistics from the peer_connection. + // since these values don't need to be stored + // with byte-precision, they specify the number + // of kiB. i.e. shift left 10 bits to compare to + // byte counters. + boost::uint32_t prev_amount_upload; + boost::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 boost::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 + boost::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 + boost::uint16_t last_connected; + + // the port this torrent_peer is or was connected on + boost::uint16_t port; + + // the number of times this torrent_peer has been + // part of a piece that failed the hash check + boost::uint8_t hashfails; + + // the number of failed connection attempts + // this torrent_peer has + unsigned failcount:5; // [0, 31] + + // incoming peers (that don't advertise their listen port) + // will not be considered connectable. Peers that + // we have a listen port for will be assumed to be. + bool connectable:1; + + // true if this torrent_peer currently is unchoked + // because of an optimistic unchoke. + // when the optimistic unchoke is moved to + // another torrent_peer, this torrent_peer will be choked + // if this is true + bool optimistically_unchoked:1; + + // this is true if the torrent_peer is a seed + bool seed:1; + + // the number of times we have allowed a fast + // reconnect for this torrent_peer. + unsigned 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. + unsigned source:6; + +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + // 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 + +#if TORRENT_USE_IPV6 + // 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; +#endif +#if TORRENT_USE_I2P + // set if the i2p_destination is in use in the addr union + bool is_i2p_addr:1; +#endif + + // if this is true, the torrent_peer has previously + // participated in a piece that failed the piece + // hash check. This will put the torrent_peer on parole + // and only request entire pieces. If a piece pass + // that was partially requested from this torrent_peer it + // will leave parole mode and continue download + // pieces as normal peers. + bool on_parole:1; + + // is set to true if this torrent_peer has been banned + bool banned:1; + + // we think this torrent_peer supports uTP + bool supports_utp:1; + // we have been connected via uTP at least once + bool confirmed_supports_utp:1; + bool supports_holepunch:1; + // this is set to one for web seeds. Web seeds + // are not stored in the policy m_peers list, + // and are exempt from connect candidate bookkeeping + // so, any torrent_peer with the web_seed bit set, is + // never considered a connect candidate + bool web_seed:1; +#if TORRENT_USE_ASSERTS + bool in_use:1; +#endif + }; + + struct TORRENT_EXTRA_EXPORT ipv4_peer : torrent_peer + { + ipv4_peer(tcp::endpoint const& ip, bool connectable, int src); + ipv4_peer(ipv4_peer const& p); + + address_v4 addr; + private: + ipv4_peer& operator=(ipv4_peer const&); + }; + +#if TORRENT_USE_I2P + struct TORRENT_EXTRA_EXPORT i2p_peer : torrent_peer + { + i2p_peer(char const* destination, bool connectable, int src); + ~i2p_peer(); + + char* destination; + private: + i2p_peer(const i2p_peer&); + i2p_peer& operator=(i2p_peer const&); + }; +#endif + +#if TORRENT_USE_IPV6 + struct TORRENT_EXTRA_EXPORT ipv6_peer : torrent_peer + { + ipv6_peer(tcp::endpoint const& ip, bool connectable, int src); + + const address_v6::bytes_type addr; + private: + // explicitly disallow assignment, to silence msvc warning + ipv6_peer& operator=(ipv6_peer const&); + ipv6_peer(ipv6_peer const&); + }; +#endif + + struct peer_address_compare + { + bool operator()( + torrent_peer const* lhs, libtorrent::address const& rhs) const + { + return lhs->address() < rhs; + } + + bool operator()( + libtorrent::address const& lhs, torrent_peer const* rhs) const + { + return lhs < rhs->address(); + } + +#if TORRENT_USE_I2P + bool operator()( + torrent_peer const* lhs, char const* rhs) const + { + return strcmp(lhs->dest(), rhs) < 0; + } + + bool operator()( + char const* lhs, torrent_peer const* rhs) const + { + return strcmp(lhs, 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 strcmp(lhs->dest(), 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..697e10e --- /dev/null +++ b/include/libtorrent/torrent_peer_allocator.hpp @@ -0,0 +1,103 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEER_ALLOCATOR_HPP_INCLUDED +#define TORRENT_PEER_ALLOCATOR_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/torrent_peer.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent +{ + + struct TORRENT_EXTRA_EXPORT torrent_peer_allocator_interface + { + enum peer_type_t + { + ipv4_peer_type, + ipv6_peer_type, + i2p_peer_type + }; + + virtual torrent_peer* allocate_peer_entry(int type) = 0; + virtual void free_peer_entry(torrent_peer* p) = 0; + protected: + ~torrent_peer_allocator_interface() {} + }; + + struct TORRENT_EXTRA_EXPORT torrent_peer_allocator TORRENT_FINAL + : torrent_peer_allocator_interface + { + torrent_peer_allocator(); + + torrent_peer* allocate_peer_entry(int type); + void free_peer_entry(torrent_peer* p); + + boost::uint64_t total_bytes() const { return m_total_bytes; } + boost::uint64_t total_allocations() const { return m_total_allocations; } + int live_bytes() const { return m_live_bytes; } + int live_allocations() const { return m_live_allocations; } + + private: + + // this is a shared pool where torrent_peer objects + // are allocated. It's a pool since we're likely + // to have tens of thousands of peers, and a pool + // saves significant overhead + + boost::pool<> m_ipv4_peer_pool; +#if TORRENT_USE_IPV6 + boost::pool<> m_ipv6_peer_pool; +#endif +#if TORRENT_USE_I2P + boost::pool<> m_i2p_peer_pool; +#endif + + // the total number of bytes allocated (cumulative) + boost::uint64_t m_total_bytes; + // the total number of allocations (cumulative) + boost::uint64_t m_total_allocations; + // the number of currently live bytes + int m_live_bytes; + // the number of currently live allocations + int m_live_allocations; + }; +} + +#endif + diff --git a/include/libtorrent/torrent_status.hpp b/include/libtorrent/torrent_status.hpp new file mode 100644 index 0000000..a40938f --- /dev/null +++ b/include/libtorrent/torrent_status.hpp @@ -0,0 +1,526 @@ +/* + +Copyright (c) 2015-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#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/aux_/disable_warnings_push.hpp" + +#include + +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent +{ + // holds a snapshot of the status of a torrent, as queried by + // torrent_handle::status(). + struct TORRENT_EXPORT torrent_status + { + // hidden + torrent_status(); + ~torrent_status(); +#if __cplusplus >= 201103L + torrent_status(torrent_status const&) = default; + torrent_status& operator=(torrent_status const&) = default; +#endif + + // 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 + { +#ifndef TORRENT_NO_DEPRECATE + // 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, +#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 assumes the metadata_transfer extension is in use. + downloading_metadata, + + // The torrent is being downloaded. This is the state + // most torrents will be in most of the time. The progress + // meter will tell how much of the files that has been + // downloaded. + downloading, + + // In this state the torrent has finished downloading but + // still doesn't have the entire torrent. i.e. some pieces + // are filtered and won't get downloaded. + finished, + + // In this state the torrent has finished downloading and + // is a pure seeder. + seeding, + + // If the torrent was started in full allocation mode, this + // indicates that the (disk) storage for the torrent is + // allocated. + allocating, + + // The torrent is currently checking the fastresume 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 + }; + + // 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. +#ifndef TORRENT_NO_DEPRECATE + std::string error; +#else + // internal + std::string _dummy_string_; +#endif + error_code errc; + int error_file; + + // special values for error_file to describe which file or component + // encountered the error (``errc``). + enum error_file_t { + // the error did not occur on a file + error_file_none = -1, + + // the error occurred on m_url + error_file_url = -2, + + // the error occurred setting up the SSL context + error_file_ssl_ctx = -3, + + // the error occurred while loading the .torrent file via the user + // supplied load function + error_file_metadata = -4 + }; + + // 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``. + boost::weak_ptr torrent_file; + + // the time until the torrent will announce itself to the tracker. + time_duration next_announce; + +#ifndef TORRENT_NO_DEPRECATE + // the time the tracker want us to wait until we announce ourself + // again the next time. + time_duration announce_interval; +#else + // hidden + time_duration deprecated_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``. + boost::int64_t total_download; + boost::int64_t total_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. + boost::int64_t total_payload_download; + boost::int64_t total_payload_upload; + + // 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. + boost::int64_t total_failed_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. + boost::int64_t total_redundant_bytes; + + // 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. + 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. + 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``). + boost::int64_t total_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). + boost::int64_t total_wanted_done; + + // 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 + boost::int64_t total_wanted; + + // are accumulated upload and download payload byte counters. They are + // saved in and restored from resume data to keep totals across sessions. + boost::int64_t all_time_upload; + boost::int64_t all_time_download; + + // the posix-time when this torrent was added. i.e. what ``time(NULL)`` + // returned at the time. + time_t added_time; + + // the posix-time when this torrent was finished. If the torrent is not + // yet finished, this is 0. + time_t completed_time; + + // the time when we, or one of our peers, last saw a complete copy of + // this torrent. + time_t last_seen_complete; + + // The allocation mode for the torrent. See storage_mode_t for the + // options. For more information, see storage-allocation_. + storage_mode_t storage_mode; + + // 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; + + // 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; + + // the position this torrent has in the download + // queue. If the torrent is a seed or finished, this is -1. + int 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; + int upload_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. + int download_payload_rate; + int upload_payload_rate; + + // the number of peers that are seeding that this client is + // currently connected to. + int num_seeds; + + // 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; + + // 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; + int num_incomplete; + + // 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; + int list_peers; + + // 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; + + // 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; + + // 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; + + // 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 + // ``distrbuted_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; + + // 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; + + // 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 + // larger if the pieces are larger. + int block_size; + + // the number of unchoked peers in this torrent. + int num_uploads; + + // 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; + + // the set limit of upload slots (unchoked peers) for this torrent. + int uploads_limit; + + // the set limit of number of connections for this torrent. + int connections_limit; + + // 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; + int down_bandwidth_queue; + + // the number of seconds since any peer last uploaded from this torrent + // and the last time a downloaded piece passed the hash check, + // respectively. Note, when starting up a torrent that needs its files + // checked, piece may pass and that will be considered downloading for the + // purpose of this counter. -1 means there either hasn't been any + // uploading/downloading, or it was too long ago for libtorrent to + // remember (currently forgetting happens after about 18 hours) + int time_since_upload; + int time_since_download; + + // These keep track of the number of seconds this torrent has been active + // (not paused) and the number of seconds it has been active while being + // finished and active while being a seed. ``seeding_time`` should be <= + // ``finished_time`` which should be <= ``active_time``. They are all + // saved in and restored from resume data, to keep totals across + // sessions. + int active_time; + int finished_time; + int seeding_time; + + // 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; + + // the number of seconds since this torrent acquired scrape data. + // If it has never done that, this value is -1. + int last_scrape; + + // the priority of this torrent + int priority; + + // the main state the torrent is in. See torrent_status::state_t. + state_t state; + + // true if this torrent has unsaved changes + // to its download state and statistics since the last resume data + // was saved. + bool need_save_resume; + + // true if the session global IP filter applies + // to this torrent. This defaults to true. + bool ip_filter_applies; + + // true if the torrent is blocked from downloading. This typically + // happens when a disk write operation fails. If the torrent is + // auto-managed, it will periodically be taken out of this state, in the + // hope that the disk condition (be it disk full or permission errors) + // has been resolved. If the torrent is not auto-managed, you have to + // explicitly take it out of the upload mode by calling set_upload_mode() + // on the torrent_handle. + bool upload_mode; + + // true if the torrent is currently in share-mode, i.e. not downloading + // the torrent, but just helping the swarm out. + bool share_mode; + + // true if the torrent is in super seeding mode + bool super_seeding; + + // set to true if the torrent is paused and false otherwise. It's only + // true if the torrent itself is paused. If the torrent is not running + // because the session is paused, this is still false. To know if a + // torrent is active or not, you need to inspect both + // ``torrent_status::paused`` and ``session::is_paused()``. + bool paused; + + // set to true if the torrent is auto managed, i.e. libtorrent is + // responsible for determining whether it should be started or queued. + // For more info see queuing_ + bool auto_managed; + + // true when the torrent is in sequential download mode. In this mode + // pieces are downloaded in order rather than rarest first. + bool sequential_download; + + // true if all pieces have been downloaded. + bool is_seeding; + + // 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; + + // 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; + + // true if there has ever been an incoming connection attempt to this + // torrent. + bool has_incoming; + + // true if the torrent is in seed_mode. If the torrent was started in + // seed mode, it will leave seed mode once all pieces have been checked + // or as soon as one piece fails the hash check. + bool seed_mode; + + // 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; + + // true if this torrent is loaded into RAM. A torrent can be started + // and still not loaded into RAM, in case it has not had any peers interested in it + // yet. Torrents are loaded on demand. + bool is_loaded; + + // 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; + bool announcing_to_lsd; + bool announcing_to_dht; + + // this reflects whether the ``stop_when_ready`` flag is currently enabled + // on this torrent. For more information, see + // torrent_handle::stop_when_ready(). + bool stop_when_ready; + + // the info-hash for this torrent + sha1_hash info_hash; + }; + +} + +#endif + diff --git a/include/libtorrent/tracker_manager.hpp b/include/libtorrent/tracker_manager.hpp new file mode 100644 index 0000000..49cd84e --- /dev/null +++ b/include/libtorrent/tracker_manager.hpp @@ -0,0 +1,416 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_TRACKER_MANAGER_HPP_INCLUDED +#define TORRENT_TRACKER_MANAGER_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#ifdef TORRENT_USE_OPENSSL +#include +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/socket.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/udp_socket.hpp" // for udp_socket_observer +#include "libtorrent/io_service.hpp" + +namespace libtorrent +{ + struct request_callback; + class tracker_manager; + struct timeout_handler; + struct tracker_connection; + class udp_tracker_connection; + class http_tracker_connection; + class udp_socket; + struct resolver_interface; + struct counters; + struct ip_filter; +#if TORRENT_USE_I2P + class i2p_connection; +#endif + namespace aux { struct session_logger; struct session_settings; } + + // returns -1 if gzip header is invalid or the header size in bytes + TORRENT_EXTRA_EXPORT int gzip_header(const char* buf, int size); + + struct TORRENT_EXTRA_EXPORT tracker_request + { + tracker_request() + : downloaded(-1) + , uploaded(-1) + , left(-1) + , corrupt(0) + , redundant(0) + , listen_port(0) + , event(none) + , kind(announce_request) + , key(0) + , num_want(0) + , send_stats(true) + , triggered_manually(false) +#ifdef TORRENT_USE_OPENSSL + , ssl_ctx(0) +#endif +#if TORRENT_USE_I2P + , i2pconn(0) +#endif + {} + + enum event_t + { + none, + completed, + started, + stopped, + paused + }; + + enum kind_t + { + // do not compare against announce_request ! check if not scrape instead + announce_request = 0, + scrape_request = 1, + // affects interpretation of peers string in HTTP response + // see parse_tracker_response() + i2p = 2 + }; + + std::string url; + std::string trackerid; +#ifndef TORRENT_NO_DEPRECATE + std::string auth; +#endif + + boost::shared_ptr filter; + + boost::int64_t downloaded; + boost::int64_t uploaded; + boost::int64_t left; + boost::int64_t corrupt; + boost::int64_t redundant; + boost::uint16_t listen_port; + + // values from event_t + boost::uint8_t event; + + // values from kind_t + boost::uint8_t kind; + + boost::uint32_t key; + int num_want; +#if TORRENT_USE_IPV6 + address_v6 ipv6; +#endif + sha1_hash info_hash; + peer_id pid; + address bind_ip; + + bool send_stats; + + // this is set to true if this request was triggered by a "manual" call to + // scrape_tracker() or force_reannounce() + bool triggered_manually; + +#ifdef TORRENT_USE_OPENSSL + boost::asio::ssl::context* ssl_ctx; +#endif +#if TORRENT_USE_I2P + i2p_connection* i2pconn; +#endif + }; + + struct tracker_response + { + tracker_response() + : interval(1800) + , min_interval(120) + , complete(-1) + , incomplete(-1) + , downloaders(-1) + , downloaded(-1) + {} + + // peers from the tracker, in various forms + std::vector peers; + std::vector peers4; +#if TORRENT_USE_IPV6 + std::vector peers6; +#endif + // 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 + int interval; + + // the lowest force-announce interval + int 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 suppored, -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 + , int response_code + , error_code const& ec + , const std::string& msg + , int retry_interval) = 0; + +#ifndef TORRENT_DISABLE_LOGGING + virtual void debug_log(const char* fmt, ...) const TORRENT_FORMAT(2,3) = 0; +#endif + }; + + struct TORRENT_EXTRA_EXPORT timeout_handler + : boost::enable_shared_from_this + , boost::noncopyable + { + timeout_handler(io_service& str); + + void set_timeout(int completion_timeout, int read_timeout); + void restart_read_timeout(); + void cancel(); + bool cancelled() const { return m_abort; } + + virtual void on_timeout(error_code const& ec) = 0; + virtual ~timeout_handler() {} + + io_service& get_io_service() { return m_timeout.get_io_service(); } + + private: + + void timeout_callback(error_code const&); + + int m_completion_timeout; + + typedef mutex mutex_t; + mutable mutex_t m_mutex; + + // 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; + + bool m_abort; + }; + + // TODO: 2 this class probably doesn't need to have virtual functions. + struct TORRENT_EXTRA_EXPORT tracker_connection + : timeout_handler + { + tracker_connection(tracker_manager& man + , tracker_request const& req + , io_service& ios + , boost::weak_ptr r); + + void update_transaction_id(boost::shared_ptr c + , boost::uint64_t tid); + + boost::shared_ptr requester() const; + virtual ~tracker_connection() {} + + tracker_request const& tracker_req() const { return m_req; } + + void fail(error_code const& ec, int code = -1, char const* msg = "" + , int interval = 0, int min_interval = 0); + virtual void start() = 0; + virtual void close(); + address const& bind_interface() const { return m_req.bind_ip; } + void sent_bytes(int bytes); + void received_bytes(int bytes); + virtual bool on_receive(error_code const&, udp::endpoint const& + , char const* /* buf */, int /* size */) { return false; } + virtual bool on_receive_hostname(error_code const& + , char const* /* hostname */ + , char const* /* buf */, int /* size */) { return false; } + + boost::shared_ptr shared_from_this() + { + return boost::static_pointer_cast( + timeout_handler::shared_from_this()); + } + + private: + + const tracker_request m_req; + + protected: + + void fail_impl(error_code const& ec, int code = -1, std::string msg = std::string() + , int interval = 0, int min_interval = 0); + + boost::weak_ptr m_requester; + + tracker_manager& m_man; + }; + + class TORRENT_EXTRA_EXPORT tracker_manager TORRENT_FINAL + : public udp_socket_observer + , boost::noncopyable + { + public: + + tracker_manager(udp_socket& sock + , counters& stats_counters + , resolver_interface& resolver + , aux::session_settings const& sett +#if !defined TORRENT_DISABLE_LOGGING || TORRENT_USE_ASSERTS + , aux::session_logger& ses +#endif + ); + virtual ~tracker_manager(); + + void queue_request( + io_service& ios + , tracker_request r + , boost::weak_ptr c + = boost::weak_ptr()); + void abort_all_requests(bool all = false); + + void remove_request(tracker_connection const*); + bool empty() const; + int num_requests() const; + + void sent_bytes(int bytes); + void received_bytes(int bytes); + + virtual bool incoming_packet(error_code const& e, udp::endpoint const& ep + , char const* buf, int size) TORRENT_OVERRIDE; + + // this is only used for SOCKS packets, since + // they may be addressed to hostname + virtual bool incoming_packet(error_code const& e, char const* hostname + , char const* buf, int size) TORRENT_OVERRIDE; + + void update_transaction_id( + boost::shared_ptr c + , boost::uint64_t tid); + + aux::session_settings const& settings() const { return m_settings; } + udp_socket& get_udp_socket() { return m_udp_socket; } + resolver_interface& host_resolver() { return m_host_resolver; } + + private: + + typedef mutex mutex_t; + mutable mutex_t m_mutex; + + // maps transactionid to the udp_tracker_connection + // TODO: this should be unique_ptr in the future + typedef boost::unordered_map > udp_conns_t; + udp_conns_t m_udp_conns; + + typedef std::vector > http_conns_t; + http_conns_t m_http_conns; + + class udp_socket& m_udp_socket; + resolver_interface& m_host_resolver; + aux::session_settings const& m_settings; + counters& m_stats_counters; +#if !defined TORRENT_DISABLE_LOGGING || TORRENT_USE_ASSERTS + aux::session_logger& m_ses; +#endif + + bool m_abort; + }; +} + +#endif // TORRENT_TRACKER_MANAGER_HPP_INCLUDED + diff --git a/include/libtorrent/udp_socket.hpp b/include/libtorrent/udp_socket.hpp new file mode 100644 index 0000000..47a3587 --- /dev/null +++ b/include/libtorrent/udp_socket.hpp @@ -0,0 +1,332 @@ +/* + +Copyright (c) 2007-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_UDP_SOCKET_HPP_INCLUDED +#define TORRENT_UDP_SOCKET_HPP_INCLUDED + +#include "libtorrent/socket.hpp" +#include "libtorrent/io_service.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/buffer.hpp" +#include "libtorrent/thread.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/aux_/allocating_handler.hpp" + +#include + +namespace libtorrent +{ + struct TORRENT_EXTRA_EXPORT udp_socket_observer + { + // return true if the packet was handled (it won't be + // propagated to the next observer) + virtual bool incoming_packet(error_code const& ec + , udp::endpoint const&, char const* buf, int size) = 0; + virtual bool incoming_packet(error_code const& /* ec */ + , char const* /* hostname */, char const* /* buf */, int /* size */) { return false; } + + // called when the socket becomes writeable, after having + // failed with EWOULDBLOCK + virtual void writable() {} + + // called every time the socket is drained of packets + virtual void socket_drained() {} + protected: + ~udp_socket_observer() {} + }; + + class TORRENT_EXTRA_EXPORT udp_socket : single_threaded + { + public: + udp_socket(io_service& ios); + ~udp_socket(); + + enum flags_t { + dont_drop = 1 + , peer_connection = 2 + , tracker_connection = 4 + , dont_queue = 8 + }; + + bool is_open() const { return m_abort == false; } + io_service& get_io_service() { return m_ipv4_sock.get_io_service(); } + + void subscribe(udp_socket_observer* o); + void unsubscribe(udp_socket_observer* o); + + // this is only valid when using a socks5 proxy + void send_hostname(char const* hostname, int port, char const* p + , int len, error_code& ec, int flags = 0); + + void send(udp::endpoint const& ep, char const* p, int len + , error_code& ec, int flags = 0); + 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::proxy_settings const& get_proxy_settings() { return m_proxy_settings; } + void set_force_proxy(bool f) { m_force_proxy = f; } + + bool is_closed() const { return m_abort; } + tcp::endpoint local_endpoint(error_code& ec) const + { + udp::endpoint ep = m_ipv4_sock.local_endpoint(ec); + return tcp::endpoint(ep.address(), ep.port()); + } + + void set_buf_size(int s); + + typedef udp::socket::receive_buffer_size receive_buffer_size; + typedef udp::socket::send_buffer_size send_buffer_size; + + template + void get_option(SocketOption const& opt, error_code& ec) + { +#if TORRENT_USE_IPV6 + if (opt.level(udp::v6()) == IPPROTO_IPV6) + m_ipv6_sock.get_option(opt, ec); + else +#endif + m_ipv4_sock.get_option(opt, ec); + } + + template + void set_option(SocketOption const& opt, error_code& ec) + { + if (opt.level(udp::v4()) != IPPROTO_IPV6) + m_ipv4_sock.set_option(opt, ec); +#if TORRENT_USE_IPV6 + if (opt.level(udp::v6()) != IPPROTO_IP) + m_ipv6_sock.set_option(opt, ec); +#endif + } + + template + void get_option(SocketOption& opt, error_code& ec) + { +#if TORRENT_USE_IPV6 + if (opt.level(udp::v6()) == IPPROTO_IPV6) + m_ipv6_sock.get_option(opt, ec); + else +#endif + m_ipv4_sock.get_option(opt, ec); + } + + udp::endpoint proxy_addr() const { return m_proxy_addr; } + + private: + + struct queued_packet + { + queued_packet() + : hostname(NULL) + , flags(0) + {} + + udp::endpoint ep; + char* hostname; + buffer buf; + int flags; + }; + + // number of outstanding UDP socket operations + // using the UDP socket buffer + int num_outstanding() const + { + return m_v4_outstanding +#if TORRENT_USE_IPV6 + + m_v6_outstanding +#endif + ; + } + + // non-copyable + udp_socket(udp_socket const&); + udp_socket& operator=(udp_socket const&); + + void close_impl(); + + // observers on this udp socket + std::vector m_observers; + std::vector m_added_observers; + + template + aux::allocating_handler + make_read_handler4(Handler const& handler) + { + return aux::allocating_handler( + handler, m_v4_read_handler_storage + ); + } + +#if TORRENT_USE_IPV6 + template + aux::allocating_handler + make_read_handler6(Handler const& handler) + { + return aux::allocating_handler( + handler, m_v6_read_handler_storage + ); + } +#endif + + // this is true while iterating over the observers + // vector, invoking observer hooks. We may not + // add new observers during this time, since it + // may invalidate the iterator. If this is true, + // instead add new observers to m_added_observers + // and they will be added later + bool m_observers_locked; + + void call_handler(error_code const& ec, udp::endpoint const& ep + , char const* buf, int size); + void call_handler(error_code const& ec, const char* host + , char const* buf, int size); + void call_drained_handler(); + void call_writable_handler(); + + void on_writable(error_code const& ec, udp::socket* s); + + void setup_read(udp::socket* s); + void on_read(error_code const& ec, udp::socket* s); + void on_read_impl(udp::endpoint const& ep + , error_code const& e, std::size_t bytes_transferred); + void on_name_lookup(error_code const& e, tcp::resolver::iterator i); + void on_connect_timeout(error_code const& ec); + void on_connected(error_code const& ec); + 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 drain_queue(); + + void wrap(udp::endpoint const& ep, char const* p, int len, error_code& ec); + void wrap(char const* hostname, int port, char const* p, int len, error_code& ec); + void unwrap(error_code const& e, char const* buf, int size); + + udp::socket m_ipv4_sock; + aux::handler_storage m_v4_read_handler_storage; + deadline_timer m_timer; + int m_buf_size; + + // if the buffer size is attempted + // to be changed while the buffer is + // being used, this member is set to + // the desired size, and it's resized + // later + int m_new_buf_size; + char* m_buf; + +#if TORRENT_USE_IPV6 + udp::socket m_ipv6_sock; + aux::handler_storage m_v6_read_handler_storage; +#endif + + boost::uint16_t m_bind_port; + boost::uint8_t m_v4_outstanding; + boost::uint8_t m_restart_v4; +#if TORRENT_USE_IPV6 + boost::uint8_t m_v6_outstanding; + boost::uint8_t m_restart_v6; +#endif + + tcp::socket m_socks5_sock; + aux::proxy_settings m_proxy_settings; + tcp::resolver m_resolver; + char m_tmp_buf[270]; + bool m_queue_packets; + bool m_tunnel_packets; + bool m_force_proxy; + bool m_abort; + + // 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. + udp::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; + + // while we're connecting to the proxy + // we have to queue the packets, we'll flush + // them once we're connected + std::deque m_queue; + + // counts the number of outstanding async + // operations hanging on this socket + int m_outstanding_ops; + +#if TORRENT_USE_IPV6 + bool m_v6_write_subscribed:1; +#endif + bool m_v4_write_subscribed:1; + +#if TORRENT_USE_ASSERTS + bool m_started; + int m_magic; + int m_outstanding_when_aborted; + int m_outstanding_connect; + int m_outstanding_timeout; + int m_outstanding_resolve; + int m_outstanding_socks; +#endif + }; + + struct rate_limited_udp_socket : public udp_socket + { + rate_limited_udp_socket(io_service& ios); + void set_rate_limit(int limit) { m_rate_limit = limit; } + bool send(udp::endpoint const& ep, char const* p, int len + , error_code& ec, int flags = 0); + bool has_quota(); + + private: + + int m_rate_limit; + int m_quota; + time_point m_last_tick; + }; +} + +#endif + diff --git a/include/libtorrent/udp_tracker_connection.hpp b/include/libtorrent/udp_tracker_connection.hpp new file mode 100644 index 0000000..fe41b88 --- /dev/null +++ b/include/libtorrent/udp_tracker_connection.hpp @@ -0,0 +1,143 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_UDP_TRACKER_CONNECTION_HPP_INCLUDED +#define TORRENT_UDP_TRACKER_CONNECTION_HPP_INCLUDED + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include + +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/udp_socket.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/peer.hpp" +#include "libtorrent/tracker_manager.hpp" +#include "libtorrent/config.hpp" + +namespace libtorrent +{ + class TORRENT_EXTRA_EXPORT udp_tracker_connection: public tracker_connection + { + friend class tracker_manager; + public: + + udp_tracker_connection( + io_service& ios + , tracker_manager& man + , tracker_request const& req + , boost::weak_ptr c); + + void start(); + void close(); + + boost::uint32_t transaction_id() const { return m_transaction_id; } + + private: + + enum action_t + { + action_connect, + action_announce, + action_scrape, + action_error + }; + + boost::shared_ptr shared_from_this() + { + return boost::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 timeout(error_code const& error); + void start_announce(); + + bool on_receive(error_code const& e, udp::endpoint const& ep + , char const* buf, int size); + bool on_receive_hostname(error_code const& e, char const* hostname + , char const* buf, int size); + bool on_connect_response(char const* buf, int size); + bool on_announce_response(char const* buf, int size); + bool on_scrape_response(char const* buf, int size); + + // wraps tracker_connection::fail + void fail(error_code const& ec, int code = -1 + , char const* msg = "", int interval = 0, int min_interval = 0); + + void send_udp_connect(); + void send_udp_announce(); + void send_udp_scrape(); + + virtual void on_timeout(error_code const& ec); + + udp::endpoint pick_target_endpoint() const; + + std::string m_hostname; + std::vector m_endpoints; + + struct connection_cache_entry + { + boost::int64_t connection_id; + time_point expires; + }; + + static std::map m_connection_cache; + static mutex m_cache_mutex; + + udp::endpoint m_target; + + boost::uint32_t m_transaction_id; + int m_attempts; + + // action_t + boost::uint8_t m_state; + + bool m_abort; + }; + +} + +#endif // TORRENT_UDP_TRACKER_CONNECTION_HPP_INCLUDED + diff --git a/include/libtorrent/uncork_interface.hpp b/include/libtorrent/uncork_interface.hpp new file mode 100644 index 0000000..8772d07 --- /dev/null +++ b/include/libtorrent/uncork_interface.hpp @@ -0,0 +1,59 @@ +/* + +Copyright (c) 2012-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_UNCORK_INTERFACE_HPP +#define TORRENT_UNCORK_INTERFACE_HPP + +#include "libtorrent/export.hpp" + +namespace libtorrent +{ + // the uncork interface is used by the disk_io_thread + // to indicate that it has called all the disk job handlers + // in the current batch. The intention is for the peer + // connections to be able to not issue any sends on their + // sockets until they have recevied all the disk jobs + // that are ready first. This makes the networking more + // efficient since it can send larger buffers down to the + // kernel per system call. + // uncorking refers to releasing the "cork" in the peers + // preventing them to issue sends + struct TORRENT_EXTRA_EXPORT uncork_interface + { + virtual void do_delayed_uncork() = 0; + protected: + ~uncork_interface() {} + }; +} + +#endif + diff --git a/include/libtorrent/union_endpoint.hpp b/include/libtorrent/union_endpoint.hpp new file mode 100644 index 0000000..457c058 --- /dev/null +++ b/include/libtorrent/union_endpoint.hpp @@ -0,0 +1,134 @@ +/* + +Copyright (c) 2010-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_UNION_ENDPOINT_HPP_INCLUDED +#define TORRENT_UNION_ENDPOINT_HPP_INCLUDED + +#include "libtorrent/socket.hpp" +#include "libtorrent/address.hpp" + +namespace libtorrent +{ + + struct union_endpoint + { + union_endpoint(tcp::endpoint const& ep) + { + *this = ep; + } + + union_endpoint(udp::endpoint const& ep) + { + *this = ep; + } + + union_endpoint() + { + *this = tcp::endpoint(); + } + + union_endpoint& operator=(udp::endpoint const& ep) + { +#if TORRENT_USE_IPV6 + v4 = ep.address().is_v4(); + if (v4) + addr.v4 = ep.address().to_v4().to_bytes(); + else + addr.v6 = ep.address().to_v6().to_bytes(); +#else + addr.v4 = ep.address().to_v4().to_bytes(); +#endif + port = ep.port(); + return *this; + } + + operator udp::endpoint() const + { +#if TORRENT_USE_IPV6 + if (v4) return udp::endpoint(address_v4(addr.v4), port); + else return udp::endpoint(address_v6(addr.v6), port); +#else + return udp::endpoint(address_v4(addr.v4), port); +#endif + } + + union_endpoint& operator=(tcp::endpoint const& ep) + { +#if TORRENT_USE_IPV6 + v4 = ep.address().is_v4(); + if (v4) + addr.v4 = ep.address().to_v4().to_bytes(); + else + addr.v6 = ep.address().to_v6().to_bytes(); +#else + addr.v4 = ep.address().to_v4().to_bytes(); +#endif + port = ep.port(); + return *this; + } + + libtorrent::address address() const + { +#if TORRENT_USE_IPV6 + if (v4) return address_v4(addr.v4); + else return address_v6(addr.v6); +#else + return address_v4(addr.v4); +#endif + } + + operator tcp::endpoint() const + { +#if TORRENT_USE_IPV6 + if (v4) return tcp::endpoint(address_v4(addr.v4), port); + else return tcp::endpoint(address_v6(addr.v6), port); +#else + return tcp::endpoint(address_v4(addr.v4), port); +#endif + } + + TORRENT_UNION addr_t + { + address_v4::bytes_type v4; +#if TORRENT_USE_IPV6 + address_v6::bytes_type v6; +#endif + } addr; + boost::uint16_t port; +#if TORRENT_USE_IPV6 + bool v4:1; +#endif + }; +} + +#endif + diff --git a/include/libtorrent/upnp.hpp b/include/libtorrent/upnp.hpp new file mode 100644 index 0000000..691d275 --- /dev/null +++ b/include/libtorrent/upnp.hpp @@ -0,0 +1,418 @@ +/* + +Copyright (c) 2007-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_UPNP_HPP +#define TORRENT_UPNP_HPP + +#include "libtorrent/socket.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/broadcast_socket.hpp" +#include "libtorrent/http_connection.hpp" +#include "libtorrent/thread.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/enum_net.hpp" +#include "libtorrent/resolver.hpp" + +#include +#include +#include +#include +#include +#include + +namespace libtorrent +{ + + 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 wildcarded, 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 addres or DNS name + remote_host_must_be_wildcard = 726, + // ExternalPort must be a wildcard and cannot be a + // specific port + external_port_must_be_wildcard = 727 + }; + + // hidden + TORRENT_EXPORT boost::system::error_code make_error_code(error_code_enum e); + } + + // the boost.system error category for UPnP errors + TORRENT_EXPORT boost::system::error_category& get_upnp_category(); + +// int: port-mapping index +// address: external address as queried from router +// int: external port +// int: protocol (UDP, TCP) +// std::string: error message +// an empty string as error means success +typedef boost::function portmap_callback_t; +typedef boost::function log_callback_t; + +struct parse_state +{ + parse_state(): in_service(false) {} + bool in_service; + std::list tag_stack; + std::string control_url; + std::string service_type; + std::string model; + std::string url_base; + bool top_tags(const char* str1, const char* str2) + { + std::list::reverse_iterator i = tag_stack.rbegin(); + if (i == tag_stack.rend()) return false; + if (!string_equal_no_case(i->c_str(), str2)) return false; + ++i; + if (i == tag_stack.rend()) return false; + if (!string_equal_no_case(i->c_str(), str1)) return false; + return true; + } +}; + +TORRENT_EXTRA_EXPORT void find_control_url(int type, char const* string + , int str_len, parse_state& state); + +// TODO: support using the windows API for UPnP operations as well +class TORRENT_EXTRA_EXPORT upnp : public boost::enable_shared_from_this +{ +public: + upnp(io_service& ios + , address const& listen_interface, std::string const& user_agent + , portmap_callback_t const& cb, log_callback_t const& lcb + , bool ignore_nonrouters); + ~upnp(); + + void start(); + + enum protocol_type { none = 0, udp = 1, tcp = 2 }; + + // 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. + int add_mapping(protocol_type p, int external_port, int local_port); + + // 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(int mapping_index); + + bool get_mapping(int mapping_index, int& local_port, int& external_port, int& protocol) const; + + void discover_device(); + void close(); + + // This is only available for UPnP routers. If the model is advertized by + // the router, it can be queried through this function. + std::string router_model() + { + mutex::scoped_lock l(m_mutex); + return m_model; + } + +private: + + boost::shared_ptr self() { return shared_from_this(); } + + void map_timer(error_code const& ec); + void try_map_upnp(mutex::scoped_lock& l, bool timer = false); + void discover_device_impl(mutex::scoped_lock& l); + static address_v4 upnp_multicast_address; + static udp::endpoint upnp_multicast_endpoint; + + // there are routers that's don't support timed + // port maps, without returning error 725. It seems + // safer to always assume that we have to ask for + // permanent leases + enum { default_lease_time = 0 }; + + void resend_request(error_code const& e); + void on_reply(udp::endpoint const& from, char* buffer + , std::size_t bytes_transferred); + + struct rootdevice; + void next(rootdevice& d, int i, mutex::scoped_lock& l); + void update_map(rootdevice& d, int i, mutex::scoped_lock& l); + + + 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 + , int mapping, http_connection& c); + void on_upnp_unmap_response(error_code const& e + , libtorrent::http_parser const& p, rootdevice& d + , int mapping, http_connection& c); + void on_expire(error_code const& e); + + void disable(error_code const& ec, mutex::scoped_lock& l); + void return_error(int mapping, int code, mutex::scoped_lock& l); + void log(char const* msg, mutex::scoped_lock& l); + + void get_ip_address(rootdevice& d); + void delete_port_mapping(rootdevice& d, int i); + void create_port_mapping(http_connection& c, rootdevice& d, int i); + void post(upnp::rootdevice const& d, char const* soap + , char const* soap_action, mutex::scoped_lock& l); + + int num_mappings() const { return int(m_mappings.size()); } + + struct global_mapping_t + { + global_mapping_t() + : protocol(none) + , external_port(0) + , local_port(0) + {} + int protocol; + int external_port; + int local_port; + }; + + struct mapping_t + { + enum action_t { action_none, action_add, action_delete }; + mapping_t() + : action(action_none) + , local_port(0) + , external_port(0) + , protocol(none) + , failcount(0) + {} + + // the time the port mapping will expire + time_point expires; + + int action; + + // the local port for this mapping. If this is set + // to 0, the mapping is not in use + int local_port; + + // the external (on the NAT router) port + // for the mapping. This is the port we + // should announce to others + int external_port; + + // 2 = udp, 1 = tcp + int protocol; + + // the number of times this mapping has failed + int failcount; + }; + + struct rootdevice + { + rootdevice(): service_namespace(0) + , port(0) + , lease_duration(default_lease_time) + , supports_specific_external(true) + , disabled(false) + , non_router(false) + { +#if TORRENT_USE_ASSERTS + magic = 1337; +#endif + } + +#if TORRENT_USE_ASSERTS + ~rootdevice() + { + TORRENT_ASSERT(magic == 1337); + magic = 0; + } +#if __cplusplus >= 201103L + rootdevice(rootdevice const&) = default; + rootdevice& operator=(rootdevice const&) = default; +#endif +#endif + + // 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 + char const* service_namespace; + + std::vector mapping; + + // this is the hostname, port and path + // component of the url or the control_url + // if it has been found + std::string hostname; + int port; + std::string path; + address external_ip; + + int lease_duration; + // true if the device supports specifying a + // specific external port, false if it doesn't + bool supports_specific_external; + + bool disabled; + + // this is true if the IP of this device is not + // one of our default routes. i.e. it may be someone + // else's router, we just happen to have multicast + // enabled across networks + // this is only relevant if ignore_non_routers is set. + bool non_router; + + mutable boost::shared_ptr upnp_connection; + +#if TORRENT_USE_ASSERTS + int magic; +#endif + void close() const + { + TORRENT_ASSERT(magic == 1337); + if (!upnp_connection) return; + upnp_connection->close(); + upnp_connection.reset(); + } + + bool operator<(rootdevice const& rhs) const + { return url < rhs.url; } + }; + + struct upnp_state_t + { + std::vector mappings; + std::set devices; + }; + + std::vector m_mappings; + + std::string const& m_user_agent; + + // the set of devices we've found + std::set m_devices; + + portmap_callback_t m_callback; + log_callback_t m_log_callback; + + // current retry count + int m_retry_count; + + io_service& m_io_service; + + resolver m_resolver; + + // the udp socket used to send and receive + // multicast messages on the network + broadcast_socket m_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 reponses. 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; + bool m_closing; + bool m_ignore_non_routers; + + mutex m_mutex; + + std::string m_model; + + // cache of interfaces + mutable std::vector m_interfaces; + mutable time_point m_last_if_update; +}; + +} + +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/utf8.hpp b/include/libtorrent/utf8.hpp new file mode 100644 index 0000000..50856f3 --- /dev/null +++ b/include/libtorrent/utf8.hpp @@ -0,0 +1,78 @@ +/* + +Copyright (c) 2006-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_UTF8_HPP_INCLUDED +#define TORRENT_UTF8_HPP_INCLUDED + +#include "libtorrent/export.hpp" + +// on windows we need these functions for +// convert_to_native and convert_from_native +#if TORRENT_USE_WSTRING || defined TORRENT_WINDOWS + +#include +#include + +namespace libtorrent +{ + + // internal + // results from UTF-8 conversion functions utf8_wchar and + // wchar_utf8 + enum utf8_conv_result_t + { + // conversion successful + conversion_ok, + + // partial character in source, but hit end + source_exhausted, + + // insuff. room in target for conversion + target_exhausted, + + // source sequence is illegal/malformed + source_illegal + }; + + // ``utf8_wchar`` converts a UTF-8 string (``utf8``) to a wide character + // string (``wide``). ``wchar_utf8`` converts a wide character string + // (``wide``) to a UTF-8 string (``utf8``). The return value is one of + // the enumeration values from utf8_conv_result_t. + TORRENT_EXTRA_EXPORT utf8_conv_result_t utf8_wchar( + const std::string &utf8, std::wstring &wide); + TORRENT_EXTRA_EXPORT utf8_conv_result_t wchar_utf8( + const std::wstring &wide, std::string &utf8); +} +#endif // !BOOST_NO_STD_WSTRING + +#endif + diff --git a/include/libtorrent/utp_socket_manager.hpp b/include/libtorrent/utp_socket_manager.hpp new file mode 100644 index 0000000..ea72980 --- /dev/null +++ b/include/libtorrent/utp_socket_manager.hpp @@ -0,0 +1,178 @@ +/* + +Copyright (c) 2009-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_UTP_SOCKET_MANAGER_HPP_INCLUDED +#define TORRENT_UTP_SOCKET_MANAGER_HPP_INCLUDED + +#include + +#include "libtorrent/socket_type.hpp" +#include "libtorrent/session_status.hpp" +#include "libtorrent/enum_net.hpp" +#include "libtorrent/aux_/session_settings.hpp" + +namespace libtorrent +{ + class udp_socket; + class utp_stream; + struct utp_socket_impl; + struct counters; + + typedef boost::function const&)> incoming_utp_callback_t; + + struct utp_socket_manager TORRENT_FINAL : udp_socket_observer + { + utp_socket_manager(aux::session_settings const& sett, udp_socket& s + , counters& cnt, void* ssl_context, incoming_utp_callback_t cb); + ~utp_socket_manager(); + + // return false if this is not a uTP packet + virtual bool incoming_packet(error_code const& ec, udp::endpoint const& ep + , char const* p, int size) TORRENT_OVERRIDE; + virtual bool incoming_packet(error_code const&, char const*, char const*, int) TORRENT_OVERRIDE + { return false; } + virtual void writable() TORRENT_OVERRIDE; + + virtual void socket_drained() TORRENT_OVERRIDE; + + void tick(time_point now); + + tcp::endpoint local_endpoint(address const& remote, error_code& ec) const; + int local_port(error_code& ec) const; + + // flags for send_packet + enum { dont_fragment = 1 }; + void send_packet(udp::endpoint const& ep, char const* p, int len + , error_code& ec, int flags = 0); + void subscribe_writable(utp_socket_impl* s); + + // internal, used by utp_stream + void remove_socket(boost::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); } + + void mtu_for_dest(address const& addr, int& link_mtu, int& utp_mtu); + void set_sock_buf(int size); + int num_sockets() const { return m_utp_sockets.size(); } + + void defer_ack(utp_socket_impl* s); + void subscribe_drained(utp_socket_impl* s); + + void restrict_mtu(int mtu) + { + m_restrict_mtu[m_mtu_idx] = mtu; + m_mtu_idx = (m_mtu_idx + 1) % 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); + + private: + // explicitly disallow assignment, to silence msvc warning + utp_socket_manager& operator=(utp_socket_manager const&); + + udp_socket& m_sock; + incoming_utp_callback_t m_cb; + + // replace with a hash-map + typedef std::multimap socket_map_t; + socket_map_t m_utp_sockets; + + // this is a list of sockets that needs to send an ack. + // once the UDP socket is drained, all of these will + // have a chance to do that. This is to avoid sending + // an ack for every single packet + std::vector m_deferred_acks; + + // 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. + std::vector m_drained_event; + + // list of sockets that received EWOULDBLOCK from the + // underlying socket. They are notified when the socket + // becomes writable again + std::vector m_stalled_sockets; + + // the last socket we received a packet on + utp_socket_impl* m_last_socket; + + int m_new_connection; + + aux::session_settings const& m_sett; + + // this is a copy of the routing table, used + // to initialize MTU sizes of uTP sockets + mutable std::vector m_routes; + + // the timestamp for the last time we updated + // the routing table + mutable time_point m_last_route_update; + + // cache of interfaces + mutable std::vector m_interfaces; + mutable time_point m_last_if_update; + + // the buffer size of the socket. This is used + // to now lower the buffer size + int m_sock_buf_size; + + // stats counters + counters& m_counters; + + boost::array m_restrict_mtu; + int m_mtu_idx; + + // this is passed on to the instantiate connection + // if this is non-null it will create SSL connections over uTP + void* m_ssl_context; + }; +} + +#endif + diff --git a/include/libtorrent/utp_stream.hpp b/include/libtorrent/utp_stream.hpp new file mode 100644 index 0000000..ede524e --- /dev/null +++ b/include/libtorrent/utp_stream.hpp @@ -0,0 +1,510 @@ +/* + +Copyright (c) 2009-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_UTP_STREAM_HPP_INCLUDED +#define TORRENT_UTP_STREAM_HPP_INCLUDED + +#include "libtorrent/proxy_base.hpp" +#include "libtorrent/udp_socket.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/packet_buffer.hpp" +#include "libtorrent/error_code.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include + +#ifndef BOOST_NO_EXCEPTIONS +#include +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#define CCONTROL_TARGET 100 + +namespace libtorrent +{ +#ifndef TORRENT_UTP_LOG_ENABLE + #define TORRENT_UTP_LOG 0 + #define TORRENT_VERBOSE_UTP_LOG 0 +#else + #define TORRENT_UTP_LOG 1 + #define TORRENT_VERBOSE_UTP_LOG 1 +#endif + +#if TORRENT_UTP_LOG + TORRENT_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(boost::uint32_t lhs + , boost::uint32_t rhs, boost::uint32_t mask); + + struct utp_socket_manager; + + // internal: some MTU and protocol header sizes constants + enum + { + TORRENT_IPV4_HEADER = 20, + TORRENT_IPV6_HEADER = 40, + TORRENT_UDP_HEADER = 8, + TORRENT_SOCKS5_HEADER = 6, // plus the size of the destination address + + TORRENT_ETHERNET_MTU = 1500, + TORRENT_TEREDO_MTU = 1280, + TORRENT_INET_MIN_MTU = 576, + TORRENT_INET_MAX_MTU = 0xffff + }; + + // internal: the point of the bif_endian_int is two-fold + // one purpose is to not have any alignment requirements + // so that any buffer received from the network can be cast + // to it and read as an integer of various sizes without + // triggering a bus error. The other purpose is to convert + // from network byte order to host byte order when read and + // written, to offer a convenient interface to both interpreting + // and writing network packets + template struct big_endian_int + { + big_endian_int& operator=(T v) + { + char* p = m_storage; + detail::write_impl(v, p); + return *this; + } + operator T() const + { + const char* p = m_storage; + return detail::read_impl(p, detail::type()); + } + private: + char m_storage[sizeof(T)]; + }; + + typedef big_endian_int be_uint64; + typedef big_endian_int be_uint32; + typedef big_endian_int be_uint16; + typedef big_endian_int be_int64; + typedef big_endian_int be_int32; + typedef big_endian_int be_int16; + +/* + uTP header from BEP 29 + + 0 4 8 16 24 32 + +-------+-------+---------------+---------------+---------------+ + | type | ver | extension | connection_id | + +-------+-------+---------------+---------------+---------------+ + | timestamp_microseconds | + +---------------+---------------+---------------+---------------+ + | timestamp_difference_microseconds | + +---------------+---------------+---------------+---------------+ + | wnd_size | + +---------------+---------------+---------------+---------------+ + | seq_nr | ack_nr | + +---------------+---------------+---------------+---------------+ + +*/ + +// internal: the different kinds of uTP packets +enum utp_socket_state_t +{ ST_DATA, ST_FIN, ST_STATE, ST_RESET, ST_SYN, NUM_TYPES }; + +// internal: extension headers. 2 is skipped because there is a deprecated +// extension with that number in the wild +enum utp_extensions_t +{ utp_no_extension = 0, utp_sack = 1, utp_close_reason = 3 }; + +struct utp_header +{ + unsigned char type_ver; + unsigned char 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; + +utp_socket_impl* construct_utp_impl(boost::uint16_t recv_id + , boost::uint16_t send_id, void* userdata + , utp_socket_manager* sm); +void detach_utp_impl(utp_socket_impl* s); +void delete_utp_impl(utp_socket_impl* s); +bool should_delete(utp_socket_impl* s); +void tick_utp_impl(utp_socket_impl* s, time_point now); +void utp_init_mtu(utp_socket_impl* s, int link_mtu, int utp_mtu); +bool utp_incoming_packet(utp_socket_impl* s, char const* p + , int size, udp::endpoint const& ep, time_point receive_time); +bool utp_match(utp_socket_impl* s, udp::endpoint const& ep, boost::uint16_t id); +udp::endpoint utp_remote_endpoint(utp_socket_impl* s); +boost::uint16_t utp_receive_id(utp_socket_impl* s); +int utp_socket_state(utp_socket_impl const* s); +void utp_send_ack(utp_socket_impl* s); +void utp_socket_drained(utp_socket_impl* s); +void utp_writable(utp_socket_impl* s); + +// this is the user-level stream interface to utp sockets. +// the reason why it's split up in a utp_stream class and +// an implementation class is because the socket state has +// to be able to out-live the user level socket. For instance +// when sending data on a stream and then closing it, the +// state holding the send buffer has to be kept around until +// it has been flushed, which may be longer than the client +// will keep the utp_stream object around for. +// for more details, see utp_socket_impl, which is analogous +// to the kernel state for a socket. It's defined in utp_stream.cpp +class TORRENT_EXTRA_EXPORT utp_stream +{ +public: + + typedef utp_stream lowest_layer_type; + typedef tcp::socket::endpoint_type endpoint_type; + typedef tcp::socket::protocol_type protocol_type; + + explicit utp_stream(io_service& io_service); + ~utp_stream(); + + lowest_layer_type& lowest_layer() { return *this; } + + // used for incoming connections + void set_impl(utp_socket_impl* s); + utp_socket_impl* get_impl(); + +#ifndef BOOST_NO_EXCEPTIONS + template + void io_control(IO_Control_Command&) {} +#endif + + template + void io_control(IO_Control_Command&, error_code&) {} + +#ifndef BOOST_NO_EXCEPTIONS + void bind(endpoint_type const& /*endpoint*/) {} +#endif + + void bind(endpoint_type const&, error_code&); + +#ifndef BOOST_NO_EXCEPTIONS + template + void set_option(SettableSocketOption const&) {} +#endif + + template + error_code set_option(SettableSocketOption const&, error_code& ec) { return ec; } + +#ifndef BOOST_NO_EXCEPTIONS + template + void get_option(GettableSocketOption&) {} +#endif + + template + error_code get_option(GettableSocketOption&, error_code& ec) + { return ec; } + + error_code cancel(error_code&) + { + cancel_handlers(boost::asio::error::operation_aborted); + return error_code(); + } + + void close(); + void close(error_code const& /*ec*/) { close(); } + + void set_close_reason(boost::uint16_t code); + boost::uint16_t get_close_reason(); + + bool is_open() const { return m_open; } + + int read_buffer_size() const; + static void on_read(void* self, size_t bytes_transferred + , error_code const& ec, bool kill); + static void on_write(void* self, size_t bytes_transferred + , error_code const& ec, bool kill); + static void on_connect(void* self, error_code const& ec, bool kill); + static void on_close_reason(void* self, boost::uint16_t reason); + + void add_read_buffer(void* buf, size_t len); + void issue_read(); + void add_write_buffer(void const* buf, size_t len); + void issue_write(); + size_t read_some(bool clear_buffers); + + int send_delay() const; + int recv_delay() const; + + void do_connect(tcp::endpoint const& ep); + + endpoint_type local_endpoint() const + { + error_code ec; + return local_endpoint(ec); + } + + endpoint_type local_endpoint(error_code& ec) const; + + endpoint_type remote_endpoint() const + { + error_code ec; + return remote_endpoint(ec); + } + + endpoint_type remote_endpoint(error_code& ec) const; + + std::size_t available() const; + std::size_t available(error_code& /*ec*/) const { return available(); } + + io_service& get_io_service() { return m_io_service; } + + template + void async_connect(endpoint_type const& endpoint, Handler const& handler) + { + if (!endpoint.address().is_v4()) + { + m_io_service.post(boost::bind(handler, boost::asio::error::operation_not_supported, 0)); + return; + } + + if (m_impl == 0) + { + m_io_service.post(boost::bind(handler, boost::asio::error::not_connected, 0)); + return; + } + + m_connect_handler = handler; + do_connect(endpoint); + } + + template + void async_read_some(Mutable_Buffers const& buffers, Handler const& handler) + { + if (m_impl == 0) + { + m_io_service.post(boost::bind(handler, boost::asio::error::not_connected, 0)); + return; + } + + TORRENT_ASSERT(!m_read_handler); + if (m_read_handler) + { + m_io_service.post(boost::bind(handler, boost::asio::error::operation_not_supported, 0)); + return; + } + std::size_t bytes_added = 0; + for (typename Mutable_Buffers::const_iterator i = buffers.begin() + , end(buffers.end()); i != end; ++i) + { + if (buffer_size(*i) == 0) continue; + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + add_read_buffer(buffer_cast(*i), buffer_size(*i)); + bytes_added += buffer_size(*i); + } + if (bytes_added == 0) + { + // if we're reading 0 bytes, post handler immediately + // asio's SSL layer depends on this behavior + m_io_service.post(boost::bind(handler, error_code(), 0)); + return; + } + + m_read_handler = handler; + issue_read(); + } + + template + void async_read_some(null_buffers const&, Handler const& handler) + { + if (m_impl == 0) + { + m_io_service.post(boost::bind(handler, boost::asio::error::not_connected, 0)); + return; + } + + TORRENT_ASSERT(!m_read_handler); + if (m_read_handler) + { + TORRENT_ASSERT(false); // we should never do this! + m_io_service.post(boost::bind(handler, boost::asio::error::operation_not_supported, 0)); + return; + } + m_read_handler = handler; + issue_read(); + } + + void do_async_connect(endpoint_type const& ep + , boost::function const& handler); + + 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 == 0) + { + 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 (typename Mutable_Buffers::const_iterator i = buffers.begin() + , end(buffers.end()); i != end; ++i) + { + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + add_read_buffer(buffer_cast(*i), buffer_size(*i)); +#if TORRENT_USE_ASSERTS + buf_size += buffer_size(*i); +#endif + } + std::size_t ret = read_some(true); + TORRENT_ASSERT(ret <= buf_size); + TORRENT_ASSERT(ret > 0); + return ret; + } + + template + std::size_t write_some(Const_Buffers const& /* buffers */, error_code& /* ec */) + { + TORRENT_ASSERT(false && "not implemented!"); + // TODO: implement blocking write. Low priority since it's not used (yet) + return 0; + } + +#ifndef BOOST_NO_EXCEPTIONS + template + std::size_t read_some(Mutable_Buffers const& buffers) + { + error_code ec; + std::size_t ret = read_some(buffers, ec); + if (ec) + boost::throw_exception(boost::system::system_error(ec)); + return ret; + } + + template + std::size_t write_some(Const_Buffers const& buffers) + { + error_code ec; + std::size_t ret = write_some(buffers, ec); + if (ec) + boost::throw_exception(boost::system::system_error(ec)); + return ret; + } +#endif + + template + void async_write_some(Const_Buffers const& buffers, Handler const& handler) + { + if (m_impl == 0) + { + m_io_service.post(boost::bind(handler + , boost::asio::error::not_connected, 0)); + return; + } + + TORRENT_ASSERT(!m_write_handler); + if (m_write_handler) + { + m_io_service.post(boost::bind(handler + , boost::asio::error::operation_not_supported, 0)); + return; + } + + std::size_t bytes_added = 0; + for (typename Const_Buffers::const_iterator i = buffers.begin() + , end(buffers.end()); i != end; ++i) + { + if (buffer_size(*i) == 0) continue; + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + add_write_buffer(buffer_cast(*i), buffer_size(*i)); + bytes_added += buffer_size(*i); + } + if (bytes_added == 0) + { + // if we're writing 0 bytes, post handler immediately + // asio's SSL layer depends on this behavior + m_io_service.post(boost::bind(handler, error_code(), 0)); + return; + } + m_write_handler = handler; + issue_write(); + } + +private: + // explicitly disallow assignment, to silence msvc warning + utp_stream& operator=(utp_stream const&); + + void cancel_handlers(error_code const&); + + boost::function1 m_connect_handler; + boost::function2 m_read_handler; + boost::function2 m_write_handler; + + io_service& m_io_service; + utp_socket_impl* m_impl; + + boost::uint16_t m_incoming_close_reason; + + // this field requires another 8 bytes (including padding) + bool m_open; +}; + +} + +#endif diff --git a/include/libtorrent/vector_utils.hpp b/include/libtorrent/vector_utils.hpp new file mode 100644 index 0000000..42892e7 --- /dev/null +++ b/include/libtorrent/vector_utils.hpp @@ -0,0 +1,70 @@ +/* + +Copyright (c) 2012-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_VECTOR_UTILS_HPP_INCLUDE +#define TORRENT_VECTOR_UTILS_HPP_INCLUDE + +#include +#include + +namespace libtorrent { + + template + typename std::vector::iterator sorted_find(std::vector& container + , T v) + { + typename std::vector::iterator 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 + typename std::vector::const_iterator sorted_find(std::vector const& container + , T const* v) + { + return sorted_find(const_cast&>(container) + , const_cast(v)); + } + + template + void sorted_insert(std::vector& container, T v) + { + typename std::vector::iterator 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..75f8d53 --- /dev/null +++ b/include/libtorrent/version.hpp @@ -0,0 +1,57 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_VERSION_HPP_INCLUDED +#define TORRENT_VERSION_HPP_INCLUDED + +#include "libtorrent/export.hpp" + +#define LIBTORRENT_VERSION_MAJOR 1 +#define LIBTORRENT_VERSION_MINOR 1 +#define LIBTORRENT_VERSION_TINY 1 + +// the format of this version is: MMmmtt +// M = Major version, m = minor version, t = tiny version +#define LIBTORRENT_VERSION_NUM ((LIBTORRENT_VERSION_MAJOR * 10000) + (LIBTORRENT_VERSION_MINOR * 100) + LIBTORRENT_VERSION_TINY) + +#define LIBTORRENT_VERSION "1.1.1.0" +#define LIBTORRENT_REVISION "1229491" + +namespace libtorrent { + + // returns the libtorrent version as string form in this format: + // "..." + TORRENT_EXPORT char const* version(); + +} + +#endif diff --git a/include/libtorrent/web_connection_base.hpp b/include/libtorrent/web_connection_base.hpp new file mode 100644 index 0000000..c102294 --- /dev/null +++ b/include/libtorrent/web_connection_base.hpp @@ -0,0 +1,161 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef WEB_CONNECTION_BASE_HPP_INCLUDED +#define WEB_CONNECTION_BASE_HPP_INCLUDED + +#include "libtorrent/debug.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/buffer.hpp" +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/storage.hpp" +#include "libtorrent/stat.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/peer_request.hpp" +#include "libtorrent/piece_block_progress.hpp" +#include "libtorrent/config.hpp" +// parse_url +#include "libtorrent/tracker_manager.hpp" +#include "libtorrent/http_parser.hpp" + +namespace libtorrent +{ + class torrent; + + class TORRENT_EXTRA_EXPORT web_connection_base + : public peer_connection + { + friend class invariant_access; + public: + + // this is the constructor where the we are the active part. + // The peer_conenction should handshake and verify that the + // other end has the correct id + web_connection_base(peer_connection_args const& pack + , web_seed_t& web); + + virtual int timeout() const; + void start(); + + ~web_connection_base(); + + // 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); + + virtual std::string const& url() const = 0; + + bool in_handshake() const; + + // the following functions appends messages + // to the send buffer + void write_choke() {} + void write_unchoke() {} + void write_interested() {} + void write_not_interested() {} + virtual void write_request(peer_request const&) = 0; + void write_cancel(peer_request const&) {} + void write_have(int) {} + void write_dont_have(int) {} + void write_piece(peer_request const&, disk_buffer_holder&) + { TORRENT_ASSERT(false); } + void write_keepalive() {} + void on_connected(); + void write_reject_request(peer_request const&) {} + void write_allow_fast(int) {} + void write_suggest(int) {} + void write_bitfield() {} + +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const; +#endif + + virtual void get_specific_peer_info(peer_info& p) const; + + 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..6168deb --- /dev/null +++ b/include/libtorrent/web_peer_connection.hpp @@ -0,0 +1,160 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_WEB_PEER_CONNECTION_HPP_INCLUDED +#define TORRENT_WEB_PEER_CONNECTION_HPP_INCLUDED + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/config.hpp" +#include "libtorrent/web_connection_base.hpp" +#include "libtorrent/disk_buffer_holder.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/piece_block_progress.hpp" +#include "libtorrent/http_parser.hpp" +#include "libtorrent/operations.hpp" // for operation_t enum + +namespace libtorrent +{ + class torrent; + + class TORRENT_EXTRA_EXPORT web_peer_connection + : public web_connection_base + { + friend class invariant_access; + public: + + // this is the constructor where the we are the active part. + // The peer_conenction should handshake and verify that the + // other end has the correct id + web_peer_connection(peer_connection_args const& pack + , web_seed_t& web); + + virtual void on_connected() TORRENT_OVERRIDE; + + virtual int type() const TORRENT_OVERRIDE + { return peer_connection::url_seed_connection; } + + // called from the main loop when this connection has any + // work to do. + virtual void on_receive(error_code const& error + , std::size_t bytes_transferred) TORRENT_OVERRIDE; + + std::string const& url() const TORRENT_OVERRIDE { return m_url; } + + virtual void get_specific_peer_info(peer_info& p) const TORRENT_OVERRIDE; + virtual void disconnect(error_code const& ec + , operation_t op, int error = 0) TORRENT_OVERRIDE; + + virtual void write_request(peer_request const& r) TORRENT_OVERRIDE; + + virtual bool received_invalid_data(int index, bool single_peer) TORRENT_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. + boost::optional downloading_piece_progress() const TORRENT_OVERRIDE; + + void handle_padfile(); + + // this has one entry per http-request + // (might be more than the bt requests) + struct file_request_t + { + int file_index; + int length; + boost::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 + std::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/xml_parse.hpp b/include/libtorrent/xml_parse.hpp new file mode 100644 index 0000000..c2024ad --- /dev/null +++ b/include/libtorrent/xml_parse.hpp @@ -0,0 +1,77 @@ +/* + +Copyright (c) 2007-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_XML_PARSE_HPP +#define TORRENT_XML_PARSE_HPP + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include + +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.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 null terminated, but their lengths are specified via + // name_len and val_len respectively + TORRENT_EXTRA_EXPORT void xml_parse(char const* p, char const* end + , boost::function callback); +} + + +#endif + diff --git a/libtorrent-rasterbar-cmake.pc.in b/libtorrent-rasterbar-cmake.pc.in new file mode 100644 index 0000000..ee5f519 --- /dev/null +++ b/libtorrent-rasterbar-cmake.pc.in @@ -0,0 +1,6 @@ +Name: libtorrent-rasterbar +Description: Bittorrent library. +Version: @VERSION@ +Libs: -L${CMAKE_INSTALL_PREFIX}/lib -ltorrent-rasterbar +Cflags: -I${CMAKE_INSTALL_PREFIX}/include -I${CMAKE_INSTALL_PREFIX}/include/libtorrent @COMPILETIME_OPTIONS@ @CXX_DEFINES@ + diff --git a/libtorrent-rasterbar.pc.in b/libtorrent-rasterbar.pc.in new file mode 100644 index 0000000..dc600c2 --- /dev/null +++ b/libtorrent-rasterbar.pc.in @@ -0,0 +1,16 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +bindir=@bindir@ +libdir=@libdir@ +datarootdir=@datarootdir@ +datadir=@datadir@ +sysconfdir=@sysconfdir@ +includedir=@includedir@ +package=@PACKAGE@ + +Name: libtorrent-rasterbar +Description: Bittorrent library. +Version: @VERSION@ +Libs: -L${libdir} -ltorrent-rasterbar @BOOST_SYSTEM_LIB@ +Libs.private: @LIBS@ @PTHREAD_LIBS@ @OPENSSL_LIBS@ +Cflags: -I${includedir} -I${includedir}/libtorrent @COMPILETIME_OPTIONS@ diff --git a/m4/._libtool.m4 b/m4/._libtool.m4 new file mode 100644 index 0000000000000000000000000000000000000000..4b8deb252af82e3b26969a2ec09907e2dce2b680 GIT binary patch literal 239 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@m?SX@!tb65x_AdBnYYuq+<>c2cv0Y zM2L$g=jZCB6y+BrmXs7_CY6??76TPj%vl}grFlx<$Vqox1Ojhs@R)|o50+1L3ClDI}@m?SX@!tb65x_AdBnYYuq+<>c2cv0Y zM2L$g=jZCB6y+BrmXs7_CY6??76TPj%vl}grFlx<$Vqox1Ojhs@R)|o50+1L3ClDI}@m?SX@!tb65x_AdBnYYuq+<>c2cv0Y zM2L$g=jZCB6y+BrmXs7_CY6??76TPj%vl}grFlx<$Vqox1Ojhs@R)|o50+1L3ClDI}@m?SX@!tb65x_AdBnYYuq+<>c2cv0Y zM2L$g=jZCB6y+BrmXs7_CY6??76TPj%vl}grFlx<. +# +# This macro calls: +# +# AC_SUBST(BOOST_CPPFLAGS) / AC_SUBST(BOOST_LDFLAGS) +# +# And sets: +# +# HAVE_BOOST +# +# LICENSE +# +# Copyright (c) 2008 Thomas Porschberg +# Copyright (c) 2009 Peter Adolphs +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 26 + +AC_DEFUN([AX_BOOST_BASE], +[ +AC_ARG_WITH([boost], + [AS_HELP_STRING([--with-boost@<:@=ARG@:>@], + [use Boost library from a standard location (ARG=yes), + from the specified location (ARG=), + or disable it (ARG=no) + @<:@ARG=yes@:>@ ])], + [ + if test "$withval" = "no"; then + want_boost="no" + elif test "$withval" = "yes"; then + want_boost="yes" + ac_boost_path="" + else + want_boost="yes" + ac_boost_path="$withval" + fi + ], + [want_boost="yes"]) + + +AC_ARG_WITH([boost-libdir], + AS_HELP_STRING([--with-boost-libdir=LIB_DIR], + [Force given directory for boost libraries. Note that this will override library path detection, so use this parameter only if default library detection fails and you know exactly where your boost libraries are located.]), + [ + if test -d "$withval" + then + ac_boost_lib_path="$withval" + else + AC_MSG_ERROR(--with-boost-libdir expected directory name) + fi + ], + [ac_boost_lib_path=""] +) + +if test "x$want_boost" = "xyes"; then + boost_lib_version_req=ifelse([$1], ,1.20.0,$1) + boost_lib_version_req_shorten=`expr $boost_lib_version_req : '\([[0-9]]*\.[[0-9]]*\)'` + boost_lib_version_req_major=`expr $boost_lib_version_req : '\([[0-9]]*\)'` + boost_lib_version_req_minor=`expr $boost_lib_version_req : '[[0-9]]*\.\([[0-9]]*\)'` + boost_lib_version_req_sub_minor=`expr $boost_lib_version_req : '[[0-9]]*\.[[0-9]]*\.\([[0-9]]*\)'` + if test "x$boost_lib_version_req_sub_minor" = "x" ; then + boost_lib_version_req_sub_minor="0" + fi + WANT_BOOST_VERSION=`expr $boost_lib_version_req_major \* 100000 \+ $boost_lib_version_req_minor \* 100 \+ $boost_lib_version_req_sub_minor` + AC_MSG_CHECKING(for boostlib >= $boost_lib_version_req) + succeeded=no + + dnl On 64-bit systems check for system libraries in both lib64 and lib. + dnl The former is specified by FHS, but e.g. Debian does not adhere to + dnl this (as it rises problems for generic multi-arch support). + dnl The last entry in the list is chosen by default when no libraries + dnl are found, e.g. when only header-only libraries are installed! + libsubdirs="lib" + ax_arch=`uname -m` + case $ax_arch in + x86_64) + libsubdirs="lib64 libx32 lib lib64" + ;; + ppc64|s390x|sparc64|aarch64|ppc64le) + libsubdirs="lib64 lib lib64 ppc64le" + ;; + esac + + dnl allow for real multi-arch paths e.g. /usr/lib/x86_64-linux-gnu. Give + dnl them priority over the other paths since, if libs are found there, they + dnl are almost assuredly the ones desired. + AC_REQUIRE([AC_CANONICAL_HOST]) + libsubdirs="lib/${host_cpu}-${host_os} $libsubdirs" + + case ${host_cpu} in + i?86) + libsubdirs="lib/i386-${host_os} $libsubdirs" + ;; + esac + + dnl first we check the system location for boost libraries + dnl this location ist chosen if boost libraries are installed with the --layout=system option + dnl or if you install boost with RPM + if test "$ac_boost_path" != ""; then + BOOST_CPPFLAGS="-I$ac_boost_path/include" + for ac_boost_path_tmp in $libsubdirs; do + if test -d "$ac_boost_path"/"$ac_boost_path_tmp" ; then + BOOST_LDFLAGS="-L$ac_boost_path/$ac_boost_path_tmp" + break + fi + done + elif test "$cross_compiling" != yes; then + for ac_boost_path_tmp in /usr /usr/local /opt /opt/local ; do + if test -d "$ac_boost_path_tmp/include/boost" && test -r "$ac_boost_path_tmp/include/boost"; then + for libsubdir in $libsubdirs ; do + if ls "$ac_boost_path_tmp/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi + done + BOOST_LDFLAGS="-L$ac_boost_path_tmp/$libsubdir" + BOOST_CPPFLAGS="-I$ac_boost_path_tmp/include" + break; + fi + done + fi + + dnl overwrite ld flags if we have required special directory with + dnl --with-boost-libdir parameter + if test "$ac_boost_lib_path" != ""; then + BOOST_LDFLAGS="-L$ac_boost_lib_path" + fi + + CPPFLAGS_SAVED="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" + export CPPFLAGS + + LDFLAGS_SAVED="$LDFLAGS" + LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" + export LDFLAGS + + AC_REQUIRE([AC_PROG_CXX]) + AC_LANG_PUSH(C++) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ + @%:@include + ]], [[ + #if BOOST_VERSION >= $WANT_BOOST_VERSION + // Everything is okay + #else + # error Boost version is too old + #endif + ]])],[ + AC_MSG_RESULT(yes) + succeeded=yes + found_system=yes + ],[ + ]) + AC_LANG_POP([C++]) + + + + dnl if we found no boost with system layout we search for boost libraries + dnl built and installed without the --layout=system option or for a staged(not installed) version + if test "x$succeeded" != "xyes"; then + CPPFLAGS="$CPPFLAGS_SAVED" + LDFLAGS="$LDFLAGS_SAVED" + BOOST_CPPFLAGS= + BOOST_LDFLAGS= + _version=0 + if test "$ac_boost_path" != ""; then + if test -d "$ac_boost_path" && test -r "$ac_boost_path"; then + for i in `ls -d $ac_boost_path/include/boost-* 2>/dev/null`; do + _version_tmp=`echo $i | sed "s#$ac_boost_path##" | sed 's/\/include\/boost-//' | sed 's/_/./'` + V_CHECK=`expr $_version_tmp \> $_version` + if test "$V_CHECK" = "1" ; then + _version=$_version_tmp + fi + VERSION_UNDERSCORE=`echo $_version | sed 's/\./_/'` + BOOST_CPPFLAGS="-I$ac_boost_path/include/boost-$VERSION_UNDERSCORE" + done + dnl if nothing found search for layout used in Windows distributions + if test -z "$BOOST_CPPFLAGS"; then + if test -d "$ac_boost_path/boost" && test -r "$ac_boost_path/boost"; then + BOOST_CPPFLAGS="-I$ac_boost_path" + fi + fi + fi + else + if test "$cross_compiling" != yes; then + for ac_boost_path in /usr /usr/local /opt /opt/local ; do + if test -d "$ac_boost_path" && test -r "$ac_boost_path"; then + for i in `ls -d $ac_boost_path/include/boost-* 2>/dev/null`; do + _version_tmp=`echo $i | sed "s#$ac_boost_path##" | sed 's/\/include\/boost-//' | sed 's/_/./'` + V_CHECK=`expr $_version_tmp \> $_version` + if test "$V_CHECK" = "1" ; then + _version=$_version_tmp + best_path=$ac_boost_path + fi + done + fi + done + + VERSION_UNDERSCORE=`echo $_version | sed 's/\./_/'` + BOOST_CPPFLAGS="-I$best_path/include/boost-$VERSION_UNDERSCORE" + if test "$ac_boost_lib_path" = ""; then + for libsubdir in $libsubdirs ; do + if ls "$best_path/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi + done + BOOST_LDFLAGS="-L$best_path/$libsubdir" + fi + fi + + if test "x$BOOST_ROOT" != "x"; then + for libsubdir in $libsubdirs ; do + if ls "$BOOST_ROOT/stage/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi + done + if test -d "$BOOST_ROOT" && test -r "$BOOST_ROOT" && test -d "$BOOST_ROOT/stage/$libsubdir" && test -r "$BOOST_ROOT/stage/$libsubdir"; then + version_dir=`expr //$BOOST_ROOT : '.*/\(.*\)'` + stage_version=`echo $version_dir | sed 's/boost_//' | sed 's/_/./g'` + stage_version_shorten=`expr $stage_version : '\([[0-9]]*\.[[0-9]]*\)'` + V_CHECK=`expr $stage_version_shorten \>\= $_version` + if test "$V_CHECK" = "1" -a "$ac_boost_lib_path" = "" ; then + AC_MSG_NOTICE(We will use a staged boost library from $BOOST_ROOT) + BOOST_CPPFLAGS="-I$BOOST_ROOT" + BOOST_LDFLAGS="-L$BOOST_ROOT/stage/$libsubdir" + fi + fi + fi + fi + + CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" + export CPPFLAGS + LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" + export LDFLAGS + + AC_LANG_PUSH(C++) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ + @%:@include + ]], [[ + #if BOOST_VERSION >= $WANT_BOOST_VERSION + // Everything is okay + #else + # error Boost version is too old + #endif + ]])],[ + AC_MSG_RESULT(yes) + succeeded=yes + found_system=yes + ],[ + ]) + AC_LANG_POP([C++]) + fi + + if test "$succeeded" != "yes" ; then + if test "$_version" = "0" ; then + AC_MSG_NOTICE([[We could not detect the boost libraries (version $boost_lib_version_req_shorten or higher). If you have a staged boost library (still not installed) please specify \$BOOST_ROOT in your environment and do not give a PATH to --with-boost option. If you are sure you have boost installed, then check your version number looking in . See http://randspringer.de/boost for more documentation.]]) + else + AC_MSG_NOTICE([Your boost libraries seems to old (version $_version).]) + fi + # execute ACTION-IF-NOT-FOUND (if present): + ifelse([$3], , :, [$3]) + else + AC_SUBST(BOOST_CPPFLAGS) + AC_SUBST(BOOST_LDFLAGS) + AC_DEFINE(HAVE_BOOST,,[define if the Boost library is available]) + # execute ACTION-IF-FOUND (if present): + ifelse([$2], , :, [$2]) + fi + + CPPFLAGS="$CPPFLAGS_SAVED" + LDFLAGS="$LDFLAGS_SAVED" +fi + +]) + diff --git a/m4/ax_boost_chrono.m4 b/m4/ax_boost_chrono.m4 new file mode 100644 index 0000000..5d065e2 --- /dev/null +++ b/m4/ax_boost_chrono.m4 @@ -0,0 +1,119 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_boost_chrono.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_BOOST_CHRONO +# +# DESCRIPTION +# +# Test for System library from the Boost C++ libraries. The macro requires +# a preceding call to AX_BOOST_BASE. Further documentation is available at +# . +# +# This macro calls: +# +# AC_SUBST(BOOST_CHRONO_LIB) +# +# And sets: +# +# HAVE_BOOST_CHRONO +# +# LICENSE +# +# Copyright (c) 2012 Xiyue Deng +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 1 + +AC_DEFUN([AX_BOOST_CHRONO], +[ + AC_ARG_WITH([boost-chrono], + AS_HELP_STRING([--with-boost-chrono@<:@=special-lib@:>@], + [use the Chrono library from boost - it is possible to specify a certain library for the linker + e.g. --with-boost-chrono=boost_chrono-gcc-mt ]), + [ + if test "$withval" = "no"; then + want_boost="no" + elif test "$withval" = "yes"; then + want_boost="yes" + ax_boost_user_chrono_lib="" + else + want_boost="yes" + ax_boost_user_chrono_lib="$withval" + fi + ], + [want_boost="yes"] + ) + + if test "x$want_boost" = "xyes"; then + AC_REQUIRE([AC_PROG_CC]) + AC_REQUIRE([AC_CANONICAL_BUILD]) + CPPFLAGS_SAVED="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" + export CPPFLAGS + + LDFLAGS_SAVED="$LDFLAGS" + LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" + export LDFLAGS + + AC_CACHE_CHECK(whether the Boost::Chrono library is available, + ax_cv_boost_chrono, + [AC_LANG_PUSH([C++]) + CXXFLAGS_SAVE=$CXXFLAGS + + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[@%:@include ]], + [[boost::chrono::system_clock::time_point time;]])], + ax_cv_boost_chrono=yes, ax_cv_boost_chrono=no) + CXXFLAGS=$CXXFLAGS_SAVE + AC_LANG_POP([C++]) + ]) + if test "x$ax_cv_boost_chrono" = "xyes"; then + AC_SUBST(BOOST_CPPFLAGS) + + AC_DEFINE(HAVE_BOOST_CHRONO,,[define if the Boost::Chrono library is available]) + BOOSTLIBDIR=`echo $BOOST_LDFLAGS | sed -e 's/@<:@^\/@:>@*//'` + + LDFLAGS_SAVE=$LDFLAGS + if test "x$ax_boost_user_chrono_lib" = "x"; then + for libextension in `ls $BOOSTLIBDIR/libboost_chrono*.so* $BOOSTLIBDIR/libboost_chrono*.dylib* $BOOSTLIBDIR/libboost_chrono*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^lib\(boost_chrono.*\)\.so.*$;\1;' -e 's;^lib\(boost_chrono.*\)\.dylib.*$;\1;' -e 's;^lib\(boost_chrono.*\)\.a.*$;\1;'` ; do + ax_lib=${libextension} + AC_CHECK_LIB($ax_lib, exit, + [BOOST_CHRONO_LIB="-l$ax_lib"; AC_SUBST(BOOST_CHRONO_LIB) link_chrono="yes"; break], + [link_chrono="no"]) + done + if test "x$link_chrono" != "xyes"; then + for libextension in `ls $BOOSTLIBDIR/boost_chrono*.dll* $BOOSTLIBDIR/boost_chrono*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^\(boost_chrono.*\)\.dll.*$;\1;' -e 's;^\(boost_chrono.*\)\.a.*$;\1;'` ; do + ax_lib=${libextension} + AC_CHECK_LIB($ax_lib, exit, + [BOOST_CHRONO_LIB="-l$ax_lib"; AC_SUBST(BOOST_CHRONO_LIB) link_chrono="yes"; break], + [link_chrono="no"]) + done + fi + + else + for ax_lib in $ax_boost_user_chrono_lib boost_chrono-$ax_boost_user_chrono_lib; do + AC_CHECK_LIB($ax_lib, exit, + [BOOST_CHRONO_LIB="-l$ax_lib"; AC_SUBST(BOOST_CHRONO_LIB) link_chrono="yes"; break], + [link_chrono="no"]) + done + + fi + if test "x$ax_lib" = "x"; then + AC_MSG_ERROR(Could not find a version of the library!) + fi + if test "x$link_chrono" = "xno"; then + AC_MSG_ERROR(Could not link against $ax_lib !) + fi + fi + + CPPFLAGS="$CPPFLAGS_SAVED" + LDFLAGS="$LDFLAGS_SAVED" + fi +]) + diff --git a/m4/ax_boost_python.m4 b/m4/ax_boost_python.m4 new file mode 100644 index 0000000..5d21fd3 --- /dev/null +++ b/m4/ax_boost_python.m4 @@ -0,0 +1,120 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_boost_python.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_BOOST_PYTHON +# +# DESCRIPTION +# +# This macro checks to see if the Boost.Python library is installed. It +# also attempts to guess the correct library name using several attempts. +# It tries to build the library name using a user supplied name or suffix +# and then just the raw library. +# +# If the library is found, HAVE_BOOST_PYTHON is defined and +# BOOST_PYTHON_LIB is set to the name of the library. +# +# This macro calls AC_SUBST(BOOST_PYTHON_LIB). +# +# In order to ensure that the Python headers and the Boost libraries are +# specified on the include path, this macro requires AX_PYTHON_DEVEL and +# AX_BOOST_BASE to be called. +# +# LICENSE +# +# Copyright (c) 2008 Michael Tindal +# Copyright (c) 2013 Daniel M"ullner +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 21 + +AC_DEFUN([AX_BOOST_PYTHON], +[AC_REQUIRE([AX_PYTHON_DEVEL])dnl +AC_REQUIRE([AX_BOOST_BASE])dnl +AC_LANG_PUSH([C++]) +ax_boost_python_save_CPPFLAGS="$CPPFLAGS" +ax_boost_python_save_LDFLAGS="$LDFLAGS" +ax_boost_python_save_LIBS="$LIBS" +if test "x$PYTHON_CPPFLAGS" != "x"; then + CPPFLAGS="$PYTHON_CPPFLAGS $CPPFLAGS" +fi + +# Versions of AX_PYTHON_DEVEL() before serial 18 provided PYTHON_LDFLAGS +# instead of PYTHON_LIBS, so this is just here for compatibility. +if test "x$PYTHON_LDFLAGS" != "x"; then + LDFLAGS="$PYTHON_LDFLAGS $LDFLAGS" +fi + +# Note: Only versions of AX_PYTHON_DEVEL() since serial 18 provide PYTHON_LIBS +# instead of PYTHON_LDFLAGS. +if test "x$PYTHON_LIBS" != "x"; then + LIBS="$PYTHON_LIBS $LIBS" +fi + +if test "x$BOOST_CPPFLAGS" != "x"; then + CPPFLAGS="$BOOST_CPPFLAGS $CPPFLAGS" +fi +if test "x$BOOST_LDFLAGS" != "x"; then + LDFLAGS="$BOOST_LDFLAGS $LDFLAGS" +fi +AC_CACHE_CHECK(whether the Boost::Python library is available, +ac_cv_boost_python, +[AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ +#include +BOOST_PYTHON_MODULE(test) { throw "Boost::Python test."; }]], [])], + ac_cv_boost_python=yes, ac_cv_boost_python=no) +]) +if test "$ac_cv_boost_python" = "yes"; then + AC_DEFINE(HAVE_BOOST_PYTHON,,[define if the Boost::Python library is available]) + ax_python_lib=boost_python + AC_ARG_WITH([boost-python],AS_HELP_STRING([--with-boost-python],[specify yes/no or the boost python library or suffix to use]), + [if test "x$with_boost_python" != "xno" -a "x$with_boost_python" != "xyes"; then + ax_python_lib=$with_boost_python + ax_boost_python_lib=boost_python-$with_boost_python + fi]) + BOOSTLIBDIR=`echo $BOOST_LDFLAGS | sed -e 's/@<:@^\/@:>@*//'` + for ax_lib in $ax_python_lib $ax_boost_python_lib `ls $BOOSTLIBDIR/libboost_python*.so* $BOOSTLIBDIR/libboost_python*.dylib* $BOOSTLIBDIR/libboost_python*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^lib\(boost_python.*\)\.so.*$;\1;' -e 's;^lib\(boost_python.*\)\.dylib.*$;\1;' -e 's;^lib\(boost_python.*\)\.a.*$;\1;' ` boost_python boost_python3; do + AS_VAR_PUSHDEF([ax_Lib], [ax_cv_lib_$ax_lib''_main])dnl + AC_CACHE_CHECK([whether $ax_lib is the correct library], [ax_Lib], + [LIBS="-l$ax_lib $ax_boost_python_save_LIBS $PYTHON_LIBS" + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ + ]], [])], + [AS_VAR_SET([ax_Lib], [yes])], + [AS_VAR_SET([ax_Lib], [no])])]) + AS_VAR_IF([ax_Lib], [yes], [BOOST_PYTHON_LIB=$ax_lib break], []) + AS_VAR_POPDEF([ax_Lib])dnl + done + AC_SUBST(BOOST_PYTHON_LIB) +fi +CPPFLAGS="$ax_boost_python_save_CPPFLAGS" +LDFLAGS="$ax_boost_python_save_LDFLAGS" +LIBS="$ax_boost_python_save_LIBS" +AC_LANG_POP([C++]) +])dnl diff --git a/m4/ax_boost_random.m4 b/m4/ax_boost_random.m4 new file mode 100644 index 0000000..f7cf645 --- /dev/null +++ b/m4/ax_boost_random.m4 @@ -0,0 +1,123 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_boost_random.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_BOOST_RANDOM +# +# DESCRIPTION +# +# Test for Random library from the Boost C++ libraries. The macro requires a +# preceding call to AX_BOOST_BASE. Further documentation is available at +# . +# +# This macro calls: +# +# AC_SUBST(BOOST_RANDOM_LIB) +# +# And sets: +# +# HAVE_BOOST_RANDOM +# +# LICENSE +# +# Copyright (c) 2008 Thomas Porschberg +# Copyright (c) 2008 Michael Tindal +# Copyright (c) 2013 Daniel Casimiro +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 1 + +AC_DEFUN([AX_BOOST_RANDOM], +[ + AC_ARG_WITH([boost-random], + AS_HELP_STRING([--with-boost-random@<:@=special-lib@:>@], + [use the Random library from boost - it is possible to specify a certain library for the linker + e.g. --with-boost-random=boost_random-gcc-mt ]), [ + if test "$withval" = "no"; then + want_boost="no" + elif test "$withval" = "yes"; then + want_boost="yes" + ax_boost_user_random_lib="" + else + want_boost="yes" + ax_boost_user_random_lib="$withval" + fi + ], [want_boost="yes"] + ) + + if test "x$want_boost" = "xyes"; then + AC_REQUIRE([AC_PROG_CC]) + AC_REQUIRE([AC_CANONICAL_BUILD]) + + CPPFLAGS_SAVED="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" + export CPPFLAGS + + LDFLAGS_SAVED="$LDFLAGS" + LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" + export LDFLAGS + + AC_CACHE_CHECK(whether the Boost::Random library is available, + ax_cv_boost_random, + [AC_LANG_PUSH([C++]) + CXXFLAGS_SAVE=$CXXFLAGS + + AC_COMPILE_IFELSE([AC_LANG_PROGRAM( + [[@%:@include ]], + [[boost::random::random_device()();]])], + ax_cv_boost_random=yes, ax_cv_boost_random=no) + CXXFLAGS=$CXXFLAGS_SAVE + AC_LANG_POP([C++]) + ]) + + if test "x$ax_cv_boost_random" = "xyes"; then + AC_SUBST(BOOST_CPPFLAGS) + + AC_DEFINE(HAVE_BOOST_RANDOM,,[define if the Boost::Random library is available]) + BOOSTLIBDIR=`echo $BOOST_LDFLAGS | sed -e 's/@<:@^\/@:>@*//'` + + if test "x$ax_boost_user_random_lib" = "x"; then + for libextension in `ls $BOOSTLIBDIR/libboost_random*.so* $BOOSTLIBDIR/libboost_random*.dylib* $BOOSTLIBDIR/libboost_random*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^lib\(boost_random.*\)\.so.*$;\1;' -e 's;^lib\(boost_random.*\)\.dylib.*$;\1;' -e 's;^lib\(boost_random.*\)\.a.*$;\1;'` ; do + ax_lib=${libextension} + AC_CHECK_LIB($ax_lib, exit, + [BOOST_RANDOM_LIB="-l$ax_lib"; AC_SUBST(BOOST_RANDOM_LIB) link_random="yes"; break], + [link_random="no"]) + done + + if test "x$link_random" != "xyes"; then + for libextension in `ls $BOOSTLIBDIR/boost_random*.dll* $BOOSTLIBDIR/boost_random*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^\(boost_random.*\)\.dll.*$;\1;' -e 's;^\(boost_random.*\)\.a.*$;\1;'` ; do + ax_lib=${libextension} + AC_CHECK_LIB($ax_lib, exit, + [BOOST_RANDOM_LIB="-l$ax_lib"; AC_SUBST(BOOST_RANDOM_LIB) link_random="yes"; break], + [link_random="no"]) + done + fi + + else + for ax_lib in $ax_boost_user_random_lib boost_random-$ax_boost_user_random_lib; do + AC_CHECK_LIB($ax_lib, exit, + [BOOST_RANDOM_LIB="-l$ax_lib"; AC_SUBST(BOOST_RANDOM_LIB) link_random="yes"; break], + [link_random="no"]) + done + fi + + if test "x$ax_lib" = "x"; then + AC_MSG_ERROR(Could not find a version of the library!) + fi + + if test "x$link_random" = "xno"; then + AC_MSG_ERROR(Could not link against $ax_lib !) + fi + fi + + CPPFLAGS="$CPPFLAGS_SAVED" + LDFLAGS="$LDFLAGS_SAVED" + fi +]) + diff --git a/m4/ax_boost_system.m4 b/m4/ax_boost_system.m4 new file mode 100644 index 0000000..48475fc --- /dev/null +++ b/m4/ax_boost_system.m4 @@ -0,0 +1,114 @@ +# =========================================================================== +# http://www.nongnu.org/autoconf-archive/ax_boost_system.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_BOOST_SYSTEM +# +# DESCRIPTION +# +# Test for System library from the Boost C++ libraries. The macro requires +# a preceding call to AX_BOOST_BASE. Further documentation is available at +# . +# +# This macro calls: +# +# AC_SUBST(BOOST_SYSTEM_LIB) +# +# And sets: +# +# HAVE_BOOST_SYSTEM +# +# LICENSE +# +# Copyright (c) 2008 Thomas Porschberg +# Copyright (c) 2008 Michael Tindal +# Copyright (c) 2008 Daniel Casimiro +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. + +AC_DEFUN([AX_BOOST_SYSTEM], +[ + AC_ARG_WITH([boost-system], + AS_HELP_STRING([--with-boost-system@<:@=special-lib@:>@], + [use the System library from boost - it is possible to specify a certain library for the linker + e.g. --with-boost-system=boost_system-gcc-mt ]), + [ + if test "x$withval" = "xno"; then + want_boost="no" + elif test "x$withval" = "xyes"; then + want_boost="yes" + ax_boost_user_system_lib="" + else + want_boost="yes" + ax_boost_user_system_lib="$withval" + fi + ], + [want_boost="yes"] + ) + + if test "x$want_boost" = "xyes"; then + AC_REQUIRE([AC_PROG_CC]) + AC_REQUIRE([AC_CANONICAL_BUILD]) + CPPFLAGS_SAVED="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" + export CPPFLAGS + + LDFLAGS_SAVED="$LDFLAGS" + LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" + export LDFLAGS + + AC_CACHE_CHECK(whether the Boost::System library is available, + ax_cv_boost_system, + [AC_LANG_PUSH([C++]) + CXXFLAGS_SAVE=$CXXFLAGS + + AC_COMPILE_IFELSE(AC_LANG_PROGRAM([[@%:@include ]], + [[boost::system::system_category]]), + ax_cv_boost_system=yes, ax_cv_boost_system=no) + CXXFLAGS=$CXXFLAGS_SAVE + AC_LANG_POP([C++]) + ]) + if test "x$ax_cv_boost_system" = "xyes"; then + AC_SUBST(BOOST_CPPFLAGS) + + AC_DEFINE(HAVE_BOOST_SYSTEM,[1],[define if the Boost::System library is available]) + BOOSTLIBDIR=`echo $BOOST_LDFLAGS | sed -e 's/@<:@^\/@:>@*//'` + + LDFLAGS_SAVE=$LDFLAGS + if test "x$ax_boost_user_system_lib" = "x"; then + for libextension in `ls $BOOSTLIBDIR/libboost_system*.{so,dylib,a}* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^lib\(boost_system.*\)\.so.*$;\1;' -e 's;^lib\(boost_system.*\)\.a*$;\1;' -e 's;^lib\(boost_system.*\)\.dylib$;\1;'` ; do + ax_lib=${libextension} + AC_CHECK_LIB($ax_lib, exit, + [BOOST_SYSTEM_LIB="-l$ax_lib"; AC_SUBST(BOOST_SYSTEM_LIB) link_system="yes"; break], + [link_system="no"]) + done + if test "x$link_system" != "xyes"; then + for libextension in `ls $BOOSTLIBDIR/boost_system*.{dll,a}* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^\(boost_system.*\)\.dll.*$;\1;' -e 's;^\(boost_system.*\)\.a*$;\1;'` ; do + ax_lib=${libextension} + AC_CHECK_LIB($ax_lib, exit, + [BOOST_SYSTEM_LIB="-l$ax_lib"; AC_SUBST(BOOST_SYSTEM_LIB) link_system="yes"; break], + [link_system="no"]) + done + fi + + else + for ax_lib in $ax_boost_user_system_lib boost_system-$ax_boost_user_system_lib; do + AC_CHECK_LIB($ax_lib, exit, + [BOOST_SYSTEM_LIB="-l$ax_lib"; AC_SUBST(BOOST_SYSTEM_LIB) link_system="yes"; break], + [link_system="no"]) + done + + fi + if test "x$link_system" = "xno"; then + AC_MSG_ERROR(Could not link against $ax_lib !) + fi + fi + + CPPFLAGS="$CPPFLAGS_SAVED" + LDFLAGS="$LDFLAGS_SAVED" + fi +]) diff --git a/m4/ax_check_openssl.m4 b/m4/ax_check_openssl.m4 new file mode 100644 index 0000000..aecf208 --- /dev/null +++ b/m4/ax_check_openssl.m4 @@ -0,0 +1,124 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_check_openssl.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_CHECK_OPENSSL([action-if-found[, action-if-not-found]]) +# +# DESCRIPTION +# +# Look for OpenSSL in a number of default spots, or in a user-selected +# spot (via --with-openssl). Sets +# +# OPENSSL_INCLUDES to the include directives required +# OPENSSL_LIBS to the -l directives required +# OPENSSL_LDFLAGS to the -L or -R flags required +# +# and calls ACTION-IF-FOUND or ACTION-IF-NOT-FOUND appropriately +# +# This macro sets OPENSSL_INCLUDES such that source files should use the +# openssl/ directory in include directives: +# +# #include +# +# LICENSE +# +# Copyright (c) 2009, 2010 Zmanda Inc. +# Copyright (c) 2009, 2010 Dustin J. Mitchell +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 6 + +AU_ALIAS([CHECK_SSL], [AX_CHECK_OPENSSL]) +AC_DEFUN([AX_CHECK_OPENSSL], [ + found=false + AC_ARG_WITH(openssl, + AS_HELP_STRING([--with-openssl=DIR], + [root of the OpenSSL directory]), + [ + case "$withval" in + "" | y | ye | yes | n | no) + AC_MSG_ERROR([Invalid --with-openssl value]) + ;; + *) ssldirs="$withval" + ;; + esac + ], [ + # if pkg-config is installed and openssl has installed a .pc file, + # then use that information and don't search ssldirs + AC_PATH_PROG(PKG_CONFIG, pkg-config) + if test x"$PKG_CONFIG" != x""; then + OPENSSL_LDFLAGS=`$PKG_CONFIG openssl --libs-only-L 2>/dev/null` + if test $? = 0; then + OPENSSL_LIBS=`$PKG_CONFIG openssl --libs-only-l 2>/dev/null` + OPENSSL_INCLUDES=`$PKG_CONFIG openssl --cflags-only-I 2>/dev/null` + found=true + fi + fi + + # no such luck; use some default ssldirs + if ! $found; then + ssldirs="/usr/local/ssl /usr/lib/ssl /usr/ssl /usr/pkg /usr/local /usr" + fi + ] + ) + + + # note that we #include , so the OpenSSL headers have to be in + # an 'openssl' subdirectory + + if ! $found; then + OPENSSL_INCLUDES= + for ssldir in $ssldirs; do + AC_MSG_CHECKING([for openssl/ssl.h in $ssldir]) + if test -f "$ssldir/include/openssl/ssl.h"; then + OPENSSL_INCLUDES="-I$ssldir/include" + OPENSSL_LDFLAGS="-L$ssldir/lib" + OPENSSL_LIBS="-lssl -lcrypto" + found=true + AC_MSG_RESULT([yes]) + break + else + AC_MSG_RESULT([no]) + fi + done + + # if the file wasn't found, well, go ahead and try the link anyway -- maybe + # it will just work! + fi + + # try the preprocessor and linker with our new flags, + # being careful not to pollute the global LIBS, LDFLAGS, and CPPFLAGS + + AC_MSG_CHECKING([whether compiling and linking against OpenSSL works]) + echo "Trying link with OPENSSL_LDFLAGS=$OPENSSL_LDFLAGS;" \ + "OPENSSL_LIBS=$OPENSSL_LIBS; OPENSSL_INCLUDES=$OPENSSL_INCLUDES" >&AS_MESSAGE_LOG_FD + + save_LIBS="$LIBS" + save_LDFLAGS="$LDFLAGS" + save_CPPFLAGS="$CPPFLAGS" + LDFLAGS="$LDFLAGS $OPENSSL_LDFLAGS" + LIBS="$OPENSSL_LIBS $LIBS" + CPPFLAGS="$OPENSSL_INCLUDES $CPPFLAGS" + AC_LINK_IFELSE( + AC_LANG_PROGRAM([#include ], [SSL_new(NULL)]), + [ + AC_MSG_RESULT([yes]) + $1 + ], [ + AC_MSG_RESULT([no]) + $2 + ]) + CPPFLAGS="$save_CPPFLAGS" + LDFLAGS="$save_LDFLAGS" + LIBS="$save_LIBS" + + AC_SUBST([OPENSSL_INCLUDES]) + AC_SUBST([OPENSSL_LIBS]) + AC_SUBST([OPENSSL_LDFLAGS]) +]) diff --git a/m4/ax_pthread.m4 b/m4/ax_pthread.m4 new file mode 100644 index 0000000..8a7a64b --- /dev/null +++ b/m4/ax_pthread.m4 @@ -0,0 +1,383 @@ +# =========================================================================== +# http://www.nongnu.org/autoconf-archive/ax_pthread.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]]) +# +# DESCRIPTION +# +# This macro figures out how to build C programs using POSIX threads. It +# sets the PTHREAD_LIBS output variable to the threads library and linker +# flags, and the PTHREAD_CFLAGS output variable to any special C compiler +# flags that are needed. (The user can also force certain compiler +# flags/libs to be tested by setting these environment variables.) +# +# Also sets PTHREAD_CC to any special C compiler that is needed for +# multi-threaded programs (defaults to the value of CC otherwise). (This +# is necessary on AIX to use the special cc_r compiler alias.) +# +# NOTE: You are assumed to not only compile your program with these flags, +# but also link it with them as well. e.g. you should link with +# $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS +# +# If you are only building threads programs, you may wish to use these +# variables in your default LIBS, CFLAGS, and CC: +# +# LIBS="$PTHREAD_LIBS $LIBS" +# CFLAGS="$CFLAGS $PTHREAD_CFLAGS" +# CC="$PTHREAD_CC" +# +# In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant +# has a nonstandard name, defines PTHREAD_CREATE_JOINABLE to that name +# (e.g. PTHREAD_CREATE_UNDETACHED on AIX). +# +# ACTION-IF-FOUND is a list of shell commands to run if a threads library +# is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it +# is not found. If ACTION-IF-FOUND is not specified, the default action +# will define HAVE_PTHREAD. +# +# Please let the authors know if this macro fails on any platform, or if +# you have any other suggestions or comments. This macro was based on work +# by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help +# from M. Frigo), as well as ac_pthread and hb_pthread macros posted by +# Alejandro Forero Cuervo to the autoconf macro repository. We are also +# grateful for the helpful feedback of numerous users. +# +# LICENSE +# +# Copyright (c) 2008 Steven G. Johnson +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD]) +AC_DEFUN([AX_PTHREAD], [ +AC_REQUIRE([AC_CANONICAL_HOST]) +AC_LANG_SAVE +AC_LANG_C +ax_pthread_ok=no + +# We used to check for pthread.h first, but this fails if pthread.h +# requires special compiler flags (e.g. on True64 or Sequent). +# It gets checked for in the link test anyway. + +# First of all, check if the user has set any of the PTHREAD_LIBS, +# etcetera environment variables, and if threads linking works using +# them: +if test "x$PTHREAD_LIBS$PTHREAD_CFLAGS" != "x"; then + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + save_LIBS="$LIBS" + LIBS="$PTHREAD_LIBS $LIBS" + AC_MSG_CHECKING([for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS]) + AC_TRY_LINK_FUNC(pthread_join, ax_pthread_ok=yes) + AC_MSG_RESULT($ax_pthread_ok) + if test "x$ax_pthread_ok" = "xno"; then + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" + fi + LIBS="$save_LIBS" + CFLAGS="$save_CFLAGS" +fi + +# We must check for the threads library under a number of different +# names; the ordering is very important because some systems +# (e.g. DEC) have both -lpthread and -lpthreads, where one of the +# libraries is broken (non-POSIX). + +# Create a list of thread flags to try. Items starting with a "-" are +# C compiler flags, and other items are library names, except for "none" +# which indicates that we try without any flags at all, and "pthread-config" +# which is a program returning the flags for the Pth emulation library. + +ax_pthread_flags="pthreads none -Kthread -kthread lthread -lpthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" + +# The ordering *is* (sometimes) important. Some notes on the +# individual items follow: + +# pthreads: AIX (must check this before -lpthread) +# none: in case threads are in libc; should be tried before -Kthread and +# other compiler flags to prevent continual compiler warnings +# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) +# -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) +# lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) +# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads) +# -pthreads: Solaris/gcc +# -mthreads: Mingw32/gcc, Lynx/gcc +# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it +# doesn't hurt to check since this sometimes defines pthreads too; +# also defines -D_REENTRANT) +# ... -mt is also the pthreads flag for HP/aCC +# pthread: Linux, etcetera +# --thread-safe: KAI C++ +# pthread-config: use pthread-config program (for GNU Pth library) + +case "${host_cpu}-${host_os}" in + *solaris*) + + # On Solaris (at least, for some versions), libc contains stubbed + # (non-functional) versions of the pthreads routines, so link-based + # tests will erroneously succeed. (We need to link with -pthreads/-mt/ + # -lpthread.) (The stubs are missing pthread_cleanup_push, or rather + # a function called by this macro, so we could check for that, but + # who knows whether they'll stub that too in a future libc.) So, + # we'll just look for -pthreads and -lpthread first: + + ax_pthread_flags="-pthreads pthread -mt -pthread $ax_pthread_flags" + ;; +esac + +if test "x$ax_pthread_ok" = "xno"; then +for flag in $ax_pthread_flags; do + + case $flag in + none) + AC_MSG_CHECKING([whether pthreads work without any flags]) + ;; + + -*) + AC_MSG_CHECKING([whether pthreads work with $flag]) + PTHREAD_CFLAGS="$flag" + ;; + + pthread-config) + AC_CHECK_PROG(ax_pthread_config, pthread-config, yes, no) + if test "x$ax_pthread_config" = "xno"; then continue; fi + PTHREAD_CFLAGS="`pthread-config --cflags`" + PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" + ;; + + *) + AC_MSG_CHECKING([for the pthreads library -l$flag]) + PTHREAD_LIBS="-l$flag" + ;; + esac + + save_LIBS="$LIBS" + save_CFLAGS="$CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + + # Check for various functions. We must include pthread.h, + # since some functions may be macros. (On the Sequent, we + # need a special flag -Kthread to make this header compile.) + # We check for pthread_join because it is in -lpthread on IRIX + # while pthread_create is in libc. We check for pthread_attr_init + # due to DEC craziness with -lpthreads. We check for + # pthread_cleanup_push because it is one of the few pthread + # functions on Solaris that doesn't have a non-functional libc stub. + # We try pthread_create on general principles. + AC_TRY_LINK([#include ], + [pthread_t th; pthread_join(th, 0); + pthread_attr_init(0); pthread_cleanup_push(0, 0); + pthread_create(0,0,0,0); pthread_cleanup_pop(0); ], + [ax_pthread_ok=yes]) + + LIBS="$save_LIBS" + CFLAGS="$save_CFLAGS" + + AC_MSG_RESULT($ax_pthread_ok) + if test "x$ax_pthread_ok" = "xyes"; then + break; + fi + + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" +done +fi + +# Various other checks: +if test "x$ax_pthread_ok" = "xyes"; then + save_LIBS="$LIBS" + LIBS="$PTHREAD_LIBS $LIBS" + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + + # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. + AC_MSG_CHECKING([for joinable pthread attribute]) + attr_name=unknown + for attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do + AC_TRY_LINK([#include ], [int attr=$attr; return attr;], + [attr_name=$attr; break]) + done + AC_MSG_RESULT($attr_name) + if test "x$attr_name" != "xPTHREAD_CREATE_JOINABLE"; then + AC_DEFINE_UNQUOTED(PTHREAD_CREATE_JOINABLE, $attr_name, + [Define to necessary symbol if this constant + uses a non-standard name on your system.]) + fi + + AC_MSG_CHECKING([if more special flags are required for pthreads]) + flag=no + case "${host_cpu}-${host_os}" in + *-aix* | *-freebsd* | *-darwin*) flag="-D_THREAD_SAFE";; + *solaris* | *-osf* | *-hpux*) flag="-D_REENTRANT";; + esac + AC_MSG_RESULT(${flag}) + if test "x$flag" != "xno"; then + PTHREAD_CFLAGS="$flag $PTHREAD_CFLAGS" + fi + + LIBS="$save_LIBS" + CFLAGS="$save_CFLAGS" + + # More AIX lossage: must compile with xlc_r or cc_r + if test "x$GCC" != "xyes"; then + AC_CHECK_PROGS(PTHREAD_CC, xlc_r cc_r, ${CC}) + else + PTHREAD_CC=$CC + fi + + # The next part tries to detect GCC inconsistency with -shared on some + # architectures and systems. The problem is that in certain + # configurations, when -shared is specified, GCC "forgets" to + # internally use various flags which are still necessary. + + # + # Prepare the flags + # + save_LDFLAGS="$LDFLAGS" + save_CFLAGS="$CFLAGS" + save_LIBS="$LIBS" + save_CC="$CC" + + # Try with the flags determined by the earlier checks. + # + # -Wl,-z,defs forces link-time symbol resolution, so that the + # linking checks with -shared actually have any value + # + # FIXME: -fPIC is required for -shared on many architectures, + # so we specify it here, but the right way would probably be to + # properly detect whether it is actually required. + CFLAGS="-shared -fPIC -Wl,-z,defs $CFLAGS $PTHREAD_CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + CC="$PTHREAD_CC" + + # In order not to create several levels of indentation, we test + # the value of "$done" until we find the cure or run out of ideas. + done="no" + + # First, make sure the CFLAGS we added are actually accepted by our + # compiler. If not (and OS X's ld, for instance, does not accept -z), + # then we can't do this test. + if test x"$done" = xno; then + AC_MSG_CHECKING([whether to check for GCC pthread/shared inconsistencies]) + AC_TRY_LINK(,, , [done=yes]) + + if test "x$done" = xyes ; then + AC_MSG_RESULT([no]) + else + AC_MSG_RESULT([yes]) + fi + fi + + if test x"$done" = xyes; then + done="no" + AC_MSG_CHECKING([whether -pthread is sufficient with -shared]) + AC_TRY_LINK([#include ], + [pthread_t th; pthread_join(th, 0); + pthread_attr_init(0); pthread_cleanup_push(0, 0); + pthread_create(0,0,0,0); pthread_cleanup_pop(0); ], + [done=yes]) + + if test "x$done" = xyes; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + fi + fi + + # + # Linux gcc on some architectures such as mips/mipsel forgets + # about -lpthread + # + if test x"$done" = xno; then + AC_MSG_CHECKING([whether -lpthread fixes that]) + LIBS="-lpthread $PTHREAD_LIBS $save_LIBS" + AC_TRY_LINK([#include ], + [pthread_t th; pthread_join(th, 0); + pthread_attr_init(0); pthread_cleanup_push(0, 0); + pthread_create(0,0,0,0); pthread_cleanup_pop(0); ], + [done=yes]) + + if test "x$done" = xyes; then + AC_MSG_RESULT([yes]) + PTHREAD_LIBS="-lpthread $PTHREAD_LIBS" + else + AC_MSG_RESULT([no]) + fi + fi + # + # FreeBSD 4.10 gcc forgets to use -lc_r instead of -lc + # + if test x"$done" = xno; then + AC_MSG_CHECKING([whether -lc_r fixes that]) + LIBS="-lc_r $PTHREAD_LIBS $save_LIBS" + AC_TRY_LINK([#include ], + [pthread_t th; pthread_join(th, 0); + pthread_attr_init(0); pthread_cleanup_push(0, 0); + pthread_create(0,0,0,0); pthread_cleanup_pop(0); ], + [done=yes]) + + if test "x$done" = xyes; then + AC_MSG_RESULT([yes]) + PTHREAD_LIBS="-lc_r $PTHREAD_LIBS" + else + AC_MSG_RESULT([no]) + fi + fi + if test x"$done" = xno; then + # OK, we have run out of ideas + AC_MSG_WARN([Impossible to determine how to use pthreads with shared libraries]) + + # so it's not safe to assume that we may use pthreads + acx_pthread_ok=no + fi + + CFLAGS="$save_CFLAGS" + LIBS="$save_LIBS" + CC="$save_CC" + +else + PTHREAD_CC="$CC" +fi + +AC_SUBST(PTHREAD_LIBS) +AC_SUBST(PTHREAD_CFLAGS) +AC_SUBST(PTHREAD_CC) + +# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: +if test "x$ax_pthread_ok" = "xyes"; then + ifelse([$1],,AC_DEFINE(HAVE_PTHREAD,[1],[Define if you have POSIX threads libraries and header files.]),[$1]) + : +else + ax_pthread_ok=no + $2 +fi +AC_LANG_RESTORE +])dnl AX_PTHREAD diff --git a/m4/ax_python_devel.m4 b/m4/ax_python_devel.m4 new file mode 100644 index 0000000..11f1f51 --- /dev/null +++ b/m4/ax_python_devel.m4 @@ -0,0 +1,327 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_python_devel.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_PYTHON_DEVEL([version]) +# +# DESCRIPTION +# +# Note: Defines as a precious variable "PYTHON_VERSION". Don't override it +# in your configure.ac. +# +# This macro checks for Python and tries to get the include path to +# 'Python.h'. It provides the $(PYTHON_CPPFLAGS) and $(PYTHON_LIBS) output +# variables. It also exports $(PYTHON_EXTRA_LIBS) and +# $(PYTHON_EXTRA_LDFLAGS) for embedding Python in your code. +# +# You can search for some particular version of Python by passing a +# parameter to this macro, for example ">= '2.3.1'", or "== '2.4'". Please +# note that you *have* to pass also an operator along with the version to +# match, and pay special attention to the single quotes surrounding the +# version number. Don't use "PYTHON_VERSION" for this: that environment +# variable is declared as precious and thus reserved for the end-user. +# +# This macro should work for all versions of Python >= 2.1.0. As an end +# user, you can disable the check for the python version by setting the +# PYTHON_NOVERSIONCHECK environment variable to something else than the +# empty string. +# +# If you need to use this macro for an older Python version, please +# contact the authors. We're always open for feedback. +# +# LICENSE +# +# Copyright (c) 2009 Sebastian Huber +# Copyright (c) 2009 Alan W. Irwin +# Copyright (c) 2009 Rafael Laboissiere +# Copyright (c) 2009 Andrew Collier +# Copyright (c) 2009 Matteo Settenvini +# Copyright (c) 2009 Horst Knorr +# Copyright (c) 2013 Daniel Mullner +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 18 + +AU_ALIAS([AC_PYTHON_DEVEL], [AX_PYTHON_DEVEL]) +AC_DEFUN([AX_PYTHON_DEVEL],[ + # + # Allow the use of a (user set) custom python version + # + AC_ARG_VAR([PYTHON_VERSION],[The installed Python + version to use, for example '2.3'. This string + will be appended to the Python interpreter + canonical name.]) + + AC_PATH_PROG([PYTHON],[python[$PYTHON_VERSION]]) + if test -z "$PYTHON"; then + AC_MSG_ERROR([Cannot find python$PYTHON_VERSION in your system path]) + PYTHON_VERSION="" + fi + + # + # Check for a version of Python >= 2.1.0 + # + AC_MSG_CHECKING([for a version of Python >= '2.1.0']) + ac_supports_python_ver=`$PYTHON -c "import sys; \ + ver = sys.version.split ()[[0]]; \ + print (ver >= '2.1.0')"` + if test "$ac_supports_python_ver" != "True"; then + if test -z "$PYTHON_NOVERSIONCHECK"; then + AC_MSG_RESULT([no]) + AC_MSG_FAILURE([ +This version of the AC@&t@_PYTHON_DEVEL macro +doesn't work properly with versions of Python before +2.1.0. You may need to re-run configure, setting the +variables PYTHON_CPPFLAGS, PYTHON_LIBS, PYTHON_SITE_PKG, +PYTHON_EXTRA_LIBS and PYTHON_EXTRA_LDFLAGS by hand. +Moreover, to disable this check, set PYTHON_NOVERSIONCHECK +to something else than an empty string. +]) + else + AC_MSG_RESULT([skip at user request]) + fi + else + AC_MSG_RESULT([yes]) + fi + + # + # if the macro parameter ``version'' is set, honour it + # + if test -n "$1"; then + AC_MSG_CHECKING([for a version of Python $1]) + ac_supports_python_ver=`$PYTHON -c "import sys; \ + ver = sys.version.split ()[[0]]; \ + print (ver $1)"` + if test "$ac_supports_python_ver" = "True"; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + AC_MSG_ERROR([this package requires Python $1. +If you have it installed, but it isn't the default Python +interpreter in your system path, please pass the PYTHON_VERSION +variable to configure. See ``configure --help'' for reference. +]) + PYTHON_VERSION="" + fi + fi + + # + # Check if you have distutils, else fail + # + AC_MSG_CHECKING([for the distutils Python package]) + ac_distutils_result=`$PYTHON -c "import distutils" 2>&1 | grep -v '^\[[0-9]\{1,\} refs\]'` + if test -z "$ac_distutils_result"; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + AC_MSG_ERROR([cannot import Python module "distutils". +Please check your Python installation. The error was: +$ac_distutils_result]) + PYTHON_VERSION="" + fi + + # + # Check for Python include path + # + AC_MSG_CHECKING([for Python include path]) + if test -z "$PYTHON_CPPFLAGS"; then + python_path=`$PYTHON -c "import distutils.sysconfig; \ + print (distutils.sysconfig.get_python_inc ());"` + plat_python_path=`$PYTHON -c "import distutils.sysconfig; \ + print (distutils.sysconfig.get_python_inc (plat_specific=1));"` + if test -n "${python_path}"; then + if test "${plat_python_path}" != "${python_path}"; then + python_path="-I$python_path -I$plat_python_path" + else + python_path="-I$python_path" + fi + fi + PYTHON_CPPFLAGS=$python_path + fi + AC_MSG_RESULT([$PYTHON_CPPFLAGS]) + AC_SUBST([PYTHON_CPPFLAGS]) + + # + # Check for Python library path + # + AC_MSG_CHECKING([for Python library path]) + if test -z "$PYTHON_LIBS"; then + # (makes two attempts to ensure we've got a version number + # from the interpreter) + ac_python_version=`cat<]], + [[Py_Initialize();]]) + ],[pythonexists=yes],[pythonexists=no]) + AC_LANG_POP([C]) + # turn back to default flags + CPPFLAGS="$ac_save_CPPFLAGS" + LIBS="$ac_save_LIBS" + LDFLAGS="$ac_save_LDFLAGS" + + AC_MSG_RESULT([$pythonexists]) + + if test ! "x$pythonexists" = "xyes"; then + AC_MSG_FAILURE([ + Could not link test program to Python. Maybe the main Python library has been + installed in some non-standard library path. If so, pass it to configure, + via the LIBS environment variable. + Example: ./configure LIBS="-L/usr/non-standard-path/python/lib" + ============================================================================ + ERROR! + You probably have to install the development version of the Python package + for your distribution. The exact name of this package varies among them. + ============================================================================ + ]) + PYTHON_VERSION="" + fi + + # + # all done! + # +]) diff --git a/m4/gettext-lib.m4 b/m4/gettext-lib.m4 new file mode 100644 index 0000000..0c081b6 --- /dev/null +++ b/m4/gettext-lib.m4 @@ -0,0 +1,818 @@ +## concatentation of files in gettext-0.14.5/autoconf-lib-link/m4 +# lib-ld.m4 serial 3 (gettext-0.13) +dnl Copyright (C) 1996-2003 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +dnl Subroutines of libtool.m4, +dnl with replacements s/AC_/AC_LIB/ and s/lt_cv/acl_cv/ to avoid collision +dnl with libtool.m4. + +dnl From libtool-1.4. Sets the variable with_gnu_ld to yes or no. +AC_DEFUN([AC_LIB_PROG_LD_GNU], +[AC_CACHE_CHECK([if the linker ($LD) is GNU ld], acl_cv_prog_gnu_ld, +[# I'd rather use --version here, but apparently some GNU ld's only accept -v. +case `$LD -v 2>&1 @])], +test "$withval" = no || with_gnu_ld=yes, with_gnu_ld=no) +AC_REQUIRE([AC_PROG_CC])dnl +AC_REQUIRE([AC_CANONICAL_HOST])dnl +# Prepare PATH_SEPARATOR. +# The user is always right. +if test "${PATH_SEPARATOR+set}" != set; then + echo "#! /bin/sh" >conf$$.sh + echo "exit 0" >>conf$$.sh + chmod +x conf$$.sh + if (PATH="/nonexistent;."; conf$$.sh) >/dev/null 2>&1; then + PATH_SEPARATOR=';' + else + PATH_SEPARATOR=: + fi + rm -f conf$$.sh +fi +ac_prog=ld +if test "$GCC" = yes; then + # Check if gcc -print-prog-name=ld gives a path. + AC_MSG_CHECKING([for ld used by GCC]) + case $host in + *-*-mingw*) + # gcc leaves a trailing carriage return which upsets mingw + ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;; + *) + ac_prog=`($CC -print-prog-name=ld) 2>&5` ;; + esac + case $ac_prog in + # Accept absolute paths. + [[\\/]* | [A-Za-z]:[\\/]*)] + [re_direlt='/[^/][^/]*/\.\./'] + # Canonicalize the path of ld + ac_prog=`echo $ac_prog| sed 's%\\\\%/%g'` + while echo $ac_prog | grep "$re_direlt" > /dev/null 2>&1; do + ac_prog=`echo $ac_prog| sed "s%$re_direlt%/%"` + done + test -z "$LD" && LD="$ac_prog" + ;; + "") + # If it fails, then pretend we aren't using GCC. + ac_prog=ld + ;; + *) + # If it is relative, then search for the first ld in PATH. + with_gnu_ld=unknown + ;; + esac +elif test "$with_gnu_ld" = yes; then + AC_MSG_CHECKING([for GNU ld]) +else + AC_MSG_CHECKING([for non-GNU ld]) +fi +AC_CACHE_VAL(acl_cv_path_LD, +[if test -z "$LD"; then + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}${PATH_SEPARATOR-:}" + for ac_dir in $PATH; do + test -z "$ac_dir" && ac_dir=. + if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then + acl_cv_path_LD="$ac_dir/$ac_prog" + # Check to see if the program is GNU ld. I'd rather use --version, + # but apparently some GNU ld's only accept -v. + # Break only if it was the GNU/non-GNU ld that we prefer. + case `"$acl_cv_path_LD" -v 2>&1 < /dev/null` in + *GNU* | *'with BFD'*) + test "$with_gnu_ld" != no && break ;; + *) + test "$with_gnu_ld" != yes && break ;; + esac + fi + done + IFS="$ac_save_ifs" +else + acl_cv_path_LD="$LD" # Let the user override the test with a path. +fi]) +LD="$acl_cv_path_LD" +if test -n "$LD"; then + AC_MSG_RESULT($LD) +else + AC_MSG_RESULT(no) +fi +test -z "$LD" && AC_MSG_ERROR([no acceptable ld found in \$PATH]) +AC_LIB_PROG_LD_GNU +]) +# lib-link.m4 serial 6 (gettext-0.14.3) +dnl Copyright (C) 2001-2005 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +dnl From Bruno Haible. + +AC_PREREQ(2.50) + +dnl AC_LIB_LINKFLAGS(name [, dependencies]) searches for libname and +dnl the libraries corresponding to explicit and implicit dependencies. +dnl Sets and AC_SUBSTs the LIB${NAME} and LTLIB${NAME} variables and +dnl augments the CPPFLAGS variable. +AC_DEFUN([AC_LIB_LINKFLAGS], +[ + AC_REQUIRE([AC_LIB_PREPARE_PREFIX]) +dnl AC_REQUIRE([AC_LIB_RPATH]) + define([Name],[translit([$1],[./-], [___])]) + define([NAME],[translit([$1],[abcdefghijklmnopqrstuvwxyz./-], + [ABCDEFGHIJKLMNOPQRSTUVWXYZ___])]) + AC_CACHE_CHECK([how to link with lib[]$1], [ac_cv_lib[]Name[]_libs], [ + AC_LIB_LINKFLAGS_BODY([$1], [$2]) + ac_cv_lib[]Name[]_libs="$LIB[]NAME" + ac_cv_lib[]Name[]_ltlibs="$LTLIB[]NAME" + ac_cv_lib[]Name[]_cppflags="$INC[]NAME" + ]) + LIB[]NAME="$ac_cv_lib[]Name[]_libs" + LTLIB[]NAME="$ac_cv_lib[]Name[]_ltlibs" + INC[]NAME="$ac_cv_lib[]Name[]_cppflags" + AC_LIB_APPENDTOVAR([CPPFLAGS], [$INC]NAME) + AC_SUBST([LIB]NAME) + AC_SUBST([LTLIB]NAME) + dnl Also set HAVE_LIB[]NAME so that AC_LIB_HAVE_LINKFLAGS can reuse the + dnl results of this search when this library appears as a dependency. + HAVE_LIB[]NAME=yes + undefine([Name]) + undefine([NAME]) +]) + +dnl AC_LIB_HAVE_LINKFLAGS(name, dependencies, includes, testcode) +dnl searches for libname and the libraries corresponding to explicit and +dnl implicit dependencies, together with the specified include files and +dnl the ability to compile and link the specified testcode. If found, it +dnl sets and AC_SUBSTs HAVE_LIB${NAME}=yes and the LIB${NAME} and +dnl LTLIB${NAME} variables and augments the CPPFLAGS variable, and +dnl #defines HAVE_LIB${NAME} to 1. Otherwise, it sets and AC_SUBSTs +dnl HAVE_LIB${NAME}=no and LIB${NAME} and LTLIB${NAME} to empty. +AC_DEFUN([AC_LIB_HAVE_LINKFLAGS], +[ + AC_REQUIRE([AC_LIB_PREPARE_PREFIX]) +dnl AC_REQUIRE([AC_LIB_RPATH]) + define([Name],[translit([$1],[./-], [___])]) + define([NAME],[translit([$1],[abcdefghijklmnopqrstuvwxyz./-], + [ABCDEFGHIJKLMNOPQRSTUVWXYZ___])]) + + dnl Search for lib[]Name and define LIB[]NAME, LTLIB[]NAME and INC[]NAME + dnl accordingly. + AC_LIB_LINKFLAGS_BODY([$1], [$2]) + + dnl Add $INC[]NAME to CPPFLAGS before performing the following checks, + dnl because if the user has installed lib[]Name and not disabled its use + dnl via --without-lib[]Name-prefix, he wants to use it. + ac_save_CPPFLAGS="$CPPFLAGS" + AC_LIB_APPENDTOVAR([CPPFLAGS], [$INC]NAME) + + AC_CACHE_CHECK([for lib[]$1], [ac_cv_lib[]Name], [ + ac_save_LIBS="$LIBS" + LIBS="$LIBS $LIB[]NAME" + AC_TRY_LINK([$3], [$4], [ac_cv_lib[]Name=yes], [ac_cv_lib[]Name=no]) + LIBS="$ac_save_LIBS" + ]) + if test "$ac_cv_lib[]Name" = yes; then + HAVE_LIB[]NAME=yes + AC_DEFINE([HAVE_LIB]NAME, 1, [Define if you have the $1 library.]) + AC_MSG_CHECKING([how to link with lib[]$1]) + AC_MSG_RESULT([$LIB[]NAME]) + else + HAVE_LIB[]NAME=no + dnl If $LIB[]NAME didn't lead to a usable library, we don't need + dnl $INC[]NAME either. + CPPFLAGS="$ac_save_CPPFLAGS" + LIB[]NAME= + LTLIB[]NAME= + fi + AC_SUBST([HAVE_LIB]NAME) + AC_SUBST([LIB]NAME) + AC_SUBST([LTLIB]NAME) + undefine([Name]) + undefine([NAME]) +]) + +dnl Determine the platform dependent parameters needed to use rpath: +dnl libext, shlibext, hardcode_libdir_flag_spec, hardcode_libdir_separator, +dnl hardcode_direct, hardcode_minus_L. +AC_DEFUN([AC_LIB_RPATH], +[ + dnl Tell automake >= 1.10 to complain if config.rpath is missing. + m4_ifdef([AC_REQUIRE_AUX_FILE], [AC_REQUIRE_AUX_FILE([config.rpath])]) + AC_REQUIRE([AC_PROG_CC]) dnl we use $CC, $GCC, $LDFLAGS +dnl AC_REQUIRE([AC_LIB_PROG_LD]) dnl we use $LD, $with_gnu_ld + AC_REQUIRE([AC_CANONICAL_HOST]) dnl we use $host + AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT]) dnl we use $ac_aux_dir + AC_CACHE_CHECK([for shared library run path origin], acl_cv_rpath, [ + CC="$CC" GCC="$GCC" LDFLAGS="$LDFLAGS" LD="$LD" with_gnu_ld="$with_gnu_ld" \ + ${CONFIG_SHELL-/bin/sh} "$ac_aux_dir/config.rpath" "$host" > conftest.sh + . ./conftest.sh + rm -f ./conftest.sh + acl_cv_rpath=done + ]) + wl="$acl_cv_wl" + libext="$acl_cv_libext" + shlibext="$acl_cv_shlibext" + hardcode_libdir_flag_spec="$acl_cv_hardcode_libdir_flag_spec" + hardcode_libdir_separator="$acl_cv_hardcode_libdir_separator" + hardcode_direct="$acl_cv_hardcode_direct" + hardcode_minus_L="$acl_cv_hardcode_minus_L" + dnl Determine whether the user wants rpath handling at all. +dnl AC_ARG_ENABLE(rpath, +dnl [ --disable-rpath do not hardcode runtime library paths], +dnl :, enable_rpath=yes) +]) + +dnl AC_LIB_LINKFLAGS_BODY(name [, dependencies]) searches for libname and +dnl the libraries corresponding to explicit and implicit dependencies. +dnl Sets the LIB${NAME}, LTLIB${NAME} and INC${NAME} variables. +AC_DEFUN([AC_LIB_LINKFLAGS_BODY], +[ + define([NAME],[translit([$1],[abcdefghijklmnopqrstuvwxyz./-], + [ABCDEFGHIJKLMNOPQRSTUVWXYZ___])]) + dnl By default, look in $includedir and $libdir. + use_additional=yes + AC_LIB_WITH_FINAL_PREFIX([ + eval additional_includedir=\"$includedir\" + eval additional_libdir=\"$libdir\" + ]) + AC_LIB_ARG_WITH([lib$1-prefix], +[ --with-lib$1-prefix[=DIR] search for lib$1 in DIR/include and DIR/lib + --without-lib$1-prefix don't search for lib$1 in includedir and libdir], +[ + if test "X$withval" = "Xno"; then + use_additional=no + else + if test "X$withval" = "X"; then + AC_LIB_WITH_FINAL_PREFIX([ + eval additional_includedir=\"$includedir\" + eval additional_libdir=\"$libdir\" + ]) + else + additional_includedir="$withval/include" + additional_libdir="$withval/lib" + fi + fi +]) + dnl Search the library and its dependencies in $additional_libdir and + dnl $LDFLAGS. Using breadth-first-seach. + LIB[]NAME= + LTLIB[]NAME= + INC[]NAME= + rpathdirs= + ltrpathdirs= + names_already_handled= + names_next_round='$1 $2' + while test -n "$names_next_round"; do + names_this_round="$names_next_round" + names_next_round= + for name in $names_this_round; do + already_handled= + for n in $names_already_handled; do + if test "$n" = "$name"; then + already_handled=yes + break + fi + done + if test -z "$already_handled"; then + names_already_handled="$names_already_handled $name" + dnl See if it was already located by an earlier AC_LIB_LINKFLAGS + dnl or AC_LIB_HAVE_LINKFLAGS call. + uppername=`echo "$name" | sed -e 'y|abcdefghijklmnopqrstuvwxyz./-|ABCDEFGHIJKLMNOPQRSTUVWXYZ___|'` + eval value=\"\$HAVE_LIB$uppername\" + if test -n "$value"; then + if test "$value" = yes; then + eval value=\"\$LIB$uppername\" + test -z "$value" || LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$value" + eval value=\"\$LTLIB$uppername\" + test -z "$value" || LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }$value" + else + dnl An earlier call to AC_LIB_HAVE_LINKFLAGS has determined + dnl that this library doesn't exist. So just drop it. + : + fi + else + dnl Search the library lib$name in $additional_libdir and $LDFLAGS + dnl and the already constructed $LIBNAME/$LTLIBNAME. + found_dir= + found_la= + found_so= + found_a= + if test $use_additional = yes; then + if test -n "$shlibext" && test -f "$additional_libdir/lib$name.$shlibext"; then + found_dir="$additional_libdir" + found_so="$additional_libdir/lib$name.$shlibext" + if test -f "$additional_libdir/lib$name.la"; then + found_la="$additional_libdir/lib$name.la" + fi + else + if test -f "$additional_libdir/lib$name.$libext"; then + found_dir="$additional_libdir" + found_a="$additional_libdir/lib$name.$libext" + if test -f "$additional_libdir/lib$name.la"; then + found_la="$additional_libdir/lib$name.la" + fi + fi + fi + fi + if test "X$found_dir" = "X"; then + for x in $LDFLAGS $LTLIB[]NAME; do + AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) + case "$x" in + -L*) + dir=`echo "X$x" | sed -e 's/^X-L//'` + if test -n "$shlibext" && test -f "$dir/lib$name.$shlibext"; then + found_dir="$dir" + found_so="$dir/lib$name.$shlibext" + if test -f "$dir/lib$name.la"; then + found_la="$dir/lib$name.la" + fi + else + if test -f "$dir/lib$name.$libext"; then + found_dir="$dir" + found_a="$dir/lib$name.$libext" + if test -f "$dir/lib$name.la"; then + found_la="$dir/lib$name.la" + fi + fi + fi + ;; + esac + if test "X$found_dir" != "X"; then + break + fi + done + fi + if test "X$found_dir" != "X"; then + dnl Found the library. + LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-L$found_dir -l$name" + if test "X$found_so" != "X"; then + dnl Linking with a shared library. We attempt to hardcode its + dnl directory into the executable's runpath, unless it's the + dnl standard /usr/lib. + if test "$enable_rpath" = no || test "X$found_dir" = "X/usr/lib"; then + dnl No hardcoding is needed. + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so" + else + dnl Use an explicit option to hardcode DIR into the resulting + dnl binary. + dnl Potentially add DIR to ltrpathdirs. + dnl The ltrpathdirs will be appended to $LTLIBNAME at the end. + haveit= + for x in $ltrpathdirs; do + if test "X$x" = "X$found_dir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + ltrpathdirs="$ltrpathdirs $found_dir" + fi + dnl The hardcoding into $LIBNAME is system dependent. + if test "$hardcode_direct" = yes; then + dnl Using DIR/libNAME.so during linking hardcodes DIR into the + dnl resulting binary. + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so" + else + if test -n "$hardcode_libdir_flag_spec" && test "$hardcode_minus_L" = no; then + dnl Use an explicit option to hardcode DIR into the resulting + dnl binary. + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so" + dnl Potentially add DIR to rpathdirs. + dnl The rpathdirs will be appended to $LIBNAME at the end. + haveit= + for x in $rpathdirs; do + if test "X$x" = "X$found_dir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + rpathdirs="$rpathdirs $found_dir" + fi + else + dnl Rely on "-L$found_dir". + dnl But don't add it if it's already contained in the LDFLAGS + dnl or the already constructed $LIBNAME + haveit= + for x in $LDFLAGS $LIB[]NAME; do + AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) + if test "X$x" = "X-L$found_dir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-L$found_dir" + fi + if test "$hardcode_minus_L" != no; then + dnl FIXME: Not sure whether we should use + dnl "-L$found_dir -l$name" or "-L$found_dir $found_so" + dnl here. + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so" + else + dnl We cannot use $hardcode_runpath_var and LD_RUN_PATH + dnl here, because this doesn't fit in flags passed to the + dnl compiler. So give up. No hardcoding. This affects only + dnl very old systems. + dnl FIXME: Not sure whether we should use + dnl "-L$found_dir -l$name" or "-L$found_dir $found_so" + dnl here. + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-l$name" + fi + fi + fi + fi + else + if test "X$found_a" != "X"; then + dnl Linking with a static library. + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_a" + else + dnl We shouldn't come here, but anyway it's good to have a + dnl fallback. + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-L$found_dir -l$name" + fi + fi + dnl Assume the include files are nearby. + additional_includedir= + case "$found_dir" in + */lib | */lib/) + basedir=`echo "X$found_dir" | sed -e 's,^X,,' -e 's,/lib/*$,,'` + additional_includedir="$basedir/include" + ;; + esac + if test "X$additional_includedir" != "X"; then + dnl Potentially add $additional_includedir to $INCNAME. + dnl But don't add it + dnl 1. if it's the standard /usr/include, + dnl 2. if it's /usr/local/include and we are using GCC on Linux, + dnl 3. if it's already present in $CPPFLAGS or the already + dnl constructed $INCNAME, + dnl 4. if it doesn't exist as a directory. + if test "X$additional_includedir" != "X/usr/include"; then + haveit= + if test "X$additional_includedir" = "X/usr/local/include"; then + if test -n "$GCC"; then + case $host_os in + linux* | gnu* | k*bsd*-gnu) haveit=yes;; + esac + fi + fi + if test -z "$haveit"; then + for x in $CPPFLAGS $INC[]NAME; do + AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) + if test "X$x" = "X-I$additional_includedir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + if test -d "$additional_includedir"; then + dnl Really add $additional_includedir to $INCNAME. + INC[]NAME="${INC[]NAME}${INC[]NAME:+ }-I$additional_includedir" + fi + fi + fi + fi + fi + dnl Look for dependencies. + if test -n "$found_la"; then + dnl Read the .la file. It defines the variables + dnl dlname, library_names, old_library, dependency_libs, current, + dnl age, revision, installed, dlopen, dlpreopen, libdir. + save_libdir="$libdir" + case "$found_la" in + */* | *\\*) . "$found_la" ;; + *) . "./$found_la" ;; + esac + libdir="$save_libdir" + dnl We use only dependency_libs. + for dep in $dependency_libs; do + case "$dep" in + -L*) + additional_libdir=`echo "X$dep" | sed -e 's/^X-L//'` + dnl Potentially add $additional_libdir to $LIBNAME and $LTLIBNAME. + dnl But don't add it + dnl 1. if it's the standard /usr/lib, + dnl 2. if it's /usr/local/lib and we are using GCC on Linux, + dnl 3. if it's already present in $LDFLAGS or the already + dnl constructed $LIBNAME, + dnl 4. if it doesn't exist as a directory. + if test "X$additional_libdir" != "X/usr/lib"; then + haveit= + if test "X$additional_libdir" = "X/usr/local/lib"; then + if test -n "$GCC"; then + case $host_os in + linux* | gnu* | k*bsd*-gnu) haveit=yes;; + esac + fi + fi + if test -z "$haveit"; then + haveit= + for x in $LDFLAGS $LIB[]NAME; do + AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) + if test "X$x" = "X-L$additional_libdir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + if test -d "$additional_libdir"; then + dnl Really add $additional_libdir to $LIBNAME. + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-L$additional_libdir" + fi + fi + haveit= + for x in $LDFLAGS $LTLIB[]NAME; do + AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) + if test "X$x" = "X-L$additional_libdir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + if test -d "$additional_libdir"; then + dnl Really add $additional_libdir to $LTLIBNAME. + LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-L$additional_libdir" + fi + fi + fi + fi + ;; + -R*) + dir=`echo "X$dep" | sed -e 's/^X-R//'` + if test "$enable_rpath" != no; then + dnl Potentially add DIR to rpathdirs. + dnl The rpathdirs will be appended to $LIBNAME at the end. + haveit= + for x in $rpathdirs; do + if test "X$x" = "X$dir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + rpathdirs="$rpathdirs $dir" + fi + dnl Potentially add DIR to ltrpathdirs. + dnl The ltrpathdirs will be appended to $LTLIBNAME at the end. + haveit= + for x in $ltrpathdirs; do + if test "X$x" = "X$dir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + ltrpathdirs="$ltrpathdirs $dir" + fi + fi + ;; + -l*) + dnl Handle this in the next round. + names_next_round="$names_next_round "`echo "X$dep" | sed -e 's/^X-l//'` + ;; + *.la) + dnl Handle this in the next round. Throw away the .la's + dnl directory; it is already contained in a preceding -L + dnl option. + names_next_round="$names_next_round "`echo "X$dep" | sed -e 's,^X.*/,,' -e 's,^lib,,' -e 's,\.la$,,'` + ;; + *) + dnl Most likely an immediate library name. + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$dep" + LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }$dep" + ;; + esac + done + fi + else + dnl Didn't find the library; assume it is in the system directories + dnl known to the linker and runtime loader. (All the system + dnl directories known to the linker should also be known to the + dnl runtime loader, otherwise the system is severely misconfigured.) + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-l$name" + LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-l$name" + fi + fi + fi + done + done + if test "X$rpathdirs" != "X"; then + if test -n "$hardcode_libdir_separator"; then + dnl Weird platform: only the last -rpath option counts, the user must + dnl pass all path elements in one option. We can arrange that for a + dnl single library, but not when more than one $LIBNAMEs are used. + alldirs= + for found_dir in $rpathdirs; do + alldirs="${alldirs}${alldirs:+$hardcode_libdir_separator}$found_dir" + done + dnl Note: hardcode_libdir_flag_spec uses $libdir and $wl. + acl_save_libdir="$libdir" + libdir="$alldirs" + eval flag=\"$hardcode_libdir_flag_spec\" + libdir="$acl_save_libdir" + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$flag" + else + dnl The -rpath options are cumulative. + for found_dir in $rpathdirs; do + acl_save_libdir="$libdir" + libdir="$found_dir" + eval flag=\"$hardcode_libdir_flag_spec\" + libdir="$acl_save_libdir" + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$flag" + done + fi + fi + if test "X$ltrpathdirs" != "X"; then + dnl When using libtool, the option that works for both libraries and + dnl executables is -R. The -R options are cumulative. + for found_dir in $ltrpathdirs; do + LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-R$found_dir" + done + fi +]) + +dnl AC_LIB_APPENDTOVAR(VAR, CONTENTS) appends the elements of CONTENTS to VAR, +dnl unless already present in VAR. +dnl Works only for CPPFLAGS, not for LIB* variables because that sometimes +dnl contains two or three consecutive elements that belong together. +AC_DEFUN([AC_LIB_APPENDTOVAR], +[ + for element in [$2]; do + haveit= + for x in $[$1]; do + AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) + if test "X$x" = "X$element"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + [$1]="${[$1]}${[$1]:+ }$element" + fi + done +]) +# lib-prefix.m4 serial 4 (gettext-0.14.2) +dnl Copyright (C) 2001-2005 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +dnl From Bruno Haible. + +dnl AC_LIB_ARG_WITH is synonymous to AC_ARG_WITH in autoconf-2.13, and +dnl similar to AC_ARG_WITH in autoconf 2.52...2.57 except that is doesn't +dnl require excessive bracketing. +ifdef([AC_HELP_STRING], +[AC_DEFUN([AC_LIB_ARG_WITH], [AC_ARG_WITH([$1],[[$2]],[$3],[$4])])], +[AC_DEFUN([AC_][LIB_ARG_WITH], [AC_ARG_WITH([$1],[$2],[$3],[$4])])]) + +dnl AC_LIB_PREFIX adds to the CPPFLAGS and LDFLAGS the flags that are needed +dnl to access previously installed libraries. The basic assumption is that +dnl a user will want packages to use other packages he previously installed +dnl with the same --prefix option. +dnl This macro is not needed if only AC_LIB_LINKFLAGS is used to locate +dnl libraries, but is otherwise very convenient. +AC_DEFUN([AC_LIB_PREFIX], +[ + AC_BEFORE([$0], [AC_LIB_LINKFLAGS]) + AC_REQUIRE([AC_PROG_CC]) + AC_REQUIRE([AC_CANONICAL_HOST]) + AC_REQUIRE([AC_LIB_PREPARE_PREFIX]) + dnl By default, look in $includedir and $libdir. + use_additional=yes + AC_LIB_WITH_FINAL_PREFIX([ + eval additional_includedir=\"$includedir\" + eval additional_libdir=\"$libdir\" + ]) + AC_LIB_ARG_WITH([lib-prefix], +[ --with-lib-prefix[=DIR] search for libraries in DIR/include and DIR/lib + --without-lib-prefix don't search for libraries in includedir and libdir], +[ + if test "X$withval" = "Xno"; then + use_additional=no + else + if test "X$withval" = "X"; then + AC_LIB_WITH_FINAL_PREFIX([ + eval additional_includedir=\"$includedir\" + eval additional_libdir=\"$libdir\" + ]) + else + additional_includedir="$withval/include" + additional_libdir="$withval/lib" + fi + fi +]) + if test $use_additional = yes; then + dnl Potentially add $additional_includedir to $CPPFLAGS. + dnl But don't add it + dnl 1. if it's the standard /usr/include, + dnl 2. if it's already present in $CPPFLAGS, + dnl 3. if it's /usr/local/include and we are using GCC on Linux, + dnl 4. if it doesn't exist as a directory. + if test "X$additional_includedir" != "X/usr/include"; then + haveit= + for x in $CPPFLAGS; do + AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) + if test "X$x" = "X-I$additional_includedir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + if test "X$additional_includedir" = "X/usr/local/include"; then + if test -n "$GCC"; then + case $host_os in + linux* | gnu* | k*bsd*-gnu) haveit=yes;; + esac + fi + fi + if test -z "$haveit"; then + if test -d "$additional_includedir"; then + dnl Really add $additional_includedir to $CPPFLAGS. + CPPFLAGS="${CPPFLAGS}${CPPFLAGS:+ }-I$additional_includedir" + fi + fi + fi + fi + dnl Potentially add $additional_libdir to $LDFLAGS. + dnl But don't add it + dnl 1. if it's the standard /usr/lib, + dnl 2. if it's already present in $LDFLAGS, + dnl 3. if it's /usr/local/lib and we are using GCC on Linux, + dnl 4. if it doesn't exist as a directory. + if test "X$additional_libdir" != "X/usr/lib"; then + haveit= + for x in $LDFLAGS; do + AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) + if test "X$x" = "X-L$additional_libdir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + if test "X$additional_libdir" = "X/usr/local/lib"; then + if test -n "$GCC"; then + case $host_os in + linux*) haveit=yes;; + esac + fi + fi + if test -z "$haveit"; then + if test -d "$additional_libdir"; then + dnl Really add $additional_libdir to $LDFLAGS. + LDFLAGS="${LDFLAGS}${LDFLAGS:+ }-L$additional_libdir" + fi + fi + fi + fi + fi +]) + +dnl AC_LIB_PREPARE_PREFIX creates variables acl_final_prefix, +dnl acl_final_exec_prefix, containing the values to which $prefix and +dnl $exec_prefix will expand at the end of the configure script. +AC_DEFUN([AC_LIB_PREPARE_PREFIX], +[ + dnl Unfortunately, prefix and exec_prefix get only finally determined + dnl at the end of configure. + if test "X$prefix" = "XNONE"; then + acl_final_prefix="$ac_default_prefix" + else + acl_final_prefix="$prefix" + fi + if test "X$exec_prefix" = "XNONE"; then + acl_final_exec_prefix='${prefix}' + else + acl_final_exec_prefix="$exec_prefix" + fi + acl_save_prefix="$prefix" + prefix="$acl_final_prefix" + eval acl_final_exec_prefix=\"$acl_final_exec_prefix\" + prefix="$acl_save_prefix" +]) + +dnl AC_LIB_WITH_FINAL_PREFIX([statement]) evaluates statement, with the +dnl variables prefix and exec_prefix bound to the values they will have +dnl at the end of the configure script. +AC_DEFUN([AC_LIB_WITH_FINAL_PREFIX], +[ + acl_save_prefix="$prefix" + prefix="$acl_final_prefix" + acl_save_exec_prefix="$exec_prefix" + exec_prefix="$acl_final_exec_prefix" + $1 + exec_prefix="$acl_save_exec_prefix" + prefix="$acl_save_prefix" +]) diff --git a/m4/iconv.m4 b/m4/iconv.m4 new file mode 100644 index 0000000..c5f3579 --- /dev/null +++ b/m4/iconv.m4 @@ -0,0 +1,103 @@ +# iconv.m4 serial AM4 (gettext-0.11.3) +dnl Copyright (C) 2000-2002 Free Software Foundation, Inc. +dnl This file is free software, distributed under the terms of the GNU +dnl General Public License. As a special exception to the GNU General +dnl Public License, this file may be distributed as part of a program +dnl that contains a configuration script generated by Autoconf, under +dnl the same distribution terms as the rest of that program. + +dnl From Bruno Haible. + +AC_DEFUN([AM_ICONV_LINKFLAGS_BODY], +[ + dnl Prerequisites of AC_LIB_LINKFLAGS_BODY. + AC_REQUIRE([AC_LIB_PREPARE_PREFIX]) + AC_REQUIRE([AC_LIB_RPATH]) + + dnl Search for libiconv and define LIBICONV, LTLIBICONV and INCICONV + dnl accordingly. + AC_LIB_LINKFLAGS_BODY([iconv]) +]) + +AC_DEFUN([AM_ICONV_LINK], +[ + dnl Some systems have iconv in libc, some have it in libiconv (OSF/1 and + dnl those with the standalone portable GNU libiconv installed). + + dnl Search for libiconv and define LIBICONV, LTLIBICONV and INCICONV + dnl accordingly. + AC_REQUIRE([AM_ICONV_LINKFLAGS_BODY]) + + dnl Add $INCICONV to CPPFLAGS before performing the following checks, + dnl because if the user has installed libiconv and not disabled its use + dnl via --without-libiconv-prefix, he wants to use it. The first + dnl AC_TRY_LINK will then fail, the second AC_TRY_LINK will succeed. + am_save_CPPFLAGS="$CPPFLAGS" + AC_LIB_APPENDTOVAR([CPPFLAGS], [$INCICONV]) + + AC_CACHE_CHECK(for iconv, am_cv_func_iconv, [ + am_cv_func_iconv="no, consider installing GNU libiconv" + am_cv_lib_iconv=no + AC_TRY_LINK([#include +#include ], + [iconv_t cd = iconv_open("",""); + iconv(cd,NULL,NULL,NULL,NULL); + iconv_close(cd);], + am_cv_func_iconv=yes) + if test "$am_cv_func_iconv" != yes; then + am_save_LIBS="$LIBS" + LIBS="$LIBS $LIBICONV" + AC_TRY_LINK([#include +#include ], + [iconv_t cd = iconv_open("",""); + iconv(cd,NULL,NULL,NULL,NULL); + iconv_close(cd);], + am_cv_lib_iconv=yes + am_cv_func_iconv=yes) + LIBS="$am_save_LIBS" + fi + ]) + if test "$am_cv_func_iconv" = yes; then + AC_DEFINE(HAVE_ICONV, 1, [Define if you have the iconv() function.]) + fi + if test "$am_cv_lib_iconv" = yes; then + AC_MSG_CHECKING([how to link with libiconv]) + AC_MSG_RESULT([$LIBICONV]) + else + dnl If $LIBICONV didn't lead to a usable library, we don't need $INCICONV + dnl either. + CPPFLAGS="$am_save_CPPFLAGS" + LIBICONV= + LTLIBICONV= + fi + AC_SUBST(LIBICONV) + AC_SUBST(LTLIBICONV) +]) + +AC_DEFUN([AM_ICONV], +[ + AM_ICONV_LINK + if test "$am_cv_func_iconv" = yes; then + AC_MSG_CHECKING([for iconv declaration]) + AC_CACHE_VAL(am_cv_proto_iconv, [ + AC_TRY_COMPILE([ +#include +#include +extern +#ifdef __cplusplus +"C" +#endif +#if defined(__STDC__) || defined(__cplusplus) +size_t iconv (iconv_t cd, char * *inbuf, size_t *inbytesleft, char * *outbuf, size_t *outbytesleft); +#else +size_t iconv(); +#endif +], [], am_cv_proto_iconv_arg1="", am_cv_proto_iconv_arg1="const") + am_cv_proto_iconv="extern size_t iconv (iconv_t cd, $am_cv_proto_iconv_arg1 char * *inbuf, size_t *inbytesleft, char * *outbuf, size_t *outbytesleft);"]) + am_cv_proto_iconv=`echo "[$]am_cv_proto_iconv" | tr -s ' ' | sed -e 's/( /(/'` + AC_MSG_RESULT([$]{ac_t:- + }[$]am_cv_proto_iconv) + AC_DEFINE_UNQUOTED(ICONV_CONST, $am_cv_proto_iconv_arg1, + [Define as const if the declaration of iconv() needs const.]) + fi +]) diff --git a/m4/libtool.m4 b/m4/libtool.m4 new file mode 100644 index 0000000..a3bc337 --- /dev/null +++ b/m4/libtool.m4 @@ -0,0 +1,8369 @@ +# libtool.m4 - Configure libtool for the host system. -*-Autoconf-*- +# +# Copyright (C) 1996-2001, 2003-2015 Free Software Foundation, Inc. +# Written by Gordon Matzigkeit, 1996 +# +# This file is free software; the Free Software Foundation gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. + +m4_define([_LT_COPYING], [dnl +# Copyright (C) 2014 Free Software Foundation, Inc. +# This is free software; see the source for copying conditions. There is NO +# warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +# GNU Libtool is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of of the License, or +# (at your option) any later version. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program or library that is built +# using GNU Libtool, you may include this file under the same +# distribution terms that you use for the rest of that program. +# +# GNU Libtool is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +]) + +# serial 58 LT_INIT + + +# LT_PREREQ(VERSION) +# ------------------ +# Complain and exit if this libtool version is less that VERSION. +m4_defun([LT_PREREQ], +[m4_if(m4_version_compare(m4_defn([LT_PACKAGE_VERSION]), [$1]), -1, + [m4_default([$3], + [m4_fatal([Libtool version $1 or higher is required], + 63)])], + [$2])]) + + +# _LT_CHECK_BUILDDIR +# ------------------ +# Complain if the absolute build directory name contains unusual characters +m4_defun([_LT_CHECK_BUILDDIR], +[case `pwd` in + *\ * | *\ *) + AC_MSG_WARN([Libtool does not cope well with whitespace in `pwd`]) ;; +esac +]) + + +# LT_INIT([OPTIONS]) +# ------------------ +AC_DEFUN([LT_INIT], +[AC_PREREQ([2.62])dnl We use AC_PATH_PROGS_FEATURE_CHECK +AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT])dnl +AC_BEFORE([$0], [LT_LANG])dnl +AC_BEFORE([$0], [LT_OUTPUT])dnl +AC_BEFORE([$0], [LTDL_INIT])dnl +m4_require([_LT_CHECK_BUILDDIR])dnl + +dnl Autoconf doesn't catch unexpanded LT_ macros by default: +m4_pattern_forbid([^_?LT_[A-Z_]+$])dnl +m4_pattern_allow([^(_LT_EOF|LT_DLGLOBAL|LT_DLLAZY_OR_NOW|LT_MULTI_MODULE)$])dnl +dnl aclocal doesn't pull ltoptions.m4, ltsugar.m4, or ltversion.m4 +dnl unless we require an AC_DEFUNed macro: +AC_REQUIRE([LTOPTIONS_VERSION])dnl +AC_REQUIRE([LTSUGAR_VERSION])dnl +AC_REQUIRE([LTVERSION_VERSION])dnl +AC_REQUIRE([LTOBSOLETE_VERSION])dnl +m4_require([_LT_PROG_LTMAIN])dnl + +_LT_SHELL_INIT([SHELL=${CONFIG_SHELL-/bin/sh}]) + +dnl Parse OPTIONS +_LT_SET_OPTIONS([$0], [$1]) + +# This can be used to rebuild libtool when needed +LIBTOOL_DEPS=$ltmain + +# Always use our own libtool. +LIBTOOL='$(SHELL) $(top_builddir)/libtool' +AC_SUBST(LIBTOOL)dnl + +_LT_SETUP + +# Only expand once: +m4_define([LT_INIT]) +])# LT_INIT + +# Old names: +AU_ALIAS([AC_PROG_LIBTOOL], [LT_INIT]) +AU_ALIAS([AM_PROG_LIBTOOL], [LT_INIT]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_PROG_LIBTOOL], []) +dnl AC_DEFUN([AM_PROG_LIBTOOL], []) + + +# _LT_PREPARE_CC_BASENAME +# ----------------------- +m4_defun([_LT_PREPARE_CC_BASENAME], [ +# Calculate cc_basename. Skip known compiler wrappers and cross-prefix. +func_cc_basename () +{ + for cc_temp in @S|@*""; do + case $cc_temp in + compile | *[[\\/]]compile | ccache | *[[\\/]]ccache ) ;; + distcc | *[[\\/]]distcc | purify | *[[\\/]]purify ) ;; + \-*) ;; + *) break;; + esac + done + func_cc_basename_result=`$ECHO "$cc_temp" | $SED "s%.*/%%; s%^$host_alias-%%"` +} +])# _LT_PREPARE_CC_BASENAME + + +# _LT_CC_BASENAME(CC) +# ------------------- +# It would be clearer to call AC_REQUIREs from _LT_PREPARE_CC_BASENAME, +# but that macro is also expanded into generated libtool script, which +# arranges for $SED and $ECHO to be set by different means. +m4_defun([_LT_CC_BASENAME], +[m4_require([_LT_PREPARE_CC_BASENAME])dnl +AC_REQUIRE([_LT_DECL_SED])dnl +AC_REQUIRE([_LT_PROG_ECHO_BACKSLASH])dnl +func_cc_basename $1 +cc_basename=$func_cc_basename_result +]) + + +# _LT_FILEUTILS_DEFAULTS +# ---------------------- +# It is okay to use these file commands and assume they have been set +# sensibly after 'm4_require([_LT_FILEUTILS_DEFAULTS])'. +m4_defun([_LT_FILEUTILS_DEFAULTS], +[: ${CP="cp -f"} +: ${MV="mv -f"} +: ${RM="rm -f"} +])# _LT_FILEUTILS_DEFAULTS + + +# _LT_SETUP +# --------- +m4_defun([_LT_SETUP], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +AC_REQUIRE([AC_CANONICAL_BUILD])dnl +AC_REQUIRE([_LT_PREPARE_SED_QUOTE_VARS])dnl +AC_REQUIRE([_LT_PROG_ECHO_BACKSLASH])dnl + +_LT_DECL([], [PATH_SEPARATOR], [1], [The PATH separator for the build system])dnl +dnl +_LT_DECL([], [host_alias], [0], [The host system])dnl +_LT_DECL([], [host], [0])dnl +_LT_DECL([], [host_os], [0])dnl +dnl +_LT_DECL([], [build_alias], [0], [The build system])dnl +_LT_DECL([], [build], [0])dnl +_LT_DECL([], [build_os], [0])dnl +dnl +AC_REQUIRE([AC_PROG_CC])dnl +AC_REQUIRE([LT_PATH_LD])dnl +AC_REQUIRE([LT_PATH_NM])dnl +dnl +AC_REQUIRE([AC_PROG_LN_S])dnl +test -z "$LN_S" && LN_S="ln -s" +_LT_DECL([], [LN_S], [1], [Whether we need soft or hard links])dnl +dnl +AC_REQUIRE([LT_CMD_MAX_LEN])dnl +_LT_DECL([objext], [ac_objext], [0], [Object file suffix (normally "o")])dnl +_LT_DECL([], [exeext], [0], [Executable file suffix (normally "")])dnl +dnl +m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_CHECK_SHELL_FEATURES])dnl +m4_require([_LT_PATH_CONVERSION_FUNCTIONS])dnl +m4_require([_LT_CMD_RELOAD])dnl +m4_require([_LT_CHECK_MAGIC_METHOD])dnl +m4_require([_LT_CHECK_SHAREDLIB_FROM_LINKLIB])dnl +m4_require([_LT_CMD_OLD_ARCHIVE])dnl +m4_require([_LT_CMD_GLOBAL_SYMBOLS])dnl +m4_require([_LT_WITH_SYSROOT])dnl +m4_require([_LT_CMD_TRUNCATE])dnl + +_LT_CONFIG_LIBTOOL_INIT([ +# See if we are running on zsh, and set the options that allow our +# commands through without removal of \ escapes INIT. +if test -n "\${ZSH_VERSION+set}"; then + setopt NO_GLOB_SUBST +fi +]) +if test -n "${ZSH_VERSION+set}"; then + setopt NO_GLOB_SUBST +fi + +_LT_CHECK_OBJDIR + +m4_require([_LT_TAG_COMPILER])dnl + +case $host_os in +aix3*) + # AIX sometimes has problems with the GCC collect2 program. For some + # reason, if we set the COLLECT_NAMES environment variable, the problems + # vanish in a puff of smoke. + if test set != "${COLLECT_NAMES+set}"; then + COLLECT_NAMES= + export COLLECT_NAMES + fi + ;; +esac + +# Global variables: +ofile=libtool +can_build_shared=yes + +# All known linkers require a '.a' archive for static linking (except MSVC, +# which needs '.lib'). +libext=a + +with_gnu_ld=$lt_cv_prog_gnu_ld + +old_CC=$CC +old_CFLAGS=$CFLAGS + +# Set sane defaults for various variables +test -z "$CC" && CC=cc +test -z "$LTCC" && LTCC=$CC +test -z "$LTCFLAGS" && LTCFLAGS=$CFLAGS +test -z "$LD" && LD=ld +test -z "$ac_objext" && ac_objext=o + +_LT_CC_BASENAME([$compiler]) + +# Only perform the check for file, if the check method requires it +test -z "$MAGIC_CMD" && MAGIC_CMD=file +case $deplibs_check_method in +file_magic*) + if test "$file_magic_cmd" = '$MAGIC_CMD'; then + _LT_PATH_MAGIC + fi + ;; +esac + +# Use C for the default configuration in the libtool script +LT_SUPPORTED_TAG([CC]) +_LT_LANG_C_CONFIG +_LT_LANG_DEFAULT_CONFIG +_LT_CONFIG_COMMANDS +])# _LT_SETUP + + +# _LT_PREPARE_SED_QUOTE_VARS +# -------------------------- +# Define a few sed substitution that help us do robust quoting. +m4_defun([_LT_PREPARE_SED_QUOTE_VARS], +[# Backslashify metacharacters that are still active within +# double-quoted strings. +sed_quote_subst='s/\([["`$\\]]\)/\\\1/g' + +# Same as above, but do not quote variable references. +double_quote_subst='s/\([["`\\]]\)/\\\1/g' + +# Sed substitution to delay expansion of an escaped shell variable in a +# double_quote_subst'ed string. +delay_variable_subst='s/\\\\\\\\\\\$/\\\\\\$/g' + +# Sed substitution to delay expansion of an escaped single quote. +delay_single_quote_subst='s/'\''/'\'\\\\\\\'\''/g' + +# Sed substitution to avoid accidental globbing in evaled expressions +no_glob_subst='s/\*/\\\*/g' +]) + +# _LT_PROG_LTMAIN +# --------------- +# Note that this code is called both from 'configure', and 'config.status' +# now that we use AC_CONFIG_COMMANDS to generate libtool. Notably, +# 'config.status' has no value for ac_aux_dir unless we are using Automake, +# so we pass a copy along to make sure it has a sensible value anyway. +m4_defun([_LT_PROG_LTMAIN], +[m4_ifdef([AC_REQUIRE_AUX_FILE], [AC_REQUIRE_AUX_FILE([ltmain.sh])])dnl +_LT_CONFIG_LIBTOOL_INIT([ac_aux_dir='$ac_aux_dir']) +ltmain=$ac_aux_dir/ltmain.sh +])# _LT_PROG_LTMAIN + + +## ------------------------------------- ## +## Accumulate code for creating libtool. ## +## ------------------------------------- ## + +# So that we can recreate a full libtool script including additional +# tags, we accumulate the chunks of code to send to AC_CONFIG_COMMANDS +# in macros and then make a single call at the end using the 'libtool' +# label. + + +# _LT_CONFIG_LIBTOOL_INIT([INIT-COMMANDS]) +# ---------------------------------------- +# Register INIT-COMMANDS to be passed to AC_CONFIG_COMMANDS later. +m4_define([_LT_CONFIG_LIBTOOL_INIT], +[m4_ifval([$1], + [m4_append([_LT_OUTPUT_LIBTOOL_INIT], + [$1 +])])]) + +# Initialize. +m4_define([_LT_OUTPUT_LIBTOOL_INIT]) + + +# _LT_CONFIG_LIBTOOL([COMMANDS]) +# ------------------------------ +# Register COMMANDS to be passed to AC_CONFIG_COMMANDS later. +m4_define([_LT_CONFIG_LIBTOOL], +[m4_ifval([$1], + [m4_append([_LT_OUTPUT_LIBTOOL_COMMANDS], + [$1 +])])]) + +# Initialize. +m4_define([_LT_OUTPUT_LIBTOOL_COMMANDS]) + + +# _LT_CONFIG_SAVE_COMMANDS([COMMANDS], [INIT_COMMANDS]) +# ----------------------------------------------------- +m4_defun([_LT_CONFIG_SAVE_COMMANDS], +[_LT_CONFIG_LIBTOOL([$1]) +_LT_CONFIG_LIBTOOL_INIT([$2]) +]) + + +# _LT_FORMAT_COMMENT([COMMENT]) +# ----------------------------- +# Add leading comment marks to the start of each line, and a trailing +# full-stop to the whole comment if one is not present already. +m4_define([_LT_FORMAT_COMMENT], +[m4_ifval([$1], [ +m4_bpatsubst([m4_bpatsubst([$1], [^ *], [# ])], + [['`$\]], [\\\&])]m4_bmatch([$1], [[!?.]$], [], [.]) +)]) + + + +## ------------------------ ## +## FIXME: Eliminate VARNAME ## +## ------------------------ ## + + +# _LT_DECL([CONFIGNAME], VARNAME, VALUE, [DESCRIPTION], [IS-TAGGED?]) +# ------------------------------------------------------------------- +# CONFIGNAME is the name given to the value in the libtool script. +# VARNAME is the (base) name used in the configure script. +# VALUE may be 0, 1 or 2 for a computed quote escaped value based on +# VARNAME. Any other value will be used directly. +m4_define([_LT_DECL], +[lt_if_append_uniq([lt_decl_varnames], [$2], [, ], + [lt_dict_add_subkey([lt_decl_dict], [$2], [libtool_name], + [m4_ifval([$1], [$1], [$2])]) + lt_dict_add_subkey([lt_decl_dict], [$2], [value], [$3]) + m4_ifval([$4], + [lt_dict_add_subkey([lt_decl_dict], [$2], [description], [$4])]) + lt_dict_add_subkey([lt_decl_dict], [$2], + [tagged?], [m4_ifval([$5], [yes], [no])])]) +]) + + +# _LT_TAGDECL([CONFIGNAME], VARNAME, VALUE, [DESCRIPTION]) +# -------------------------------------------------------- +m4_define([_LT_TAGDECL], [_LT_DECL([$1], [$2], [$3], [$4], [yes])]) + + +# lt_decl_tag_varnames([SEPARATOR], [VARNAME1...]) +# ------------------------------------------------ +m4_define([lt_decl_tag_varnames], +[_lt_decl_filter([tagged?], [yes], $@)]) + + +# _lt_decl_filter(SUBKEY, VALUE, [SEPARATOR], [VARNAME1..]) +# --------------------------------------------------------- +m4_define([_lt_decl_filter], +[m4_case([$#], + [0], [m4_fatal([$0: too few arguments: $#])], + [1], [m4_fatal([$0: too few arguments: $#: $1])], + [2], [lt_dict_filter([lt_decl_dict], [$1], [$2], [], lt_decl_varnames)], + [3], [lt_dict_filter([lt_decl_dict], [$1], [$2], [$3], lt_decl_varnames)], + [lt_dict_filter([lt_decl_dict], $@)])[]dnl +]) + + +# lt_decl_quote_varnames([SEPARATOR], [VARNAME1...]) +# -------------------------------------------------- +m4_define([lt_decl_quote_varnames], +[_lt_decl_filter([value], [1], $@)]) + + +# lt_decl_dquote_varnames([SEPARATOR], [VARNAME1...]) +# --------------------------------------------------- +m4_define([lt_decl_dquote_varnames], +[_lt_decl_filter([value], [2], $@)]) + + +# lt_decl_varnames_tagged([SEPARATOR], [VARNAME1...]) +# --------------------------------------------------- +m4_define([lt_decl_varnames_tagged], +[m4_assert([$# <= 2])dnl +_$0(m4_quote(m4_default([$1], [[, ]])), + m4_ifval([$2], [[$2]], [m4_dquote(lt_decl_tag_varnames)]), + m4_split(m4_normalize(m4_quote(_LT_TAGS)), [ ]))]) +m4_define([_lt_decl_varnames_tagged], +[m4_ifval([$3], [lt_combine([$1], [$2], [_], $3)])]) + + +# lt_decl_all_varnames([SEPARATOR], [VARNAME1...]) +# ------------------------------------------------ +m4_define([lt_decl_all_varnames], +[_$0(m4_quote(m4_default([$1], [[, ]])), + m4_if([$2], [], + m4_quote(lt_decl_varnames), + m4_quote(m4_shift($@))))[]dnl +]) +m4_define([_lt_decl_all_varnames], +[lt_join($@, lt_decl_varnames_tagged([$1], + lt_decl_tag_varnames([[, ]], m4_shift($@))))dnl +]) + + +# _LT_CONFIG_STATUS_DECLARE([VARNAME]) +# ------------------------------------ +# Quote a variable value, and forward it to 'config.status' so that its +# declaration there will have the same value as in 'configure'. VARNAME +# must have a single quote delimited value for this to work. +m4_define([_LT_CONFIG_STATUS_DECLARE], +[$1='`$ECHO "$][$1" | $SED "$delay_single_quote_subst"`']) + + +# _LT_CONFIG_STATUS_DECLARATIONS +# ------------------------------ +# We delimit libtool config variables with single quotes, so when +# we write them to config.status, we have to be sure to quote all +# embedded single quotes properly. In configure, this macro expands +# each variable declared with _LT_DECL (and _LT_TAGDECL) into: +# +# ='`$ECHO "$" | $SED "$delay_single_quote_subst"`' +m4_defun([_LT_CONFIG_STATUS_DECLARATIONS], +[m4_foreach([_lt_var], m4_quote(lt_decl_all_varnames), + [m4_n([_LT_CONFIG_STATUS_DECLARE(_lt_var)])])]) + + +# _LT_LIBTOOL_TAGS +# ---------------- +# Output comment and list of tags supported by the script +m4_defun([_LT_LIBTOOL_TAGS], +[_LT_FORMAT_COMMENT([The names of the tagged configurations supported by this script])dnl +available_tags='_LT_TAGS'dnl +]) + + +# _LT_LIBTOOL_DECLARE(VARNAME, [TAG]) +# ----------------------------------- +# Extract the dictionary values for VARNAME (optionally with TAG) and +# expand to a commented shell variable setting: +# +# # Some comment about what VAR is for. +# visible_name=$lt_internal_name +m4_define([_LT_LIBTOOL_DECLARE], +[_LT_FORMAT_COMMENT(m4_quote(lt_dict_fetch([lt_decl_dict], [$1], + [description])))[]dnl +m4_pushdef([_libtool_name], + m4_quote(lt_dict_fetch([lt_decl_dict], [$1], [libtool_name])))[]dnl +m4_case(m4_quote(lt_dict_fetch([lt_decl_dict], [$1], [value])), + [0], [_libtool_name=[$]$1], + [1], [_libtool_name=$lt_[]$1], + [2], [_libtool_name=$lt_[]$1], + [_libtool_name=lt_dict_fetch([lt_decl_dict], [$1], [value])])[]dnl +m4_ifval([$2], [_$2])[]m4_popdef([_libtool_name])[]dnl +]) + + +# _LT_LIBTOOL_CONFIG_VARS +# ----------------------- +# Produce commented declarations of non-tagged libtool config variables +# suitable for insertion in the LIBTOOL CONFIG section of the 'libtool' +# script. Tagged libtool config variables (even for the LIBTOOL CONFIG +# section) are produced by _LT_LIBTOOL_TAG_VARS. +m4_defun([_LT_LIBTOOL_CONFIG_VARS], +[m4_foreach([_lt_var], + m4_quote(_lt_decl_filter([tagged?], [no], [], lt_decl_varnames)), + [m4_n([_LT_LIBTOOL_DECLARE(_lt_var)])])]) + + +# _LT_LIBTOOL_TAG_VARS(TAG) +# ------------------------- +m4_define([_LT_LIBTOOL_TAG_VARS], +[m4_foreach([_lt_var], m4_quote(lt_decl_tag_varnames), + [m4_n([_LT_LIBTOOL_DECLARE(_lt_var, [$1])])])]) + + +# _LT_TAGVAR(VARNAME, [TAGNAME]) +# ------------------------------ +m4_define([_LT_TAGVAR], [m4_ifval([$2], [$1_$2], [$1])]) + + +# _LT_CONFIG_COMMANDS +# ------------------- +# Send accumulated output to $CONFIG_STATUS. Thanks to the lists of +# variables for single and double quote escaping we saved from calls +# to _LT_DECL, we can put quote escaped variables declarations +# into 'config.status', and then the shell code to quote escape them in +# for loops in 'config.status'. Finally, any additional code accumulated +# from calls to _LT_CONFIG_LIBTOOL_INIT is expanded. +m4_defun([_LT_CONFIG_COMMANDS], +[AC_PROVIDE_IFELSE([LT_OUTPUT], + dnl If the libtool generation code has been placed in $CONFIG_LT, + dnl instead of duplicating it all over again into config.status, + dnl then we will have config.status run $CONFIG_LT later, so it + dnl needs to know what name is stored there: + [AC_CONFIG_COMMANDS([libtool], + [$SHELL $CONFIG_LT || AS_EXIT(1)], [CONFIG_LT='$CONFIG_LT'])], + dnl If the libtool generation code is destined for config.status, + dnl expand the accumulated commands and init code now: + [AC_CONFIG_COMMANDS([libtool], + [_LT_OUTPUT_LIBTOOL_COMMANDS], [_LT_OUTPUT_LIBTOOL_COMMANDS_INIT])]) +])#_LT_CONFIG_COMMANDS + + +# Initialize. +m4_define([_LT_OUTPUT_LIBTOOL_COMMANDS_INIT], +[ + +# The HP-UX ksh and POSIX shell print the target directory to stdout +# if CDPATH is set. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + +sed_quote_subst='$sed_quote_subst' +double_quote_subst='$double_quote_subst' +delay_variable_subst='$delay_variable_subst' +_LT_CONFIG_STATUS_DECLARATIONS +LTCC='$LTCC' +LTCFLAGS='$LTCFLAGS' +compiler='$compiler_DEFAULT' + +# A function that is used when there is no print builtin or printf. +func_fallback_echo () +{ + eval 'cat <<_LTECHO_EOF +\$[]1 +_LTECHO_EOF' +} + +# Quote evaled strings. +for var in lt_decl_all_varnames([[ \ +]], lt_decl_quote_varnames); do + case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in + *[[\\\\\\\`\\"\\\$]]*) + eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED \\"\\\$sed_quote_subst\\"\\\`\\\\\\"" ## exclude from sc_prohibit_nested_quotes + ;; + *) + eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\"" + ;; + esac +done + +# Double-quote double-evaled strings. +for var in lt_decl_all_varnames([[ \ +]], lt_decl_dquote_varnames); do + case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in + *[[\\\\\\\`\\"\\\$]]*) + eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED -e \\"\\\$double_quote_subst\\" -e \\"\\\$sed_quote_subst\\" -e \\"\\\$delay_variable_subst\\"\\\`\\\\\\"" ## exclude from sc_prohibit_nested_quotes + ;; + *) + eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\"" + ;; + esac +done + +_LT_OUTPUT_LIBTOOL_INIT +]) + +# _LT_GENERATED_FILE_INIT(FILE, [COMMENT]) +# ------------------------------------ +# Generate a child script FILE with all initialization necessary to +# reuse the environment learned by the parent script, and make the +# file executable. If COMMENT is supplied, it is inserted after the +# '#!' sequence but before initialization text begins. After this +# macro, additional text can be appended to FILE to form the body of +# the child script. The macro ends with non-zero status if the +# file could not be fully written (such as if the disk is full). +m4_ifdef([AS_INIT_GENERATED], +[m4_defun([_LT_GENERATED_FILE_INIT],[AS_INIT_GENERATED($@)])], +[m4_defun([_LT_GENERATED_FILE_INIT], +[m4_require([AS_PREPARE])]dnl +[m4_pushdef([AS_MESSAGE_LOG_FD])]dnl +[lt_write_fail=0 +cat >$1 <<_ASEOF || lt_write_fail=1 +#! $SHELL +# Generated by $as_me. +$2 +SHELL=\${CONFIG_SHELL-$SHELL} +export SHELL +_ASEOF +cat >>$1 <<\_ASEOF || lt_write_fail=1 +AS_SHELL_SANITIZE +_AS_PREPARE +exec AS_MESSAGE_FD>&1 +_ASEOF +test 0 = "$lt_write_fail" && chmod +x $1[]dnl +m4_popdef([AS_MESSAGE_LOG_FD])])])# _LT_GENERATED_FILE_INIT + +# LT_OUTPUT +# --------- +# This macro allows early generation of the libtool script (before +# AC_OUTPUT is called), incase it is used in configure for compilation +# tests. +AC_DEFUN([LT_OUTPUT], +[: ${CONFIG_LT=./config.lt} +AC_MSG_NOTICE([creating $CONFIG_LT]) +_LT_GENERATED_FILE_INIT(["$CONFIG_LT"], +[# Run this file to recreate a libtool stub with the current configuration.]) + +cat >>"$CONFIG_LT" <<\_LTEOF +lt_cl_silent=false +exec AS_MESSAGE_LOG_FD>>config.log +{ + echo + AS_BOX([Running $as_me.]) +} >&AS_MESSAGE_LOG_FD + +lt_cl_help="\ +'$as_me' creates a local libtool stub from the current configuration, +for use in further configure time tests before the real libtool is +generated. + +Usage: $[0] [[OPTIONS]] + + -h, --help print this help, then exit + -V, --version print version number, then exit + -q, --quiet do not print progress messages + -d, --debug don't remove temporary files + +Report bugs to ." + +lt_cl_version="\ +m4_ifset([AC_PACKAGE_NAME], [AC_PACKAGE_NAME ])config.lt[]dnl +m4_ifset([AC_PACKAGE_VERSION], [ AC_PACKAGE_VERSION]) +configured by $[0], generated by m4_PACKAGE_STRING. + +Copyright (C) 2011 Free Software Foundation, Inc. +This config.lt script is free software; the Free Software Foundation +gives unlimited permision to copy, distribute and modify it." + +while test 0 != $[#] +do + case $[1] in + --version | --v* | -V ) + echo "$lt_cl_version"; exit 0 ;; + --help | --h* | -h ) + echo "$lt_cl_help"; exit 0 ;; + --debug | --d* | -d ) + debug=: ;; + --quiet | --q* | --silent | --s* | -q ) + lt_cl_silent=: ;; + + -*) AC_MSG_ERROR([unrecognized option: $[1] +Try '$[0] --help' for more information.]) ;; + + *) AC_MSG_ERROR([unrecognized argument: $[1] +Try '$[0] --help' for more information.]) ;; + esac + shift +done + +if $lt_cl_silent; then + exec AS_MESSAGE_FD>/dev/null +fi +_LTEOF + +cat >>"$CONFIG_LT" <<_LTEOF +_LT_OUTPUT_LIBTOOL_COMMANDS_INIT +_LTEOF + +cat >>"$CONFIG_LT" <<\_LTEOF +AC_MSG_NOTICE([creating $ofile]) +_LT_OUTPUT_LIBTOOL_COMMANDS +AS_EXIT(0) +_LTEOF +chmod +x "$CONFIG_LT" + +# configure is writing to config.log, but config.lt does its own redirection, +# appending to config.log, which fails on DOS, as config.log is still kept +# open by configure. Here we exec the FD to /dev/null, effectively closing +# config.log, so it can be properly (re)opened and appended to by config.lt. +lt_cl_success=: +test yes = "$silent" && + lt_config_lt_args="$lt_config_lt_args --quiet" +exec AS_MESSAGE_LOG_FD>/dev/null +$SHELL "$CONFIG_LT" $lt_config_lt_args || lt_cl_success=false +exec AS_MESSAGE_LOG_FD>>config.log +$lt_cl_success || AS_EXIT(1) +])# LT_OUTPUT + + +# _LT_CONFIG(TAG) +# --------------- +# If TAG is the built-in tag, create an initial libtool script with a +# default configuration from the untagged config vars. Otherwise add code +# to config.status for appending the configuration named by TAG from the +# matching tagged config vars. +m4_defun([_LT_CONFIG], +[m4_require([_LT_FILEUTILS_DEFAULTS])dnl +_LT_CONFIG_SAVE_COMMANDS([ + m4_define([_LT_TAG], m4_if([$1], [], [C], [$1]))dnl + m4_if(_LT_TAG, [C], [ + # See if we are running on zsh, and set the options that allow our + # commands through without removal of \ escapes. + if test -n "${ZSH_VERSION+set}"; then + setopt NO_GLOB_SUBST + fi + + cfgfile=${ofile}T + trap "$RM \"$cfgfile\"; exit 1" 1 2 15 + $RM "$cfgfile" + + cat <<_LT_EOF >> "$cfgfile" +#! $SHELL +# Generated automatically by $as_me ($PACKAGE) $VERSION +# Libtool was configured on host `(hostname || uname -n) 2>/dev/null | sed 1q`: +# NOTE: Changes made to this file will be lost: look at ltmain.sh. + +# Provide generalized library-building support services. +# Written by Gordon Matzigkeit, 1996 + +_LT_COPYING +_LT_LIBTOOL_TAGS + +# Configured defaults for sys_lib_dlsearch_path munging. +: \${LT_SYS_LIBRARY_PATH="$configure_time_lt_sys_library_path"} + +# ### BEGIN LIBTOOL CONFIG +_LT_LIBTOOL_CONFIG_VARS +_LT_LIBTOOL_TAG_VARS +# ### END LIBTOOL CONFIG + +_LT_EOF + + cat <<'_LT_EOF' >> "$cfgfile" + +# ### BEGIN FUNCTIONS SHARED WITH CONFIGURE + +_LT_PREPARE_MUNGE_PATH_LIST +_LT_PREPARE_CC_BASENAME + +# ### END FUNCTIONS SHARED WITH CONFIGURE + +_LT_EOF + + case $host_os in + aix3*) + cat <<\_LT_EOF >> "$cfgfile" +# AIX sometimes has problems with the GCC collect2 program. For some +# reason, if we set the COLLECT_NAMES environment variable, the problems +# vanish in a puff of smoke. +if test set != "${COLLECT_NAMES+set}"; then + COLLECT_NAMES= + export COLLECT_NAMES +fi +_LT_EOF + ;; + esac + + _LT_PROG_LTMAIN + + # We use sed instead of cat because bash on DJGPP gets confused if + # if finds mixed CR/LF and LF-only lines. Since sed operates in + # text mode, it properly converts lines to CR/LF. This bash problem + # is reportedly fixed, but why not run on old versions too? + sed '$q' "$ltmain" >> "$cfgfile" \ + || (rm -f "$cfgfile"; exit 1) + + mv -f "$cfgfile" "$ofile" || + (rm -f "$ofile" && cp "$cfgfile" "$ofile" && rm -f "$cfgfile") + chmod +x "$ofile" +], +[cat <<_LT_EOF >> "$ofile" + +dnl Unfortunately we have to use $1 here, since _LT_TAG is not expanded +dnl in a comment (ie after a #). +# ### BEGIN LIBTOOL TAG CONFIG: $1 +_LT_LIBTOOL_TAG_VARS(_LT_TAG) +# ### END LIBTOOL TAG CONFIG: $1 +_LT_EOF +])dnl /m4_if +], +[m4_if([$1], [], [ + PACKAGE='$PACKAGE' + VERSION='$VERSION' + RM='$RM' + ofile='$ofile'], []) +])dnl /_LT_CONFIG_SAVE_COMMANDS +])# _LT_CONFIG + + +# LT_SUPPORTED_TAG(TAG) +# --------------------- +# Trace this macro to discover what tags are supported by the libtool +# --tag option, using: +# autoconf --trace 'LT_SUPPORTED_TAG:$1' +AC_DEFUN([LT_SUPPORTED_TAG], []) + + +# C support is built-in for now +m4_define([_LT_LANG_C_enabled], []) +m4_define([_LT_TAGS], []) + + +# LT_LANG(LANG) +# ------------- +# Enable libtool support for the given language if not already enabled. +AC_DEFUN([LT_LANG], +[AC_BEFORE([$0], [LT_OUTPUT])dnl +m4_case([$1], + [C], [_LT_LANG(C)], + [C++], [_LT_LANG(CXX)], + [Go], [_LT_LANG(GO)], + [Java], [_LT_LANG(GCJ)], + [Fortran 77], [_LT_LANG(F77)], + [Fortran], [_LT_LANG(FC)], + [Windows Resource], [_LT_LANG(RC)], + [m4_ifdef([_LT_LANG_]$1[_CONFIG], + [_LT_LANG($1)], + [m4_fatal([$0: unsupported language: "$1"])])])dnl +])# LT_LANG + + +# _LT_LANG(LANGNAME) +# ------------------ +m4_defun([_LT_LANG], +[m4_ifdef([_LT_LANG_]$1[_enabled], [], + [LT_SUPPORTED_TAG([$1])dnl + m4_append([_LT_TAGS], [$1 ])dnl + m4_define([_LT_LANG_]$1[_enabled], [])dnl + _LT_LANG_$1_CONFIG($1)])dnl +])# _LT_LANG + + +m4_ifndef([AC_PROG_GO], [ +############################################################ +# NOTE: This macro has been submitted for inclusion into # +# GNU Autoconf as AC_PROG_GO. When it is available in # +# a released version of Autoconf we should remove this # +# macro and use it instead. # +############################################################ +m4_defun([AC_PROG_GO], +[AC_LANG_PUSH(Go)dnl +AC_ARG_VAR([GOC], [Go compiler command])dnl +AC_ARG_VAR([GOFLAGS], [Go compiler flags])dnl +_AC_ARG_VAR_LDFLAGS()dnl +AC_CHECK_TOOL(GOC, gccgo) +if test -z "$GOC"; then + if test -n "$ac_tool_prefix"; then + AC_CHECK_PROG(GOC, [${ac_tool_prefix}gccgo], [${ac_tool_prefix}gccgo]) + fi +fi +if test -z "$GOC"; then + AC_CHECK_PROG(GOC, gccgo, gccgo, false) +fi +])#m4_defun +])#m4_ifndef + + +# _LT_LANG_DEFAULT_CONFIG +# ----------------------- +m4_defun([_LT_LANG_DEFAULT_CONFIG], +[AC_PROVIDE_IFELSE([AC_PROG_CXX], + [LT_LANG(CXX)], + [m4_define([AC_PROG_CXX], defn([AC_PROG_CXX])[LT_LANG(CXX)])]) + +AC_PROVIDE_IFELSE([AC_PROG_F77], + [LT_LANG(F77)], + [m4_define([AC_PROG_F77], defn([AC_PROG_F77])[LT_LANG(F77)])]) + +AC_PROVIDE_IFELSE([AC_PROG_FC], + [LT_LANG(FC)], + [m4_define([AC_PROG_FC], defn([AC_PROG_FC])[LT_LANG(FC)])]) + +dnl The call to [A][M_PROG_GCJ] is quoted like that to stop aclocal +dnl pulling things in needlessly. +AC_PROVIDE_IFELSE([AC_PROG_GCJ], + [LT_LANG(GCJ)], + [AC_PROVIDE_IFELSE([A][M_PROG_GCJ], + [LT_LANG(GCJ)], + [AC_PROVIDE_IFELSE([LT_PROG_GCJ], + [LT_LANG(GCJ)], + [m4_ifdef([AC_PROG_GCJ], + [m4_define([AC_PROG_GCJ], defn([AC_PROG_GCJ])[LT_LANG(GCJ)])]) + m4_ifdef([A][M_PROG_GCJ], + [m4_define([A][M_PROG_GCJ], defn([A][M_PROG_GCJ])[LT_LANG(GCJ)])]) + m4_ifdef([LT_PROG_GCJ], + [m4_define([LT_PROG_GCJ], defn([LT_PROG_GCJ])[LT_LANG(GCJ)])])])])]) + +AC_PROVIDE_IFELSE([AC_PROG_GO], + [LT_LANG(GO)], + [m4_define([AC_PROG_GO], defn([AC_PROG_GO])[LT_LANG(GO)])]) + +AC_PROVIDE_IFELSE([LT_PROG_RC], + [LT_LANG(RC)], + [m4_define([LT_PROG_RC], defn([LT_PROG_RC])[LT_LANG(RC)])]) +])# _LT_LANG_DEFAULT_CONFIG + +# Obsolete macros: +AU_DEFUN([AC_LIBTOOL_CXX], [LT_LANG(C++)]) +AU_DEFUN([AC_LIBTOOL_F77], [LT_LANG(Fortran 77)]) +AU_DEFUN([AC_LIBTOOL_FC], [LT_LANG(Fortran)]) +AU_DEFUN([AC_LIBTOOL_GCJ], [LT_LANG(Java)]) +AU_DEFUN([AC_LIBTOOL_RC], [LT_LANG(Windows Resource)]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_CXX], []) +dnl AC_DEFUN([AC_LIBTOOL_F77], []) +dnl AC_DEFUN([AC_LIBTOOL_FC], []) +dnl AC_DEFUN([AC_LIBTOOL_GCJ], []) +dnl AC_DEFUN([AC_LIBTOOL_RC], []) + + +# _LT_TAG_COMPILER +# ---------------- +m4_defun([_LT_TAG_COMPILER], +[AC_REQUIRE([AC_PROG_CC])dnl + +_LT_DECL([LTCC], [CC], [1], [A C compiler])dnl +_LT_DECL([LTCFLAGS], [CFLAGS], [1], [LTCC compiler flags])dnl +_LT_TAGDECL([CC], [compiler], [1], [A language specific compiler])dnl +_LT_TAGDECL([with_gcc], [GCC], [0], [Is the compiler the GNU compiler?])dnl + +# If no C compiler was specified, use CC. +LTCC=${LTCC-"$CC"} + +# If no C compiler flags were specified, use CFLAGS. +LTCFLAGS=${LTCFLAGS-"$CFLAGS"} + +# Allow CC to be a program name with arguments. +compiler=$CC +])# _LT_TAG_COMPILER + + +# _LT_COMPILER_BOILERPLATE +# ------------------------ +# Check for compiler boilerplate output or warnings with +# the simple compiler test code. +m4_defun([_LT_COMPILER_BOILERPLATE], +[m4_require([_LT_DECL_SED])dnl +ac_outfile=conftest.$ac_objext +echo "$lt_simple_compile_test_code" >conftest.$ac_ext +eval "$ac_compile" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err +_lt_compiler_boilerplate=`cat conftest.err` +$RM conftest* +])# _LT_COMPILER_BOILERPLATE + + +# _LT_LINKER_BOILERPLATE +# ---------------------- +# Check for linker boilerplate output or warnings with +# the simple link test code. +m4_defun([_LT_LINKER_BOILERPLATE], +[m4_require([_LT_DECL_SED])dnl +ac_outfile=conftest.$ac_objext +echo "$lt_simple_link_test_code" >conftest.$ac_ext +eval "$ac_link" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err +_lt_linker_boilerplate=`cat conftest.err` +$RM -r conftest* +])# _LT_LINKER_BOILERPLATE + +# _LT_REQUIRED_DARWIN_CHECKS +# ------------------------- +m4_defun_once([_LT_REQUIRED_DARWIN_CHECKS],[ + case $host_os in + rhapsody* | darwin*) + AC_CHECK_TOOL([DSYMUTIL], [dsymutil], [:]) + AC_CHECK_TOOL([NMEDIT], [nmedit], [:]) + AC_CHECK_TOOL([LIPO], [lipo], [:]) + AC_CHECK_TOOL([OTOOL], [otool], [:]) + AC_CHECK_TOOL([OTOOL64], [otool64], [:]) + _LT_DECL([], [DSYMUTIL], [1], + [Tool to manipulate archived DWARF debug symbol files on Mac OS X]) + _LT_DECL([], [NMEDIT], [1], + [Tool to change global to local symbols on Mac OS X]) + _LT_DECL([], [LIPO], [1], + [Tool to manipulate fat objects and archives on Mac OS X]) + _LT_DECL([], [OTOOL], [1], + [ldd/readelf like tool for Mach-O binaries on Mac OS X]) + _LT_DECL([], [OTOOL64], [1], + [ldd/readelf like tool for 64 bit Mach-O binaries on Mac OS X 10.4]) + + AC_CACHE_CHECK([for -single_module linker flag],[lt_cv_apple_cc_single_mod], + [lt_cv_apple_cc_single_mod=no + if test -z "$LT_MULTI_MODULE"; then + # By default we will add the -single_module flag. You can override + # by either setting the environment variable LT_MULTI_MODULE + # non-empty at configure time, or by adding -multi_module to the + # link flags. + rm -rf libconftest.dylib* + echo "int foo(void){return 1;}" > conftest.c + echo "$LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \ +-dynamiclib -Wl,-single_module conftest.c" >&AS_MESSAGE_LOG_FD + $LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \ + -dynamiclib -Wl,-single_module conftest.c 2>conftest.err + _lt_result=$? + # If there is a non-empty error log, and "single_module" + # appears in it, assume the flag caused a linker warning + if test -s conftest.err && $GREP single_module conftest.err; then + cat conftest.err >&AS_MESSAGE_LOG_FD + # Otherwise, if the output was created with a 0 exit code from + # the compiler, it worked. + elif test -f libconftest.dylib && test 0 = "$_lt_result"; then + lt_cv_apple_cc_single_mod=yes + else + cat conftest.err >&AS_MESSAGE_LOG_FD + fi + rm -rf libconftest.dylib* + rm -f conftest.* + fi]) + + AC_CACHE_CHECK([for -exported_symbols_list linker flag], + [lt_cv_ld_exported_symbols_list], + [lt_cv_ld_exported_symbols_list=no + save_LDFLAGS=$LDFLAGS + echo "_main" > conftest.sym + LDFLAGS="$LDFLAGS -Wl,-exported_symbols_list,conftest.sym" + AC_LINK_IFELSE([AC_LANG_PROGRAM([],[])], + [lt_cv_ld_exported_symbols_list=yes], + [lt_cv_ld_exported_symbols_list=no]) + LDFLAGS=$save_LDFLAGS + ]) + + AC_CACHE_CHECK([for -force_load linker flag],[lt_cv_ld_force_load], + [lt_cv_ld_force_load=no + cat > conftest.c << _LT_EOF +int forced_loaded() { return 2;} +_LT_EOF + echo "$LTCC $LTCFLAGS -c -o conftest.o conftest.c" >&AS_MESSAGE_LOG_FD + $LTCC $LTCFLAGS -c -o conftest.o conftest.c 2>&AS_MESSAGE_LOG_FD + echo "$AR cru libconftest.a conftest.o" >&AS_MESSAGE_LOG_FD + $AR cru libconftest.a conftest.o 2>&AS_MESSAGE_LOG_FD + echo "$RANLIB libconftest.a" >&AS_MESSAGE_LOG_FD + $RANLIB libconftest.a 2>&AS_MESSAGE_LOG_FD + cat > conftest.c << _LT_EOF +int main() { return 0;} +_LT_EOF + echo "$LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a" >&AS_MESSAGE_LOG_FD + $LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a 2>conftest.err + _lt_result=$? + if test -s conftest.err && $GREP force_load conftest.err; then + cat conftest.err >&AS_MESSAGE_LOG_FD + elif test -f conftest && test 0 = "$_lt_result" && $GREP forced_load conftest >/dev/null 2>&1; then + lt_cv_ld_force_load=yes + else + cat conftest.err >&AS_MESSAGE_LOG_FD + fi + rm -f conftest.err libconftest.a conftest conftest.c + rm -rf conftest.dSYM + ]) + case $host_os in + rhapsody* | darwin1.[[012]]) + _lt_dar_allow_undefined='$wl-undefined ${wl}suppress' ;; + darwin1.*) + _lt_dar_allow_undefined='$wl-flat_namespace $wl-undefined ${wl}suppress' ;; + darwin*) # darwin 5.x on + # if running on 10.5 or later, the deployment target defaults + # to the OS version, if on x86, and 10.4, the deployment + # target defaults to 10.4. Don't you love it? + case ${MACOSX_DEPLOYMENT_TARGET-10.0},$host in + 10.0,*86*-darwin8*|10.0,*-darwin[[91]]*) + _lt_dar_allow_undefined='$wl-undefined ${wl}dynamic_lookup' ;; + 10.[[012]][[,.]]*) + _lt_dar_allow_undefined='$wl-flat_namespace $wl-undefined ${wl}suppress' ;; + 10.*) + _lt_dar_allow_undefined='$wl-undefined ${wl}dynamic_lookup' ;; + esac + ;; + esac + if test yes = "$lt_cv_apple_cc_single_mod"; then + _lt_dar_single_mod='$single_module' + fi + if test yes = "$lt_cv_ld_exported_symbols_list"; then + _lt_dar_export_syms=' $wl-exported_symbols_list,$output_objdir/$libname-symbols.expsym' + else + _lt_dar_export_syms='~$NMEDIT -s $output_objdir/$libname-symbols.expsym $lib' + fi + if test : != "$DSYMUTIL" && test no = "$lt_cv_ld_force_load"; then + _lt_dsymutil='~$DSYMUTIL $lib || :' + else + _lt_dsymutil= + fi + ;; + esac +]) + + +# _LT_DARWIN_LINKER_FEATURES([TAG]) +# --------------------------------- +# Checks for linker and compiler features on darwin +m4_defun([_LT_DARWIN_LINKER_FEATURES], +[ + m4_require([_LT_REQUIRED_DARWIN_CHECKS]) + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_automatic, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=unsupported + if test yes = "$lt_cv_ld_force_load"; then + _LT_TAGVAR(whole_archive_flag_spec, $1)='`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience $wl-force_load,$conv\"; done; func_echo_all \"$new_convenience\"`' + m4_case([$1], [F77], [_LT_TAGVAR(compiler_needs_object, $1)=yes], + [FC], [_LT_TAGVAR(compiler_needs_object, $1)=yes]) + else + _LT_TAGVAR(whole_archive_flag_spec, $1)='' + fi + _LT_TAGVAR(link_all_deplibs, $1)=yes + _LT_TAGVAR(allow_undefined_flag, $1)=$_lt_dar_allow_undefined + case $cc_basename in + ifort*|nagfor*) _lt_dar_can_shared=yes ;; + *) _lt_dar_can_shared=$GCC ;; + esac + if test yes = "$_lt_dar_can_shared"; then + output_verbose_link_cmd=func_echo_all + _LT_TAGVAR(archive_cmds, $1)="\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod$_lt_dsymutil" + _LT_TAGVAR(module_cmds, $1)="\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags$_lt_dsymutil" + _LT_TAGVAR(archive_expsym_cmds, $1)="sed 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod$_lt_dar_export_syms$_lt_dsymutil" + _LT_TAGVAR(module_expsym_cmds, $1)="sed -e 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags$_lt_dar_export_syms$_lt_dsymutil" + m4_if([$1], [CXX], +[ if test yes != "$lt_cv_apple_cc_single_mod"; then + _LT_TAGVAR(archive_cmds, $1)="\$CC -r -keep_private_externs -nostdlib -o \$lib-master.o \$libobjs~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$lib-master.o \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring$_lt_dsymutil" + _LT_TAGVAR(archive_expsym_cmds, $1)="sed 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC -r -keep_private_externs -nostdlib -o \$lib-master.o \$libobjs~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$lib-master.o \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring$_lt_dar_export_syms$_lt_dsymutil" + fi +],[]) + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi +]) + +# _LT_SYS_MODULE_PATH_AIX([TAGNAME]) +# ---------------------------------- +# Links a minimal program and checks the executable +# for the system default hardcoded library path. In most cases, +# this is /usr/lib:/lib, but when the MPI compilers are used +# the location of the communication and MPI libs are included too. +# If we don't find anything, use the default library path according +# to the aix ld manual. +# Store the results from the different compilers for each TAGNAME. +# Allow to override them for all tags through lt_cv_aix_libpath. +m4_defun([_LT_SYS_MODULE_PATH_AIX], +[m4_require([_LT_DECL_SED])dnl +if test set = "${lt_cv_aix_libpath+set}"; then + aix_libpath=$lt_cv_aix_libpath +else + AC_CACHE_VAL([_LT_TAGVAR([lt_cv_aix_libpath_], [$1])], + [AC_LINK_IFELSE([AC_LANG_PROGRAM],[ + lt_aix_libpath_sed='[ + /Import File Strings/,/^$/ { + /^0/ { + s/^0 *\([^ ]*\) *$/\1/ + p + } + }]' + _LT_TAGVAR([lt_cv_aix_libpath_], [$1])=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + # Check for a 64-bit object if we didn't find anything. + if test -z "$_LT_TAGVAR([lt_cv_aix_libpath_], [$1])"; then + _LT_TAGVAR([lt_cv_aix_libpath_], [$1])=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + fi],[]) + if test -z "$_LT_TAGVAR([lt_cv_aix_libpath_], [$1])"; then + _LT_TAGVAR([lt_cv_aix_libpath_], [$1])=/usr/lib:/lib + fi + ]) + aix_libpath=$_LT_TAGVAR([lt_cv_aix_libpath_], [$1]) +fi +])# _LT_SYS_MODULE_PATH_AIX + + +# _LT_SHELL_INIT(ARG) +# ------------------- +m4_define([_LT_SHELL_INIT], +[m4_divert_text([M4SH-INIT], [$1 +])])# _LT_SHELL_INIT + + + +# _LT_PROG_ECHO_BACKSLASH +# ----------------------- +# Find how we can fake an echo command that does not interpret backslash. +# In particular, with Autoconf 2.60 or later we add some code to the start +# of the generated configure script that will find a shell with a builtin +# printf (that we can use as an echo command). +m4_defun([_LT_PROG_ECHO_BACKSLASH], +[ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO +ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO$ECHO + +AC_MSG_CHECKING([how to print strings]) +# Test print first, because it will be a builtin if present. +if test "X`( print -r -- -n ) 2>/dev/null`" = X-n && \ + test "X`print -r -- $ECHO 2>/dev/null`" = "X$ECHO"; then + ECHO='print -r --' +elif test "X`printf %s $ECHO 2>/dev/null`" = "X$ECHO"; then + ECHO='printf %s\n' +else + # Use this function as a fallback that always works. + func_fallback_echo () + { + eval 'cat <<_LTECHO_EOF +$[]1 +_LTECHO_EOF' + } + ECHO='func_fallback_echo' +fi + +# func_echo_all arg... +# Invoke $ECHO with all args, space-separated. +func_echo_all () +{ + $ECHO "$*" +} + +case $ECHO in + printf*) AC_MSG_RESULT([printf]) ;; + print*) AC_MSG_RESULT([print -r]) ;; + *) AC_MSG_RESULT([cat]) ;; +esac + +m4_ifdef([_AS_DETECT_SUGGESTED], +[_AS_DETECT_SUGGESTED([ + test -n "${ZSH_VERSION+set}${BASH_VERSION+set}" || ( + ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' + ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO + ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO$ECHO + PATH=/empty FPATH=/empty; export PATH FPATH + test "X`printf %s $ECHO`" = "X$ECHO" \ + || test "X`print -r -- $ECHO`" = "X$ECHO" )])]) + +_LT_DECL([], [SHELL], [1], [Shell to use when invoking shell scripts]) +_LT_DECL([], [ECHO], [1], [An echo program that protects backslashes]) +])# _LT_PROG_ECHO_BACKSLASH + + +# _LT_WITH_SYSROOT +# ---------------- +AC_DEFUN([_LT_WITH_SYSROOT], +[AC_MSG_CHECKING([for sysroot]) +AC_ARG_WITH([sysroot], +[AS_HELP_STRING([--with-sysroot@<:@=DIR@:>@], + [Search for dependent libraries within DIR (or the compiler's sysroot + if not specified).])], +[], [with_sysroot=no]) + +dnl lt_sysroot will always be passed unquoted. We quote it here +dnl in case the user passed a directory name. +lt_sysroot= +case $with_sysroot in #( + yes) + if test yes = "$GCC"; then + lt_sysroot=`$CC --print-sysroot 2>/dev/null` + fi + ;; #( + /*) + lt_sysroot=`echo "$with_sysroot" | sed -e "$sed_quote_subst"` + ;; #( + no|'') + ;; #( + *) + AC_MSG_RESULT([$with_sysroot]) + AC_MSG_ERROR([The sysroot must be an absolute path.]) + ;; +esac + + AC_MSG_RESULT([${lt_sysroot:-no}]) +_LT_DECL([], [lt_sysroot], [0], [The root where to search for ]dnl +[dependent libraries, and where our libraries should be installed.])]) + +# _LT_ENABLE_LOCK +# --------------- +m4_defun([_LT_ENABLE_LOCK], +[AC_ARG_ENABLE([libtool-lock], + [AS_HELP_STRING([--disable-libtool-lock], + [avoid locking (might break parallel builds)])]) +test no = "$enable_libtool_lock" || enable_libtool_lock=yes + +# Some flags need to be propagated to the compiler or linker for good +# libtool support. +case $host in +ia64-*-hpux*) + # Find out what ABI is being produced by ac_compile, and set mode + # options accordingly. + echo 'int i;' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + case `/usr/bin/file conftest.$ac_objext` in + *ELF-32*) + HPUX_IA64_MODE=32 + ;; + *ELF-64*) + HPUX_IA64_MODE=64 + ;; + esac + fi + rm -rf conftest* + ;; +*-*-irix6*) + # Find out what ABI is being produced by ac_compile, and set linker + # options accordingly. + echo '[#]line '$LINENO' "configure"' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + if test yes = "$lt_cv_prog_gnu_ld"; then + case `/usr/bin/file conftest.$ac_objext` in + *32-bit*) + LD="${LD-ld} -melf32bsmip" + ;; + *N32*) + LD="${LD-ld} -melf32bmipn32" + ;; + *64-bit*) + LD="${LD-ld} -melf64bmip" + ;; + esac + else + case `/usr/bin/file conftest.$ac_objext` in + *32-bit*) + LD="${LD-ld} -32" + ;; + *N32*) + LD="${LD-ld} -n32" + ;; + *64-bit*) + LD="${LD-ld} -64" + ;; + esac + fi + fi + rm -rf conftest* + ;; + +mips64*-*linux*) + # Find out what ABI is being produced by ac_compile, and set linker + # options accordingly. + echo '[#]line '$LINENO' "configure"' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + emul=elf + case `/usr/bin/file conftest.$ac_objext` in + *32-bit*) + emul="${emul}32" + ;; + *64-bit*) + emul="${emul}64" + ;; + esac + case `/usr/bin/file conftest.$ac_objext` in + *MSB*) + emul="${emul}btsmip" + ;; + *LSB*) + emul="${emul}ltsmip" + ;; + esac + case `/usr/bin/file conftest.$ac_objext` in + *N32*) + emul="${emul}n32" + ;; + esac + LD="${LD-ld} -m $emul" + fi + rm -rf conftest* + ;; + +x86_64-*kfreebsd*-gnu|x86_64-*linux*|powerpc*-*linux*| \ +s390*-*linux*|s390*-*tpf*|sparc*-*linux*) + # Find out what ABI is being produced by ac_compile, and set linker + # options accordingly. Note that the listed cases only cover the + # situations where additional linker options are needed (such as when + # doing 32-bit compilation for a host where ld defaults to 64-bit, or + # vice versa); the common cases where no linker options are needed do + # not appear in the list. + echo 'int i;' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + case `/usr/bin/file conftest.o` in + *32-bit*) + case $host in + x86_64-*kfreebsd*-gnu) + LD="${LD-ld} -m elf_i386_fbsd" + ;; + x86_64-*linux*) + case `/usr/bin/file conftest.o` in + *x86-64*) + LD="${LD-ld} -m elf32_x86_64" + ;; + *) + LD="${LD-ld} -m elf_i386" + ;; + esac + ;; + powerpc64le-*linux*) + LD="${LD-ld} -m elf32lppclinux" + ;; + powerpc64-*linux*) + LD="${LD-ld} -m elf32ppclinux" + ;; + s390x-*linux*) + LD="${LD-ld} -m elf_s390" + ;; + sparc64-*linux*) + LD="${LD-ld} -m elf32_sparc" + ;; + esac + ;; + *64-bit*) + case $host in + x86_64-*kfreebsd*-gnu) + LD="${LD-ld} -m elf_x86_64_fbsd" + ;; + x86_64-*linux*) + LD="${LD-ld} -m elf_x86_64" + ;; + powerpcle-*linux*) + LD="${LD-ld} -m elf64lppc" + ;; + powerpc-*linux*) + LD="${LD-ld} -m elf64ppc" + ;; + s390*-*linux*|s390*-*tpf*) + LD="${LD-ld} -m elf64_s390" + ;; + sparc*-*linux*) + LD="${LD-ld} -m elf64_sparc" + ;; + esac + ;; + esac + fi + rm -rf conftest* + ;; + +*-*-sco3.2v5*) + # On SCO OpenServer 5, we need -belf to get full-featured binaries. + SAVE_CFLAGS=$CFLAGS + CFLAGS="$CFLAGS -belf" + AC_CACHE_CHECK([whether the C compiler needs -belf], lt_cv_cc_needs_belf, + [AC_LANG_PUSH(C) + AC_LINK_IFELSE([AC_LANG_PROGRAM([[]],[[]])],[lt_cv_cc_needs_belf=yes],[lt_cv_cc_needs_belf=no]) + AC_LANG_POP]) + if test yes != "$lt_cv_cc_needs_belf"; then + # this is probably gcc 2.8.0, egcs 1.0 or newer; no need for -belf + CFLAGS=$SAVE_CFLAGS + fi + ;; +*-*solaris*) + # Find out what ABI is being produced by ac_compile, and set linker + # options accordingly. + echo 'int i;' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + case `/usr/bin/file conftest.o` in + *64-bit*) + case $lt_cv_prog_gnu_ld in + yes*) + case $host in + i?86-*-solaris*|x86_64-*-solaris*) + LD="${LD-ld} -m elf_x86_64" + ;; + sparc*-*-solaris*) + LD="${LD-ld} -m elf64_sparc" + ;; + esac + # GNU ld 2.21 introduced _sol2 emulations. Use them if available. + if ${LD-ld} -V | grep _sol2 >/dev/null 2>&1; then + LD=${LD-ld}_sol2 + fi + ;; + *) + if ${LD-ld} -64 -r -o conftest2.o conftest.o >/dev/null 2>&1; then + LD="${LD-ld} -64" + fi + ;; + esac + ;; + esac + fi + rm -rf conftest* + ;; +esac + +need_locks=$enable_libtool_lock +])# _LT_ENABLE_LOCK + + +# _LT_PROG_AR +# ----------- +m4_defun([_LT_PROG_AR], +[AC_CHECK_TOOLS(AR, [ar], false) +: ${AR=ar} +: ${AR_FLAGS=cru} +_LT_DECL([], [AR], [1], [The archiver]) +_LT_DECL([], [AR_FLAGS], [1], [Flags to create an archive]) + +AC_CACHE_CHECK([for archiver @FILE support], [lt_cv_ar_at_file], + [lt_cv_ar_at_file=no + AC_COMPILE_IFELSE([AC_LANG_PROGRAM], + [echo conftest.$ac_objext > conftest.lst + lt_ar_try='$AR $AR_FLAGS libconftest.a @conftest.lst >&AS_MESSAGE_LOG_FD' + AC_TRY_EVAL([lt_ar_try]) + if test 0 -eq "$ac_status"; then + # Ensure the archiver fails upon bogus file names. + rm -f conftest.$ac_objext libconftest.a + AC_TRY_EVAL([lt_ar_try]) + if test 0 -ne "$ac_status"; then + lt_cv_ar_at_file=@ + fi + fi + rm -f conftest.* libconftest.a + ]) + ]) + +if test no = "$lt_cv_ar_at_file"; then + archiver_list_spec= +else + archiver_list_spec=$lt_cv_ar_at_file +fi +_LT_DECL([], [archiver_list_spec], [1], + [How to feed a file listing to the archiver]) +])# _LT_PROG_AR + + +# _LT_CMD_OLD_ARCHIVE +# ------------------- +m4_defun([_LT_CMD_OLD_ARCHIVE], +[_LT_PROG_AR + +AC_CHECK_TOOL(STRIP, strip, :) +test -z "$STRIP" && STRIP=: +_LT_DECL([], [STRIP], [1], [A symbol stripping program]) + +AC_CHECK_TOOL(RANLIB, ranlib, :) +test -z "$RANLIB" && RANLIB=: +_LT_DECL([], [RANLIB], [1], + [Commands used to install an old-style archive]) + +# Determine commands to create old-style static archives. +old_archive_cmds='$AR $AR_FLAGS $oldlib$oldobjs' +old_postinstall_cmds='chmod 644 $oldlib' +old_postuninstall_cmds= + +if test -n "$RANLIB"; then + case $host_os in + bitrig* | openbsd*) + old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB -t \$tool_oldlib" + ;; + *) + old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB \$tool_oldlib" + ;; + esac + old_archive_cmds="$old_archive_cmds~\$RANLIB \$tool_oldlib" +fi + +case $host_os in + darwin*) + lock_old_archive_extraction=yes ;; + *) + lock_old_archive_extraction=no ;; +esac +_LT_DECL([], [old_postinstall_cmds], [2]) +_LT_DECL([], [old_postuninstall_cmds], [2]) +_LT_TAGDECL([], [old_archive_cmds], [2], + [Commands used to build an old-style archive]) +_LT_DECL([], [lock_old_archive_extraction], [0], + [Whether to use a lock for old archive extraction]) +])# _LT_CMD_OLD_ARCHIVE + + +# _LT_COMPILER_OPTION(MESSAGE, VARIABLE-NAME, FLAGS, +# [OUTPUT-FILE], [ACTION-SUCCESS], [ACTION-FAILURE]) +# ---------------------------------------------------------------- +# Check whether the given compiler option works +AC_DEFUN([_LT_COMPILER_OPTION], +[m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_DECL_SED])dnl +AC_CACHE_CHECK([$1], [$2], + [$2=no + m4_if([$4], , [ac_outfile=conftest.$ac_objext], [ac_outfile=$4]) + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + lt_compiler_flag="$3" ## exclude from sc_useless_quotes_in_assignment + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + # The option is referenced via a variable to avoid confusing sed. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [[^ ]]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&AS_MESSAGE_LOG_FD) + (eval "$lt_compile" 2>conftest.err) + ac_status=$? + cat conftest.err >&AS_MESSAGE_LOG_FD + echo "$as_me:$LINENO: \$? = $ac_status" >&AS_MESSAGE_LOG_FD + if (exit $ac_status) && test -s "$ac_outfile"; then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings other than the usual output. + $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then + $2=yes + fi + fi + $RM conftest* +]) + +if test yes = "[$]$2"; then + m4_if([$5], , :, [$5]) +else + m4_if([$6], , :, [$6]) +fi +])# _LT_COMPILER_OPTION + +# Old name: +AU_ALIAS([AC_LIBTOOL_COMPILER_OPTION], [_LT_COMPILER_OPTION]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_COMPILER_OPTION], []) + + +# _LT_LINKER_OPTION(MESSAGE, VARIABLE-NAME, FLAGS, +# [ACTION-SUCCESS], [ACTION-FAILURE]) +# ---------------------------------------------------- +# Check whether the given linker option works +AC_DEFUN([_LT_LINKER_OPTION], +[m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_DECL_SED])dnl +AC_CACHE_CHECK([$1], [$2], + [$2=no + save_LDFLAGS=$LDFLAGS + LDFLAGS="$LDFLAGS $3" + echo "$lt_simple_link_test_code" > conftest.$ac_ext + if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then + # The linker can only warn and ignore the option if not recognized + # So say no if there are warnings + if test -s conftest.err; then + # Append any errors to the config.log. + cat conftest.err 1>&AS_MESSAGE_LOG_FD + $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if diff conftest.exp conftest.er2 >/dev/null; then + $2=yes + fi + else + $2=yes + fi + fi + $RM -r conftest* + LDFLAGS=$save_LDFLAGS +]) + +if test yes = "[$]$2"; then + m4_if([$4], , :, [$4]) +else + m4_if([$5], , :, [$5]) +fi +])# _LT_LINKER_OPTION + +# Old name: +AU_ALIAS([AC_LIBTOOL_LINKER_OPTION], [_LT_LINKER_OPTION]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_LINKER_OPTION], []) + + +# LT_CMD_MAX_LEN +#--------------- +AC_DEFUN([LT_CMD_MAX_LEN], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +# find the maximum length of command line arguments +AC_MSG_CHECKING([the maximum length of command line arguments]) +AC_CACHE_VAL([lt_cv_sys_max_cmd_len], [dnl + i=0 + teststring=ABCD + + case $build_os in + msdosdjgpp*) + # On DJGPP, this test can blow up pretty badly due to problems in libc + # (any single argument exceeding 2000 bytes causes a buffer overrun + # during glob expansion). Even if it were fixed, the result of this + # check would be larger than it should be. + lt_cv_sys_max_cmd_len=12288; # 12K is about right + ;; + + gnu*) + # Under GNU Hurd, this test is not required because there is + # no limit to the length of command line arguments. + # Libtool will interpret -1 as no limit whatsoever + lt_cv_sys_max_cmd_len=-1; + ;; + + cygwin* | mingw* | cegcc*) + # On Win9x/ME, this test blows up -- it succeeds, but takes + # about 5 minutes as the teststring grows exponentially. + # Worse, since 9x/ME are not pre-emptively multitasking, + # you end up with a "frozen" computer, even though with patience + # the test eventually succeeds (with a max line length of 256k). + # Instead, let's just punt: use the minimum linelength reported by + # all of the supported platforms: 8192 (on NT/2K/XP). + lt_cv_sys_max_cmd_len=8192; + ;; + + mint*) + # On MiNT this can take a long time and run out of memory. + lt_cv_sys_max_cmd_len=8192; + ;; + + amigaos*) + # On AmigaOS with pdksh, this test takes hours, literally. + # So we just punt and use a minimum line length of 8192. + lt_cv_sys_max_cmd_len=8192; + ;; + + bitrig* | darwin* | dragonfly* | freebsd* | netbsd* | openbsd*) + # This has been around since 386BSD, at least. Likely further. + if test -x /sbin/sysctl; then + lt_cv_sys_max_cmd_len=`/sbin/sysctl -n kern.argmax` + elif test -x /usr/sbin/sysctl; then + lt_cv_sys_max_cmd_len=`/usr/sbin/sysctl -n kern.argmax` + else + lt_cv_sys_max_cmd_len=65536 # usable default for all BSDs + fi + # And add a safety zone + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4` + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3` + ;; + + interix*) + # We know the value 262144 and hardcode it with a safety zone (like BSD) + lt_cv_sys_max_cmd_len=196608 + ;; + + os2*) + # The test takes a long time on OS/2. + lt_cv_sys_max_cmd_len=8192 + ;; + + osf*) + # Dr. Hans Ekkehard Plesser reports seeing a kernel panic running configure + # due to this test when exec_disable_arg_limit is 1 on Tru64. It is not + # nice to cause kernel panics so lets avoid the loop below. + # First set a reasonable default. + lt_cv_sys_max_cmd_len=16384 + # + if test -x /sbin/sysconfig; then + case `/sbin/sysconfig -q proc exec_disable_arg_limit` in + *1*) lt_cv_sys_max_cmd_len=-1 ;; + esac + fi + ;; + sco3.2v5*) + lt_cv_sys_max_cmd_len=102400 + ;; + sysv5* | sco5v6* | sysv4.2uw2*) + kargmax=`grep ARG_MAX /etc/conf/cf.d/stune 2>/dev/null` + if test -n "$kargmax"; then + lt_cv_sys_max_cmd_len=`echo $kargmax | sed 's/.*[[ ]]//'` + else + lt_cv_sys_max_cmd_len=32768 + fi + ;; + *) + lt_cv_sys_max_cmd_len=`(getconf ARG_MAX) 2> /dev/null` + if test -n "$lt_cv_sys_max_cmd_len" && \ + test undefined != "$lt_cv_sys_max_cmd_len"; then + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4` + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3` + else + # Make teststring a little bigger before we do anything with it. + # a 1K string should be a reasonable start. + for i in 1 2 3 4 5 6 7 8; do + teststring=$teststring$teststring + done + SHELL=${SHELL-${CONFIG_SHELL-/bin/sh}} + # If test is not a shell built-in, we'll probably end up computing a + # maximum length that is only half of the actual maximum length, but + # we can't tell. + while { test X`env echo "$teststring$teststring" 2>/dev/null` \ + = "X$teststring$teststring"; } >/dev/null 2>&1 && + test 17 != "$i" # 1/2 MB should be enough + do + i=`expr $i + 1` + teststring=$teststring$teststring + done + # Only check the string length outside the loop. + lt_cv_sys_max_cmd_len=`expr "X$teststring" : ".*" 2>&1` + teststring= + # Add a significant safety factor because C++ compilers can tack on + # massive amounts of additional arguments before passing them to the + # linker. It appears as though 1/2 is a usable value. + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 2` + fi + ;; + esac +]) +if test -n "$lt_cv_sys_max_cmd_len"; then + AC_MSG_RESULT($lt_cv_sys_max_cmd_len) +else + AC_MSG_RESULT(none) +fi +max_cmd_len=$lt_cv_sys_max_cmd_len +_LT_DECL([], [max_cmd_len], [0], + [What is the maximum length of a command?]) +])# LT_CMD_MAX_LEN + +# Old name: +AU_ALIAS([AC_LIBTOOL_SYS_MAX_CMD_LEN], [LT_CMD_MAX_LEN]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_SYS_MAX_CMD_LEN], []) + + +# _LT_HEADER_DLFCN +# ---------------- +m4_defun([_LT_HEADER_DLFCN], +[AC_CHECK_HEADERS([dlfcn.h], [], [], [AC_INCLUDES_DEFAULT])dnl +])# _LT_HEADER_DLFCN + + +# _LT_TRY_DLOPEN_SELF (ACTION-IF-TRUE, ACTION-IF-TRUE-W-USCORE, +# ACTION-IF-FALSE, ACTION-IF-CROSS-COMPILING) +# ---------------------------------------------------------------- +m4_defun([_LT_TRY_DLOPEN_SELF], +[m4_require([_LT_HEADER_DLFCN])dnl +if test yes = "$cross_compiling"; then : + [$4] +else + lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 + lt_status=$lt_dlunknown + cat > conftest.$ac_ext <<_LT_EOF +[#line $LINENO "configure" +#include "confdefs.h" + +#if HAVE_DLFCN_H +#include +#endif + +#include + +#ifdef RTLD_GLOBAL +# define LT_DLGLOBAL RTLD_GLOBAL +#else +# ifdef DL_GLOBAL +# define LT_DLGLOBAL DL_GLOBAL +# else +# define LT_DLGLOBAL 0 +# endif +#endif + +/* We may have to define LT_DLLAZY_OR_NOW in the command line if we + find out it does not work in some platform. */ +#ifndef LT_DLLAZY_OR_NOW +# ifdef RTLD_LAZY +# define LT_DLLAZY_OR_NOW RTLD_LAZY +# else +# ifdef DL_LAZY +# define LT_DLLAZY_OR_NOW DL_LAZY +# else +# ifdef RTLD_NOW +# define LT_DLLAZY_OR_NOW RTLD_NOW +# else +# ifdef DL_NOW +# define LT_DLLAZY_OR_NOW DL_NOW +# else +# define LT_DLLAZY_OR_NOW 0 +# endif +# endif +# endif +# endif +#endif + +/* When -fvisibility=hidden is used, assume the code has been annotated + correspondingly for the symbols needed. */ +#if defined __GNUC__ && (((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)) || (__GNUC__ > 3)) +int fnord () __attribute__((visibility("default"))); +#endif + +int fnord () { return 42; } +int main () +{ + void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW); + int status = $lt_dlunknown; + + if (self) + { + if (dlsym (self,"fnord")) status = $lt_dlno_uscore; + else + { + if (dlsym( self,"_fnord")) status = $lt_dlneed_uscore; + else puts (dlerror ()); + } + /* dlclose (self); */ + } + else + puts (dlerror ()); + + return status; +}] +_LT_EOF + if AC_TRY_EVAL(ac_link) && test -s "conftest$ac_exeext" 2>/dev/null; then + (./conftest; exit; ) >&AS_MESSAGE_LOG_FD 2>/dev/null + lt_status=$? + case x$lt_status in + x$lt_dlno_uscore) $1 ;; + x$lt_dlneed_uscore) $2 ;; + x$lt_dlunknown|x*) $3 ;; + esac + else : + # compilation failed + $3 + fi +fi +rm -fr conftest* +])# _LT_TRY_DLOPEN_SELF + + +# LT_SYS_DLOPEN_SELF +# ------------------ +AC_DEFUN([LT_SYS_DLOPEN_SELF], +[m4_require([_LT_HEADER_DLFCN])dnl +if test yes != "$enable_dlopen"; then + enable_dlopen=unknown + enable_dlopen_self=unknown + enable_dlopen_self_static=unknown +else + lt_cv_dlopen=no + lt_cv_dlopen_libs= + + case $host_os in + beos*) + lt_cv_dlopen=load_add_on + lt_cv_dlopen_libs= + lt_cv_dlopen_self=yes + ;; + + mingw* | pw32* | cegcc*) + lt_cv_dlopen=LoadLibrary + lt_cv_dlopen_libs= + ;; + + cygwin*) + lt_cv_dlopen=dlopen + lt_cv_dlopen_libs= + ;; + + darwin*) + # if libdl is installed we need to link against it + AC_CHECK_LIB([dl], [dlopen], + [lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-ldl],[ + lt_cv_dlopen=dyld + lt_cv_dlopen_libs= + lt_cv_dlopen_self=yes + ]) + ;; + + tpf*) + # Don't try to run any link tests for TPF. We know it's impossible + # because TPF is a cross-compiler, and we know how we open DSOs. + lt_cv_dlopen=dlopen + lt_cv_dlopen_libs= + lt_cv_dlopen_self=no + ;; + + *) + AC_CHECK_FUNC([shl_load], + [lt_cv_dlopen=shl_load], + [AC_CHECK_LIB([dld], [shl_load], + [lt_cv_dlopen=shl_load lt_cv_dlopen_libs=-ldld], + [AC_CHECK_FUNC([dlopen], + [lt_cv_dlopen=dlopen], + [AC_CHECK_LIB([dl], [dlopen], + [lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-ldl], + [AC_CHECK_LIB([svld], [dlopen], + [lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-lsvld], + [AC_CHECK_LIB([dld], [dld_link], + [lt_cv_dlopen=dld_link lt_cv_dlopen_libs=-ldld]) + ]) + ]) + ]) + ]) + ]) + ;; + esac + + if test no = "$lt_cv_dlopen"; then + enable_dlopen=no + else + enable_dlopen=yes + fi + + case $lt_cv_dlopen in + dlopen) + save_CPPFLAGS=$CPPFLAGS + test yes = "$ac_cv_header_dlfcn_h" && CPPFLAGS="$CPPFLAGS -DHAVE_DLFCN_H" + + save_LDFLAGS=$LDFLAGS + wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $export_dynamic_flag_spec\" + + save_LIBS=$LIBS + LIBS="$lt_cv_dlopen_libs $LIBS" + + AC_CACHE_CHECK([whether a program can dlopen itself], + lt_cv_dlopen_self, [dnl + _LT_TRY_DLOPEN_SELF( + lt_cv_dlopen_self=yes, lt_cv_dlopen_self=yes, + lt_cv_dlopen_self=no, lt_cv_dlopen_self=cross) + ]) + + if test yes = "$lt_cv_dlopen_self"; then + wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $lt_prog_compiler_static\" + AC_CACHE_CHECK([whether a statically linked program can dlopen itself], + lt_cv_dlopen_self_static, [dnl + _LT_TRY_DLOPEN_SELF( + lt_cv_dlopen_self_static=yes, lt_cv_dlopen_self_static=yes, + lt_cv_dlopen_self_static=no, lt_cv_dlopen_self_static=cross) + ]) + fi + + CPPFLAGS=$save_CPPFLAGS + LDFLAGS=$save_LDFLAGS + LIBS=$save_LIBS + ;; + esac + + case $lt_cv_dlopen_self in + yes|no) enable_dlopen_self=$lt_cv_dlopen_self ;; + *) enable_dlopen_self=unknown ;; + esac + + case $lt_cv_dlopen_self_static in + yes|no) enable_dlopen_self_static=$lt_cv_dlopen_self_static ;; + *) enable_dlopen_self_static=unknown ;; + esac +fi +_LT_DECL([dlopen_support], [enable_dlopen], [0], + [Whether dlopen is supported]) +_LT_DECL([dlopen_self], [enable_dlopen_self], [0], + [Whether dlopen of programs is supported]) +_LT_DECL([dlopen_self_static], [enable_dlopen_self_static], [0], + [Whether dlopen of statically linked programs is supported]) +])# LT_SYS_DLOPEN_SELF + +# Old name: +AU_ALIAS([AC_LIBTOOL_DLOPEN_SELF], [LT_SYS_DLOPEN_SELF]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_DLOPEN_SELF], []) + + +# _LT_COMPILER_C_O([TAGNAME]) +# --------------------------- +# Check to see if options -c and -o are simultaneously supported by compiler. +# This macro does not hard code the compiler like AC_PROG_CC_C_O. +m4_defun([_LT_COMPILER_C_O], +[m4_require([_LT_DECL_SED])dnl +m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_TAG_COMPILER])dnl +AC_CACHE_CHECK([if $compiler supports -c -o file.$ac_objext], + [_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)], + [_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)=no + $RM -r conftest 2>/dev/null + mkdir conftest + cd conftest + mkdir out + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + lt_compiler_flag="-o out/conftest2.$ac_objext" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [[^ ]]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&AS_MESSAGE_LOG_FD) + (eval "$lt_compile" 2>out/conftest.err) + ac_status=$? + cat out/conftest.err >&AS_MESSAGE_LOG_FD + echo "$as_me:$LINENO: \$? = $ac_status" >&AS_MESSAGE_LOG_FD + if (exit $ac_status) && test -s out/conftest2.$ac_objext + then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings + $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp + $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2 + if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then + _LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)=yes + fi + fi + chmod u+w . 2>&AS_MESSAGE_LOG_FD + $RM conftest* + # SGI C++ compiler will create directory out/ii_files/ for + # template instantiation + test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files + $RM out/* && rmdir out + cd .. + $RM -r conftest + $RM conftest* +]) +_LT_TAGDECL([compiler_c_o], [lt_cv_prog_compiler_c_o], [1], + [Does compiler simultaneously support -c and -o options?]) +])# _LT_COMPILER_C_O + + +# _LT_COMPILER_FILE_LOCKS([TAGNAME]) +# ---------------------------------- +# Check to see if we can do hard links to lock some files if needed +m4_defun([_LT_COMPILER_FILE_LOCKS], +[m4_require([_LT_ENABLE_LOCK])dnl +m4_require([_LT_FILEUTILS_DEFAULTS])dnl +_LT_COMPILER_C_O([$1]) + +hard_links=nottested +if test no = "$_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)" && test no != "$need_locks"; then + # do not overwrite the value of need_locks provided by the user + AC_MSG_CHECKING([if we can lock with hard links]) + hard_links=yes + $RM conftest* + ln conftest.a conftest.b 2>/dev/null && hard_links=no + touch conftest.a + ln conftest.a conftest.b 2>&5 || hard_links=no + ln conftest.a conftest.b 2>/dev/null && hard_links=no + AC_MSG_RESULT([$hard_links]) + if test no = "$hard_links"; then + AC_MSG_WARN(['$CC' does not support '-c -o', so 'make -j' may be unsafe]) + need_locks=warn + fi +else + need_locks=no +fi +_LT_DECL([], [need_locks], [1], [Must we lock files when doing compilation?]) +])# _LT_COMPILER_FILE_LOCKS + + +# _LT_CHECK_OBJDIR +# ---------------- +m4_defun([_LT_CHECK_OBJDIR], +[AC_CACHE_CHECK([for objdir], [lt_cv_objdir], +[rm -f .libs 2>/dev/null +mkdir .libs 2>/dev/null +if test -d .libs; then + lt_cv_objdir=.libs +else + # MS-DOS does not allow filenames that begin with a dot. + lt_cv_objdir=_libs +fi +rmdir .libs 2>/dev/null]) +objdir=$lt_cv_objdir +_LT_DECL([], [objdir], [0], + [The name of the directory that contains temporary libtool files])dnl +m4_pattern_allow([LT_OBJDIR])dnl +AC_DEFINE_UNQUOTED([LT_OBJDIR], "$lt_cv_objdir/", + [Define to the sub-directory where libtool stores uninstalled libraries.]) +])# _LT_CHECK_OBJDIR + + +# _LT_LINKER_HARDCODE_LIBPATH([TAGNAME]) +# -------------------------------------- +# Check hardcoding attributes. +m4_defun([_LT_LINKER_HARDCODE_LIBPATH], +[AC_MSG_CHECKING([how to hardcode library paths into programs]) +_LT_TAGVAR(hardcode_action, $1)= +if test -n "$_LT_TAGVAR(hardcode_libdir_flag_spec, $1)" || + test -n "$_LT_TAGVAR(runpath_var, $1)" || + test yes = "$_LT_TAGVAR(hardcode_automatic, $1)"; then + + # We can hardcode non-existent directories. + if test no != "$_LT_TAGVAR(hardcode_direct, $1)" && + # If the only mechanism to avoid hardcoding is shlibpath_var, we + # have to relink, otherwise we might link with an installed library + # when we should be linking with a yet-to-be-installed one + ## test no != "$_LT_TAGVAR(hardcode_shlibpath_var, $1)" && + test no != "$_LT_TAGVAR(hardcode_minus_L, $1)"; then + # Linking always hardcodes the temporary library directory. + _LT_TAGVAR(hardcode_action, $1)=relink + else + # We can link without hardcoding, and we can hardcode nonexisting dirs. + _LT_TAGVAR(hardcode_action, $1)=immediate + fi +else + # We cannot hardcode anything, or else we can only hardcode existing + # directories. + _LT_TAGVAR(hardcode_action, $1)=unsupported +fi +AC_MSG_RESULT([$_LT_TAGVAR(hardcode_action, $1)]) + +if test relink = "$_LT_TAGVAR(hardcode_action, $1)" || + test yes = "$_LT_TAGVAR(inherit_rpath, $1)"; then + # Fast installation is not supported + enable_fast_install=no +elif test yes = "$shlibpath_overrides_runpath" || + test no = "$enable_shared"; then + # Fast installation is not necessary + enable_fast_install=needless +fi +_LT_TAGDECL([], [hardcode_action], [0], + [How to hardcode a shared library path into an executable]) +])# _LT_LINKER_HARDCODE_LIBPATH + + +# _LT_CMD_STRIPLIB +# ---------------- +m4_defun([_LT_CMD_STRIPLIB], +[m4_require([_LT_DECL_EGREP]) +striplib= +old_striplib= +AC_MSG_CHECKING([whether stripping libraries is possible]) +if test -n "$STRIP" && $STRIP -V 2>&1 | $GREP "GNU strip" >/dev/null; then + test -z "$old_striplib" && old_striplib="$STRIP --strip-debug" + test -z "$striplib" && striplib="$STRIP --strip-unneeded" + AC_MSG_RESULT([yes]) +else +# FIXME - insert some real tests, host_os isn't really good enough + case $host_os in + darwin*) + if test -n "$STRIP"; then + striplib="$STRIP -x" + old_striplib="$STRIP -S" + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + fi + ;; + *) + AC_MSG_RESULT([no]) + ;; + esac +fi +_LT_DECL([], [old_striplib], [1], [Commands to strip libraries]) +_LT_DECL([], [striplib], [1]) +])# _LT_CMD_STRIPLIB + + +# _LT_PREPARE_MUNGE_PATH_LIST +# --------------------------- +# Make sure func_munge_path_list() is defined correctly. +m4_defun([_LT_PREPARE_MUNGE_PATH_LIST], +[[# func_munge_path_list VARIABLE PATH +# ----------------------------------- +# VARIABLE is name of variable containing _space_ separated list of +# directories to be munged by the contents of PATH, which is string +# having a format: +# "DIR[:DIR]:" +# string "DIR[ DIR]" will be prepended to VARIABLE +# ":DIR[:DIR]" +# string "DIR[ DIR]" will be appended to VARIABLE +# "DIRP[:DIRP]::[DIRA:]DIRA" +# string "DIRP[ DIRP]" will be prepended to VARIABLE and string +# "DIRA[ DIRA]" will be appended to VARIABLE +# "DIR[:DIR]" +# VARIABLE will be replaced by "DIR[ DIR]" +func_munge_path_list () +{ + case x@S|@2 in + x) + ;; + *:) + eval @S|@1=\"`$ECHO @S|@2 | $SED 's/:/ /g'` \@S|@@S|@1\" + ;; + x:*) + eval @S|@1=\"\@S|@@S|@1 `$ECHO @S|@2 | $SED 's/:/ /g'`\" + ;; + *::*) + eval @S|@1=\"\@S|@@S|@1\ `$ECHO @S|@2 | $SED -e 's/.*:://' -e 's/:/ /g'`\" + eval @S|@1=\"`$ECHO @S|@2 | $SED -e 's/::.*//' -e 's/:/ /g'`\ \@S|@@S|@1\" + ;; + *) + eval @S|@1=\"`$ECHO @S|@2 | $SED 's/:/ /g'`\" + ;; + esac +} +]])# _LT_PREPARE_PATH_LIST + + +# _LT_SYS_DYNAMIC_LINKER([TAG]) +# ----------------------------- +# PORTME Fill in your ld.so characteristics +m4_defun([_LT_SYS_DYNAMIC_LINKER], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +m4_require([_LT_DECL_EGREP])dnl +m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_DECL_OBJDUMP])dnl +m4_require([_LT_DECL_SED])dnl +m4_require([_LT_CHECK_SHELL_FEATURES])dnl +m4_require([_LT_PREPARE_MUNGE_PATH_LIST])dnl +AC_MSG_CHECKING([dynamic linker characteristics]) +m4_if([$1], + [], [ +if test yes = "$GCC"; then + case $host_os in + darwin*) lt_awk_arg='/^libraries:/,/LR/' ;; + *) lt_awk_arg='/^libraries:/' ;; + esac + case $host_os in + mingw* | cegcc*) lt_sed_strip_eq='s|=\([[A-Za-z]]:\)|\1|g' ;; + *) lt_sed_strip_eq='s|=/|/|g' ;; + esac + lt_search_path_spec=`$CC -print-search-dirs | awk $lt_awk_arg | $SED -e "s/^libraries://" -e $lt_sed_strip_eq` + case $lt_search_path_spec in + *\;*) + # if the path contains ";" then we assume it to be the separator + # otherwise default to the standard path separator (i.e. ":") - it is + # assumed that no part of a normal pathname contains ";" but that should + # okay in the real world where ";" in dirpaths is itself problematic. + lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED 's/;/ /g'` + ;; + *) + lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED "s/$PATH_SEPARATOR/ /g"` + ;; + esac + # Ok, now we have the path, separated by spaces, we can step through it + # and add multilib dir if necessary... + lt_tmp_lt_search_path_spec= + lt_multi_os_dir=/`$CC $CPPFLAGS $CFLAGS $LDFLAGS -print-multi-os-directory 2>/dev/null` + # ...but if some path component already ends with the multilib dir we assume + # that all is fine and trust -print-search-dirs as is (GCC 4.2? or newer). + case "$lt_multi_os_dir; $lt_search_path_spec " in + "/; "* | "/.; "* | "/./; "* | *"$lt_multi_os_dir "* | *"$lt_multi_os_dir/ "*) + lt_multi_os_dir= + ;; + esac + for lt_sys_path in $lt_search_path_spec; do + if test -d "$lt_sys_path$lt_multi_os_dir"; then + lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path$lt_multi_os_dir" + elif test -n "$lt_multi_os_dir"; then + test -d "$lt_sys_path" && \ + lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path" + fi + done + lt_search_path_spec=`$ECHO "$lt_tmp_lt_search_path_spec" | awk ' +BEGIN {RS = " "; FS = "/|\n";} { + lt_foo = ""; + lt_count = 0; + for (lt_i = NF; lt_i > 0; lt_i--) { + if ($lt_i != "" && $lt_i != ".") { + if ($lt_i == "..") { + lt_count++; + } else { + if (lt_count == 0) { + lt_foo = "/" $lt_i lt_foo; + } else { + lt_count--; + } + } + } + } + if (lt_foo != "") { lt_freq[[lt_foo]]++; } + if (lt_freq[[lt_foo]] == 1) { print lt_foo; } +}'` + # AWK program above erroneously prepends '/' to C:/dos/paths + # for these hosts. + case $host_os in + mingw* | cegcc*) lt_search_path_spec=`$ECHO "$lt_search_path_spec" |\ + $SED 's|/\([[A-Za-z]]:\)|\1|g'` ;; + esac + sys_lib_search_path_spec=`$ECHO "$lt_search_path_spec" | $lt_NL2SP` +else + sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib" +fi]) +library_names_spec= +libname_spec='lib$name' +soname_spec= +shrext_cmds=.so +postinstall_cmds= +postuninstall_cmds= +finish_cmds= +finish_eval= +shlibpath_var= +shlibpath_overrides_runpath=unknown +version_type=none +dynamic_linker="$host_os ld.so" +sys_lib_dlsearch_path_spec="/lib /usr/lib" +need_lib_prefix=unknown +hardcode_into_libs=no + +# when you set need_version to no, make sure it does not cause -set_version +# flags to be left without arguments +need_version=unknown + +AC_ARG_VAR([LT_SYS_LIBRARY_PATH], +[User-defined run-time library search path.]) + +case $host_os in +aix3*) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname.a' + shlibpath_var=LIBPATH + + # AIX 3 has no versioning support, so we append a major version to the name. + soname_spec='$libname$release$shared_ext$major' + ;; + +aix[[4-9]]*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + hardcode_into_libs=yes + if test ia64 = "$host_cpu"; then + # AIX 5 supports IA64 + library_names_spec='$libname$release$shared_ext$major $libname$release$shared_ext$versuffix $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + else + # With GCC up to 2.95.x, collect2 would create an import file + # for dependence libraries. The import file would start with + # the line '#! .'. This would cause the generated library to + # depend on '.', always an invalid library. This was fixed in + # development snapshots of GCC prior to 3.0. + case $host_os in + aix4 | aix4.[[01]] | aix4.[[01]].*) + if { echo '#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 97)' + echo ' yes ' + echo '#endif'; } | $CC -E - | $GREP yes > /dev/null; then + : + else + can_build_shared=no + fi + ;; + esac + # Using Import Files as archive members, it is possible to support + # filename-based versioning of shared library archives on AIX. While + # this would work for both with and without runtime linking, it will + # prevent static linking of such archives. So we do filename-based + # shared library versioning with .so extension only, which is used + # when both runtime linking and shared linking is enabled. + # Unfortunately, runtime linking may impact performance, so we do + # not want this to be the default eventually. Also, we use the + # versioned .so libs for executables only if there is the -brtl + # linker flag in LDFLAGS as well, or --with-aix-soname=svr4 only. + # To allow for filename-based versioning support, we need to create + # libNAME.so.V as an archive file, containing: + # *) an Import File, referring to the versioned filename of the + # archive as well as the shared archive member, telling the + # bitwidth (32 or 64) of that shared object, and providing the + # list of exported symbols of that shared object, eventually + # decorated with the 'weak' keyword + # *) the shared object with the F_LOADONLY flag set, to really avoid + # it being seen by the linker. + # At run time we better use the real file rather than another symlink, + # but for link time we create the symlink libNAME.so -> libNAME.so.V + + case $with_aix_soname,$aix_use_runtimelinking in + # AIX (on Power*) has no versioning support, so currently we cannot hardcode correct + # soname into executable. Probably we can add versioning support to + # collect2, so additional links can be useful in future. + aix,yes) # traditional libtool + dynamic_linker='AIX unversionable lib.so' + # If using run time linking (on AIX 4.2 or later) use lib.so + # instead of lib.a to let people know that these are not + # typical AIX shared libraries. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + ;; + aix,no) # traditional AIX only + dynamic_linker='AIX lib.a[(]lib.so.V[)]' + # We preserve .a as extension for shared libraries through AIX4.2 + # and later when we are not doing run time linking. + library_names_spec='$libname$release.a $libname.a' + soname_spec='$libname$release$shared_ext$major' + ;; + svr4,*) # full svr4 only + dynamic_linker="AIX lib.so.V[(]$shared_archive_member_spec.o[)]" + library_names_spec='$libname$release$shared_ext$major $libname$shared_ext' + # We do not specify a path in Import Files, so LIBPATH fires. + shlibpath_overrides_runpath=yes + ;; + *,yes) # both, prefer svr4 + dynamic_linker="AIX lib.so.V[(]$shared_archive_member_spec.o[)], lib.a[(]lib.so.V[)]" + library_names_spec='$libname$release$shared_ext$major $libname$shared_ext' + # unpreferred sharedlib libNAME.a needs extra handling + postinstall_cmds='test -n "$linkname" || linkname="$realname"~func_stripname "" ".so" "$linkname"~$install_shared_prog "$dir/$func_stripname_result.$libext" "$destdir/$func_stripname_result.$libext"~test -z "$tstripme" || test -z "$striplib" || $striplib "$destdir/$func_stripname_result.$libext"' + postuninstall_cmds='for n in $library_names $old_library; do :; done~func_stripname "" ".so" "$n"~test "$func_stripname_result" = "$n" || func_append rmfiles " $odir/$func_stripname_result.$libext"' + # We do not specify a path in Import Files, so LIBPATH fires. + shlibpath_overrides_runpath=yes + ;; + *,no) # both, prefer aix + dynamic_linker="AIX lib.a[(]lib.so.V[)], lib.so.V[(]$shared_archive_member_spec.o[)]" + library_names_spec='$libname$release.a $libname.a' + soname_spec='$libname$release$shared_ext$major' + # unpreferred sharedlib libNAME.so.V and symlink libNAME.so need extra handling + postinstall_cmds='test -z "$dlname" || $install_shared_prog $dir/$dlname $destdir/$dlname~test -z "$tstripme" || test -z "$striplib" || $striplib $destdir/$dlname~test -n "$linkname" || linkname=$realname~func_stripname "" ".a" "$linkname"~(cd "$destdir" && $LN_S -f $dlname $func_stripname_result.so)' + postuninstall_cmds='test -z "$dlname" || func_append rmfiles " $odir/$dlname"~for n in $old_library $library_names; do :; done~func_stripname "" ".a" "$n"~func_append rmfiles " $odir/$func_stripname_result.so"' + ;; + esac + shlibpath_var=LIBPATH + fi + ;; + +amigaos*) + case $host_cpu in + powerpc) + # Since July 2007 AmigaOS4 officially supports .so libraries. + # When compiling the executable, add -use-dynld -Lsobjs: to the compileline. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + ;; + m68k) + library_names_spec='$libname.ixlibrary $libname.a' + # Create ${libname}_ixlibrary.a entries in /sys/libs. + finish_eval='for lib in `ls $libdir/*.ixlibrary 2>/dev/null`; do libname=`func_echo_all "$lib" | $SED '\''s%^.*/\([[^/]]*\)\.ixlibrary$%\1%'\''`; $RM /sys/libs/${libname}_ixlibrary.a; $show "cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a"; cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a || exit 1; done' + ;; + esac + ;; + +beos*) + library_names_spec='$libname$shared_ext' + dynamic_linker="$host_os ld.so" + shlibpath_var=LIBRARY_PATH + ;; + +bsdi[[45]]*) + version_type=linux # correct to gnu/linux during the next big refactor + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib" + sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib" + # the default ld.so.conf also contains /usr/contrib/lib and + # /usr/X11R6/lib (/usr/X11 is a link to /usr/X11R6), but let us allow + # libtool to hard-code these into programs + ;; + +cygwin* | mingw* | pw32* | cegcc*) + version_type=windows + shrext_cmds=.dll + need_version=no + need_lib_prefix=no + + case $GCC,$cc_basename in + yes,*) + # gcc + library_names_spec='$libname.dll.a' + # DLL is installed to $(libdir)/../bin by postinstall_cmds + postinstall_cmds='base_file=`basename \$file`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname~ + chmod a+x \$dldir/$dlname~ + if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then + eval '\''$striplib \$dldir/$dlname'\'' || exit \$?; + fi' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $RM \$dlpath' + shlibpath_overrides_runpath=yes + + case $host_os in + cygwin*) + # Cygwin DLLs use 'cyg' prefix rather than 'lib' + soname_spec='`echo $libname | sed -e 's/^lib/cyg/'``echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext' +m4_if([$1], [],[ + sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/lib/w32api"]) + ;; + mingw* | cegcc*) + # MinGW DLLs use traditional 'lib' prefix + soname_spec='$libname`echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext' + ;; + pw32*) + # pw32 DLLs use 'pw' prefix rather than 'lib' + library_names_spec='`echo $libname | sed -e 's/^lib/pw/'``echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext' + ;; + esac + dynamic_linker='Win32 ld.exe' + ;; + + *,cl*) + # Native MSVC + libname_spec='$name' + soname_spec='$libname`echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext' + library_names_spec='$libname.dll.lib' + + case $build_os in + mingw*) + sys_lib_search_path_spec= + lt_save_ifs=$IFS + IFS=';' + for lt_path in $LIB + do + IFS=$lt_save_ifs + # Let DOS variable expansion print the short 8.3 style file name. + lt_path=`cd "$lt_path" 2>/dev/null && cmd //C "for %i in (".") do @echo %~si"` + sys_lib_search_path_spec="$sys_lib_search_path_spec $lt_path" + done + IFS=$lt_save_ifs + # Convert to MSYS style. + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | sed -e 's|\\\\|/|g' -e 's| \\([[a-zA-Z]]\\):| /\\1|g' -e 's|^ ||'` + ;; + cygwin*) + # Convert to unix form, then to dos form, then back to unix form + # but this time dos style (no spaces!) so that the unix form looks + # like /cygdrive/c/PROGRA~1:/cygdr... + sys_lib_search_path_spec=`cygpath --path --unix "$LIB"` + sys_lib_search_path_spec=`cygpath --path --dos "$sys_lib_search_path_spec" 2>/dev/null` + sys_lib_search_path_spec=`cygpath --path --unix "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` + ;; + *) + sys_lib_search_path_spec=$LIB + if $ECHO "$sys_lib_search_path_spec" | [$GREP ';[c-zC-Z]:/' >/dev/null]; then + # It is most probably a Windows format PATH. + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e 's/;/ /g'` + else + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` + fi + # FIXME: find the short name or the path components, as spaces are + # common. (e.g. "Program Files" -> "PROGRA~1") + ;; + esac + + # DLL is installed to $(libdir)/../bin by postinstall_cmds + postinstall_cmds='base_file=`basename \$file`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $RM \$dlpath' + shlibpath_overrides_runpath=yes + dynamic_linker='Win32 link.exe' + ;; + + *) + # Assume MSVC wrapper + library_names_spec='$libname`echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext $libname.lib' + dynamic_linker='Win32 ld.exe' + ;; + esac + # FIXME: first we should search . and the directory the executable is in + shlibpath_var=PATH + ;; + +darwin* | rhapsody*) + dynamic_linker="$host_os dyld" + version_type=darwin + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$major$shared_ext $libname$shared_ext' + soname_spec='$libname$release$major$shared_ext' + shlibpath_overrides_runpath=yes + shlibpath_var=DYLD_LIBRARY_PATH + shrext_cmds='`test .$module = .yes && echo .so || echo .dylib`' +m4_if([$1], [],[ + sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/local/lib"]) + sys_lib_dlsearch_path_spec='/usr/local/lib /lib /usr/lib' + ;; + +dgux*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +freebsd* | dragonfly*) + # DragonFly does not have aout. When/if they implement a new + # versioning mechanism, adjust this. + if test -x /usr/bin/objformat; then + objformat=`/usr/bin/objformat` + else + case $host_os in + freebsd[[23]].*) objformat=aout ;; + *) objformat=elf ;; + esac + fi + version_type=freebsd-$objformat + case $version_type in + freebsd-elf*) + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + need_version=no + need_lib_prefix=no + ;; + freebsd-*) + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + need_version=yes + ;; + esac + shlibpath_var=LD_LIBRARY_PATH + case $host_os in + freebsd2.*) + shlibpath_overrides_runpath=yes + ;; + freebsd3.[[01]]* | freebsdelf3.[[01]]*) + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + freebsd3.[[2-9]]* | freebsdelf3.[[2-9]]* | \ + freebsd4.[[0-5]] | freebsdelf4.[[0-5]] | freebsd4.1.1 | freebsdelf4.1.1) + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + *) # from 4.6 on, and DragonFly + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + esac + ;; + +haiku*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + dynamic_linker="$host_os runtime_loader" + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LIBRARY_PATH + shlibpath_overrides_runpath=no + sys_lib_dlsearch_path_spec='/boot/home/config/lib /boot/common/lib /boot/system/lib' + hardcode_into_libs=yes + ;; + +hpux9* | hpux10* | hpux11*) + # Give a soname corresponding to the major version so that dld.sl refuses to + # link against other versions. + version_type=sunos + need_lib_prefix=no + need_version=no + case $host_cpu in + ia64*) + shrext_cmds='.so' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.so" + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + if test 32 = "$HPUX_IA64_MODE"; then + sys_lib_search_path_spec="/usr/lib/hpux32 /usr/local/lib/hpux32 /usr/local/lib" + sys_lib_dlsearch_path_spec=/usr/lib/hpux32 + else + sys_lib_search_path_spec="/usr/lib/hpux64 /usr/local/lib/hpux64" + sys_lib_dlsearch_path_spec=/usr/lib/hpux64 + fi + ;; + hppa*64*) + shrext_cmds='.sl' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.sl" + shlibpath_var=LD_LIBRARY_PATH # How should we handle SHLIB_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + sys_lib_search_path_spec="/usr/lib/pa20_64 /usr/ccs/lib/pa20_64" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + *) + shrext_cmds='.sl' + dynamic_linker="$host_os dld.sl" + shlibpath_var=SHLIB_PATH + shlibpath_overrides_runpath=no # +s is required to enable SHLIB_PATH + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + ;; + esac + # HP-UX runs *really* slowly unless shared libraries are mode 555, ... + postinstall_cmds='chmod 555 $lib' + # or fails outright, so override atomically: + install_override_mode=555 + ;; + +interix[[3-9]]*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + dynamic_linker='Interix 3.x ld.so.1 (PE, like ELF)' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + +irix5* | irix6* | nonstopux*) + case $host_os in + nonstopux*) version_type=nonstopux ;; + *) + if test yes = "$lt_cv_prog_gnu_ld"; then + version_type=linux # correct to gnu/linux during the next big refactor + else + version_type=irix + fi ;; + esac + need_lib_prefix=no + need_version=no + soname_spec='$libname$release$shared_ext$major' + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$release$shared_ext $libname$shared_ext' + case $host_os in + irix5* | nonstopux*) + libsuff= shlibsuff= + ;; + *) + case $LD in # libtool.m4 will add one of these switches to LD + *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ") + libsuff= shlibsuff= libmagic=32-bit;; + *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ") + libsuff=32 shlibsuff=N32 libmagic=N32;; + *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ") + libsuff=64 shlibsuff=64 libmagic=64-bit;; + *) libsuff= shlibsuff= libmagic=never-match;; + esac + ;; + esac + shlibpath_var=LD_LIBRARY${shlibsuff}_PATH + shlibpath_overrides_runpath=no + sys_lib_search_path_spec="/usr/lib$libsuff /lib$libsuff /usr/local/lib$libsuff" + sys_lib_dlsearch_path_spec="/usr/lib$libsuff /lib$libsuff" + hardcode_into_libs=yes + ;; + +# No shared lib support for Linux oldld, aout, or coff. +linux*oldld* | linux*aout* | linux*coff*) + dynamic_linker=no + ;; + +linux*android*) + version_type=none # Android doesn't support versioned libraries. + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext' + soname_spec='$libname$release$shared_ext' + finish_cmds= + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + + # This implies no fast_install, which is unacceptable. + # Some rework will be needed to allow for fast_install + # before this can be enabled. + hardcode_into_libs=yes + + dynamic_linker='Android linker' + # Don't embed -rpath directories since the linker doesn't support them. + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + ;; + +# This must be glibc/ELF. +linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -n $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + + # Some binutils ld are patched to set DT_RUNPATH + AC_CACHE_VAL([lt_cv_shlibpath_overrides_runpath], + [lt_cv_shlibpath_overrides_runpath=no + save_LDFLAGS=$LDFLAGS + save_libdir=$libdir + eval "libdir=/foo; wl=\"$_LT_TAGVAR(lt_prog_compiler_wl, $1)\"; \ + LDFLAGS=\"\$LDFLAGS $_LT_TAGVAR(hardcode_libdir_flag_spec, $1)\"" + AC_LINK_IFELSE([AC_LANG_PROGRAM([],[])], + [AS_IF([ ($OBJDUMP -p conftest$ac_exeext) 2>/dev/null | grep "RUNPATH.*$libdir" >/dev/null], + [lt_cv_shlibpath_overrides_runpath=yes])]) + LDFLAGS=$save_LDFLAGS + libdir=$save_libdir + ]) + shlibpath_overrides_runpath=$lt_cv_shlibpath_overrides_runpath + + # This implies no fast_install, which is unacceptable. + # Some rework will be needed to allow for fast_install + # before this can be enabled. + hardcode_into_libs=yes + + # Ideally, we could use ldconfig to report *all* directores which are + # searched for libraries, however this is still not possible. Aside from not + # being certain /sbin/ldconfig is available, command + # 'ldconfig -N -X -v | grep ^/' on 64bit Fedora does not report /usr/lib64, + # even though it is searched at run-time. Try to do the best guess by + # appending ld.so.conf contents (and includes) to the search path. + if test -f /etc/ld.so.conf; then + lt_ld_extra=`awk '/^include / { system(sprintf("cd /etc; cat %s 2>/dev/null", \[$]2)); skip = 1; } { if (!skip) print \[$]0; skip = 0; }' < /etc/ld.so.conf | $SED -e 's/#.*//;/^[ ]*hwcap[ ]/d;s/[:, ]/ /g;s/=[^=]*$//;s/=[^= ]* / /g;s/"//g;/^$/d' | tr '\n' ' '` + sys_lib_dlsearch_path_spec="/lib /usr/lib $lt_ld_extra" + fi + + # We used to test for /lib/ld.so.1 and disable shared libraries on + # powerpc, because MkLinux only supported shared libraries with the + # GNU dynamic linker. Since this was broken with cross compilers, + # most powerpc-linux boxes support dynamic linking these days and + # people can always --disable-shared, the test was removed, and we + # assume the GNU/Linux dynamic linker is in use. + dynamic_linker='GNU/Linux ld.so' + ;; + +netbsd*) + version_type=sunos + need_lib_prefix=no + need_version=no + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + dynamic_linker='NetBSD (a.out) ld.so' + else + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + dynamic_linker='NetBSD ld.elf_so' + fi + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + +newsos6) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + ;; + +*nto* | *qnx*) + version_type=qnx + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + dynamic_linker='ldqnx.so' + ;; + +openbsd* | bitrig*) + version_type=sunos + sys_lib_dlsearch_path_spec=/usr/lib + need_lib_prefix=no + if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then + need_version=no + else + need_version=yes + fi + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + ;; + +os2*) + libname_spec='$name' + version_type=windows + shrext_cmds=.dll + need_version=no + need_lib_prefix=no + # OS/2 can only load a DLL with a base name of 8 characters or less. + soname_spec='`test -n "$os2dllname" && libname="$os2dllname"; + v=$($ECHO $release$versuffix | tr -d .-); + n=$($ECHO $libname | cut -b -$((8 - ${#v})) | tr . _); + $ECHO $n$v`$shared_ext' + library_names_spec='${libname}_dll.$libext' + dynamic_linker='OS/2 ld.exe' + shlibpath_var=BEGINLIBPATH + sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + postinstall_cmds='base_file=`basename \$file`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; $ECHO \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname~ + chmod a+x \$dldir/$dlname~ + if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then + eval '\''$striplib \$dldir/$dlname'\'' || exit \$?; + fi' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; $ECHO \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $RM \$dlpath' + ;; + +osf3* | osf4* | osf5*) + version_type=osf + need_lib_prefix=no + need_version=no + soname_spec='$libname$release$shared_ext$major' + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + +rdos*) + dynamic_linker=no + ;; + +solaris*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + # ldd complains unless libraries are executable + postinstall_cmds='chmod +x $lib' + ;; + +sunos4*) + version_type=sunos + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + finish_cmds='PATH="\$PATH:/usr/etc" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + if test yes = "$with_gnu_ld"; then + need_lib_prefix=no + fi + need_version=yes + ;; + +sysv4 | sysv4.3*) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + case $host_vendor in + sni) + shlibpath_overrides_runpath=no + need_lib_prefix=no + runpath_var=LD_RUN_PATH + ;; + siemens) + need_lib_prefix=no + ;; + motorola) + need_lib_prefix=no + need_version=no + shlibpath_overrides_runpath=no + sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib' + ;; + esac + ;; + +sysv4*MP*) + if test -d /usr/nec; then + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$shared_ext.$versuffix $libname$shared_ext.$major $libname$shared_ext' + soname_spec='$libname$shared_ext.$major' + shlibpath_var=LD_LIBRARY_PATH + fi + ;; + +sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) + version_type=sco + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + if test yes = "$with_gnu_ld"; then + sys_lib_search_path_spec='/usr/local/lib /usr/gnu/lib /usr/ccs/lib /usr/lib /lib' + else + sys_lib_search_path_spec='/usr/ccs/lib /usr/lib' + case $host_os in + sco3.2v5*) + sys_lib_search_path_spec="$sys_lib_search_path_spec /lib" + ;; + esac + fi + sys_lib_dlsearch_path_spec='/usr/lib' + ;; + +tpf*) + # TPF is a cross-target only. Preferred cross-host = GNU/Linux. + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + +uts4*) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +*) + dynamic_linker=no + ;; +esac +AC_MSG_RESULT([$dynamic_linker]) +test no = "$dynamic_linker" && can_build_shared=no + +variables_saved_for_relink="PATH $shlibpath_var $runpath_var" +if test yes = "$GCC"; then + variables_saved_for_relink="$variables_saved_for_relink GCC_EXEC_PREFIX COMPILER_PATH LIBRARY_PATH" +fi + +if test set = "${lt_cv_sys_lib_search_path_spec+set}"; then + sys_lib_search_path_spec=$lt_cv_sys_lib_search_path_spec +fi + +if test set = "${lt_cv_sys_lib_dlsearch_path_spec+set}"; then + sys_lib_dlsearch_path_spec=$lt_cv_sys_lib_dlsearch_path_spec +fi + +# remember unaugmented sys_lib_dlsearch_path content for libtool script decls... +configure_time_dlsearch_path=$sys_lib_dlsearch_path_spec + +# ... but it needs LT_SYS_LIBRARY_PATH munging for other configure-time code +func_munge_path_list sys_lib_dlsearch_path_spec "$LT_SYS_LIBRARY_PATH" + +# to be used as default LT_SYS_LIBRARY_PATH value in generated libtool +configure_time_lt_sys_library_path=$LT_SYS_LIBRARY_PATH + +_LT_DECL([], [variables_saved_for_relink], [1], + [Variables whose values should be saved in libtool wrapper scripts and + restored at link time]) +_LT_DECL([], [need_lib_prefix], [0], + [Do we need the "lib" prefix for modules?]) +_LT_DECL([], [need_version], [0], [Do we need a version for libraries?]) +_LT_DECL([], [version_type], [0], [Library versioning type]) +_LT_DECL([], [runpath_var], [0], [Shared library runtime path variable]) +_LT_DECL([], [shlibpath_var], [0],[Shared library path variable]) +_LT_DECL([], [shlibpath_overrides_runpath], [0], + [Is shlibpath searched before the hard-coded library search path?]) +_LT_DECL([], [libname_spec], [1], [Format of library name prefix]) +_LT_DECL([], [library_names_spec], [1], + [[List of archive names. First name is the real one, the rest are links. + The last name is the one that the linker finds with -lNAME]]) +_LT_DECL([], [soname_spec], [1], + [[The coded name of the library, if different from the real name]]) +_LT_DECL([], [install_override_mode], [1], + [Permission mode override for installation of shared libraries]) +_LT_DECL([], [postinstall_cmds], [2], + [Command to use after installation of a shared archive]) +_LT_DECL([], [postuninstall_cmds], [2], + [Command to use after uninstallation of a shared archive]) +_LT_DECL([], [finish_cmds], [2], + [Commands used to finish a libtool library installation in a directory]) +_LT_DECL([], [finish_eval], [1], + [[As "finish_cmds", except a single script fragment to be evaled but + not shown]]) +_LT_DECL([], [hardcode_into_libs], [0], + [Whether we should hardcode library paths into libraries]) +_LT_DECL([], [sys_lib_search_path_spec], [2], + [Compile-time system search path for libraries]) +_LT_DECL([sys_lib_dlsearch_path_spec], [configure_time_dlsearch_path], [2], + [Detected run-time system search path for libraries]) +_LT_DECL([], [configure_time_lt_sys_library_path], [2], + [Explicit LT_SYS_LIBRARY_PATH set during ./configure time]) +])# _LT_SYS_DYNAMIC_LINKER + + +# _LT_PATH_TOOL_PREFIX(TOOL) +# -------------------------- +# find a file program that can recognize shared library +AC_DEFUN([_LT_PATH_TOOL_PREFIX], +[m4_require([_LT_DECL_EGREP])dnl +AC_MSG_CHECKING([for $1]) +AC_CACHE_VAL(lt_cv_path_MAGIC_CMD, +[case $MAGIC_CMD in +[[\\/*] | ?:[\\/]*]) + lt_cv_path_MAGIC_CMD=$MAGIC_CMD # Let the user override the test with a path. + ;; +*) + lt_save_MAGIC_CMD=$MAGIC_CMD + lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR +dnl $ac_dummy forces splitting on constant user-supplied paths. +dnl POSIX.2 word splitting is done only on the output of word expansions, +dnl not every word. This closes a longstanding sh security hole. + ac_dummy="m4_if([$2], , $PATH, [$2])" + for ac_dir in $ac_dummy; do + IFS=$lt_save_ifs + test -z "$ac_dir" && ac_dir=. + if test -f "$ac_dir/$1"; then + lt_cv_path_MAGIC_CMD=$ac_dir/"$1" + if test -n "$file_magic_test_file"; then + case $deplibs_check_method in + "file_magic "*) + file_magic_regex=`expr "$deplibs_check_method" : "file_magic \(.*\)"` + MAGIC_CMD=$lt_cv_path_MAGIC_CMD + if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null | + $EGREP "$file_magic_regex" > /dev/null; then + : + else + cat <<_LT_EOF 1>&2 + +*** Warning: the command libtool uses to detect shared libraries, +*** $file_magic_cmd, produces output that libtool cannot recognize. +*** The result is that libtool may fail to recognize shared libraries +*** as such. This will affect the creation of libtool libraries that +*** depend on shared libraries, but programs linked with such libtool +*** libraries will work regardless of this problem. Nevertheless, you +*** may want to report the problem to your system manager and/or to +*** bug-libtool@gnu.org + +_LT_EOF + fi ;; + esac + fi + break + fi + done + IFS=$lt_save_ifs + MAGIC_CMD=$lt_save_MAGIC_CMD + ;; +esac]) +MAGIC_CMD=$lt_cv_path_MAGIC_CMD +if test -n "$MAGIC_CMD"; then + AC_MSG_RESULT($MAGIC_CMD) +else + AC_MSG_RESULT(no) +fi +_LT_DECL([], [MAGIC_CMD], [0], + [Used to examine libraries when file_magic_cmd begins with "file"])dnl +])# _LT_PATH_TOOL_PREFIX + +# Old name: +AU_ALIAS([AC_PATH_TOOL_PREFIX], [_LT_PATH_TOOL_PREFIX]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_PATH_TOOL_PREFIX], []) + + +# _LT_PATH_MAGIC +# -------------- +# find a file program that can recognize a shared library +m4_defun([_LT_PATH_MAGIC], +[_LT_PATH_TOOL_PREFIX(${ac_tool_prefix}file, /usr/bin$PATH_SEPARATOR$PATH) +if test -z "$lt_cv_path_MAGIC_CMD"; then + if test -n "$ac_tool_prefix"; then + _LT_PATH_TOOL_PREFIX(file, /usr/bin$PATH_SEPARATOR$PATH) + else + MAGIC_CMD=: + fi +fi +])# _LT_PATH_MAGIC + + +# LT_PATH_LD +# ---------- +# find the pathname to the GNU or non-GNU linker +AC_DEFUN([LT_PATH_LD], +[AC_REQUIRE([AC_PROG_CC])dnl +AC_REQUIRE([AC_CANONICAL_HOST])dnl +AC_REQUIRE([AC_CANONICAL_BUILD])dnl +m4_require([_LT_DECL_SED])dnl +m4_require([_LT_DECL_EGREP])dnl +m4_require([_LT_PROG_ECHO_BACKSLASH])dnl + +AC_ARG_WITH([gnu-ld], + [AS_HELP_STRING([--with-gnu-ld], + [assume the C compiler uses GNU ld @<:@default=no@:>@])], + [test no = "$withval" || with_gnu_ld=yes], + [with_gnu_ld=no])dnl + +ac_prog=ld +if test yes = "$GCC"; then + # Check if gcc -print-prog-name=ld gives a path. + AC_MSG_CHECKING([for ld used by $CC]) + case $host in + *-*-mingw*) + # gcc leaves a trailing carriage return, which upsets mingw + ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;; + *) + ac_prog=`($CC -print-prog-name=ld) 2>&5` ;; + esac + case $ac_prog in + # Accept absolute paths. + [[\\/]]* | ?:[[\\/]]*) + re_direlt='/[[^/]][[^/]]*/\.\./' + # Canonicalize the pathname of ld + ac_prog=`$ECHO "$ac_prog"| $SED 's%\\\\%/%g'` + while $ECHO "$ac_prog" | $GREP "$re_direlt" > /dev/null 2>&1; do + ac_prog=`$ECHO $ac_prog| $SED "s%$re_direlt%/%"` + done + test -z "$LD" && LD=$ac_prog + ;; + "") + # If it fails, then pretend we aren't using GCC. + ac_prog=ld + ;; + *) + # If it is relative, then search for the first ld in PATH. + with_gnu_ld=unknown + ;; + esac +elif test yes = "$with_gnu_ld"; then + AC_MSG_CHECKING([for GNU ld]) +else + AC_MSG_CHECKING([for non-GNU ld]) +fi +AC_CACHE_VAL(lt_cv_path_LD, +[if test -z "$LD"; then + lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR + for ac_dir in $PATH; do + IFS=$lt_save_ifs + test -z "$ac_dir" && ac_dir=. + if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then + lt_cv_path_LD=$ac_dir/$ac_prog + # Check to see if the program is GNU ld. I'd rather use --version, + # but apparently some variants of GNU ld only accept -v. + # Break only if it was the GNU/non-GNU ld that we prefer. + case `"$lt_cv_path_LD" -v 2>&1 &1 conftest.i +cat conftest.i conftest.i >conftest2.i +: ${lt_DD:=$DD} +AC_PATH_PROGS_FEATURE_CHECK([lt_DD], [dd], +[if "$ac_path_lt_DD" bs=32 count=1 conftest.out 2>/dev/null; then + cmp -s conftest.i conftest.out \ + && ac_cv_path_lt_DD="$ac_path_lt_DD" ac_path_lt_DD_found=: +fi]) +rm -f conftest.i conftest2.i conftest.out]) +])# _LT_PATH_DD + + +# _LT_CMD_TRUNCATE +# ---------------- +# find command to truncate a binary pipe +m4_defun([_LT_CMD_TRUNCATE], +[m4_require([_LT_PATH_DD]) +AC_CACHE_CHECK([how to truncate binary pipes], [lt_cv_truncate_bin], +[printf 0123456789abcdef0123456789abcdef >conftest.i +cat conftest.i conftest.i >conftest2.i +lt_cv_truncate_bin= +if "$ac_cv_path_lt_DD" bs=32 count=1 conftest.out 2>/dev/null; then + cmp -s conftest.i conftest.out \ + && lt_cv_truncate_bin="$ac_cv_path_lt_DD bs=4096 count=1" +fi +rm -f conftest.i conftest2.i conftest.out +test -z "$lt_cv_truncate_bin" && lt_cv_truncate_bin="$SED -e 4q"]) +_LT_DECL([lt_truncate_bin], [lt_cv_truncate_bin], [1], + [Command to truncate a binary pipe]) +])# _LT_CMD_TRUNCATE + + +# _LT_CHECK_MAGIC_METHOD +# ---------------------- +# how to check for library dependencies +# -- PORTME fill in with the dynamic library characteristics +m4_defun([_LT_CHECK_MAGIC_METHOD], +[m4_require([_LT_DECL_EGREP]) +m4_require([_LT_DECL_OBJDUMP]) +AC_CACHE_CHECK([how to recognize dependent libraries], +lt_cv_deplibs_check_method, +[lt_cv_file_magic_cmd='$MAGIC_CMD' +lt_cv_file_magic_test_file= +lt_cv_deplibs_check_method='unknown' +# Need to set the preceding variable on all platforms that support +# interlibrary dependencies. +# 'none' -- dependencies not supported. +# 'unknown' -- same as none, but documents that we really don't know. +# 'pass_all' -- all dependencies passed with no checks. +# 'test_compile' -- check by making test program. +# 'file_magic [[regex]]' -- check by looking for files in library path +# that responds to the $file_magic_cmd with a given extended regex. +# If you have 'file' or equivalent on your system and you're not sure +# whether 'pass_all' will *always* work, you probably want this one. + +case $host_os in +aix[[4-9]]*) + lt_cv_deplibs_check_method=pass_all + ;; + +beos*) + lt_cv_deplibs_check_method=pass_all + ;; + +bsdi[[45]]*) + lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (shared object|dynamic lib)' + lt_cv_file_magic_cmd='/usr/bin/file -L' + lt_cv_file_magic_test_file=/shlib/libc.so + ;; + +cygwin*) + # func_win32_libid is a shell function defined in ltmain.sh + lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL' + lt_cv_file_magic_cmd='func_win32_libid' + ;; + +mingw* | pw32*) + # Base MSYS/MinGW do not provide the 'file' command needed by + # func_win32_libid shell function, so use a weaker test based on 'objdump', + # unless we find 'file', for example because we are cross-compiling. + if ( file / ) >/dev/null 2>&1; then + lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL' + lt_cv_file_magic_cmd='func_win32_libid' + else + # Keep this pattern in sync with the one in func_win32_libid. + lt_cv_deplibs_check_method='file_magic file format (pei*-i386(.*architecture: i386)?|pe-arm-wince|pe-x86-64)' + lt_cv_file_magic_cmd='$OBJDUMP -f' + fi + ;; + +cegcc*) + # use the weaker test based on 'objdump'. See mingw*. + lt_cv_deplibs_check_method='file_magic file format pe-arm-.*little(.*architecture: arm)?' + lt_cv_file_magic_cmd='$OBJDUMP -f' + ;; + +darwin* | rhapsody*) + lt_cv_deplibs_check_method=pass_all + ;; + +freebsd* | dragonfly*) + if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then + case $host_cpu in + i*86 ) + # Not sure whether the presence of OpenBSD here was a mistake. + # Let's accept both of them until this is cleared up. + lt_cv_deplibs_check_method='file_magic (FreeBSD|OpenBSD|DragonFly)/i[[3-9]]86 (compact )?demand paged shared library' + lt_cv_file_magic_cmd=/usr/bin/file + lt_cv_file_magic_test_file=`echo /usr/lib/libc.so.*` + ;; + esac + else + lt_cv_deplibs_check_method=pass_all + fi + ;; + +haiku*) + lt_cv_deplibs_check_method=pass_all + ;; + +hpux10.20* | hpux11*) + lt_cv_file_magic_cmd=/usr/bin/file + case $host_cpu in + ia64*) + lt_cv_deplibs_check_method='file_magic (s[[0-9]][[0-9]][[0-9]]|ELF-[[0-9]][[0-9]]) shared object file - IA64' + lt_cv_file_magic_test_file=/usr/lib/hpux32/libc.so + ;; + hppa*64*) + [lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|ELF[ -][0-9][0-9])(-bit)?( [LM]SB)? shared object( file)?[, -]* PA-RISC [0-9]\.[0-9]'] + lt_cv_file_magic_test_file=/usr/lib/pa20_64/libc.sl + ;; + *) + lt_cv_deplibs_check_method='file_magic (s[[0-9]][[0-9]][[0-9]]|PA-RISC[[0-9]]\.[[0-9]]) shared library' + lt_cv_file_magic_test_file=/usr/lib/libc.sl + ;; + esac + ;; + +interix[[3-9]]*) + # PIC code is broken on Interix 3.x, that's why |\.a not |_pic\.a here + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so|\.a)$' + ;; + +irix5* | irix6* | nonstopux*) + case $LD in + *-32|*"-32 ") libmagic=32-bit;; + *-n32|*"-n32 ") libmagic=N32;; + *-64|*"-64 ") libmagic=64-bit;; + *) libmagic=never-match;; + esac + lt_cv_deplibs_check_method=pass_all + ;; + +# This must be glibc/ELF. +linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + lt_cv_deplibs_check_method=pass_all + ;; + +netbsd*) + if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|_pic\.a)$' + else + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so|_pic\.a)$' + fi + ;; + +newos6*) + lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (executable|dynamic lib)' + lt_cv_file_magic_cmd=/usr/bin/file + lt_cv_file_magic_test_file=/usr/lib/libnls.so + ;; + +*nto* | *qnx*) + lt_cv_deplibs_check_method=pass_all + ;; + +openbsd* | bitrig*) + if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|\.so|_pic\.a)$' + else + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|_pic\.a)$' + fi + ;; + +osf3* | osf4* | osf5*) + lt_cv_deplibs_check_method=pass_all + ;; + +rdos*) + lt_cv_deplibs_check_method=pass_all + ;; + +solaris*) + lt_cv_deplibs_check_method=pass_all + ;; + +sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) + lt_cv_deplibs_check_method=pass_all + ;; + +sysv4 | sysv4.3*) + case $host_vendor in + motorola) + lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (shared object|dynamic lib) M[[0-9]][[0-9]]* Version [[0-9]]' + lt_cv_file_magic_test_file=`echo /usr/lib/libc.so*` + ;; + ncr) + lt_cv_deplibs_check_method=pass_all + ;; + sequent) + lt_cv_file_magic_cmd='/bin/file' + lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[LM]]SB (shared object|dynamic lib )' + ;; + sni) + lt_cv_file_magic_cmd='/bin/file' + lt_cv_deplibs_check_method="file_magic ELF [[0-9]][[0-9]]*-bit [[LM]]SB dynamic lib" + lt_cv_file_magic_test_file=/lib/libc.so + ;; + siemens) + lt_cv_deplibs_check_method=pass_all + ;; + pc) + lt_cv_deplibs_check_method=pass_all + ;; + esac + ;; + +tpf*) + lt_cv_deplibs_check_method=pass_all + ;; +os2*) + lt_cv_deplibs_check_method=pass_all + ;; +esac +]) + +file_magic_glob= +want_nocaseglob=no +if test "$build" = "$host"; then + case $host_os in + mingw* | pw32*) + if ( shopt | grep nocaseglob ) >/dev/null 2>&1; then + want_nocaseglob=yes + else + file_magic_glob=`echo aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ | $SED -e "s/\(..\)/s\/[[\1]]\/[[\1]]\/g;/g"` + fi + ;; + esac +fi + +file_magic_cmd=$lt_cv_file_magic_cmd +deplibs_check_method=$lt_cv_deplibs_check_method +test -z "$deplibs_check_method" && deplibs_check_method=unknown + +_LT_DECL([], [deplibs_check_method], [1], + [Method to check whether dependent libraries are shared objects]) +_LT_DECL([], [file_magic_cmd], [1], + [Command to use when deplibs_check_method = "file_magic"]) +_LT_DECL([], [file_magic_glob], [1], + [How to find potential files when deplibs_check_method = "file_magic"]) +_LT_DECL([], [want_nocaseglob], [1], + [Find potential files using nocaseglob when deplibs_check_method = "file_magic"]) +])# _LT_CHECK_MAGIC_METHOD + + +# LT_PATH_NM +# ---------- +# find the pathname to a BSD- or MS-compatible name lister +AC_DEFUN([LT_PATH_NM], +[AC_REQUIRE([AC_PROG_CC])dnl +AC_CACHE_CHECK([for BSD- or MS-compatible name lister (nm)], lt_cv_path_NM, +[if test -n "$NM"; then + # Let the user override the test. + lt_cv_path_NM=$NM +else + lt_nm_to_check=${ac_tool_prefix}nm + if test -n "$ac_tool_prefix" && test "$build" = "$host"; then + lt_nm_to_check="$lt_nm_to_check nm" + fi + for lt_tmp_nm in $lt_nm_to_check; do + lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR + for ac_dir in $PATH /usr/ccs/bin/elf /usr/ccs/bin /usr/ucb /bin; do + IFS=$lt_save_ifs + test -z "$ac_dir" && ac_dir=. + tmp_nm=$ac_dir/$lt_tmp_nm + if test -f "$tmp_nm" || test -f "$tmp_nm$ac_exeext"; then + # Check to see if the nm accepts a BSD-compat flag. + # Adding the 'sed 1q' prevents false positives on HP-UX, which says: + # nm: unknown option "B" ignored + # Tru64's nm complains that /dev/null is an invalid object file + # MSYS converts /dev/null to NUL, MinGW nm treats NUL as empty + case $build_os in + mingw*) lt_bad_file=conftest.nm/nofile ;; + *) lt_bad_file=/dev/null ;; + esac + case `"$tmp_nm" -B $lt_bad_file 2>&1 | sed '1q'` in + *$lt_bad_file* | *'Invalid file or object type'*) + lt_cv_path_NM="$tmp_nm -B" + break 2 + ;; + *) + case `"$tmp_nm" -p /dev/null 2>&1 | sed '1q'` in + */dev/null*) + lt_cv_path_NM="$tmp_nm -p" + break 2 + ;; + *) + lt_cv_path_NM=${lt_cv_path_NM="$tmp_nm"} # keep the first match, but + continue # so that we can try to find one that supports BSD flags + ;; + esac + ;; + esac + fi + done + IFS=$lt_save_ifs + done + : ${lt_cv_path_NM=no} +fi]) +if test no != "$lt_cv_path_NM"; then + NM=$lt_cv_path_NM +else + # Didn't find any BSD compatible name lister, look for dumpbin. + if test -n "$DUMPBIN"; then : + # Let the user override the test. + else + AC_CHECK_TOOLS(DUMPBIN, [dumpbin "link -dump"], :) + case `$DUMPBIN -symbols -headers /dev/null 2>&1 | sed '1q'` in + *COFF*) + DUMPBIN="$DUMPBIN -symbols -headers" + ;; + *) + DUMPBIN=: + ;; + esac + fi + AC_SUBST([DUMPBIN]) + if test : != "$DUMPBIN"; then + NM=$DUMPBIN + fi +fi +test -z "$NM" && NM=nm +AC_SUBST([NM]) +_LT_DECL([], [NM], [1], [A BSD- or MS-compatible name lister])dnl + +AC_CACHE_CHECK([the name lister ($NM) interface], [lt_cv_nm_interface], + [lt_cv_nm_interface="BSD nm" + echo "int some_variable = 0;" > conftest.$ac_ext + (eval echo "\"\$as_me:$LINENO: $ac_compile\"" >&AS_MESSAGE_LOG_FD) + (eval "$ac_compile" 2>conftest.err) + cat conftest.err >&AS_MESSAGE_LOG_FD + (eval echo "\"\$as_me:$LINENO: $NM \\\"conftest.$ac_objext\\\"\"" >&AS_MESSAGE_LOG_FD) + (eval "$NM \"conftest.$ac_objext\"" 2>conftest.err > conftest.out) + cat conftest.err >&AS_MESSAGE_LOG_FD + (eval echo "\"\$as_me:$LINENO: output\"" >&AS_MESSAGE_LOG_FD) + cat conftest.out >&AS_MESSAGE_LOG_FD + if $GREP 'External.*some_variable' conftest.out > /dev/null; then + lt_cv_nm_interface="MS dumpbin" + fi + rm -f conftest*]) +])# LT_PATH_NM + +# Old names: +AU_ALIAS([AM_PROG_NM], [LT_PATH_NM]) +AU_ALIAS([AC_PROG_NM], [LT_PATH_NM]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AM_PROG_NM], []) +dnl AC_DEFUN([AC_PROG_NM], []) + +# _LT_CHECK_SHAREDLIB_FROM_LINKLIB +# -------------------------------- +# how to determine the name of the shared library +# associated with a specific link library. +# -- PORTME fill in with the dynamic library characteristics +m4_defun([_LT_CHECK_SHAREDLIB_FROM_LINKLIB], +[m4_require([_LT_DECL_EGREP]) +m4_require([_LT_DECL_OBJDUMP]) +m4_require([_LT_DECL_DLLTOOL]) +AC_CACHE_CHECK([how to associate runtime and link libraries], +lt_cv_sharedlib_from_linklib_cmd, +[lt_cv_sharedlib_from_linklib_cmd='unknown' + +case $host_os in +cygwin* | mingw* | pw32* | cegcc*) + # two different shell functions defined in ltmain.sh; + # decide which one to use based on capabilities of $DLLTOOL + case `$DLLTOOL --help 2>&1` in + *--identify-strict*) + lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib + ;; + *) + lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib_fallback + ;; + esac + ;; +*) + # fallback: assume linklib IS sharedlib + lt_cv_sharedlib_from_linklib_cmd=$ECHO + ;; +esac +]) +sharedlib_from_linklib_cmd=$lt_cv_sharedlib_from_linklib_cmd +test -z "$sharedlib_from_linklib_cmd" && sharedlib_from_linklib_cmd=$ECHO + +_LT_DECL([], [sharedlib_from_linklib_cmd], [1], + [Command to associate shared and link libraries]) +])# _LT_CHECK_SHAREDLIB_FROM_LINKLIB + + +# _LT_PATH_MANIFEST_TOOL +# ---------------------- +# locate the manifest tool +m4_defun([_LT_PATH_MANIFEST_TOOL], +[AC_CHECK_TOOL(MANIFEST_TOOL, mt, :) +test -z "$MANIFEST_TOOL" && MANIFEST_TOOL=mt +AC_CACHE_CHECK([if $MANIFEST_TOOL is a manifest tool], [lt_cv_path_mainfest_tool], + [lt_cv_path_mainfest_tool=no + echo "$as_me:$LINENO: $MANIFEST_TOOL '-?'" >&AS_MESSAGE_LOG_FD + $MANIFEST_TOOL '-?' 2>conftest.err > conftest.out + cat conftest.err >&AS_MESSAGE_LOG_FD + if $GREP 'Manifest Tool' conftest.out > /dev/null; then + lt_cv_path_mainfest_tool=yes + fi + rm -f conftest*]) +if test yes != "$lt_cv_path_mainfest_tool"; then + MANIFEST_TOOL=: +fi +_LT_DECL([], [MANIFEST_TOOL], [1], [Manifest tool])dnl +])# _LT_PATH_MANIFEST_TOOL + + +# _LT_DLL_DEF_P([FILE]) +# --------------------- +# True iff FILE is a Windows DLL '.def' file. +# Keep in sync with func_dll_def_p in the libtool script +AC_DEFUN([_LT_DLL_DEF_P], +[dnl + test DEF = "`$SED -n dnl + -e '\''s/^[[ ]]*//'\'' dnl Strip leading whitespace + -e '\''/^\(;.*\)*$/d'\'' dnl Delete empty lines and comments + -e '\''s/^\(EXPORTS\|LIBRARY\)\([[ ]].*\)*$/DEF/p'\'' dnl + -e q dnl Only consider the first "real" line + $1`" dnl +])# _LT_DLL_DEF_P + + +# LT_LIB_M +# -------- +# check for math library +AC_DEFUN([LT_LIB_M], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +LIBM= +case $host in +*-*-beos* | *-*-cegcc* | *-*-cygwin* | *-*-haiku* | *-*-pw32* | *-*-darwin*) + # These system don't have libm, or don't need it + ;; +*-ncr-sysv4.3*) + AC_CHECK_LIB(mw, _mwvalidcheckl, LIBM=-lmw) + AC_CHECK_LIB(m, cos, LIBM="$LIBM -lm") + ;; +*) + AC_CHECK_LIB(m, cos, LIBM=-lm) + ;; +esac +AC_SUBST([LIBM]) +])# LT_LIB_M + +# Old name: +AU_ALIAS([AC_CHECK_LIBM], [LT_LIB_M]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_CHECK_LIBM], []) + + +# _LT_COMPILER_NO_RTTI([TAGNAME]) +# ------------------------------- +m4_defun([_LT_COMPILER_NO_RTTI], +[m4_require([_LT_TAG_COMPILER])dnl + +_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)= + +if test yes = "$GCC"; then + case $cc_basename in + nvcc*) + _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -Xcompiler -fno-builtin' ;; + *) + _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -fno-builtin' ;; + esac + + _LT_COMPILER_OPTION([if $compiler supports -fno-rtti -fno-exceptions], + lt_cv_prog_compiler_rtti_exceptions, + [-fno-rtti -fno-exceptions], [], + [_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)="$_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1) -fno-rtti -fno-exceptions"]) +fi +_LT_TAGDECL([no_builtin_flag], [lt_prog_compiler_no_builtin_flag], [1], + [Compiler flag to turn off builtin functions]) +])# _LT_COMPILER_NO_RTTI + + +# _LT_CMD_GLOBAL_SYMBOLS +# ---------------------- +m4_defun([_LT_CMD_GLOBAL_SYMBOLS], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +AC_REQUIRE([AC_PROG_CC])dnl +AC_REQUIRE([AC_PROG_AWK])dnl +AC_REQUIRE([LT_PATH_NM])dnl +AC_REQUIRE([LT_PATH_LD])dnl +m4_require([_LT_DECL_SED])dnl +m4_require([_LT_DECL_EGREP])dnl +m4_require([_LT_TAG_COMPILER])dnl + +# Check for command to grab the raw symbol name followed by C symbol from nm. +AC_MSG_CHECKING([command to parse $NM output from $compiler object]) +AC_CACHE_VAL([lt_cv_sys_global_symbol_pipe], +[ +# These are sane defaults that work on at least a few old systems. +# [They come from Ultrix. What could be older than Ultrix?!! ;)] + +# Character class describing NM global symbol codes. +symcode='[[BCDEGRST]]' + +# Regexp to match symbols that can be accessed directly from C. +sympat='\([[_A-Za-z]][[_A-Za-z0-9]]*\)' + +# Define system-specific variables. +case $host_os in +aix*) + symcode='[[BCDT]]' + ;; +cygwin* | mingw* | pw32* | cegcc*) + symcode='[[ABCDGISTW]]' + ;; +hpux*) + if test ia64 = "$host_cpu"; then + symcode='[[ABCDEGRST]]' + fi + ;; +irix* | nonstopux*) + symcode='[[BCDEGRST]]' + ;; +osf*) + symcode='[[BCDEGQRST]]' + ;; +solaris*) + symcode='[[BDRT]]' + ;; +sco3.2v5*) + symcode='[[DT]]' + ;; +sysv4.2uw2*) + symcode='[[DT]]' + ;; +sysv5* | sco5v6* | unixware* | OpenUNIX*) + symcode='[[ABDT]]' + ;; +sysv4) + symcode='[[DFNSTU]]' + ;; +esac + +# If we're using GNU nm, then use its standard symbol codes. +case `$NM -V 2>&1` in +*GNU* | *'with BFD'*) + symcode='[[ABCDGIRSTW]]' ;; +esac + +if test "$lt_cv_nm_interface" = "MS dumpbin"; then + # Gets list of data symbols to import. + lt_cv_sys_global_symbol_to_import="sed -n -e 's/^I .* \(.*\)$/\1/p'" + # Adjust the below global symbol transforms to fixup imported variables. + lt_cdecl_hook=" -e 's/^I .* \(.*\)$/extern __declspec(dllimport) char \1;/p'" + lt_c_name_hook=" -e 's/^I .* \(.*\)$/ {\"\1\", (void *) 0},/p'" + lt_c_name_lib_hook="\ + -e 's/^I .* \(lib.*\)$/ {\"\1\", (void *) 0},/p'\ + -e 's/^I .* \(.*\)$/ {\"lib\1\", (void *) 0},/p'" +else + # Disable hooks by default. + lt_cv_sys_global_symbol_to_import= + lt_cdecl_hook= + lt_c_name_hook= + lt_c_name_lib_hook= +fi + +# Transform an extracted symbol line into a proper C declaration. +# Some systems (esp. on ia64) link data and code symbols differently, +# so use this general approach. +lt_cv_sys_global_symbol_to_cdecl="sed -n"\ +$lt_cdecl_hook\ +" -e 's/^T .* \(.*\)$/extern int \1();/p'"\ +" -e 's/^$symcode$symcode* .* \(.*\)$/extern char \1;/p'" + +# Transform an extracted symbol line into symbol name and symbol address +lt_cv_sys_global_symbol_to_c_name_address="sed -n"\ +$lt_c_name_hook\ +" -e 's/^: \(.*\) .*$/ {\"\1\", (void *) 0},/p'"\ +" -e 's/^$symcode$symcode* .* \(.*\)$/ {\"\1\", (void *) \&\1},/p'" + +# Transform an extracted symbol line into symbol name with lib prefix and +# symbol address. +lt_cv_sys_global_symbol_to_c_name_address_lib_prefix="sed -n"\ +$lt_c_name_lib_hook\ +" -e 's/^: \(.*\) .*$/ {\"\1\", (void *) 0},/p'"\ +" -e 's/^$symcode$symcode* .* \(lib.*\)$/ {\"\1\", (void *) \&\1},/p'"\ +" -e 's/^$symcode$symcode* .* \(.*\)$/ {\"lib\1\", (void *) \&\1},/p'" + +# Handle CRLF in mingw tool chain +opt_cr= +case $build_os in +mingw*) + opt_cr=`$ECHO 'x\{0,1\}' | tr x '\015'` # option cr in regexp + ;; +esac + +# Try without a prefix underscore, then with it. +for ac_symprfx in "" "_"; do + + # Transform symcode, sympat, and symprfx into a raw symbol and a C symbol. + symxfrm="\\1 $ac_symprfx\\2 \\2" + + # Write the raw and C identifiers. + if test "$lt_cv_nm_interface" = "MS dumpbin"; then + # Fake it for dumpbin and say T for any non-static function, + # D for any global variable and I for any imported variable. + # Also find C++ and __fastcall symbols from MSVC++, + # which start with @ or ?. + lt_cv_sys_global_symbol_pipe="$AWK ['"\ +" {last_section=section; section=\$ 3};"\ +" /^COFF SYMBOL TABLE/{for(i in hide) delete hide[i]};"\ +" /Section length .*#relocs.*(pick any)/{hide[last_section]=1};"\ +" /^ *Symbol name *: /{split(\$ 0,sn,\":\"); si=substr(sn[2],2)};"\ +" /^ *Type *: code/{print \"T\",si,substr(si,length(prfx))};"\ +" /^ *Type *: data/{print \"I\",si,substr(si,length(prfx))};"\ +" \$ 0!~/External *\|/{next};"\ +" / 0+ UNDEF /{next}; / UNDEF \([^|]\)*()/{next};"\ +" {if(hide[section]) next};"\ +" {f=\"D\"}; \$ 0~/\(\).*\|/{f=\"T\"};"\ +" {split(\$ 0,a,/\||\r/); split(a[2],s)};"\ +" s[1]~/^[@?]/{print f,s[1],s[1]; next};"\ +" s[1]~prfx {split(s[1],t,\"@\"); print f,t[1],substr(t[1],length(prfx))}"\ +" ' prfx=^$ac_symprfx]" + else + lt_cv_sys_global_symbol_pipe="sed -n -e 's/^.*[[ ]]\($symcode$symcode*\)[[ ]][[ ]]*$ac_symprfx$sympat$opt_cr$/$symxfrm/p'" + fi + lt_cv_sys_global_symbol_pipe="$lt_cv_sys_global_symbol_pipe | sed '/ __gnu_lto/d'" + + # Check to see that the pipe works correctly. + pipe_works=no + + rm -f conftest* + cat > conftest.$ac_ext <<_LT_EOF +#ifdef __cplusplus +extern "C" { +#endif +char nm_test_var; +void nm_test_func(void); +void nm_test_func(void){} +#ifdef __cplusplus +} +#endif +int main(){nm_test_var='a';nm_test_func();return(0);} +_LT_EOF + + if AC_TRY_EVAL(ac_compile); then + # Now try to grab the symbols. + nlist=conftest.nm + if AC_TRY_EVAL(NM conftest.$ac_objext \| "$lt_cv_sys_global_symbol_pipe" \> $nlist) && test -s "$nlist"; then + # Try sorting and uniquifying the output. + if sort "$nlist" | uniq > "$nlist"T; then + mv -f "$nlist"T "$nlist" + else + rm -f "$nlist"T + fi + + # Make sure that we snagged all the symbols we need. + if $GREP ' nm_test_var$' "$nlist" >/dev/null; then + if $GREP ' nm_test_func$' "$nlist" >/dev/null; then + cat <<_LT_EOF > conftest.$ac_ext +/* Keep this code in sync between libtool.m4, ltmain, lt_system.h, and tests. */ +#if defined _WIN32 || defined __CYGWIN__ || defined _WIN32_WCE +/* DATA imports from DLLs on WIN32 can't be const, because runtime + relocations are performed -- see ld's documentation on pseudo-relocs. */ +# define LT@&t@_DLSYM_CONST +#elif defined __osf__ +/* This system does not cope well with relocations in const data. */ +# define LT@&t@_DLSYM_CONST +#else +# define LT@&t@_DLSYM_CONST const +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +_LT_EOF + # Now generate the symbol file. + eval "$lt_cv_sys_global_symbol_to_cdecl"' < "$nlist" | $GREP -v main >> conftest.$ac_ext' + + cat <<_LT_EOF >> conftest.$ac_ext + +/* The mapping between symbol names and symbols. */ +LT@&t@_DLSYM_CONST struct { + const char *name; + void *address; +} +lt__PROGRAM__LTX_preloaded_symbols[[]] = +{ + { "@PROGRAM@", (void *) 0 }, +_LT_EOF + $SED "s/^$symcode$symcode* .* \(.*\)$/ {\"\1\", (void *) \&\1},/" < "$nlist" | $GREP -v main >> conftest.$ac_ext + cat <<\_LT_EOF >> conftest.$ac_ext + {0, (void *) 0} +}; + +/* This works around a problem in FreeBSD linker */ +#ifdef FREEBSD_WORKAROUND +static const void *lt_preloaded_setup() { + return lt__PROGRAM__LTX_preloaded_symbols; +} +#endif + +#ifdef __cplusplus +} +#endif +_LT_EOF + # Now try linking the two files. + mv conftest.$ac_objext conftstm.$ac_objext + lt_globsym_save_LIBS=$LIBS + lt_globsym_save_CFLAGS=$CFLAGS + LIBS=conftstm.$ac_objext + CFLAGS="$CFLAGS$_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)" + if AC_TRY_EVAL(ac_link) && test -s conftest$ac_exeext; then + pipe_works=yes + fi + LIBS=$lt_globsym_save_LIBS + CFLAGS=$lt_globsym_save_CFLAGS + else + echo "cannot find nm_test_func in $nlist" >&AS_MESSAGE_LOG_FD + fi + else + echo "cannot find nm_test_var in $nlist" >&AS_MESSAGE_LOG_FD + fi + else + echo "cannot run $lt_cv_sys_global_symbol_pipe" >&AS_MESSAGE_LOG_FD + fi + else + echo "$progname: failed program was:" >&AS_MESSAGE_LOG_FD + cat conftest.$ac_ext >&5 + fi + rm -rf conftest* conftst* + + # Do not use the global_symbol_pipe unless it works. + if test yes = "$pipe_works"; then + break + else + lt_cv_sys_global_symbol_pipe= + fi +done +]) +if test -z "$lt_cv_sys_global_symbol_pipe"; then + lt_cv_sys_global_symbol_to_cdecl= +fi +if test -z "$lt_cv_sys_global_symbol_pipe$lt_cv_sys_global_symbol_to_cdecl"; then + AC_MSG_RESULT(failed) +else + AC_MSG_RESULT(ok) +fi + +# Response file support. +if test "$lt_cv_nm_interface" = "MS dumpbin"; then + nm_file_list_spec='@' +elif $NM --help 2>/dev/null | grep '[[@]]FILE' >/dev/null; then + nm_file_list_spec='@' +fi + +_LT_DECL([global_symbol_pipe], [lt_cv_sys_global_symbol_pipe], [1], + [Take the output of nm and produce a listing of raw symbols and C names]) +_LT_DECL([global_symbol_to_cdecl], [lt_cv_sys_global_symbol_to_cdecl], [1], + [Transform the output of nm in a proper C declaration]) +_LT_DECL([global_symbol_to_import], [lt_cv_sys_global_symbol_to_import], [1], + [Transform the output of nm into a list of symbols to manually relocate]) +_LT_DECL([global_symbol_to_c_name_address], + [lt_cv_sys_global_symbol_to_c_name_address], [1], + [Transform the output of nm in a C name address pair]) +_LT_DECL([global_symbol_to_c_name_address_lib_prefix], + [lt_cv_sys_global_symbol_to_c_name_address_lib_prefix], [1], + [Transform the output of nm in a C name address pair when lib prefix is needed]) +_LT_DECL([nm_interface], [lt_cv_nm_interface], [1], + [The name lister interface]) +_LT_DECL([], [nm_file_list_spec], [1], + [Specify filename containing input files for $NM]) +]) # _LT_CMD_GLOBAL_SYMBOLS + + +# _LT_COMPILER_PIC([TAGNAME]) +# --------------------------- +m4_defun([_LT_COMPILER_PIC], +[m4_require([_LT_TAG_COMPILER])dnl +_LT_TAGVAR(lt_prog_compiler_wl, $1)= +_LT_TAGVAR(lt_prog_compiler_pic, $1)= +_LT_TAGVAR(lt_prog_compiler_static, $1)= + +m4_if([$1], [CXX], [ + # C++ specific cases for pic, static, wl, etc. + if test yes = "$GXX"; then + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + + case $host_os in + aix*) + # All AIX code is PIC. + if test ia64 = "$host_cpu"; then + # AIX 5 now supports IA64 processor + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + fi + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + m68k) + # FIXME: we need at least 68020 code to build shared libraries, but + # adding the '-m68020' flag to GCC prevents building anything better, + # like '-m68040'. + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-m68020 -resident32 -malways-restore-a4' + ;; + esac + ;; + + beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) + # PIC is the default for these OSes. + ;; + mingw* | cygwin* | os2* | pw32* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + # Although the cygwin gcc ignores -fPIC, still need this for old-style + # (--disable-auto-import) libraries + m4_if([$1], [GCJ], [], + [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT']) + case $host_os in + os2*) + _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-static' + ;; + esac + ;; + darwin* | rhapsody*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fno-common' + ;; + *djgpp*) + # DJGPP does not support shared libraries at all + _LT_TAGVAR(lt_prog_compiler_pic, $1)= + ;; + haiku*) + # PIC is the default for Haiku. + # The "-static" flag exists, but is broken. + _LT_TAGVAR(lt_prog_compiler_static, $1)= + ;; + interix[[3-9]]*) + # Interix 3.x gcc -fpic/-fPIC options generate broken code. + # Instead, we relocate shared libraries at runtime. + ;; + sysv4*MP*) + if test -d /usr/nec; then + _LT_TAGVAR(lt_prog_compiler_pic, $1)=-Kconform_pic + fi + ;; + hpux*) + # PIC is the default for 64-bit PA HP-UX, but not for 32-bit + # PA HP-UX. On IA64 HP-UX, PIC is the default but the pic flag + # sets the default TLS model and affects inlining. + case $host_cpu in + hppa*64*) + ;; + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + esac + ;; + *qnx* | *nto*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared' + ;; + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + esac + else + case $host_os in + aix[[4-9]]*) + # All AIX code is PIC. + if test ia64 = "$host_cpu"; then + # AIX 5 now supports IA64 processor + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + else + _LT_TAGVAR(lt_prog_compiler_static, $1)='-bnso -bI:/lib/syscalls.exp' + fi + ;; + chorus*) + case $cc_basename in + cxch68*) + # Green Hills C++ Compiler + # _LT_TAGVAR(lt_prog_compiler_static, $1)="--no_auto_instantiation -u __main -u __premain -u _abort -r $COOL_DIR/lib/libOrb.a $MVME_DIR/lib/CC/libC.a $MVME_DIR/lib/classix/libcx.s.a" + ;; + esac + ;; + mingw* | cygwin* | os2* | pw32* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + m4_if([$1], [GCJ], [], + [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT']) + ;; + dgux*) + case $cc_basename in + ec++*) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + ;; + ghcx*) + # Green Hills C++ Compiler + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + ;; + *) + ;; + esac + ;; + freebsd* | dragonfly*) + # FreeBSD uses GNU C++ + ;; + hpux9* | hpux10* | hpux11*) + case $cc_basename in + CC*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-a ${wl}archive' + if test ia64 != "$host_cpu"; then + _LT_TAGVAR(lt_prog_compiler_pic, $1)='+Z' + fi + ;; + aCC*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-a ${wl}archive' + case $host_cpu in + hppa*64*|ia64*) + # +Z the default + ;; + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='+Z' + ;; + esac + ;; + *) + ;; + esac + ;; + interix*) + # This is c89, which is MS Visual C++ (no shared libs) + # Anyone wants to do a port? + ;; + irix5* | irix6* | nonstopux*) + case $cc_basename in + CC*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + # CC pic flag -KPIC is the default. + ;; + *) + ;; + esac + ;; + linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + case $cc_basename in + KCC*) + # KAI C++ Compiler + _LT_TAGVAR(lt_prog_compiler_wl, $1)='--backend -Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + ecpc* ) + # old Intel C++ for x86_64, which still supported -KPIC. + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + icpc* ) + # Intel C++, used to be incompatible with GCC. + # ICC 10 doesn't accept -KPIC any more. + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + pgCC* | pgcpp*) + # Portland Group C++ compiler + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fpic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + cxx*) + # Compaq C++ + # Make sure the PIC flag is empty. It appears that all Alpha + # Linux and Compaq Tru64 Unix objects are PIC. + _LT_TAGVAR(lt_prog_compiler_pic, $1)= + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + xlc* | xlC* | bgxl[[cC]]* | mpixl[[cC]]*) + # IBM XL 8.0, 9.0 on PPC and BlueGene + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-qpic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-qstaticlink' + ;; + *) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) + # Sun C++ 5.9 + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ' + ;; + esac + ;; + esac + ;; + lynxos*) + ;; + m88k*) + ;; + mvs*) + case $cc_basename in + cxx*) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-W c,exportall' + ;; + *) + ;; + esac + ;; + netbsd*) + ;; + *qnx* | *nto*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared' + ;; + osf3* | osf4* | osf5*) + case $cc_basename in + KCC*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='--backend -Wl,' + ;; + RCC*) + # Rational C++ 2.4.1 + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + ;; + cxx*) + # Digital/Compaq C++ + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # Make sure the PIC flag is empty. It appears that all Alpha + # Linux and Compaq Tru64 Unix objects are PIC. + _LT_TAGVAR(lt_prog_compiler_pic, $1)= + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + *) + ;; + esac + ;; + psos*) + ;; + solaris*) + case $cc_basename in + CC* | sunCC*) + # Sun C++ 4.2, 5.x and Centerline C++ + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ' + ;; + gcx*) + # Green Hills C++ Compiler + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC' + ;; + *) + ;; + esac + ;; + sunos4*) + case $cc_basename in + CC*) + # Sun C++ 4.x + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + lcc*) + # Lucid + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + ;; + *) + ;; + esac + ;; + sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) + case $cc_basename in + CC*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + esac + ;; + tandem*) + case $cc_basename in + NCC*) + # NonStop-UX NCC 3.20 + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + ;; + *) + ;; + esac + ;; + vxworks*) + ;; + *) + _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no + ;; + esac + fi +], +[ + if test yes = "$GCC"; then + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + + case $host_os in + aix*) + # All AIX code is PIC. + if test ia64 = "$host_cpu"; then + # AIX 5 now supports IA64 processor + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + fi + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + m68k) + # FIXME: we need at least 68020 code to build shared libraries, but + # adding the '-m68020' flag to GCC prevents building anything better, + # like '-m68040'. + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-m68020 -resident32 -malways-restore-a4' + ;; + esac + ;; + + beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) + # PIC is the default for these OSes. + ;; + + mingw* | cygwin* | pw32* | os2* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + # Although the cygwin gcc ignores -fPIC, still need this for old-style + # (--disable-auto-import) libraries + m4_if([$1], [GCJ], [], + [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT']) + case $host_os in + os2*) + _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-static' + ;; + esac + ;; + + darwin* | rhapsody*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fno-common' + ;; + + haiku*) + # PIC is the default for Haiku. + # The "-static" flag exists, but is broken. + _LT_TAGVAR(lt_prog_compiler_static, $1)= + ;; + + hpux*) + # PIC is the default for 64-bit PA HP-UX, but not for 32-bit + # PA HP-UX. On IA64 HP-UX, PIC is the default but the pic flag + # sets the default TLS model and affects inlining. + case $host_cpu in + hppa*64*) + # +Z the default + ;; + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + esac + ;; + + interix[[3-9]]*) + # Interix 3.x gcc -fpic/-fPIC options generate broken code. + # Instead, we relocate shared libraries at runtime. + ;; + + msdosdjgpp*) + # Just because we use GCC doesn't mean we suddenly get shared libraries + # on systems that don't support them. + _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no + enable_shared=no + ;; + + *nto* | *qnx*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared' + ;; + + sysv4*MP*) + if test -d /usr/nec; then + _LT_TAGVAR(lt_prog_compiler_pic, $1)=-Kconform_pic + fi + ;; + + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + esac + + case $cc_basename in + nvcc*) # Cuda Compiler Driver 2.2 + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Xlinker ' + if test -n "$_LT_TAGVAR(lt_prog_compiler_pic, $1)"; then + _LT_TAGVAR(lt_prog_compiler_pic, $1)="-Xcompiler $_LT_TAGVAR(lt_prog_compiler_pic, $1)" + fi + ;; + esac + else + # PORTME Check for flag to pass linker flags through the system compiler. + case $host_os in + aix*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + if test ia64 = "$host_cpu"; then + # AIX 5 now supports IA64 processor + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + else + _LT_TAGVAR(lt_prog_compiler_static, $1)='-bnso -bI:/lib/syscalls.exp' + fi + ;; + + darwin* | rhapsody*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fno-common' + case $cc_basename in + nagfor*) + # NAG Fortran compiler + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,-Wl,,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + esac + ;; + + mingw* | cygwin* | pw32* | os2* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + m4_if([$1], [GCJ], [], + [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT']) + case $host_os in + os2*) + _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-static' + ;; + esac + ;; + + hpux9* | hpux10* | hpux11*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but + # not for PA HP-UX. + case $host_cpu in + hppa*64*|ia64*) + # +Z the default + ;; + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='+Z' + ;; + esac + # Is there a better lt_prog_compiler_static that works with the bundled CC? + _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-a ${wl}archive' + ;; + + irix5* | irix6* | nonstopux*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # PIC (with -KPIC) is the default. + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + + linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + case $cc_basename in + # old Intel for x86_64, which still supported -KPIC. + ecc*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + # icc used to be incompatible with GCC. + # ICC 10 doesn't accept -KPIC any more. + icc* | ifort*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + # Lahey Fortran 8.1. + lf95*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='--shared' + _LT_TAGVAR(lt_prog_compiler_static, $1)='--static' + ;; + nagfor*) + # NAG Fortran compiler + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,-Wl,,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + tcc*) + # Fabrice Bellard et al's Tiny C Compiler + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + pgcc* | pgf77* | pgf90* | pgf95* | pgfortran*) + # Portland Group compilers (*not* the Pentium gcc compiler, + # which looks to be a dead project) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fpic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + ccc*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # All Alpha code is PIC. + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + xl* | bgxl* | bgf* | mpixl*) + # IBM XL C 8.0/Fortran 10.1, 11.1 on PPC and BlueGene + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-qpic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-qstaticlink' + ;; + *) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ Ceres\ Fortran* | *Sun*Fortran*\ [[1-7]].* | *Sun*Fortran*\ 8.[[0-3]]*) + # Sun Fortran 8.3 passes all unrecognized flags to the linker + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + _LT_TAGVAR(lt_prog_compiler_wl, $1)='' + ;; + *Sun\ F* | *Sun*Fortran*) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ' + ;; + *Sun\ C*) + # Sun C 5.9 + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + ;; + *Intel*\ [[CF]]*Compiler*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + *Portland\ Group*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fpic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + esac + ;; + esac + ;; + + newsos6) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + *nto* | *qnx*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared' + ;; + + osf3* | osf4* | osf5*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # All OSF/1 code is PIC. + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + + rdos*) + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + + solaris*) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + case $cc_basename in + f77* | f90* | f95* | sunf77* | sunf90* | sunf95*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ';; + *) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,';; + esac + ;; + + sunos4*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + sysv4 | sysv4.2uw2* | sysv4.3*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + sysv4*MP*) + if test -d /usr/nec; then + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-Kconform_pic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + fi + ;; + + sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + unicos*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no + ;; + + uts4*) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + *) + _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no + ;; + esac + fi +]) +case $host_os in + # For platforms that do not support PIC, -DPIC is meaningless: + *djgpp*) + _LT_TAGVAR(lt_prog_compiler_pic, $1)= + ;; + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)="$_LT_TAGVAR(lt_prog_compiler_pic, $1)@&t@m4_if([$1],[],[ -DPIC],[m4_if([$1],[CXX],[ -DPIC],[])])" + ;; +esac + +AC_CACHE_CHECK([for $compiler option to produce PIC], + [_LT_TAGVAR(lt_cv_prog_compiler_pic, $1)], + [_LT_TAGVAR(lt_cv_prog_compiler_pic, $1)=$_LT_TAGVAR(lt_prog_compiler_pic, $1)]) +_LT_TAGVAR(lt_prog_compiler_pic, $1)=$_LT_TAGVAR(lt_cv_prog_compiler_pic, $1) + +# +# Check to make sure the PIC flag actually works. +# +if test -n "$_LT_TAGVAR(lt_prog_compiler_pic, $1)"; then + _LT_COMPILER_OPTION([if $compiler PIC flag $_LT_TAGVAR(lt_prog_compiler_pic, $1) works], + [_LT_TAGVAR(lt_cv_prog_compiler_pic_works, $1)], + [$_LT_TAGVAR(lt_prog_compiler_pic, $1)@&t@m4_if([$1],[],[ -DPIC],[m4_if([$1],[CXX],[ -DPIC],[])])], [], + [case $_LT_TAGVAR(lt_prog_compiler_pic, $1) in + "" | " "*) ;; + *) _LT_TAGVAR(lt_prog_compiler_pic, $1)=" $_LT_TAGVAR(lt_prog_compiler_pic, $1)" ;; + esac], + [_LT_TAGVAR(lt_prog_compiler_pic, $1)= + _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no]) +fi +_LT_TAGDECL([pic_flag], [lt_prog_compiler_pic], [1], + [Additional compiler flags for building library objects]) + +_LT_TAGDECL([wl], [lt_prog_compiler_wl], [1], + [How to pass a linker flag through the compiler]) +# +# Check to make sure the static flag actually works. +# +wl=$_LT_TAGVAR(lt_prog_compiler_wl, $1) eval lt_tmp_static_flag=\"$_LT_TAGVAR(lt_prog_compiler_static, $1)\" +_LT_LINKER_OPTION([if $compiler static flag $lt_tmp_static_flag works], + _LT_TAGVAR(lt_cv_prog_compiler_static_works, $1), + $lt_tmp_static_flag, + [], + [_LT_TAGVAR(lt_prog_compiler_static, $1)=]) +_LT_TAGDECL([link_static_flag], [lt_prog_compiler_static], [1], + [Compiler flag to prevent dynamic linking]) +])# _LT_COMPILER_PIC + + +# _LT_LINKER_SHLIBS([TAGNAME]) +# ---------------------------- +# See if the linker supports building shared libraries. +m4_defun([_LT_LINKER_SHLIBS], +[AC_REQUIRE([LT_PATH_LD])dnl +AC_REQUIRE([LT_PATH_NM])dnl +m4_require([_LT_PATH_MANIFEST_TOOL])dnl +m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_DECL_EGREP])dnl +m4_require([_LT_DECL_SED])dnl +m4_require([_LT_CMD_GLOBAL_SYMBOLS])dnl +m4_require([_LT_TAG_COMPILER])dnl +AC_MSG_CHECKING([whether the $compiler linker ($LD) supports shared libraries]) +m4_if([$1], [CXX], [ + _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + _LT_TAGVAR(exclude_expsyms, $1)=['_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*'] + case $host_os in + aix[[4-9]]*) + # If we're using GNU nm, then we don't want the "-C" option. + # -C means demangle to GNU nm, but means don't demangle to AIX nm. + # Without the "-l" option, or with the "-B" option, AIX nm treats + # weak defined symbols like other global defined symbols, whereas + # GNU nm marks them as "W". + # While the 'weak' keyword is ignored in the Export File, we need + # it in the Import File for the 'aix-soname' feature, so we have + # to replace the "-B" option with "-P" for AIX nm. + if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then + _LT_TAGVAR(export_symbols_cmds, $1)='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && ([substr](\$ 3,1,1) != ".")) { if (\$ 2 == "W") { print \$ 3 " weak" } else { print \$ 3 } } }'\'' | sort -u > $export_symbols' + else + _LT_TAGVAR(export_symbols_cmds, $1)='`func_echo_all $NM | $SED -e '\''s/B\([[^B]]*\)$/P\1/'\''` -PCpgl $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) && ([substr](\$ 1,1,1) != ".")) { if ((\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) { print \$ 1 " weak" } else { print \$ 1 } } }'\'' | sort -u > $export_symbols' + fi + ;; + pw32*) + _LT_TAGVAR(export_symbols_cmds, $1)=$ltdll_cmds + ;; + cygwin* | mingw* | cegcc*) + case $cc_basename in + cl*) + _LT_TAGVAR(exclude_expsyms, $1)='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*' + ;; + *) + _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGRS]][[ ]]/s/.*[[ ]]\([[^ ]]*\)/\1 DATA/;s/^.*[[ ]]__nm__\([[^ ]]*\)[[ ]][[^ ]]*/\1 DATA/;/^I[[ ]]/d;/^[[AITW]][[ ]]/s/.* //'\'' | sort | uniq > $export_symbols' + _LT_TAGVAR(exclude_expsyms, $1)=['[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname'] + ;; + esac + ;; + *) + _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + ;; + esac +], [ + runpath_var= + _LT_TAGVAR(allow_undefined_flag, $1)= + _LT_TAGVAR(always_export_symbols, $1)=no + _LT_TAGVAR(archive_cmds, $1)= + _LT_TAGVAR(archive_expsym_cmds, $1)= + _LT_TAGVAR(compiler_needs_object, $1)=no + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no + _LT_TAGVAR(export_dynamic_flag_spec, $1)= + _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + _LT_TAGVAR(hardcode_automatic, $1)=no + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_direct_absolute, $1)=no + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)= + _LT_TAGVAR(hardcode_libdir_separator, $1)= + _LT_TAGVAR(hardcode_minus_L, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=unsupported + _LT_TAGVAR(inherit_rpath, $1)=no + _LT_TAGVAR(link_all_deplibs, $1)=unknown + _LT_TAGVAR(module_cmds, $1)= + _LT_TAGVAR(module_expsym_cmds, $1)= + _LT_TAGVAR(old_archive_from_new_cmds, $1)= + _LT_TAGVAR(old_archive_from_expsyms_cmds, $1)= + _LT_TAGVAR(thread_safe_flag_spec, $1)= + _LT_TAGVAR(whole_archive_flag_spec, $1)= + # include_expsyms should be a list of space-separated symbols to be *always* + # included in the symbol list + _LT_TAGVAR(include_expsyms, $1)= + # exclude_expsyms can be an extended regexp of symbols to exclude + # it will be wrapped by ' (' and ')$', so one must not match beginning or + # end of line. Example: 'a|bc|.*d.*' will exclude the symbols 'a' and 'bc', + # as well as any symbol that contains 'd'. + _LT_TAGVAR(exclude_expsyms, $1)=['_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*'] + # Although _GLOBAL_OFFSET_TABLE_ is a valid symbol C name, most a.out + # platforms (ab)use it in PIC code, but their linkers get confused if + # the symbol is explicitly referenced. Since portable code cannot + # rely on this symbol name, it's probably fine to never include it in + # preloaded symbol tables. + # Exclude shared library initialization/finalization symbols. +dnl Note also adjust exclude_expsyms for C++ above. + extract_expsyms_cmds= + + case $host_os in + cygwin* | mingw* | pw32* | cegcc*) + # FIXME: the MSVC++ port hasn't been tested in a loooong time + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + if test yes != "$GCC"; then + with_gnu_ld=no + fi + ;; + interix*) + # we just hope/assume this is gcc and not c89 (= MSVC++) + with_gnu_ld=yes + ;; + openbsd* | bitrig*) + with_gnu_ld=no + ;; + esac + + _LT_TAGVAR(ld_shlibs, $1)=yes + + # On some targets, GNU ld is compatible enough with the native linker + # that we're better off using the native interface for both. + lt_use_gnu_ld_interface=no + if test yes = "$with_gnu_ld"; then + case $host_os in + aix*) + # The AIX port of GNU ld has always aspired to compatibility + # with the native linker. However, as the warning in the GNU ld + # block says, versions before 2.19.5* couldn't really create working + # shared libraries, regardless of the interface used. + case `$LD -v 2>&1` in + *\ \(GNU\ Binutils\)\ 2.19.5*) ;; + *\ \(GNU\ Binutils\)\ 2.[[2-9]]*) ;; + *\ \(GNU\ Binutils\)\ [[3-9]]*) ;; + *) + lt_use_gnu_ld_interface=yes + ;; + esac + ;; + *) + lt_use_gnu_ld_interface=yes + ;; + esac + fi + + if test yes = "$lt_use_gnu_ld_interface"; then + # If archive_cmds runs LD, not CC, wlarc should be empty + wlarc='$wl' + + # Set some defaults for GNU ld with shared library support. These + # are reset later if shared libraries are not supported. Putting them + # here allows them to be overridden if necessary. + runpath_var=LD_RUN_PATH + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' + # ancient GNU ld didn't support --whole-archive et. al. + if $LD --help 2>&1 | $GREP 'no-whole-archive' > /dev/null; then + _LT_TAGVAR(whole_archive_flag_spec, $1)=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive' + else + _LT_TAGVAR(whole_archive_flag_spec, $1)= + fi + supports_anon_versioning=no + case `$LD -v | $SED -e 's/([^)]\+)\s\+//' 2>&1` in + *GNU\ gold*) supports_anon_versioning=yes ;; + *\ [[01]].* | *\ 2.[[0-9]].* | *\ 2.10.*) ;; # catch versions < 2.11 + *\ 2.11.93.0.2\ *) supports_anon_versioning=yes ;; # RH7.3 ... + *\ 2.11.92.0.12\ *) supports_anon_versioning=yes ;; # Mandrake 8.2 ... + *\ 2.11.*) ;; # other 2.11 versions + *) supports_anon_versioning=yes ;; + esac + + # See if GNU ld supports shared libraries. + case $host_os in + aix[[3-9]]*) + # On AIX/PPC, the GNU linker is very broken + if test ia64 != "$host_cpu"; then + _LT_TAGVAR(ld_shlibs, $1)=no + cat <<_LT_EOF 1>&2 + +*** Warning: the GNU linker, at least up to release 2.19, is reported +*** to be unable to reliably create shared libraries on AIX. +*** Therefore, libtool is disabling shared libraries support. If you +*** really care for shared libraries, you may want to install binutils +*** 2.20 or above, or modify your PATH so that a non-GNU linker is found. +*** You will then need to restart the configuration process. + +_LT_EOF + fi + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='' + ;; + m68k) + _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_minus_L, $1)=yes + ;; + esac + ;; + + beos*) + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + # Joseph Beckenbach says some releases of gcc + # support --undefined. This deserves some investigation. FIXME + _LT_TAGVAR(archive_cmds, $1)='$CC -nostart $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + cygwin* | mingw* | pw32* | cegcc*) + # _LT_TAGVAR(hardcode_libdir_flag_spec, $1) is actually meaningless, + # as there is no search path for DLLs. + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-all-symbols' + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + _LT_TAGVAR(always_export_symbols, $1)=no + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGRS]][[ ]]/s/.*[[ ]]\([[^ ]]*\)/\1 DATA/;s/^.*[[ ]]__nm__\([[^ ]]*\)[[ ]][[^ ]]*/\1 DATA/;/^I[[ ]]/d;/^[[AITW]][[ ]]/s/.* //'\'' | sort | uniq > $export_symbols' + _LT_TAGVAR(exclude_expsyms, $1)=['[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname'] + + if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + # If the export-symbols file already is a .def file, use it as + # is; otherwise, prepend EXPORTS... + _LT_TAGVAR(archive_expsym_cmds, $1)='if _LT_DLL_DEF_P([$export_symbols]); then + cp $export_symbols $output_objdir/$soname.def; + else + echo EXPORTS > $output_objdir/$soname.def; + cat $export_symbols >> $output_objdir/$soname.def; + fi~ + $CC -shared $output_objdir/$soname.def $libobjs $deplibs $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + haiku*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(link_all_deplibs, $1)=yes + ;; + + os2*) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + shrext_cmds=.dll + _LT_TAGVAR(archive_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + _LT_TAGVAR(archive_expsym_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + prefix_cmds="$SED"~ + if test EXPORTS = "`$SED 1q $export_symbols`"; then + prefix_cmds="$prefix_cmds -e 1d"; + fi~ + prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~ + cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + _LT_TAGVAR(old_archive_From_new_cmds, $1)='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def' + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + ;; + + interix[[3-9]]*) + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc. + # Instead, shared libraries are loaded at an image base (0x10000000 by + # default) and relocated if they conflict, which is a slow very memory + # consuming and fragmenting process. To avoid this, we pick a random, + # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link + # time. Moving up from 0x10000000 also allows more sbrk(2) space. + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='sed "s|^|_|" $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--retain-symbols-file,$output_objdir/$soname.expsym $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + ;; + + gnu* | linux* | tpf* | k*bsd*-gnu | kopensolaris*-gnu) + tmp_diet=no + if test linux-dietlibc = "$host_os"; then + case $cc_basename in + diet\ *) tmp_diet=yes;; # linux-dietlibc with static linking (!diet-dyn) + esac + fi + if $LD --help 2>&1 | $EGREP ': supported targets:.* elf' > /dev/null \ + && test no = "$tmp_diet" + then + tmp_addflag=' $pic_flag' + tmp_sharedflag='-shared' + case $cc_basename,$host_cpu in + pgcc*) # Portland Group C compiler + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + tmp_addflag=' $pic_flag' + ;; + pgf77* | pgf90* | pgf95* | pgfortran*) + # Portland Group f77 and f90 compilers + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + tmp_addflag=' $pic_flag -Mnomain' ;; + ecc*,ia64* | icc*,ia64*) # Intel C compiler on ia64 + tmp_addflag=' -i_dynamic' ;; + efc*,ia64* | ifort*,ia64*) # Intel Fortran compiler on ia64 + tmp_addflag=' -i_dynamic -nofor_main' ;; + ifc* | ifort*) # Intel Fortran compiler + tmp_addflag=' -nofor_main' ;; + lf95*) # Lahey Fortran 8.1 + _LT_TAGVAR(whole_archive_flag_spec, $1)= + tmp_sharedflag='--shared' ;; + nagfor*) # NAGFOR 5.3 + tmp_sharedflag='-Wl,-shared' ;; + xl[[cC]]* | bgxl[[cC]]* | mpixl[[cC]]*) # IBM XL C 8.0 on PPC (deal with xlf below) + tmp_sharedflag='-qmkshrobj' + tmp_addflag= ;; + nvcc*) # Cuda Compiler Driver 2.2 + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + _LT_TAGVAR(compiler_needs_object, $1)=yes + ;; + esac + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) # Sun C 5.9 + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + _LT_TAGVAR(compiler_needs_object, $1)=yes + tmp_sharedflag='-G' ;; + *Sun\ F*) # Sun Fortran 8.3 + tmp_sharedflag='-G' ;; + esac + _LT_TAGVAR(archive_cmds, $1)='$CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + + if test yes = "$supports_anon_versioning"; then + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $output_objdir/$libname.ver~ + cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ + echo "local: *; };" >> $output_objdir/$libname.ver~ + $CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-version-script $wl$output_objdir/$libname.ver -o $lib' + fi + + case $cc_basename in + tcc*) + _LT_TAGVAR(export_dynamic_flag_spec, $1)='-rdynamic' + ;; + xlf* | bgf* | bgxlf* | mpixlf*) + # IBM XL Fortran 10.1 on PPC cannot create shared libs itself + _LT_TAGVAR(whole_archive_flag_spec, $1)='--whole-archive$convenience --no-whole-archive' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(archive_cmds, $1)='$LD -shared $libobjs $deplibs $linker_flags -soname $soname -o $lib' + if test yes = "$supports_anon_versioning"; then + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $output_objdir/$libname.ver~ + cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ + echo "local: *; };" >> $output_objdir/$libname.ver~ + $LD -shared $libobjs $deplibs $linker_flags -soname $soname -version-script $output_objdir/$libname.ver -o $lib' + fi + ;; + esac + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + netbsd*) + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable $libobjs $deplibs $linker_flags -o $lib' + wlarc= + else + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + fi + ;; + + solaris*) + if $LD -v 2>&1 | $GREP 'BFD 2\.8' > /dev/null; then + _LT_TAGVAR(ld_shlibs, $1)=no + cat <<_LT_EOF 1>&2 + +*** Warning: The releases 2.8.* of the GNU linker cannot reliably +*** create shared libraries on Solaris systems. Therefore, libtool +*** is disabling shared libraries support. We urge you to upgrade GNU +*** binutils to release 2.9.1 or newer. Another option is to modify +*** your PATH or compiler configuration so that the native linker is +*** used, and then restart. + +_LT_EOF + elif $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX*) + case `$LD -v 2>&1` in + *\ [[01]].* | *\ 2.[[0-9]].* | *\ 2.1[[0-5]].*) + _LT_TAGVAR(ld_shlibs, $1)=no + cat <<_LT_EOF 1>&2 + +*** Warning: Releases of the GNU linker prior to 2.16.91.0.3 cannot +*** reliably create shared libraries on SCO systems. Therefore, libtool +*** is disabling shared libraries support. We urge you to upgrade GNU +*** binutils to release 2.16.91.0.3 or newer. Another option is to modify +*** your PATH or compiler configuration so that the native linker is +*** used, and then restart. + +_LT_EOF + ;; + *) + # For security reasons, it is highly recommended that you always + # use absolute paths for naming shared libraries, and exclude the + # DT_RUNPATH tag from executables and libraries. But doing so + # requires that you compile everything twice, which is a pain. + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + + sunos4*) + _LT_TAGVAR(archive_cmds, $1)='$LD -assert pure-text -Bshareable -o $lib $libobjs $deplibs $linker_flags' + wlarc= + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + *) + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + + if test no = "$_LT_TAGVAR(ld_shlibs, $1)"; then + runpath_var= + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)= + _LT_TAGVAR(export_dynamic_flag_spec, $1)= + _LT_TAGVAR(whole_archive_flag_spec, $1)= + fi + else + # PORTME fill in a description of your system's linker (not GNU ld) + case $host_os in + aix3*) + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + _LT_TAGVAR(always_export_symbols, $1)=yes + _LT_TAGVAR(archive_expsym_cmds, $1)='$LD -o $output_objdir/$soname $libobjs $deplibs $linker_flags -bE:$export_symbols -T512 -H512 -bM:SRE~$AR $AR_FLAGS $lib $output_objdir/$soname' + # Note: this linker hardcodes the directories in LIBPATH if there + # are no directories specified by -L. + _LT_TAGVAR(hardcode_minus_L, $1)=yes + if test yes = "$GCC" && test -z "$lt_prog_compiler_static"; then + # Neither direct hardcoding nor static linking is supported with a + # broken collect2. + _LT_TAGVAR(hardcode_direct, $1)=unsupported + fi + ;; + + aix[[4-9]]*) + if test ia64 = "$host_cpu"; then + # On IA64, the linker does run time linking by default, so we don't + # have to do anything special. + aix_use_runtimelinking=no + exp_sym_flag='-Bexport' + no_entry_flag= + else + # If we're using GNU nm, then we don't want the "-C" option. + # -C means demangle to GNU nm, but means don't demangle to AIX nm. + # Without the "-l" option, or with the "-B" option, AIX nm treats + # weak defined symbols like other global defined symbols, whereas + # GNU nm marks them as "W". + # While the 'weak' keyword is ignored in the Export File, we need + # it in the Import File for the 'aix-soname' feature, so we have + # to replace the "-B" option with "-P" for AIX nm. + if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then + _LT_TAGVAR(export_symbols_cmds, $1)='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && ([substr](\$ 3,1,1) != ".")) { if (\$ 2 == "W") { print \$ 3 " weak" } else { print \$ 3 } } }'\'' | sort -u > $export_symbols' + else + _LT_TAGVAR(export_symbols_cmds, $1)='`func_echo_all $NM | $SED -e '\''s/B\([[^B]]*\)$/P\1/'\''` -PCpgl $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) && ([substr](\$ 1,1,1) != ".")) { if ((\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) { print \$ 1 " weak" } else { print \$ 1 } } }'\'' | sort -u > $export_symbols' + fi + aix_use_runtimelinking=no + + # Test if we are trying to use run time linking or normal + # AIX style linking. If -brtl is somewhere in LDFLAGS, we + # have runtime linking enabled, and use it for executables. + # For shared libraries, we enable/disable runtime linking + # depending on the kind of the shared library created - + # when "with_aix_soname,aix_use_runtimelinking" is: + # "aix,no" lib.a(lib.so.V) shared, rtl:no, for executables + # "aix,yes" lib.so shared, rtl:yes, for executables + # lib.a static archive + # "both,no" lib.so.V(shr.o) shared, rtl:yes + # lib.a(lib.so.V) shared, rtl:no, for executables + # "both,yes" lib.so.V(shr.o) shared, rtl:yes, for executables + # lib.a(lib.so.V) shared, rtl:no + # "svr4,*" lib.so.V(shr.o) shared, rtl:yes, for executables + # lib.a static archive + case $host_os in aix4.[[23]]|aix4.[[23]].*|aix[[5-9]]*) + for ld_flag in $LDFLAGS; do + if (test x-brtl = "x$ld_flag" || test x-Wl,-brtl = "x$ld_flag"); then + aix_use_runtimelinking=yes + break + fi + done + if test svr4,no = "$with_aix_soname,$aix_use_runtimelinking"; then + # With aix-soname=svr4, we create the lib.so.V shared archives only, + # so we don't have lib.a shared libs to link our executables. + # We have to force runtime linking in this case. + aix_use_runtimelinking=yes + LDFLAGS="$LDFLAGS -Wl,-brtl" + fi + ;; + esac + + exp_sym_flag='-bexport' + no_entry_flag='-bnoentry' + fi + + # When large executables or shared objects are built, AIX ld can + # have problems creating the table of contents. If linking a library + # or program results in "error TOC overflow" add -mminimal-toc to + # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not + # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS. + + _LT_TAGVAR(archive_cmds, $1)='' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + _LT_TAGVAR(hardcode_libdir_separator, $1)=':' + _LT_TAGVAR(link_all_deplibs, $1)=yes + _LT_TAGVAR(file_list_spec, $1)='$wl-f,' + case $with_aix_soname,$aix_use_runtimelinking in + aix,*) ;; # traditional, no import file + svr4,* | *,yes) # use import file + # The Import File defines what to hardcode. + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_direct_absolute, $1)=no + ;; + esac + + if test yes = "$GCC"; then + case $host_os in aix4.[[012]]|aix4.[[012]].*) + # We only want to do this on AIX 4.2 and lower, the check + # below for broken collect2 doesn't work under 4.3+ + collect2name=`$CC -print-prog-name=collect2` + if test -f "$collect2name" && + strings "$collect2name" | $GREP resolve_lib_name >/dev/null + then + # We have reworked collect2 + : + else + # We have old collect2 + _LT_TAGVAR(hardcode_direct, $1)=unsupported + # It fails to find uninstalled libraries when the uninstalled + # path is not listed in the libpath. Setting hardcode_minus_L + # to unsupported forces relinking + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)= + fi + ;; + esac + shared_flag='-shared' + if test yes = "$aix_use_runtimelinking"; then + shared_flag="$shared_flag "'$wl-G' + fi + # Need to ensure runtime linking is disabled for the traditional + # shared library, or the linker may eventually find shared libraries + # /with/ Import File - we do not want to mix them. + shared_flag_aix='-shared' + shared_flag_svr4='-shared $wl-G' + else + # not using gcc + if test ia64 = "$host_cpu"; then + # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release + # chokes on -Wl,-G. The following line is correct: + shared_flag='-G' + else + if test yes = "$aix_use_runtimelinking"; then + shared_flag='$wl-G' + else + shared_flag='$wl-bM:SRE' + fi + shared_flag_aix='$wl-bM:SRE' + shared_flag_svr4='$wl-G' + fi + fi + + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-bexpall' + # It seems that -bexpall does not export symbols beginning with + # underscore (_), so it is better to generate a list of symbols to export. + _LT_TAGVAR(always_export_symbols, $1)=yes + if test aix,yes = "$with_aix_soname,$aix_use_runtimelinking"; then + # Warning - without using the other runtime loading flags (-brtl), + # -berok will link without error, but may produce a broken library. + _LT_TAGVAR(allow_undefined_flag, $1)='-berok' + # Determine the default libpath from the value encoded in an + # empty executable. + _LT_SYS_MODULE_PATH_AIX([$1]) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-blibpath:$libdir:'"$aix_libpath" + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $deplibs $wl'$no_entry_flag' $compiler_flags `if test -n "$allow_undefined_flag"; then func_echo_all "$wl$allow_undefined_flag"; else :; fi` $wl'$exp_sym_flag:\$export_symbols' '$shared_flag + else + if test ia64 = "$host_cpu"; then + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R $libdir:/usr/lib:/lib' + _LT_TAGVAR(allow_undefined_flag, $1)="-z nodefs" + _LT_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\$wl$no_entry_flag"' $compiler_flags $wl$allow_undefined_flag '"\$wl$exp_sym_flag:\$export_symbols" + else + # Determine the default libpath from the value encoded in an + # empty executable. + _LT_SYS_MODULE_PATH_AIX([$1]) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-blibpath:$libdir:'"$aix_libpath" + # Warning - without using the other run time loading flags, + # -berok will link without error, but may produce a broken library. + _LT_TAGVAR(no_undefined_flag, $1)=' $wl-bernotok' + _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-berok' + if test yes = "$with_gnu_ld"; then + # We only use this code for GNU lds that support --whole-archive. + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive$convenience $wl--no-whole-archive' + else + # Exported symbols can be pulled into shared objects from archives + _LT_TAGVAR(whole_archive_flag_spec, $1)='$convenience' + fi + _LT_TAGVAR(archive_cmds_need_lc, $1)=yes + _LT_TAGVAR(archive_expsym_cmds, $1)='$RM -r $output_objdir/$realname.d~$MKDIR $output_objdir/$realname.d' + # -brtl affects multiple linker settings, -berok does not and is overridden later + compiler_flags_filtered='`func_echo_all "$compiler_flags " | $SED -e "s%-brtl\\([[, ]]\\)%-berok\\1%g"`' + if test svr4 != "$with_aix_soname"; then + # This is similar to how AIX traditionally builds its shared libraries. + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$CC '$shared_flag_aix' -o $output_objdir/$realname.d/$soname $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$realname.d/$soname' + fi + if test aix != "$with_aix_soname"; then + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$CC '$shared_flag_svr4' -o $output_objdir/$realname.d/$shared_archive_member_spec.o $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$STRIP -e $output_objdir/$realname.d/$shared_archive_member_spec.o~( func_echo_all "#! $soname($shared_archive_member_spec.o)"; if test shr_64 = "$shared_archive_member_spec"; then func_echo_all "# 64"; else func_echo_all "# 32"; fi; cat $export_symbols ) > $output_objdir/$realname.d/$shared_archive_member_spec.imp~$AR $AR_FLAGS $output_objdir/$soname $output_objdir/$realname.d/$shared_archive_member_spec.o $output_objdir/$realname.d/$shared_archive_member_spec.imp' + else + # used by -dlpreopen to get the symbols + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$MV $output_objdir/$realname.d/$soname $output_objdir' + fi + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$RM -r $output_objdir/$realname.d' + fi + fi + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='' + ;; + m68k) + _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_minus_L, $1)=yes + ;; + esac + ;; + + bsdi[[45]]*) + _LT_TAGVAR(export_dynamic_flag_spec, $1)=-rdynamic + ;; + + cygwin* | mingw* | pw32* | cegcc*) + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + # hardcode_libdir_flag_spec is actually meaningless, as there is + # no search path for DLLs. + case $cc_basename in + cl*) + # Native MSVC + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)=' ' + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + _LT_TAGVAR(always_export_symbols, $1)=yes + _LT_TAGVAR(file_list_spec, $1)='@' + # Tell ltmain to make .lib files, not .a files. + libext=lib + # Tell ltmain to make .dll files, not .so files. + shrext_cmds=.dll + # FIXME: Setting linknames here is a bad hack. + _LT_TAGVAR(archive_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~linknames=' + _LT_TAGVAR(archive_expsym_cmds, $1)='if _LT_DLL_DEF_P([$export_symbols]); then + cp "$export_symbols" "$output_objdir/$soname.def"; + echo "$tool_output_objdir$soname.def" > "$output_objdir/$soname.exp"; + else + $SED -e '\''s/^/-link -EXPORT:/'\'' < $export_symbols > $output_objdir/$soname.exp; + fi~ + $CC -o $tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~ + linknames=' + # The linker will not automatically build a static lib if we build a DLL. + # _LT_TAGVAR(old_archive_from_new_cmds, $1)='true' + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + _LT_TAGVAR(exclude_expsyms, $1)='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*' + _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGRS]][[ ]]/s/.*[[ ]]\([[^ ]]*\)/\1,DATA/'\'' | $SED -e '\''/^[[AITW]][[ ]]/s/.*[[ ]]//'\'' | sort | uniq > $export_symbols' + # Don't use ranlib + _LT_TAGVAR(old_postinstall_cmds, $1)='chmod 644 $oldlib' + _LT_TAGVAR(postlink_cmds, $1)='lt_outputfile="@OUTPUT@"~ + lt_tool_outputfile="@TOOL_OUTPUT@"~ + case $lt_outputfile in + *.exe|*.EXE) ;; + *) + lt_outputfile=$lt_outputfile.exe + lt_tool_outputfile=$lt_tool_outputfile.exe + ;; + esac~ + if test : != "$MANIFEST_TOOL" && test -f "$lt_outputfile.manifest"; then + $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1; + $RM "$lt_outputfile.manifest"; + fi' + ;; + *) + # Assume MSVC wrapper + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)=' ' + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + # Tell ltmain to make .lib files, not .a files. + libext=lib + # Tell ltmain to make .dll files, not .so files. + shrext_cmds=.dll + # FIXME: Setting linknames here is a bad hack. + _LT_TAGVAR(archive_cmds, $1)='$CC -o $lib $libobjs $compiler_flags `func_echo_all "$deplibs" | $SED '\''s/ -lc$//'\''` -link -dll~linknames=' + # The linker will automatically build a .lib file if we build a DLL. + _LT_TAGVAR(old_archive_from_new_cmds, $1)='true' + # FIXME: Should let the user specify the lib program. + _LT_TAGVAR(old_archive_cmds, $1)='lib -OUT:$oldlib$oldobjs$old_deplibs' + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + ;; + esac + ;; + + darwin* | rhapsody*) + _LT_DARWIN_LINKER_FEATURES($1) + ;; + + dgux*) + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + # FreeBSD 2.2.[012] allows us to include c++rt0.o to get C++ constructor + # support. Future versions do this automatically, but an explicit c++rt0.o + # does not break anything, and helps significantly (at the cost of a little + # extra space). + freebsd2.2*) + _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags /usr/lib/c++rt0.o' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + # Unfortunately, older versions of FreeBSD 2 do not have this feature. + freebsd2.*) + _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + # FreeBSD 3 and greater uses gcc -shared to do shared libraries. + freebsd* | dragonfly*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + hpux9*) + if test yes = "$GCC"; then + _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$CC -shared $pic_flag $wl+b $wl$install_libdir -o $output_objdir/$soname $libobjs $deplibs $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' + else + _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$LD -b +b $install_libdir -o $output_objdir/$soname $libobjs $deplibs $linker_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' + fi + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_TAGVAR(hardcode_direct, $1)=yes + + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + ;; + + hpux10*) + if test yes,no = "$GCC,$with_gnu_ld"; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags' + else + _LT_TAGVAR(archive_cmds, $1)='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags' + fi + if test no = "$with_gnu_ld"; then + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + _LT_TAGVAR(hardcode_minus_L, $1)=yes + fi + ;; + + hpux11*) + if test yes,no = "$GCC,$with_gnu_ld"; then + case $host_cpu in + hppa*64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl+h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + ia64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $wl+h $wl$soname $wl+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + else + case $host_cpu in + hppa*64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + ia64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + m4_if($1, [], [ + # Older versions of the 11.00 compiler do not understand -b yet + # (HP92453-01 A.11.01.20 doesn't, HP92453-01 B.11.X.35175-35176.GP does) + _LT_LINKER_OPTION([if $CC understands -b], + _LT_TAGVAR(lt_cv_prog_compiler__b, $1), [-b], + [_LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags'], + [_LT_TAGVAR(archive_cmds, $1)='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags'])], + [_LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags']) + ;; + esac + fi + if test no = "$with_gnu_ld"; then + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + + case $host_cpu in + hppa*64*|ia64*) + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + *) + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + _LT_TAGVAR(hardcode_minus_L, $1)=yes + ;; + esac + fi + ;; + + irix5* | irix6* | nonstopux*) + if test yes = "$GCC"; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + # Try to use the -exported_symbol ld option, if it does not + # work, assume that -exports_file does not work either and + # implicitly export all symbols. + # This should be the same for all languages, so no per-tag cache variable. + AC_CACHE_CHECK([whether the $host_os linker accepts -exported_symbol], + [lt_cv_irix_exported_symbol], + [save_LDFLAGS=$LDFLAGS + LDFLAGS="$LDFLAGS -shared $wl-exported_symbol ${wl}foo $wl-update_registry $wl/dev/null" + AC_LINK_IFELSE( + [AC_LANG_SOURCE( + [AC_LANG_CASE([C], [[int foo (void) { return 0; }]], + [C++], [[int foo (void) { return 0; }]], + [Fortran 77], [[ + subroutine foo + end]], + [Fortran], [[ + subroutine foo + end]])])], + [lt_cv_irix_exported_symbol=yes], + [lt_cv_irix_exported_symbol=no]) + LDFLAGS=$save_LDFLAGS]) + if test yes = "$lt_cv_irix_exported_symbol"; then + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations $wl-exports_file $wl$export_symbols -o $lib' + fi + else + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -exports_file $export_symbols -o $lib' + fi + _LT_TAGVAR(archive_cmds_need_lc, $1)='no' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_TAGVAR(inherit_rpath, $1)=yes + _LT_TAGVAR(link_all_deplibs, $1)=yes + ;; + + linux*) + case $cc_basename in + tcc*) + # Fabrice Bellard et al's Tiny C Compiler + _LT_TAGVAR(ld_shlibs, $1)=yes + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + ;; + + netbsd*) + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' # a.out + else + _LT_TAGVAR(archive_cmds, $1)='$LD -shared -o $lib $libobjs $deplibs $linker_flags' # ELF + fi + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + newsos6) + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + *nto* | *qnx*) + ;; + + openbsd* | bitrig*) + if test -f /usr/libexec/ld.so; then + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags $wl-retain-symbols-file,$export_symbols' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + else + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + fi + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + os2*) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + shrext_cmds=.dll + _LT_TAGVAR(archive_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + _LT_TAGVAR(archive_expsym_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + prefix_cmds="$SED"~ + if test EXPORTS = "`$SED 1q $export_symbols`"; then + prefix_cmds="$prefix_cmds -e 1d"; + fi~ + prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~ + cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + _LT_TAGVAR(old_archive_From_new_cmds, $1)='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def' + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + ;; + + osf3*) + if test yes = "$GCC"; then + _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-expect_unresolved $wl\*' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + else + _LT_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + fi + _LT_TAGVAR(archive_cmds_need_lc, $1)='no' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + ;; + + osf4* | osf5*) # as osf3* with the addition of -msym flag + if test yes = "$GCC"; then + _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-expect_unresolved $wl\*' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $pic_flag $libobjs $deplibs $compiler_flags $wl-msym $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + else + _LT_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done; printf "%s\\n" "-hidden">> $lib.exp~ + $CC -shared$allow_undefined_flag $wl-input $wl$lib.exp $compiler_flags $libobjs $deplibs -soname $soname `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib~$RM $lib.exp' + + # Both c and cxx compiler support -rpath directly + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir' + fi + _LT_TAGVAR(archive_cmds_need_lc, $1)='no' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + ;; + + solaris*) + _LT_TAGVAR(no_undefined_flag, $1)=' -z defs' + if test yes = "$GCC"; then + wlarc='$wl' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $wl-z ${wl}text $wl-h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -shared $pic_flag $wl-z ${wl}text $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp' + else + case `$CC -V 2>&1` in + *"Compilers 5.0"*) + wlarc='' + _LT_TAGVAR(archive_cmds, $1)='$LD -G$allow_undefined_flag -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $LD -G$allow_undefined_flag -M $lib.exp -h $soname -o $lib $libobjs $deplibs $linker_flags~$RM $lib.exp' + ;; + *) + wlarc='$wl' + _LT_TAGVAR(archive_cmds, $1)='$CC -G$allow_undefined_flag -h $soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -G$allow_undefined_flag -M $lib.exp -h $soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp' + ;; + esac + fi + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + case $host_os in + solaris2.[[0-5]] | solaris2.[[0-5]].*) ;; + *) + # The compiler driver will combine and reorder linker options, + # but understands '-z linker_flag'. GCC discards it without '$wl', + # but is careful enough not to reorder. + # Supported since Solaris 2.6 (maybe 2.5.1?) + if test yes = "$GCC"; then + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl-z ${wl}allextract$convenience $wl-z ${wl}defaultextract' + else + _LT_TAGVAR(whole_archive_flag_spec, $1)='-z allextract$convenience -z defaultextract' + fi + ;; + esac + _LT_TAGVAR(link_all_deplibs, $1)=yes + ;; + + sunos4*) + if test sequent = "$host_vendor"; then + # Use $CC to link under sequent, because it throws in some extra .o + # files that make .init and .fini sections work. + _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h $soname -o $lib $libobjs $deplibs $compiler_flags' + else + _LT_TAGVAR(archive_cmds, $1)='$LD -assert pure-text -Bstatic -o $lib $libobjs $deplibs $linker_flags' + fi + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + sysv4) + case $host_vendor in + sni) + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_direct, $1)=yes # is this really true??? + ;; + siemens) + ## LD is ld it makes a PLAMLIB + ## CC just makes a GrossModule. + _LT_TAGVAR(archive_cmds, $1)='$LD -G -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(reload_cmds, $1)='$CC -r -o $output$reload_objs' + _LT_TAGVAR(hardcode_direct, $1)=no + ;; + motorola) + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_direct, $1)=no #Motorola manual says yes, but my tests say they lie + ;; + esac + runpath_var='LD_RUN_PATH' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + sysv4.3*) + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(export_dynamic_flag_spec, $1)='-Bexport' + ;; + + sysv4*MP*) + if test -d /usr/nec; then + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + runpath_var=LD_RUN_PATH + hardcode_runpath_var=yes + _LT_TAGVAR(ld_shlibs, $1)=yes + fi + ;; + + sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[[01]].[[10]]* | unixware7* | sco3.2v5.0.[[024]]*) + _LT_TAGVAR(no_undefined_flag, $1)='$wl-z,text' + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + runpath_var='LD_RUN_PATH' + + if test yes = "$GCC"; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + else + _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + fi + ;; + + sysv5* | sco3.2v5* | sco5v6*) + # Note: We CANNOT use -z defs as we might desire, because we do not + # link with -lc, and that would cause any symbols used from libc to + # always be unresolved, which means just about no library would + # ever link correctly. If we're not using GNU ld we use -z text + # though, which does catch some bad symbols but isn't as heavy-handed + # as -z defs. + _LT_TAGVAR(no_undefined_flag, $1)='$wl-z,text' + _LT_TAGVAR(allow_undefined_flag, $1)='$wl-z,nodefs' + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R,$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=':' + _LT_TAGVAR(link_all_deplibs, $1)=yes + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-Bexport' + runpath_var='LD_RUN_PATH' + + if test yes = "$GCC"; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + else + _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + fi + ;; + + uts4*) + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + *) + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + + if test sni = "$host_vendor"; then + case $host in + sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*) + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-Blargedynsym' + ;; + esac + fi + fi +]) +AC_MSG_RESULT([$_LT_TAGVAR(ld_shlibs, $1)]) +test no = "$_LT_TAGVAR(ld_shlibs, $1)" && can_build_shared=no + +_LT_TAGVAR(with_gnu_ld, $1)=$with_gnu_ld + +_LT_DECL([], [libext], [0], [Old archive suffix (normally "a")])dnl +_LT_DECL([], [shrext_cmds], [1], [Shared library suffix (normally ".so")])dnl +_LT_DECL([], [extract_expsyms_cmds], [2], + [The commands to extract the exported symbol list from a shared archive]) + +# +# Do we need to explicitly link libc? +# +case "x$_LT_TAGVAR(archive_cmds_need_lc, $1)" in +x|xyes) + # Assume -lc should be added + _LT_TAGVAR(archive_cmds_need_lc, $1)=yes + + if test yes,yes = "$GCC,$enable_shared"; then + case $_LT_TAGVAR(archive_cmds, $1) in + *'~'*) + # FIXME: we may have to deal with multi-command sequences. + ;; + '$CC '*) + # Test whether the compiler implicitly links with -lc since on some + # systems, -lgcc has to come before -lc. If gcc already passes -lc + # to ld, don't add -lc before -lgcc. + AC_CACHE_CHECK([whether -lc should be explicitly linked in], + [lt_cv_]_LT_TAGVAR(archive_cmds_need_lc, $1), + [$RM conftest* + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + if AC_TRY_EVAL(ac_compile) 2>conftest.err; then + soname=conftest + lib=conftest + libobjs=conftest.$ac_objext + deplibs= + wl=$_LT_TAGVAR(lt_prog_compiler_wl, $1) + pic_flag=$_LT_TAGVAR(lt_prog_compiler_pic, $1) + compiler_flags=-v + linker_flags=-v + verstring= + output_objdir=. + libname=conftest + lt_save_allow_undefined_flag=$_LT_TAGVAR(allow_undefined_flag, $1) + _LT_TAGVAR(allow_undefined_flag, $1)= + if AC_TRY_EVAL(_LT_TAGVAR(archive_cmds, $1) 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1) + then + lt_cv_[]_LT_TAGVAR(archive_cmds_need_lc, $1)=no + else + lt_cv_[]_LT_TAGVAR(archive_cmds_need_lc, $1)=yes + fi + _LT_TAGVAR(allow_undefined_flag, $1)=$lt_save_allow_undefined_flag + else + cat conftest.err 1>&5 + fi + $RM conftest* + ]) + _LT_TAGVAR(archive_cmds_need_lc, $1)=$lt_cv_[]_LT_TAGVAR(archive_cmds_need_lc, $1) + ;; + esac + fi + ;; +esac + +_LT_TAGDECL([build_libtool_need_lc], [archive_cmds_need_lc], [0], + [Whether or not to add -lc for building shared libraries]) +_LT_TAGDECL([allow_libtool_libs_with_static_runtimes], + [enable_shared_with_static_runtimes], [0], + [Whether or not to disallow shared libs when runtime libs are static]) +_LT_TAGDECL([], [export_dynamic_flag_spec], [1], + [Compiler flag to allow reflexive dlopens]) +_LT_TAGDECL([], [whole_archive_flag_spec], [1], + [Compiler flag to generate shared objects directly from archives]) +_LT_TAGDECL([], [compiler_needs_object], [1], + [Whether the compiler copes with passing no objects directly]) +_LT_TAGDECL([], [old_archive_from_new_cmds], [2], + [Create an old-style archive from a shared archive]) +_LT_TAGDECL([], [old_archive_from_expsyms_cmds], [2], + [Create a temporary old-style archive to link instead of a shared archive]) +_LT_TAGDECL([], [archive_cmds], [2], [Commands used to build a shared archive]) +_LT_TAGDECL([], [archive_expsym_cmds], [2]) +_LT_TAGDECL([], [module_cmds], [2], + [Commands used to build a loadable module if different from building + a shared archive.]) +_LT_TAGDECL([], [module_expsym_cmds], [2]) +_LT_TAGDECL([], [with_gnu_ld], [1], + [Whether we are building with GNU ld or not]) +_LT_TAGDECL([], [allow_undefined_flag], [1], + [Flag that allows shared libraries with undefined symbols to be built]) +_LT_TAGDECL([], [no_undefined_flag], [1], + [Flag that enforces no undefined symbols]) +_LT_TAGDECL([], [hardcode_libdir_flag_spec], [1], + [Flag to hardcode $libdir into a binary during linking. + This must work even if $libdir does not exist]) +_LT_TAGDECL([], [hardcode_libdir_separator], [1], + [Whether we need a single "-rpath" flag with a separated argument]) +_LT_TAGDECL([], [hardcode_direct], [0], + [Set to "yes" if using DIR/libNAME$shared_ext during linking hardcodes + DIR into the resulting binary]) +_LT_TAGDECL([], [hardcode_direct_absolute], [0], + [Set to "yes" if using DIR/libNAME$shared_ext during linking hardcodes + DIR into the resulting binary and the resulting library dependency is + "absolute", i.e impossible to change by setting $shlibpath_var if the + library is relocated]) +_LT_TAGDECL([], [hardcode_minus_L], [0], + [Set to "yes" if using the -LDIR flag during linking hardcodes DIR + into the resulting binary]) +_LT_TAGDECL([], [hardcode_shlibpath_var], [0], + [Set to "yes" if using SHLIBPATH_VAR=DIR during linking hardcodes DIR + into the resulting binary]) +_LT_TAGDECL([], [hardcode_automatic], [0], + [Set to "yes" if building a shared library automatically hardcodes DIR + into the library and all subsequent libraries and executables linked + against it]) +_LT_TAGDECL([], [inherit_rpath], [0], + [Set to yes if linker adds runtime paths of dependent libraries + to runtime path list]) +_LT_TAGDECL([], [link_all_deplibs], [0], + [Whether libtool must link a program against all its dependency libraries]) +_LT_TAGDECL([], [always_export_symbols], [0], + [Set to "yes" if exported symbols are required]) +_LT_TAGDECL([], [export_symbols_cmds], [2], + [The commands to list exported symbols]) +_LT_TAGDECL([], [exclude_expsyms], [1], + [Symbols that should not be listed in the preloaded symbols]) +_LT_TAGDECL([], [include_expsyms], [1], + [Symbols that must always be exported]) +_LT_TAGDECL([], [prelink_cmds], [2], + [Commands necessary for linking programs (against libraries) with templates]) +_LT_TAGDECL([], [postlink_cmds], [2], + [Commands necessary for finishing linking programs]) +_LT_TAGDECL([], [file_list_spec], [1], + [Specify filename containing input files]) +dnl FIXME: Not yet implemented +dnl _LT_TAGDECL([], [thread_safe_flag_spec], [1], +dnl [Compiler flag to generate thread safe objects]) +])# _LT_LINKER_SHLIBS + + +# _LT_LANG_C_CONFIG([TAG]) +# ------------------------ +# Ensure that the configuration variables for a C compiler are suitably +# defined. These variables are subsequently used by _LT_CONFIG to write +# the compiler configuration to 'libtool'. +m4_defun([_LT_LANG_C_CONFIG], +[m4_require([_LT_DECL_EGREP])dnl +lt_save_CC=$CC +AC_LANG_PUSH(C) + +# Source file extension for C test sources. +ac_ext=c + +# Object file extension for compiled C test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code="int some_variable = 0;" + +# Code to be used in simple link tests +lt_simple_link_test_code='int main(){return(0);}' + +_LT_TAG_COMPILER +# Save the default compiler, since it gets overwritten when the other +# tags are being tested, and _LT_TAGVAR(compiler, []) is a NOP. +compiler_DEFAULT=$CC + +# save warnings/boilerplate of simple test code +_LT_COMPILER_BOILERPLATE +_LT_LINKER_BOILERPLATE + +## CAVEAT EMPTOR: +## There is no encapsulation within the following macros, do not change +## the running order or otherwise move them around unless you know exactly +## what you are doing... +if test -n "$compiler"; then + _LT_COMPILER_NO_RTTI($1) + _LT_COMPILER_PIC($1) + _LT_COMPILER_C_O($1) + _LT_COMPILER_FILE_LOCKS($1) + _LT_LINKER_SHLIBS($1) + _LT_SYS_DYNAMIC_LINKER($1) + _LT_LINKER_HARDCODE_LIBPATH($1) + LT_SYS_DLOPEN_SELF + _LT_CMD_STRIPLIB + + # Report what library types will actually be built + AC_MSG_CHECKING([if libtool supports shared libraries]) + AC_MSG_RESULT([$can_build_shared]) + + AC_MSG_CHECKING([whether to build shared libraries]) + test no = "$can_build_shared" && enable_shared=no + + # On AIX, shared libraries and static libraries use the same namespace, and + # are all built from PIC. + case $host_os in + aix3*) + test yes = "$enable_shared" && enable_static=no + if test -n "$RANLIB"; then + archive_cmds="$archive_cmds~\$RANLIB \$lib" + postinstall_cmds='$RANLIB $lib' + fi + ;; + + aix[[4-9]]*) + if test ia64 != "$host_cpu"; then + case $enable_shared,$with_aix_soname,$aix_use_runtimelinking in + yes,aix,yes) ;; # shared object as lib.so file only + yes,svr4,*) ;; # shared object as lib.so archive member only + yes,*) enable_static=no ;; # shared object in lib.a archive as well + esac + fi + ;; + esac + AC_MSG_RESULT([$enable_shared]) + + AC_MSG_CHECKING([whether to build static libraries]) + # Make sure either enable_shared or enable_static is yes. + test yes = "$enable_shared" || enable_static=yes + AC_MSG_RESULT([$enable_static]) + + _LT_CONFIG($1) +fi +AC_LANG_POP +CC=$lt_save_CC +])# _LT_LANG_C_CONFIG + + +# _LT_LANG_CXX_CONFIG([TAG]) +# -------------------------- +# Ensure that the configuration variables for a C++ compiler are suitably +# defined. These variables are subsequently used by _LT_CONFIG to write +# the compiler configuration to 'libtool'. +m4_defun([_LT_LANG_CXX_CONFIG], +[m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_DECL_EGREP])dnl +m4_require([_LT_PATH_MANIFEST_TOOL])dnl +if test -n "$CXX" && ( test no != "$CXX" && + ( (test g++ = "$CXX" && `g++ -v >/dev/null 2>&1` ) || + (test g++ != "$CXX"))); then + AC_PROG_CXXCPP +else + _lt_caught_CXX_error=yes +fi + +AC_LANG_PUSH(C++) +_LT_TAGVAR(archive_cmds_need_lc, $1)=no +_LT_TAGVAR(allow_undefined_flag, $1)= +_LT_TAGVAR(always_export_symbols, $1)=no +_LT_TAGVAR(archive_expsym_cmds, $1)= +_LT_TAGVAR(compiler_needs_object, $1)=no +_LT_TAGVAR(export_dynamic_flag_spec, $1)= +_LT_TAGVAR(hardcode_direct, $1)=no +_LT_TAGVAR(hardcode_direct_absolute, $1)=no +_LT_TAGVAR(hardcode_libdir_flag_spec, $1)= +_LT_TAGVAR(hardcode_libdir_separator, $1)= +_LT_TAGVAR(hardcode_minus_L, $1)=no +_LT_TAGVAR(hardcode_shlibpath_var, $1)=unsupported +_LT_TAGVAR(hardcode_automatic, $1)=no +_LT_TAGVAR(inherit_rpath, $1)=no +_LT_TAGVAR(module_cmds, $1)= +_LT_TAGVAR(module_expsym_cmds, $1)= +_LT_TAGVAR(link_all_deplibs, $1)=unknown +_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds +_LT_TAGVAR(reload_flag, $1)=$reload_flag +_LT_TAGVAR(reload_cmds, $1)=$reload_cmds +_LT_TAGVAR(no_undefined_flag, $1)= +_LT_TAGVAR(whole_archive_flag_spec, $1)= +_LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no + +# Source file extension for C++ test sources. +ac_ext=cpp + +# Object file extension for compiled C++ test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# No sense in running all these tests if we already determined that +# the CXX compiler isn't working. Some variables (like enable_shared) +# are currently assumed to apply to all compilers on this platform, +# and will be corrupted by setting them based on a non-working compiler. +if test yes != "$_lt_caught_CXX_error"; then + # Code to be used in simple compile tests + lt_simple_compile_test_code="int some_variable = 0;" + + # Code to be used in simple link tests + lt_simple_link_test_code='int main(int, char *[[]]) { return(0); }' + + # ltmain only uses $CC for tagged configurations so make sure $CC is set. + _LT_TAG_COMPILER + + # save warnings/boilerplate of simple test code + _LT_COMPILER_BOILERPLATE + _LT_LINKER_BOILERPLATE + + # Allow CC to be a program name with arguments. + lt_save_CC=$CC + lt_save_CFLAGS=$CFLAGS + lt_save_LD=$LD + lt_save_GCC=$GCC + GCC=$GXX + lt_save_with_gnu_ld=$with_gnu_ld + lt_save_path_LD=$lt_cv_path_LD + if test -n "${lt_cv_prog_gnu_ldcxx+set}"; then + lt_cv_prog_gnu_ld=$lt_cv_prog_gnu_ldcxx + else + $as_unset lt_cv_prog_gnu_ld + fi + if test -n "${lt_cv_path_LDCXX+set}"; then + lt_cv_path_LD=$lt_cv_path_LDCXX + else + $as_unset lt_cv_path_LD + fi + test -z "${LDCXX+set}" || LD=$LDCXX + CC=${CXX-"c++"} + CFLAGS=$CXXFLAGS + compiler=$CC + _LT_TAGVAR(compiler, $1)=$CC + _LT_CC_BASENAME([$compiler]) + + if test -n "$compiler"; then + # We don't want -fno-exception when compiling C++ code, so set the + # no_builtin_flag separately + if test yes = "$GXX"; then + _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -fno-builtin' + else + _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)= + fi + + if test yes = "$GXX"; then + # Set up default GNU C++ configuration + + LT_PATH_LD + + # Check if GNU C++ uses GNU ld as the underlying linker, since the + # archiving commands below assume that GNU ld is being used. + if test yes = "$with_gnu_ld"; then + _LT_TAGVAR(archive_cmds, $1)='$CC $pic_flag -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC $pic_flag -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' + + # If archive_cmds runs LD, not CC, wlarc should be empty + # XXX I think wlarc can be eliminated in ltcf-cxx, but I need to + # investigate it a little bit more. (MM) + wlarc='$wl' + + # ancient GNU ld didn't support --whole-archive et. al. + if eval "`$CC -print-prog-name=ld` --help 2>&1" | + $GREP 'no-whole-archive' > /dev/null; then + _LT_TAGVAR(whole_archive_flag_spec, $1)=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive' + else + _LT_TAGVAR(whole_archive_flag_spec, $1)= + fi + else + with_gnu_ld=no + wlarc= + + # A generic and very simple default shared library creation + # command for GNU C++ for the case where it uses the native + # linker, instead of GNU ld. If possible, this setting should + # overridden to take advantage of the native linker features on + # the platform it is being used on. + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $lib' + fi + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"' + + else + GXX=no + with_gnu_ld=no + wlarc= + fi + + # PORTME: fill in a description of your system's C++ link characteristics + AC_MSG_CHECKING([whether the $compiler linker ($LD) supports shared libraries]) + _LT_TAGVAR(ld_shlibs, $1)=yes + case $host_os in + aix3*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + aix[[4-9]]*) + if test ia64 = "$host_cpu"; then + # On IA64, the linker does run time linking by default, so we don't + # have to do anything special. + aix_use_runtimelinking=no + exp_sym_flag='-Bexport' + no_entry_flag= + else + aix_use_runtimelinking=no + + # Test if we are trying to use run time linking or normal + # AIX style linking. If -brtl is somewhere in LDFLAGS, we + # have runtime linking enabled, and use it for executables. + # For shared libraries, we enable/disable runtime linking + # depending on the kind of the shared library created - + # when "with_aix_soname,aix_use_runtimelinking" is: + # "aix,no" lib.a(lib.so.V) shared, rtl:no, for executables + # "aix,yes" lib.so shared, rtl:yes, for executables + # lib.a static archive + # "both,no" lib.so.V(shr.o) shared, rtl:yes + # lib.a(lib.so.V) shared, rtl:no, for executables + # "both,yes" lib.so.V(shr.o) shared, rtl:yes, for executables + # lib.a(lib.so.V) shared, rtl:no + # "svr4,*" lib.so.V(shr.o) shared, rtl:yes, for executables + # lib.a static archive + case $host_os in aix4.[[23]]|aix4.[[23]].*|aix[[5-9]]*) + for ld_flag in $LDFLAGS; do + case $ld_flag in + *-brtl*) + aix_use_runtimelinking=yes + break + ;; + esac + done + if test svr4,no = "$with_aix_soname,$aix_use_runtimelinking"; then + # With aix-soname=svr4, we create the lib.so.V shared archives only, + # so we don't have lib.a shared libs to link our executables. + # We have to force runtime linking in this case. + aix_use_runtimelinking=yes + LDFLAGS="$LDFLAGS -Wl,-brtl" + fi + ;; + esac + + exp_sym_flag='-bexport' + no_entry_flag='-bnoentry' + fi + + # When large executables or shared objects are built, AIX ld can + # have problems creating the table of contents. If linking a library + # or program results in "error TOC overflow" add -mminimal-toc to + # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not + # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS. + + _LT_TAGVAR(archive_cmds, $1)='' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + _LT_TAGVAR(hardcode_libdir_separator, $1)=':' + _LT_TAGVAR(link_all_deplibs, $1)=yes + _LT_TAGVAR(file_list_spec, $1)='$wl-f,' + case $with_aix_soname,$aix_use_runtimelinking in + aix,*) ;; # no import file + svr4,* | *,yes) # use import file + # The Import File defines what to hardcode. + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_direct_absolute, $1)=no + ;; + esac + + if test yes = "$GXX"; then + case $host_os in aix4.[[012]]|aix4.[[012]].*) + # We only want to do this on AIX 4.2 and lower, the check + # below for broken collect2 doesn't work under 4.3+ + collect2name=`$CC -print-prog-name=collect2` + if test -f "$collect2name" && + strings "$collect2name" | $GREP resolve_lib_name >/dev/null + then + # We have reworked collect2 + : + else + # We have old collect2 + _LT_TAGVAR(hardcode_direct, $1)=unsupported + # It fails to find uninstalled libraries when the uninstalled + # path is not listed in the libpath. Setting hardcode_minus_L + # to unsupported forces relinking + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)= + fi + esac + shared_flag='-shared' + if test yes = "$aix_use_runtimelinking"; then + shared_flag=$shared_flag' $wl-G' + fi + # Need to ensure runtime linking is disabled for the traditional + # shared library, or the linker may eventually find shared libraries + # /with/ Import File - we do not want to mix them. + shared_flag_aix='-shared' + shared_flag_svr4='-shared $wl-G' + else + # not using gcc + if test ia64 = "$host_cpu"; then + # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release + # chokes on -Wl,-G. The following line is correct: + shared_flag='-G' + else + if test yes = "$aix_use_runtimelinking"; then + shared_flag='$wl-G' + else + shared_flag='$wl-bM:SRE' + fi + shared_flag_aix='$wl-bM:SRE' + shared_flag_svr4='$wl-G' + fi + fi + + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-bexpall' + # It seems that -bexpall does not export symbols beginning with + # underscore (_), so it is better to generate a list of symbols to + # export. + _LT_TAGVAR(always_export_symbols, $1)=yes + if test aix,yes = "$with_aix_soname,$aix_use_runtimelinking"; then + # Warning - without using the other runtime loading flags (-brtl), + # -berok will link without error, but may produce a broken library. + # The "-G" linker flag allows undefined symbols. + _LT_TAGVAR(no_undefined_flag, $1)='-bernotok' + # Determine the default libpath from the value encoded in an empty + # executable. + _LT_SYS_MODULE_PATH_AIX([$1]) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-blibpath:$libdir:'"$aix_libpath" + + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $deplibs $wl'$no_entry_flag' $compiler_flags `if test -n "$allow_undefined_flag"; then func_echo_all "$wl$allow_undefined_flag"; else :; fi` $wl'$exp_sym_flag:\$export_symbols' '$shared_flag + else + if test ia64 = "$host_cpu"; then + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R $libdir:/usr/lib:/lib' + _LT_TAGVAR(allow_undefined_flag, $1)="-z nodefs" + _LT_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\$wl$no_entry_flag"' $compiler_flags $wl$allow_undefined_flag '"\$wl$exp_sym_flag:\$export_symbols" + else + # Determine the default libpath from the value encoded in an + # empty executable. + _LT_SYS_MODULE_PATH_AIX([$1]) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-blibpath:$libdir:'"$aix_libpath" + # Warning - without using the other run time loading flags, + # -berok will link without error, but may produce a broken library. + _LT_TAGVAR(no_undefined_flag, $1)=' $wl-bernotok' + _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-berok' + if test yes = "$with_gnu_ld"; then + # We only use this code for GNU lds that support --whole-archive. + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive$convenience $wl--no-whole-archive' + else + # Exported symbols can be pulled into shared objects from archives + _LT_TAGVAR(whole_archive_flag_spec, $1)='$convenience' + fi + _LT_TAGVAR(archive_cmds_need_lc, $1)=yes + _LT_TAGVAR(archive_expsym_cmds, $1)='$RM -r $output_objdir/$realname.d~$MKDIR $output_objdir/$realname.d' + # -brtl affects multiple linker settings, -berok does not and is overridden later + compiler_flags_filtered='`func_echo_all "$compiler_flags " | $SED -e "s%-brtl\\([[, ]]\\)%-berok\\1%g"`' + if test svr4 != "$with_aix_soname"; then + # This is similar to how AIX traditionally builds its shared + # libraries. Need -bnortl late, we may have -brtl in LDFLAGS. + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$CC '$shared_flag_aix' -o $output_objdir/$realname.d/$soname $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$realname.d/$soname' + fi + if test aix != "$with_aix_soname"; then + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$CC '$shared_flag_svr4' -o $output_objdir/$realname.d/$shared_archive_member_spec.o $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$STRIP -e $output_objdir/$realname.d/$shared_archive_member_spec.o~( func_echo_all "#! $soname($shared_archive_member_spec.o)"; if test shr_64 = "$shared_archive_member_spec"; then func_echo_all "# 64"; else func_echo_all "# 32"; fi; cat $export_symbols ) > $output_objdir/$realname.d/$shared_archive_member_spec.imp~$AR $AR_FLAGS $output_objdir/$soname $output_objdir/$realname.d/$shared_archive_member_spec.o $output_objdir/$realname.d/$shared_archive_member_spec.imp' + else + # used by -dlpreopen to get the symbols + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$MV $output_objdir/$realname.d/$soname $output_objdir' + fi + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$RM -r $output_objdir/$realname.d' + fi + fi + ;; + + beos*) + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + # Joseph Beckenbach says some releases of gcc + # support --undefined. This deserves some investigation. FIXME + _LT_TAGVAR(archive_cmds, $1)='$CC -nostart $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + chorus*) + case $cc_basename in + *) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + + cygwin* | mingw* | pw32* | cegcc*) + case $GXX,$cc_basename in + ,cl* | no,cl*) + # Native MSVC + # hardcode_libdir_flag_spec is actually meaningless, as there is + # no search path for DLLs. + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)=' ' + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + _LT_TAGVAR(always_export_symbols, $1)=yes + _LT_TAGVAR(file_list_spec, $1)='@' + # Tell ltmain to make .lib files, not .a files. + libext=lib + # Tell ltmain to make .dll files, not .so files. + shrext_cmds=.dll + # FIXME: Setting linknames here is a bad hack. + _LT_TAGVAR(archive_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~linknames=' + _LT_TAGVAR(archive_expsym_cmds, $1)='if _LT_DLL_DEF_P([$export_symbols]); then + cp "$export_symbols" "$output_objdir/$soname.def"; + echo "$tool_output_objdir$soname.def" > "$output_objdir/$soname.exp"; + else + $SED -e '\''s/^/-link -EXPORT:/'\'' < $export_symbols > $output_objdir/$soname.exp; + fi~ + $CC -o $tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~ + linknames=' + # The linker will not automatically build a static lib if we build a DLL. + # _LT_TAGVAR(old_archive_from_new_cmds, $1)='true' + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + # Don't use ranlib + _LT_TAGVAR(old_postinstall_cmds, $1)='chmod 644 $oldlib' + _LT_TAGVAR(postlink_cmds, $1)='lt_outputfile="@OUTPUT@"~ + lt_tool_outputfile="@TOOL_OUTPUT@"~ + case $lt_outputfile in + *.exe|*.EXE) ;; + *) + lt_outputfile=$lt_outputfile.exe + lt_tool_outputfile=$lt_tool_outputfile.exe + ;; + esac~ + func_to_tool_file "$lt_outputfile"~ + if test : != "$MANIFEST_TOOL" && test -f "$lt_outputfile.manifest"; then + $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1; + $RM "$lt_outputfile.manifest"; + fi' + ;; + *) + # g++ + # _LT_TAGVAR(hardcode_libdir_flag_spec, $1) is actually meaningless, + # as there is no search path for DLLs. + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-all-symbols' + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + _LT_TAGVAR(always_export_symbols, $1)=no + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + + if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + # If the export-symbols file already is a .def file, use it as + # is; otherwise, prepend EXPORTS... + _LT_TAGVAR(archive_expsym_cmds, $1)='if _LT_DLL_DEF_P([$export_symbols]); then + cp $export_symbols $output_objdir/$soname.def; + else + echo EXPORTS > $output_objdir/$soname.def; + cat $export_symbols >> $output_objdir/$soname.def; + fi~ + $CC -shared -nostdlib $output_objdir/$soname.def $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + darwin* | rhapsody*) + _LT_DARWIN_LINKER_FEATURES($1) + ;; + + os2*) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + shrext_cmds=.dll + _LT_TAGVAR(archive_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + _LT_TAGVAR(archive_expsym_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + prefix_cmds="$SED"~ + if test EXPORTS = "`$SED 1q $export_symbols`"; then + prefix_cmds="$prefix_cmds -e 1d"; + fi~ + prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~ + cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + _LT_TAGVAR(old_archive_From_new_cmds, $1)='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def' + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + ;; + + dgux*) + case $cc_basename in + ec++*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + ghcx*) + # Green Hills C++ Compiler + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + + freebsd2.*) + # C++ shared libraries reported to be fairly broken before + # switch to ELF + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + + freebsd-elf*) + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + ;; + + freebsd* | dragonfly*) + # FreeBSD 3 and later use GNU C++ and GNU ld with standard ELF + # conventions + _LT_TAGVAR(ld_shlibs, $1)=yes + ;; + + haiku*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(link_all_deplibs, $1)=yes + ;; + + hpux9*) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_minus_L, $1)=yes # Not in the search PATH, + # but as the default + # location of the library. + + case $cc_basename in + CC*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + aCC*) + _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$CC -b $wl+b $wl$install_libdir -o $output_objdir/$soname $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | $EGREP "\-L"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' + ;; + *) + if test yes = "$GXX"; then + _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$CC -shared -nostdlib $pic_flag $wl+b $wl$install_libdir -o $output_objdir/$soname $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' + else + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + + hpux10*|hpux11*) + if test no = "$with_gnu_ld"; then + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + + case $host_cpu in + hppa*64*|ia64*) + ;; + *) + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + ;; + esac + fi + case $host_cpu in + hppa*64*|ia64*) + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + *) + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + _LT_TAGVAR(hardcode_minus_L, $1)=yes # Not in the search PATH, + # but as the default + # location of the library. + ;; + esac + + case $cc_basename in + CC*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + aCC*) + case $host_cpu in + hppa*64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + ia64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+nodefaultrpath -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + esac + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | $GREP "\-L"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' + ;; + *) + if test yes = "$GXX"; then + if test no = "$with_gnu_ld"; then + case $host_cpu in + hppa*64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib -fPIC $wl+h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + ia64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $pic_flag $wl+h $wl$soname $wl+nodefaultrpath -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + esac + fi + else + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + + interix[[3-9]]*) + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc. + # Instead, shared libraries are loaded at an image base (0x10000000 by + # default) and relocated if they conflict, which is a slow very memory + # consuming and fragmenting process. To avoid this, we pick a random, + # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link + # time. Moving up from 0x10000000 also allows more sbrk(2) space. + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='sed "s|^|_|" $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--retain-symbols-file,$output_objdir/$soname.expsym $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + ;; + irix5* | irix6*) + case $cc_basename in + CC*) + # SGI C++ + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -all -multigot $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + + # Archives containing C++ object files must be created using + # "CC -ar", where "CC" is the IRIX C++ compiler. This is + # necessary to make sure instantiated templates are included + # in the archive. + _LT_TAGVAR(old_archive_cmds, $1)='$CC -ar -WR,-u -o $oldlib $oldobjs' + ;; + *) + if test yes = "$GXX"; then + if test no = "$with_gnu_ld"; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + else + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` -o $lib' + fi + fi + _LT_TAGVAR(link_all_deplibs, $1)=yes + ;; + esac + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_TAGVAR(inherit_rpath, $1)=yes + ;; + + linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + case $cc_basename in + KCC*) + # Kuck and Associates, Inc. (KAI) C++ Compiler + + # KCC will only create a shared library if the output file + # ends with ".so" (or ".sl" for HP-UX), so rename the library + # to its proper name (with version) after linking. + _LT_TAGVAR(archive_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\$tempext\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\$tempext\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib $wl-retain-symbols-file,$export_symbols; mv \$templib $lib' + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1 | $GREP "ld"`; rm -f libconftest$shared_ext; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' + + # Archives containing C++ object files must be created using + # "CC -Bstatic", where "CC" is the KAI C++ compiler. + _LT_TAGVAR(old_archive_cmds, $1)='$CC -Bstatic -o $oldlib $oldobjs' + ;; + icpc* | ecpc* ) + # Intel C++ + with_gnu_ld=yes + # version 8.0 and above of icpc choke on multiply defined symbols + # if we add $predep_objects and $postdep_objects, however 7.1 and + # earlier do not add the objects themselves. + case `$CC -V 2>&1` in + *"Version 7."*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + ;; + *) # Version 8.0 or newer + tmp_idyn= + case $host_cpu in + ia64*) tmp_idyn=' -i_dynamic';; + esac + _LT_TAGVAR(archive_cmds, $1)='$CC -shared'"$tmp_idyn"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared'"$tmp_idyn"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + ;; + esac + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive$convenience $wl--no-whole-archive' + ;; + pgCC* | pgcpp*) + # Portland Group C++ compiler + case `$CC -V` in + *pgCC\ [[1-5]].* | *pgcpp\ [[1-5]].*) + _LT_TAGVAR(prelink_cmds, $1)='tpldir=Template.dir~ + rm -rf $tpldir~ + $CC --prelink_objects --instantiation_dir $tpldir $objs $libobjs $compile_deplibs~ + compile_command="$compile_command `find $tpldir -name \*.o | sort | $NL2SP`"' + _LT_TAGVAR(old_archive_cmds, $1)='tpldir=Template.dir~ + rm -rf $tpldir~ + $CC --prelink_objects --instantiation_dir $tpldir $oldobjs$old_deplibs~ + $AR $AR_FLAGS $oldlib$oldobjs$old_deplibs `find $tpldir -name \*.o | sort | $NL2SP`~ + $RANLIB $oldlib' + _LT_TAGVAR(archive_cmds, $1)='tpldir=Template.dir~ + rm -rf $tpldir~ + $CC --prelink_objects --instantiation_dir $tpldir $predep_objects $libobjs $deplibs $convenience $postdep_objects~ + $CC -shared $pic_flag $predep_objects $libobjs $deplibs `find $tpldir -name \*.o | sort | $NL2SP` $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='tpldir=Template.dir~ + rm -rf $tpldir~ + $CC --prelink_objects --instantiation_dir $tpldir $predep_objects $libobjs $deplibs $convenience $postdep_objects~ + $CC -shared $pic_flag $predep_objects $libobjs $deplibs `find $tpldir -name \*.o | sort | $NL2SP` $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + ;; + *) # Version 6 and above use weak symbols + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + ;; + esac + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl--rpath $wl$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + ;; + cxx*) + # Compaq C++ + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib $wl-retain-symbols-file $wl$export_symbols' + + runpath_var=LD_RUN_PATH + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP "ld"`; templist=`func_echo_all "$templist" | $SED "s/\(^.*ld.*\)\( .*ld .*$\)/\1/"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "X$list" | $Xsed' + ;; + xl* | mpixl* | bgxl*) + # IBM XL 8.0 on PPC, with GNU ld + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' + _LT_TAGVAR(archive_cmds, $1)='$CC -qmkshrobj $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + if test yes = "$supports_anon_versioning"; then + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $output_objdir/$libname.ver~ + cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ + echo "local: *; };" >> $output_objdir/$libname.ver~ + $CC -qmkshrobj $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-version-script $wl$output_objdir/$libname.ver -o $lib' + fi + ;; + *) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) + # Sun C++ 5.9 + _LT_TAGVAR(no_undefined_flag, $1)=' -zdefs' + _LT_TAGVAR(archive_cmds, $1)='$CC -G$allow_undefined_flag -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G$allow_undefined_flag -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-retain-symbols-file $wl$export_symbols' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + _LT_TAGVAR(compiler_needs_object, $1)=yes + + # Not sure whether something based on + # $CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1 + # would be better. + output_verbose_link_cmd='func_echo_all' + + # Archives containing C++ object files must be created using + # "CC -xar", where "CC" is the Sun C++ compiler. This is + # necessary to make sure instantiated templates are included + # in the archive. + _LT_TAGVAR(old_archive_cmds, $1)='$CC -xar -o $oldlib $oldobjs' + ;; + esac + ;; + esac + ;; + + lynxos*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + + m88k*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + + mvs*) + case $cc_basename in + cxx*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + + netbsd*) + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $predep_objects $libobjs $deplibs $postdep_objects $linker_flags' + wlarc= + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + fi + # Workaround some broken pre-1.5 toolchains + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP conftest.$objext | $SED -e "s:-lgcc -lc -lgcc::"' + ;; + + *nto* | *qnx*) + _LT_TAGVAR(ld_shlibs, $1)=yes + ;; + + openbsd* | bitrig*) + if test -f /usr/libexec/ld.so; then + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $lib' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`"; then + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-retain-symbols-file,$export_symbols -o $lib' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + _LT_TAGVAR(whole_archive_flag_spec, $1)=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive' + fi + output_verbose_link_cmd=func_echo_all + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + osf3* | osf4* | osf5*) + case $cc_basename in + KCC*) + # Kuck and Associates, Inc. (KAI) C++ Compiler + + # KCC will only create a shared library if the output file + # ends with ".so" (or ".sl" for HP-UX), so rename the library + # to its proper name (with version) after linking. + _LT_TAGVAR(archive_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo "$lib" | $SED -e "s/\$tempext\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib' + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + + # Archives containing C++ object files must be created using + # the KAI C++ compiler. + case $host in + osf3*) _LT_TAGVAR(old_archive_cmds, $1)='$CC -Bstatic -o $oldlib $oldobjs' ;; + *) _LT_TAGVAR(old_archive_cmds, $1)='$CC -o $oldlib $oldobjs' ;; + esac + ;; + RCC*) + # Rational C++ 2.4.1 + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + cxx*) + case $host in + osf3*) + _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-expect_unresolved $wl\*' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $soname `test -n "$verstring" && func_echo_all "$wl-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + ;; + *) + _LT_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done~ + echo "-hidden">> $lib.exp~ + $CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -msym -soname $soname $wl-input $wl$lib.exp `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib~ + $RM $lib.exp' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir' + ;; + esac + + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP "ld" | $GREP -v "ld:"`; templist=`func_echo_all "$templist" | $SED "s/\(^.*ld.*\)\( .*ld.*$\)/\1/"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' + ;; + *) + if test yes,no = "$GXX,$with_gnu_ld"; then + _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-expect_unresolved $wl\*' + case $host in + osf3*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-msym $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + ;; + esac + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"' + + else + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + + psos*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + + sunos4*) + case $cc_basename in + CC*) + # Sun C++ 4.x + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + lcc*) + # Lucid + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + + solaris*) + case $cc_basename in + CC* | sunCC*) + # Sun C++ 4.2, 5.x and Centerline C++ + _LT_TAGVAR(archive_cmds_need_lc,$1)=yes + _LT_TAGVAR(no_undefined_flag, $1)=' -zdefs' + _LT_TAGVAR(archive_cmds, $1)='$CC -G$allow_undefined_flag -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -G$allow_undefined_flag $wl-M $wl$lib.exp -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp' + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + case $host_os in + solaris2.[[0-5]] | solaris2.[[0-5]].*) ;; + *) + # The compiler driver will combine and reorder linker options, + # but understands '-z linker_flag'. + # Supported since Solaris 2.6 (maybe 2.5.1?) + _LT_TAGVAR(whole_archive_flag_spec, $1)='-z allextract$convenience -z defaultextract' + ;; + esac + _LT_TAGVAR(link_all_deplibs, $1)=yes + + output_verbose_link_cmd='func_echo_all' + + # Archives containing C++ object files must be created using + # "CC -xar", where "CC" is the Sun C++ compiler. This is + # necessary to make sure instantiated templates are included + # in the archive. + _LT_TAGVAR(old_archive_cmds, $1)='$CC -xar -o $oldlib $oldobjs' + ;; + gcx*) + # Green Hills C++ Compiler + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-h $wl$soname -o $lib' + + # The C++ compiler must be used to create the archive. + _LT_TAGVAR(old_archive_cmds, $1)='$CC $LDFLAGS -archive -o $oldlib $oldobjs' + ;; + *) + # GNU C++ compiler with Solaris linker + if test yes,no = "$GXX,$with_gnu_ld"; then + _LT_TAGVAR(no_undefined_flag, $1)=' $wl-z ${wl}defs' + if $CC --version | $GREP -v '^2\.7' > /dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-h $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -shared $pic_flag -nostdlib $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp' + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"' + else + # g++ 2.7 appears to require '-G' NOT '-shared' on this + # platform. + _LT_TAGVAR(archive_cmds, $1)='$CC -G -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-h $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -G -nostdlib $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp' + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -G $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"' + fi + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R $wl$libdir' + case $host_os in + solaris2.[[0-5]] | solaris2.[[0-5]].*) ;; + *) + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl-z ${wl}allextract$convenience $wl-z ${wl}defaultextract' + ;; + esac + fi + ;; + esac + ;; + + sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[[01]].[[10]]* | unixware7* | sco3.2v5.0.[[024]]*) + _LT_TAGVAR(no_undefined_flag, $1)='$wl-z,text' + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + runpath_var='LD_RUN_PATH' + + case $cc_basename in + CC*) + _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + ;; + + sysv5* | sco3.2v5* | sco5v6*) + # Note: We CANNOT use -z defs as we might desire, because we do not + # link with -lc, and that would cause any symbols used from libc to + # always be unresolved, which means just about no library would + # ever link correctly. If we're not using GNU ld we use -z text + # though, which does catch some bad symbols but isn't as heavy-handed + # as -z defs. + _LT_TAGVAR(no_undefined_flag, $1)='$wl-z,text' + _LT_TAGVAR(allow_undefined_flag, $1)='$wl-z,nodefs' + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R,$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=':' + _LT_TAGVAR(link_all_deplibs, $1)=yes + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-Bexport' + runpath_var='LD_RUN_PATH' + + case $cc_basename in + CC*) + _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(old_archive_cmds, $1)='$CC -Tprelink_objects $oldobjs~ + '"$_LT_TAGVAR(old_archive_cmds, $1)" + _LT_TAGVAR(reload_cmds, $1)='$CC -Tprelink_objects $reload_objs~ + '"$_LT_TAGVAR(reload_cmds, $1)" + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + ;; + + tandem*) + case $cc_basename in + NCC*) + # NonStop-UX NCC 3.20 + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + + vxworks*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + + *) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + + AC_MSG_RESULT([$_LT_TAGVAR(ld_shlibs, $1)]) + test no = "$_LT_TAGVAR(ld_shlibs, $1)" && can_build_shared=no + + _LT_TAGVAR(GCC, $1)=$GXX + _LT_TAGVAR(LD, $1)=$LD + + ## CAVEAT EMPTOR: + ## There is no encapsulation within the following macros, do not change + ## the running order or otherwise move them around unless you know exactly + ## what you are doing... + _LT_SYS_HIDDEN_LIBDEPS($1) + _LT_COMPILER_PIC($1) + _LT_COMPILER_C_O($1) + _LT_COMPILER_FILE_LOCKS($1) + _LT_LINKER_SHLIBS($1) + _LT_SYS_DYNAMIC_LINKER($1) + _LT_LINKER_HARDCODE_LIBPATH($1) + + _LT_CONFIG($1) + fi # test -n "$compiler" + + CC=$lt_save_CC + CFLAGS=$lt_save_CFLAGS + LDCXX=$LD + LD=$lt_save_LD + GCC=$lt_save_GCC + with_gnu_ld=$lt_save_with_gnu_ld + lt_cv_path_LDCXX=$lt_cv_path_LD + lt_cv_path_LD=$lt_save_path_LD + lt_cv_prog_gnu_ldcxx=$lt_cv_prog_gnu_ld + lt_cv_prog_gnu_ld=$lt_save_with_gnu_ld +fi # test yes != "$_lt_caught_CXX_error" + +AC_LANG_POP +])# _LT_LANG_CXX_CONFIG + + +# _LT_FUNC_STRIPNAME_CNF +# ---------------------- +# func_stripname_cnf prefix suffix name +# strip PREFIX and SUFFIX off of NAME. +# PREFIX and SUFFIX must not contain globbing or regex special +# characters, hashes, percent signs, but SUFFIX may contain a leading +# dot (in which case that matches only a dot). +# +# This function is identical to the (non-XSI) version of func_stripname, +# except this one can be used by m4 code that may be executed by configure, +# rather than the libtool script. +m4_defun([_LT_FUNC_STRIPNAME_CNF],[dnl +AC_REQUIRE([_LT_DECL_SED]) +AC_REQUIRE([_LT_PROG_ECHO_BACKSLASH]) +func_stripname_cnf () +{ + case @S|@2 in + .*) func_stripname_result=`$ECHO "@S|@3" | $SED "s%^@S|@1%%; s%\\\\@S|@2\$%%"`;; + *) func_stripname_result=`$ECHO "@S|@3" | $SED "s%^@S|@1%%; s%@S|@2\$%%"`;; + esac +} # func_stripname_cnf +])# _LT_FUNC_STRIPNAME_CNF + + +# _LT_SYS_HIDDEN_LIBDEPS([TAGNAME]) +# --------------------------------- +# Figure out "hidden" library dependencies from verbose +# compiler output when linking a shared library. +# Parse the compiler output and extract the necessary +# objects, libraries and library flags. +m4_defun([_LT_SYS_HIDDEN_LIBDEPS], +[m4_require([_LT_FILEUTILS_DEFAULTS])dnl +AC_REQUIRE([_LT_FUNC_STRIPNAME_CNF])dnl +# Dependencies to place before and after the object being linked: +_LT_TAGVAR(predep_objects, $1)= +_LT_TAGVAR(postdep_objects, $1)= +_LT_TAGVAR(predeps, $1)= +_LT_TAGVAR(postdeps, $1)= +_LT_TAGVAR(compiler_lib_search_path, $1)= + +dnl we can't use the lt_simple_compile_test_code here, +dnl because it contains code intended for an executable, +dnl not a library. It's possible we should let each +dnl tag define a new lt_????_link_test_code variable, +dnl but it's only used here... +m4_if([$1], [], [cat > conftest.$ac_ext <<_LT_EOF +int a; +void foo (void) { a = 0; } +_LT_EOF +], [$1], [CXX], [cat > conftest.$ac_ext <<_LT_EOF +class Foo +{ +public: + Foo (void) { a = 0; } +private: + int a; +}; +_LT_EOF +], [$1], [F77], [cat > conftest.$ac_ext <<_LT_EOF + subroutine foo + implicit none + integer*4 a + a=0 + return + end +_LT_EOF +], [$1], [FC], [cat > conftest.$ac_ext <<_LT_EOF + subroutine foo + implicit none + integer a + a=0 + return + end +_LT_EOF +], [$1], [GCJ], [cat > conftest.$ac_ext <<_LT_EOF +public class foo { + private int a; + public void bar (void) { + a = 0; + } +}; +_LT_EOF +], [$1], [GO], [cat > conftest.$ac_ext <<_LT_EOF +package foo +func foo() { +} +_LT_EOF +]) + +_lt_libdeps_save_CFLAGS=$CFLAGS +case "$CC $CFLAGS " in #( +*\ -flto*\ *) CFLAGS="$CFLAGS -fno-lto" ;; +*\ -fwhopr*\ *) CFLAGS="$CFLAGS -fno-whopr" ;; +*\ -fuse-linker-plugin*\ *) CFLAGS="$CFLAGS -fno-use-linker-plugin" ;; +esac + +dnl Parse the compiler output and extract the necessary +dnl objects, libraries and library flags. +if AC_TRY_EVAL(ac_compile); then + # Parse the compiler output and extract the necessary + # objects, libraries and library flags. + + # Sentinel used to keep track of whether or not we are before + # the conftest object file. + pre_test_object_deps_done=no + + for p in `eval "$output_verbose_link_cmd"`; do + case $prev$p in + + -L* | -R* | -l*) + # Some compilers place space between "-{L,R}" and the path. + # Remove the space. + if test x-L = "$p" || + test x-R = "$p"; then + prev=$p + continue + fi + + # Expand the sysroot to ease extracting the directories later. + if test -z "$prev"; then + case $p in + -L*) func_stripname_cnf '-L' '' "$p"; prev=-L; p=$func_stripname_result ;; + -R*) func_stripname_cnf '-R' '' "$p"; prev=-R; p=$func_stripname_result ;; + -l*) func_stripname_cnf '-l' '' "$p"; prev=-l; p=$func_stripname_result ;; + esac + fi + case $p in + =*) func_stripname_cnf '=' '' "$p"; p=$lt_sysroot$func_stripname_result ;; + esac + if test no = "$pre_test_object_deps_done"; then + case $prev in + -L | -R) + # Internal compiler library paths should come after those + # provided the user. The postdeps already come after the + # user supplied libs so there is no need to process them. + if test -z "$_LT_TAGVAR(compiler_lib_search_path, $1)"; then + _LT_TAGVAR(compiler_lib_search_path, $1)=$prev$p + else + _LT_TAGVAR(compiler_lib_search_path, $1)="${_LT_TAGVAR(compiler_lib_search_path, $1)} $prev$p" + fi + ;; + # The "-l" case would never come before the object being + # linked, so don't bother handling this case. + esac + else + if test -z "$_LT_TAGVAR(postdeps, $1)"; then + _LT_TAGVAR(postdeps, $1)=$prev$p + else + _LT_TAGVAR(postdeps, $1)="${_LT_TAGVAR(postdeps, $1)} $prev$p" + fi + fi + prev= + ;; + + *.lto.$objext) ;; # Ignore GCC LTO objects + *.$objext) + # This assumes that the test object file only shows up + # once in the compiler output. + if test "$p" = "conftest.$objext"; then + pre_test_object_deps_done=yes + continue + fi + + if test no = "$pre_test_object_deps_done"; then + if test -z "$_LT_TAGVAR(predep_objects, $1)"; then + _LT_TAGVAR(predep_objects, $1)=$p + else + _LT_TAGVAR(predep_objects, $1)="$_LT_TAGVAR(predep_objects, $1) $p" + fi + else + if test -z "$_LT_TAGVAR(postdep_objects, $1)"; then + _LT_TAGVAR(postdep_objects, $1)=$p + else + _LT_TAGVAR(postdep_objects, $1)="$_LT_TAGVAR(postdep_objects, $1) $p" + fi + fi + ;; + + *) ;; # Ignore the rest. + + esac + done + + # Clean up. + rm -f a.out a.exe +else + echo "libtool.m4: error: problem compiling $1 test program" +fi + +$RM -f confest.$objext +CFLAGS=$_lt_libdeps_save_CFLAGS + +# PORTME: override above test on systems where it is broken +m4_if([$1], [CXX], +[case $host_os in +interix[[3-9]]*) + # Interix 3.5 installs completely hosed .la files for C++, so rather than + # hack all around it, let's just trust "g++" to DTRT. + _LT_TAGVAR(predep_objects,$1)= + _LT_TAGVAR(postdep_objects,$1)= + _LT_TAGVAR(postdeps,$1)= + ;; +esac +]) + +case " $_LT_TAGVAR(postdeps, $1) " in +*" -lc "*) _LT_TAGVAR(archive_cmds_need_lc, $1)=no ;; +esac + _LT_TAGVAR(compiler_lib_search_dirs, $1)= +if test -n "${_LT_TAGVAR(compiler_lib_search_path, $1)}"; then + _LT_TAGVAR(compiler_lib_search_dirs, $1)=`echo " ${_LT_TAGVAR(compiler_lib_search_path, $1)}" | $SED -e 's! -L! !g' -e 's!^ !!'` +fi +_LT_TAGDECL([], [compiler_lib_search_dirs], [1], + [The directories searched by this compiler when creating a shared library]) +_LT_TAGDECL([], [predep_objects], [1], + [Dependencies to place before and after the objects being linked to + create a shared library]) +_LT_TAGDECL([], [postdep_objects], [1]) +_LT_TAGDECL([], [predeps], [1]) +_LT_TAGDECL([], [postdeps], [1]) +_LT_TAGDECL([], [compiler_lib_search_path], [1], + [The library search path used internally by the compiler when linking + a shared library]) +])# _LT_SYS_HIDDEN_LIBDEPS + + +# _LT_LANG_F77_CONFIG([TAG]) +# -------------------------- +# Ensure that the configuration variables for a Fortran 77 compiler are +# suitably defined. These variables are subsequently used by _LT_CONFIG +# to write the compiler configuration to 'libtool'. +m4_defun([_LT_LANG_F77_CONFIG], +[AC_LANG_PUSH(Fortran 77) +if test -z "$F77" || test no = "$F77"; then + _lt_disable_F77=yes +fi + +_LT_TAGVAR(archive_cmds_need_lc, $1)=no +_LT_TAGVAR(allow_undefined_flag, $1)= +_LT_TAGVAR(always_export_symbols, $1)=no +_LT_TAGVAR(archive_expsym_cmds, $1)= +_LT_TAGVAR(export_dynamic_flag_spec, $1)= +_LT_TAGVAR(hardcode_direct, $1)=no +_LT_TAGVAR(hardcode_direct_absolute, $1)=no +_LT_TAGVAR(hardcode_libdir_flag_spec, $1)= +_LT_TAGVAR(hardcode_libdir_separator, $1)= +_LT_TAGVAR(hardcode_minus_L, $1)=no +_LT_TAGVAR(hardcode_automatic, $1)=no +_LT_TAGVAR(inherit_rpath, $1)=no +_LT_TAGVAR(module_cmds, $1)= +_LT_TAGVAR(module_expsym_cmds, $1)= +_LT_TAGVAR(link_all_deplibs, $1)=unknown +_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds +_LT_TAGVAR(reload_flag, $1)=$reload_flag +_LT_TAGVAR(reload_cmds, $1)=$reload_cmds +_LT_TAGVAR(no_undefined_flag, $1)= +_LT_TAGVAR(whole_archive_flag_spec, $1)= +_LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no + +# Source file extension for f77 test sources. +ac_ext=f + +# Object file extension for compiled f77 test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# No sense in running all these tests if we already determined that +# the F77 compiler isn't working. Some variables (like enable_shared) +# are currently assumed to apply to all compilers on this platform, +# and will be corrupted by setting them based on a non-working compiler. +if test yes != "$_lt_disable_F77"; then + # Code to be used in simple compile tests + lt_simple_compile_test_code="\ + subroutine t + return + end +" + + # Code to be used in simple link tests + lt_simple_link_test_code="\ + program t + end +" + + # ltmain only uses $CC for tagged configurations so make sure $CC is set. + _LT_TAG_COMPILER + + # save warnings/boilerplate of simple test code + _LT_COMPILER_BOILERPLATE + _LT_LINKER_BOILERPLATE + + # Allow CC to be a program name with arguments. + lt_save_CC=$CC + lt_save_GCC=$GCC + lt_save_CFLAGS=$CFLAGS + CC=${F77-"f77"} + CFLAGS=$FFLAGS + compiler=$CC + _LT_TAGVAR(compiler, $1)=$CC + _LT_CC_BASENAME([$compiler]) + GCC=$G77 + if test -n "$compiler"; then + AC_MSG_CHECKING([if libtool supports shared libraries]) + AC_MSG_RESULT([$can_build_shared]) + + AC_MSG_CHECKING([whether to build shared libraries]) + test no = "$can_build_shared" && enable_shared=no + + # On AIX, shared libraries and static libraries use the same namespace, and + # are all built from PIC. + case $host_os in + aix3*) + test yes = "$enable_shared" && enable_static=no + if test -n "$RANLIB"; then + archive_cmds="$archive_cmds~\$RANLIB \$lib" + postinstall_cmds='$RANLIB $lib' + fi + ;; + aix[[4-9]]*) + if test ia64 != "$host_cpu"; then + case $enable_shared,$with_aix_soname,$aix_use_runtimelinking in + yes,aix,yes) ;; # shared object as lib.so file only + yes,svr4,*) ;; # shared object as lib.so archive member only + yes,*) enable_static=no ;; # shared object in lib.a archive as well + esac + fi + ;; + esac + AC_MSG_RESULT([$enable_shared]) + + AC_MSG_CHECKING([whether to build static libraries]) + # Make sure either enable_shared or enable_static is yes. + test yes = "$enable_shared" || enable_static=yes + AC_MSG_RESULT([$enable_static]) + + _LT_TAGVAR(GCC, $1)=$G77 + _LT_TAGVAR(LD, $1)=$LD + + ## CAVEAT EMPTOR: + ## There is no encapsulation within the following macros, do not change + ## the running order or otherwise move them around unless you know exactly + ## what you are doing... + _LT_COMPILER_PIC($1) + _LT_COMPILER_C_O($1) + _LT_COMPILER_FILE_LOCKS($1) + _LT_LINKER_SHLIBS($1) + _LT_SYS_DYNAMIC_LINKER($1) + _LT_LINKER_HARDCODE_LIBPATH($1) + + _LT_CONFIG($1) + fi # test -n "$compiler" + + GCC=$lt_save_GCC + CC=$lt_save_CC + CFLAGS=$lt_save_CFLAGS +fi # test yes != "$_lt_disable_F77" + +AC_LANG_POP +])# _LT_LANG_F77_CONFIG + + +# _LT_LANG_FC_CONFIG([TAG]) +# ------------------------- +# Ensure that the configuration variables for a Fortran compiler are +# suitably defined. These variables are subsequently used by _LT_CONFIG +# to write the compiler configuration to 'libtool'. +m4_defun([_LT_LANG_FC_CONFIG], +[AC_LANG_PUSH(Fortran) + +if test -z "$FC" || test no = "$FC"; then + _lt_disable_FC=yes +fi + +_LT_TAGVAR(archive_cmds_need_lc, $1)=no +_LT_TAGVAR(allow_undefined_flag, $1)= +_LT_TAGVAR(always_export_symbols, $1)=no +_LT_TAGVAR(archive_expsym_cmds, $1)= +_LT_TAGVAR(export_dynamic_flag_spec, $1)= +_LT_TAGVAR(hardcode_direct, $1)=no +_LT_TAGVAR(hardcode_direct_absolute, $1)=no +_LT_TAGVAR(hardcode_libdir_flag_spec, $1)= +_LT_TAGVAR(hardcode_libdir_separator, $1)= +_LT_TAGVAR(hardcode_minus_L, $1)=no +_LT_TAGVAR(hardcode_automatic, $1)=no +_LT_TAGVAR(inherit_rpath, $1)=no +_LT_TAGVAR(module_cmds, $1)= +_LT_TAGVAR(module_expsym_cmds, $1)= +_LT_TAGVAR(link_all_deplibs, $1)=unknown +_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds +_LT_TAGVAR(reload_flag, $1)=$reload_flag +_LT_TAGVAR(reload_cmds, $1)=$reload_cmds +_LT_TAGVAR(no_undefined_flag, $1)= +_LT_TAGVAR(whole_archive_flag_spec, $1)= +_LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no + +# Source file extension for fc test sources. +ac_ext=${ac_fc_srcext-f} + +# Object file extension for compiled fc test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# No sense in running all these tests if we already determined that +# the FC compiler isn't working. Some variables (like enable_shared) +# are currently assumed to apply to all compilers on this platform, +# and will be corrupted by setting them based on a non-working compiler. +if test yes != "$_lt_disable_FC"; then + # Code to be used in simple compile tests + lt_simple_compile_test_code="\ + subroutine t + return + end +" + + # Code to be used in simple link tests + lt_simple_link_test_code="\ + program t + end +" + + # ltmain only uses $CC for tagged configurations so make sure $CC is set. + _LT_TAG_COMPILER + + # save warnings/boilerplate of simple test code + _LT_COMPILER_BOILERPLATE + _LT_LINKER_BOILERPLATE + + # Allow CC to be a program name with arguments. + lt_save_CC=$CC + lt_save_GCC=$GCC + lt_save_CFLAGS=$CFLAGS + CC=${FC-"f95"} + CFLAGS=$FCFLAGS + compiler=$CC + GCC=$ac_cv_fc_compiler_gnu + + _LT_TAGVAR(compiler, $1)=$CC + _LT_CC_BASENAME([$compiler]) + + if test -n "$compiler"; then + AC_MSG_CHECKING([if libtool supports shared libraries]) + AC_MSG_RESULT([$can_build_shared]) + + AC_MSG_CHECKING([whether to build shared libraries]) + test no = "$can_build_shared" && enable_shared=no + + # On AIX, shared libraries and static libraries use the same namespace, and + # are all built from PIC. + case $host_os in + aix3*) + test yes = "$enable_shared" && enable_static=no + if test -n "$RANLIB"; then + archive_cmds="$archive_cmds~\$RANLIB \$lib" + postinstall_cmds='$RANLIB $lib' + fi + ;; + aix[[4-9]]*) + if test ia64 != "$host_cpu"; then + case $enable_shared,$with_aix_soname,$aix_use_runtimelinking in + yes,aix,yes) ;; # shared object as lib.so file only + yes,svr4,*) ;; # shared object as lib.so archive member only + yes,*) enable_static=no ;; # shared object in lib.a archive as well + esac + fi + ;; + esac + AC_MSG_RESULT([$enable_shared]) + + AC_MSG_CHECKING([whether to build static libraries]) + # Make sure either enable_shared or enable_static is yes. + test yes = "$enable_shared" || enable_static=yes + AC_MSG_RESULT([$enable_static]) + + _LT_TAGVAR(GCC, $1)=$ac_cv_fc_compiler_gnu + _LT_TAGVAR(LD, $1)=$LD + + ## CAVEAT EMPTOR: + ## There is no encapsulation within the following macros, do not change + ## the running order or otherwise move them around unless you know exactly + ## what you are doing... + _LT_SYS_HIDDEN_LIBDEPS($1) + _LT_COMPILER_PIC($1) + _LT_COMPILER_C_O($1) + _LT_COMPILER_FILE_LOCKS($1) + _LT_LINKER_SHLIBS($1) + _LT_SYS_DYNAMIC_LINKER($1) + _LT_LINKER_HARDCODE_LIBPATH($1) + + _LT_CONFIG($1) + fi # test -n "$compiler" + + GCC=$lt_save_GCC + CC=$lt_save_CC + CFLAGS=$lt_save_CFLAGS +fi # test yes != "$_lt_disable_FC" + +AC_LANG_POP +])# _LT_LANG_FC_CONFIG + + +# _LT_LANG_GCJ_CONFIG([TAG]) +# -------------------------- +# Ensure that the configuration variables for the GNU Java Compiler compiler +# are suitably defined. These variables are subsequently used by _LT_CONFIG +# to write the compiler configuration to 'libtool'. +m4_defun([_LT_LANG_GCJ_CONFIG], +[AC_REQUIRE([LT_PROG_GCJ])dnl +AC_LANG_SAVE + +# Source file extension for Java test sources. +ac_ext=java + +# Object file extension for compiled Java test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code="class foo {}" + +# Code to be used in simple link tests +lt_simple_link_test_code='public class conftest { public static void main(String[[]] argv) {}; }' + +# ltmain only uses $CC for tagged configurations so make sure $CC is set. +_LT_TAG_COMPILER + +# save warnings/boilerplate of simple test code +_LT_COMPILER_BOILERPLATE +_LT_LINKER_BOILERPLATE + +# Allow CC to be a program name with arguments. +lt_save_CC=$CC +lt_save_CFLAGS=$CFLAGS +lt_save_GCC=$GCC +GCC=yes +CC=${GCJ-"gcj"} +CFLAGS=$GCJFLAGS +compiler=$CC +_LT_TAGVAR(compiler, $1)=$CC +_LT_TAGVAR(LD, $1)=$LD +_LT_CC_BASENAME([$compiler]) + +# GCJ did not exist at the time GCC didn't implicitly link libc in. +_LT_TAGVAR(archive_cmds_need_lc, $1)=no + +_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds +_LT_TAGVAR(reload_flag, $1)=$reload_flag +_LT_TAGVAR(reload_cmds, $1)=$reload_cmds + +## CAVEAT EMPTOR: +## There is no encapsulation within the following macros, do not change +## the running order or otherwise move them around unless you know exactly +## what you are doing... +if test -n "$compiler"; then + _LT_COMPILER_NO_RTTI($1) + _LT_COMPILER_PIC($1) + _LT_COMPILER_C_O($1) + _LT_COMPILER_FILE_LOCKS($1) + _LT_LINKER_SHLIBS($1) + _LT_LINKER_HARDCODE_LIBPATH($1) + + _LT_CONFIG($1) +fi + +AC_LANG_RESTORE + +GCC=$lt_save_GCC +CC=$lt_save_CC +CFLAGS=$lt_save_CFLAGS +])# _LT_LANG_GCJ_CONFIG + + +# _LT_LANG_GO_CONFIG([TAG]) +# -------------------------- +# Ensure that the configuration variables for the GNU Go compiler +# are suitably defined. These variables are subsequently used by _LT_CONFIG +# to write the compiler configuration to 'libtool'. +m4_defun([_LT_LANG_GO_CONFIG], +[AC_REQUIRE([LT_PROG_GO])dnl +AC_LANG_SAVE + +# Source file extension for Go test sources. +ac_ext=go + +# Object file extension for compiled Go test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code="package main; func main() { }" + +# Code to be used in simple link tests +lt_simple_link_test_code='package main; func main() { }' + +# ltmain only uses $CC for tagged configurations so make sure $CC is set. +_LT_TAG_COMPILER + +# save warnings/boilerplate of simple test code +_LT_COMPILER_BOILERPLATE +_LT_LINKER_BOILERPLATE + +# Allow CC to be a program name with arguments. +lt_save_CC=$CC +lt_save_CFLAGS=$CFLAGS +lt_save_GCC=$GCC +GCC=yes +CC=${GOC-"gccgo"} +CFLAGS=$GOFLAGS +compiler=$CC +_LT_TAGVAR(compiler, $1)=$CC +_LT_TAGVAR(LD, $1)=$LD +_LT_CC_BASENAME([$compiler]) + +# Go did not exist at the time GCC didn't implicitly link libc in. +_LT_TAGVAR(archive_cmds_need_lc, $1)=no + +_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds +_LT_TAGVAR(reload_flag, $1)=$reload_flag +_LT_TAGVAR(reload_cmds, $1)=$reload_cmds + +## CAVEAT EMPTOR: +## There is no encapsulation within the following macros, do not change +## the running order or otherwise move them around unless you know exactly +## what you are doing... +if test -n "$compiler"; then + _LT_COMPILER_NO_RTTI($1) + _LT_COMPILER_PIC($1) + _LT_COMPILER_C_O($1) + _LT_COMPILER_FILE_LOCKS($1) + _LT_LINKER_SHLIBS($1) + _LT_LINKER_HARDCODE_LIBPATH($1) + + _LT_CONFIG($1) +fi + +AC_LANG_RESTORE + +GCC=$lt_save_GCC +CC=$lt_save_CC +CFLAGS=$lt_save_CFLAGS +])# _LT_LANG_GO_CONFIG + + +# _LT_LANG_RC_CONFIG([TAG]) +# ------------------------- +# Ensure that the configuration variables for the Windows resource compiler +# are suitably defined. These variables are subsequently used by _LT_CONFIG +# to write the compiler configuration to 'libtool'. +m4_defun([_LT_LANG_RC_CONFIG], +[AC_REQUIRE([LT_PROG_RC])dnl +AC_LANG_SAVE + +# Source file extension for RC test sources. +ac_ext=rc + +# Object file extension for compiled RC test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code='sample MENU { MENUITEM "&Soup", 100, CHECKED }' + +# Code to be used in simple link tests +lt_simple_link_test_code=$lt_simple_compile_test_code + +# ltmain only uses $CC for tagged configurations so make sure $CC is set. +_LT_TAG_COMPILER + +# save warnings/boilerplate of simple test code +_LT_COMPILER_BOILERPLATE +_LT_LINKER_BOILERPLATE + +# Allow CC to be a program name with arguments. +lt_save_CC=$CC +lt_save_CFLAGS=$CFLAGS +lt_save_GCC=$GCC +GCC= +CC=${RC-"windres"} +CFLAGS= +compiler=$CC +_LT_TAGVAR(compiler, $1)=$CC +_LT_CC_BASENAME([$compiler]) +_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)=yes + +if test -n "$compiler"; then + : + _LT_CONFIG($1) +fi + +GCC=$lt_save_GCC +AC_LANG_RESTORE +CC=$lt_save_CC +CFLAGS=$lt_save_CFLAGS +])# _LT_LANG_RC_CONFIG + + +# LT_PROG_GCJ +# ----------- +AC_DEFUN([LT_PROG_GCJ], +[m4_ifdef([AC_PROG_GCJ], [AC_PROG_GCJ], + [m4_ifdef([A][M_PROG_GCJ], [A][M_PROG_GCJ], + [AC_CHECK_TOOL(GCJ, gcj,) + test set = "${GCJFLAGS+set}" || GCJFLAGS="-g -O2" + AC_SUBST(GCJFLAGS)])])[]dnl +]) + +# Old name: +AU_ALIAS([LT_AC_PROG_GCJ], [LT_PROG_GCJ]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([LT_AC_PROG_GCJ], []) + + +# LT_PROG_GO +# ---------- +AC_DEFUN([LT_PROG_GO], +[AC_CHECK_TOOL(GOC, gccgo,) +]) + + +# LT_PROG_RC +# ---------- +AC_DEFUN([LT_PROG_RC], +[AC_CHECK_TOOL(RC, windres,) +]) + +# Old name: +AU_ALIAS([LT_AC_PROG_RC], [LT_PROG_RC]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([LT_AC_PROG_RC], []) + + +# _LT_DECL_EGREP +# -------------- +# If we don't have a new enough Autoconf to choose the best grep +# available, choose the one first in the user's PATH. +m4_defun([_LT_DECL_EGREP], +[AC_REQUIRE([AC_PROG_EGREP])dnl +AC_REQUIRE([AC_PROG_FGREP])dnl +test -z "$GREP" && GREP=grep +_LT_DECL([], [GREP], [1], [A grep program that handles long lines]) +_LT_DECL([], [EGREP], [1], [An ERE matcher]) +_LT_DECL([], [FGREP], [1], [A literal string matcher]) +dnl Non-bleeding-edge autoconf doesn't subst GREP, so do it here too +AC_SUBST([GREP]) +]) + + +# _LT_DECL_OBJDUMP +# -------------- +# If we don't have a new enough Autoconf to choose the best objdump +# available, choose the one first in the user's PATH. +m4_defun([_LT_DECL_OBJDUMP], +[AC_CHECK_TOOL(OBJDUMP, objdump, false) +test -z "$OBJDUMP" && OBJDUMP=objdump +_LT_DECL([], [OBJDUMP], [1], [An object symbol dumper]) +AC_SUBST([OBJDUMP]) +]) + +# _LT_DECL_DLLTOOL +# ---------------- +# Ensure DLLTOOL variable is set. +m4_defun([_LT_DECL_DLLTOOL], +[AC_CHECK_TOOL(DLLTOOL, dlltool, false) +test -z "$DLLTOOL" && DLLTOOL=dlltool +_LT_DECL([], [DLLTOOL], [1], [DLL creation program]) +AC_SUBST([DLLTOOL]) +]) + +# _LT_DECL_SED +# ------------ +# Check for a fully-functional sed program, that truncates +# as few characters as possible. Prefer GNU sed if found. +m4_defun([_LT_DECL_SED], +[AC_PROG_SED +test -z "$SED" && SED=sed +Xsed="$SED -e 1s/^X//" +_LT_DECL([], [SED], [1], [A sed program that does not truncate output]) +_LT_DECL([], [Xsed], ["\$SED -e 1s/^X//"], + [Sed that helps us avoid accidentally triggering echo(1) options like -n]) +])# _LT_DECL_SED + +m4_ifndef([AC_PROG_SED], [ +############################################################ +# NOTE: This macro has been submitted for inclusion into # +# GNU Autoconf as AC_PROG_SED. When it is available in # +# a released version of Autoconf we should remove this # +# macro and use it instead. # +############################################################ + +m4_defun([AC_PROG_SED], +[AC_MSG_CHECKING([for a sed that does not truncate output]) +AC_CACHE_VAL(lt_cv_path_SED, +[# Loop through the user's path and test for sed and gsed. +# Then use that list of sed's as ones to test for truncation. +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for lt_ac_prog in sed gsed; do + for ac_exec_ext in '' $ac_executable_extensions; do + if $as_executable_p "$as_dir/$lt_ac_prog$ac_exec_ext"; then + lt_ac_sed_list="$lt_ac_sed_list $as_dir/$lt_ac_prog$ac_exec_ext" + fi + done + done +done +IFS=$as_save_IFS +lt_ac_max=0 +lt_ac_count=0 +# Add /usr/xpg4/bin/sed as it is typically found on Solaris +# along with /bin/sed that truncates output. +for lt_ac_sed in $lt_ac_sed_list /usr/xpg4/bin/sed; do + test ! -f "$lt_ac_sed" && continue + cat /dev/null > conftest.in + lt_ac_count=0 + echo $ECHO_N "0123456789$ECHO_C" >conftest.in + # Check for GNU sed and select it if it is found. + if "$lt_ac_sed" --version 2>&1 < /dev/null | grep 'GNU' > /dev/null; then + lt_cv_path_SED=$lt_ac_sed + break + fi + while true; do + cat conftest.in conftest.in >conftest.tmp + mv conftest.tmp conftest.in + cp conftest.in conftest.nl + echo >>conftest.nl + $lt_ac_sed -e 's/a$//' < conftest.nl >conftest.out || break + cmp -s conftest.out conftest.nl || break + # 10000 chars as input seems more than enough + test 10 -lt "$lt_ac_count" && break + lt_ac_count=`expr $lt_ac_count + 1` + if test "$lt_ac_count" -gt "$lt_ac_max"; then + lt_ac_max=$lt_ac_count + lt_cv_path_SED=$lt_ac_sed + fi + done +done +]) +SED=$lt_cv_path_SED +AC_SUBST([SED]) +AC_MSG_RESULT([$SED]) +])#AC_PROG_SED +])#m4_ifndef + +# Old name: +AU_ALIAS([LT_AC_PROG_SED], [AC_PROG_SED]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([LT_AC_PROG_SED], []) + + +# _LT_CHECK_SHELL_FEATURES +# ------------------------ +# Find out whether the shell is Bourne or XSI compatible, +# or has some other useful features. +m4_defun([_LT_CHECK_SHELL_FEATURES], +[if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then + lt_unset=unset +else + lt_unset=false +fi +_LT_DECL([], [lt_unset], [0], [whether the shell understands "unset"])dnl + +# test EBCDIC or ASCII +case `echo X|tr X '\101'` in + A) # ASCII based system + # \n is not interpreted correctly by Solaris 8 /usr/ucb/tr + lt_SP2NL='tr \040 \012' + lt_NL2SP='tr \015\012 \040\040' + ;; + *) # EBCDIC based system + lt_SP2NL='tr \100 \n' + lt_NL2SP='tr \r\n \100\100' + ;; +esac +_LT_DECL([SP2NL], [lt_SP2NL], [1], [turn spaces into newlines])dnl +_LT_DECL([NL2SP], [lt_NL2SP], [1], [turn newlines into spaces])dnl +])# _LT_CHECK_SHELL_FEATURES + + +# _LT_PATH_CONVERSION_FUNCTIONS +# ----------------------------- +# Determine what file name conversion functions should be used by +# func_to_host_file (and, implicitly, by func_to_host_path). These are needed +# for certain cross-compile configurations and native mingw. +m4_defun([_LT_PATH_CONVERSION_FUNCTIONS], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +AC_REQUIRE([AC_CANONICAL_BUILD])dnl +AC_MSG_CHECKING([how to convert $build file names to $host format]) +AC_CACHE_VAL(lt_cv_to_host_file_cmd, +[case $host in + *-*-mingw* ) + case $build in + *-*-mingw* ) # actually msys + lt_cv_to_host_file_cmd=func_convert_file_msys_to_w32 + ;; + *-*-cygwin* ) + lt_cv_to_host_file_cmd=func_convert_file_cygwin_to_w32 + ;; + * ) # otherwise, assume *nix + lt_cv_to_host_file_cmd=func_convert_file_nix_to_w32 + ;; + esac + ;; + *-*-cygwin* ) + case $build in + *-*-mingw* ) # actually msys + lt_cv_to_host_file_cmd=func_convert_file_msys_to_cygwin + ;; + *-*-cygwin* ) + lt_cv_to_host_file_cmd=func_convert_file_noop + ;; + * ) # otherwise, assume *nix + lt_cv_to_host_file_cmd=func_convert_file_nix_to_cygwin + ;; + esac + ;; + * ) # unhandled hosts (and "normal" native builds) + lt_cv_to_host_file_cmd=func_convert_file_noop + ;; +esac +]) +to_host_file_cmd=$lt_cv_to_host_file_cmd +AC_MSG_RESULT([$lt_cv_to_host_file_cmd]) +_LT_DECL([to_host_file_cmd], [lt_cv_to_host_file_cmd], + [0], [convert $build file names to $host format])dnl + +AC_MSG_CHECKING([how to convert $build file names to toolchain format]) +AC_CACHE_VAL(lt_cv_to_tool_file_cmd, +[#assume ordinary cross tools, or native build. +lt_cv_to_tool_file_cmd=func_convert_file_noop +case $host in + *-*-mingw* ) + case $build in + *-*-mingw* ) # actually msys + lt_cv_to_tool_file_cmd=func_convert_file_msys_to_w32 + ;; + esac + ;; +esac +]) +to_tool_file_cmd=$lt_cv_to_tool_file_cmd +AC_MSG_RESULT([$lt_cv_to_tool_file_cmd]) +_LT_DECL([to_tool_file_cmd], [lt_cv_to_tool_file_cmd], + [0], [convert $build files to toolchain format])dnl +])# _LT_PATH_CONVERSION_FUNCTIONS diff --git a/m4/ltoptions.m4 b/m4/ltoptions.m4 new file mode 100644 index 0000000..94b0829 --- /dev/null +++ b/m4/ltoptions.m4 @@ -0,0 +1,437 @@ +# Helper functions for option handling. -*- Autoconf -*- +# +# Copyright (C) 2004-2005, 2007-2009, 2011-2015 Free Software +# Foundation, Inc. +# Written by Gary V. Vaughan, 2004 +# +# This file is free software; the Free Software Foundation gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. + +# serial 8 ltoptions.m4 + +# This is to help aclocal find these macros, as it can't see m4_define. +AC_DEFUN([LTOPTIONS_VERSION], [m4_if([1])]) + + +# _LT_MANGLE_OPTION(MACRO-NAME, OPTION-NAME) +# ------------------------------------------ +m4_define([_LT_MANGLE_OPTION], +[[_LT_OPTION_]m4_bpatsubst($1__$2, [[^a-zA-Z0-9_]], [_])]) + + +# _LT_SET_OPTION(MACRO-NAME, OPTION-NAME) +# --------------------------------------- +# Set option OPTION-NAME for macro MACRO-NAME, and if there is a +# matching handler defined, dispatch to it. Other OPTION-NAMEs are +# saved as a flag. +m4_define([_LT_SET_OPTION], +[m4_define(_LT_MANGLE_OPTION([$1], [$2]))dnl +m4_ifdef(_LT_MANGLE_DEFUN([$1], [$2]), + _LT_MANGLE_DEFUN([$1], [$2]), + [m4_warning([Unknown $1 option '$2'])])[]dnl +]) + + +# _LT_IF_OPTION(MACRO-NAME, OPTION-NAME, IF-SET, [IF-NOT-SET]) +# ------------------------------------------------------------ +# Execute IF-SET if OPTION is set, IF-NOT-SET otherwise. +m4_define([_LT_IF_OPTION], +[m4_ifdef(_LT_MANGLE_OPTION([$1], [$2]), [$3], [$4])]) + + +# _LT_UNLESS_OPTIONS(MACRO-NAME, OPTION-LIST, IF-NOT-SET) +# ------------------------------------------------------- +# Execute IF-NOT-SET unless all options in OPTION-LIST for MACRO-NAME +# are set. +m4_define([_LT_UNLESS_OPTIONS], +[m4_foreach([_LT_Option], m4_split(m4_normalize([$2])), + [m4_ifdef(_LT_MANGLE_OPTION([$1], _LT_Option), + [m4_define([$0_found])])])[]dnl +m4_ifdef([$0_found], [m4_undefine([$0_found])], [$3 +])[]dnl +]) + + +# _LT_SET_OPTIONS(MACRO-NAME, OPTION-LIST) +# ---------------------------------------- +# OPTION-LIST is a space-separated list of Libtool options associated +# with MACRO-NAME. If any OPTION has a matching handler declared with +# LT_OPTION_DEFINE, dispatch to that macro; otherwise complain about +# the unknown option and exit. +m4_defun([_LT_SET_OPTIONS], +[# Set options +m4_foreach([_LT_Option], m4_split(m4_normalize([$2])), + [_LT_SET_OPTION([$1], _LT_Option)]) + +m4_if([$1],[LT_INIT],[ + dnl + dnl Simply set some default values (i.e off) if boolean options were not + dnl specified: + _LT_UNLESS_OPTIONS([LT_INIT], [dlopen], [enable_dlopen=no + ]) + _LT_UNLESS_OPTIONS([LT_INIT], [win32-dll], [enable_win32_dll=no + ]) + dnl + dnl If no reference was made to various pairs of opposing options, then + dnl we run the default mode handler for the pair. For example, if neither + dnl 'shared' nor 'disable-shared' was passed, we enable building of shared + dnl archives by default: + _LT_UNLESS_OPTIONS([LT_INIT], [shared disable-shared], [_LT_ENABLE_SHARED]) + _LT_UNLESS_OPTIONS([LT_INIT], [static disable-static], [_LT_ENABLE_STATIC]) + _LT_UNLESS_OPTIONS([LT_INIT], [pic-only no-pic], [_LT_WITH_PIC]) + _LT_UNLESS_OPTIONS([LT_INIT], [fast-install disable-fast-install], + [_LT_ENABLE_FAST_INSTALL]) + _LT_UNLESS_OPTIONS([LT_INIT], [aix-soname=aix aix-soname=both aix-soname=svr4], + [_LT_WITH_AIX_SONAME([aix])]) + ]) +])# _LT_SET_OPTIONS + + +## --------------------------------- ## +## Macros to handle LT_INIT options. ## +## --------------------------------- ## + +# _LT_MANGLE_DEFUN(MACRO-NAME, OPTION-NAME) +# ----------------------------------------- +m4_define([_LT_MANGLE_DEFUN], +[[_LT_OPTION_DEFUN_]m4_bpatsubst(m4_toupper([$1__$2]), [[^A-Z0-9_]], [_])]) + + +# LT_OPTION_DEFINE(MACRO-NAME, OPTION-NAME, CODE) +# ----------------------------------------------- +m4_define([LT_OPTION_DEFINE], +[m4_define(_LT_MANGLE_DEFUN([$1], [$2]), [$3])[]dnl +])# LT_OPTION_DEFINE + + +# dlopen +# ------ +LT_OPTION_DEFINE([LT_INIT], [dlopen], [enable_dlopen=yes +]) + +AU_DEFUN([AC_LIBTOOL_DLOPEN], +[_LT_SET_OPTION([LT_INIT], [dlopen]) +AC_DIAGNOSE([obsolete], +[$0: Remove this warning and the call to _LT_SET_OPTION when you +put the 'dlopen' option into LT_INIT's first parameter.]) +]) + +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_DLOPEN], []) + + +# win32-dll +# --------- +# Declare package support for building win32 dll's. +LT_OPTION_DEFINE([LT_INIT], [win32-dll], +[enable_win32_dll=yes + +case $host in +*-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-cegcc*) + AC_CHECK_TOOL(AS, as, false) + AC_CHECK_TOOL(DLLTOOL, dlltool, false) + AC_CHECK_TOOL(OBJDUMP, objdump, false) + ;; +esac + +test -z "$AS" && AS=as +_LT_DECL([], [AS], [1], [Assembler program])dnl + +test -z "$DLLTOOL" && DLLTOOL=dlltool +_LT_DECL([], [DLLTOOL], [1], [DLL creation program])dnl + +test -z "$OBJDUMP" && OBJDUMP=objdump +_LT_DECL([], [OBJDUMP], [1], [Object dumper program])dnl +])# win32-dll + +AU_DEFUN([AC_LIBTOOL_WIN32_DLL], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +_LT_SET_OPTION([LT_INIT], [win32-dll]) +AC_DIAGNOSE([obsolete], +[$0: Remove this warning and the call to _LT_SET_OPTION when you +put the 'win32-dll' option into LT_INIT's first parameter.]) +]) + +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_WIN32_DLL], []) + + +# _LT_ENABLE_SHARED([DEFAULT]) +# ---------------------------- +# implement the --enable-shared flag, and supports the 'shared' and +# 'disable-shared' LT_INIT options. +# DEFAULT is either 'yes' or 'no'. If omitted, it defaults to 'yes'. +m4_define([_LT_ENABLE_SHARED], +[m4_define([_LT_ENABLE_SHARED_DEFAULT], [m4_if($1, no, no, yes)])dnl +AC_ARG_ENABLE([shared], + [AS_HELP_STRING([--enable-shared@<:@=PKGS@:>@], + [build shared libraries @<:@default=]_LT_ENABLE_SHARED_DEFAULT[@:>@])], + [p=${PACKAGE-default} + case $enableval in + yes) enable_shared=yes ;; + no) enable_shared=no ;; + *) + enable_shared=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, + for pkg in $enableval; do + IFS=$lt_save_ifs + if test "X$pkg" = "X$p"; then + enable_shared=yes + fi + done + IFS=$lt_save_ifs + ;; + esac], + [enable_shared=]_LT_ENABLE_SHARED_DEFAULT) + + _LT_DECL([build_libtool_libs], [enable_shared], [0], + [Whether or not to build shared libraries]) +])# _LT_ENABLE_SHARED + +LT_OPTION_DEFINE([LT_INIT], [shared], [_LT_ENABLE_SHARED([yes])]) +LT_OPTION_DEFINE([LT_INIT], [disable-shared], [_LT_ENABLE_SHARED([no])]) + +# Old names: +AC_DEFUN([AC_ENABLE_SHARED], +[_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[shared]) +]) + +AC_DEFUN([AC_DISABLE_SHARED], +[_LT_SET_OPTION([LT_INIT], [disable-shared]) +]) + +AU_DEFUN([AM_ENABLE_SHARED], [AC_ENABLE_SHARED($@)]) +AU_DEFUN([AM_DISABLE_SHARED], [AC_DISABLE_SHARED($@)]) + +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AM_ENABLE_SHARED], []) +dnl AC_DEFUN([AM_DISABLE_SHARED], []) + + + +# _LT_ENABLE_STATIC([DEFAULT]) +# ---------------------------- +# implement the --enable-static flag, and support the 'static' and +# 'disable-static' LT_INIT options. +# DEFAULT is either 'yes' or 'no'. If omitted, it defaults to 'yes'. +m4_define([_LT_ENABLE_STATIC], +[m4_define([_LT_ENABLE_STATIC_DEFAULT], [m4_if($1, no, no, yes)])dnl +AC_ARG_ENABLE([static], + [AS_HELP_STRING([--enable-static@<:@=PKGS@:>@], + [build static libraries @<:@default=]_LT_ENABLE_STATIC_DEFAULT[@:>@])], + [p=${PACKAGE-default} + case $enableval in + yes) enable_static=yes ;; + no) enable_static=no ;; + *) + enable_static=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, + for pkg in $enableval; do + IFS=$lt_save_ifs + if test "X$pkg" = "X$p"; then + enable_static=yes + fi + done + IFS=$lt_save_ifs + ;; + esac], + [enable_static=]_LT_ENABLE_STATIC_DEFAULT) + + _LT_DECL([build_old_libs], [enable_static], [0], + [Whether or not to build static libraries]) +])# _LT_ENABLE_STATIC + +LT_OPTION_DEFINE([LT_INIT], [static], [_LT_ENABLE_STATIC([yes])]) +LT_OPTION_DEFINE([LT_INIT], [disable-static], [_LT_ENABLE_STATIC([no])]) + +# Old names: +AC_DEFUN([AC_ENABLE_STATIC], +[_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[static]) +]) + +AC_DEFUN([AC_DISABLE_STATIC], +[_LT_SET_OPTION([LT_INIT], [disable-static]) +]) + +AU_DEFUN([AM_ENABLE_STATIC], [AC_ENABLE_STATIC($@)]) +AU_DEFUN([AM_DISABLE_STATIC], [AC_DISABLE_STATIC($@)]) + +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AM_ENABLE_STATIC], []) +dnl AC_DEFUN([AM_DISABLE_STATIC], []) + + + +# _LT_ENABLE_FAST_INSTALL([DEFAULT]) +# ---------------------------------- +# implement the --enable-fast-install flag, and support the 'fast-install' +# and 'disable-fast-install' LT_INIT options. +# DEFAULT is either 'yes' or 'no'. If omitted, it defaults to 'yes'. +m4_define([_LT_ENABLE_FAST_INSTALL], +[m4_define([_LT_ENABLE_FAST_INSTALL_DEFAULT], [m4_if($1, no, no, yes)])dnl +AC_ARG_ENABLE([fast-install], + [AS_HELP_STRING([--enable-fast-install@<:@=PKGS@:>@], + [optimize for fast installation @<:@default=]_LT_ENABLE_FAST_INSTALL_DEFAULT[@:>@])], + [p=${PACKAGE-default} + case $enableval in + yes) enable_fast_install=yes ;; + no) enable_fast_install=no ;; + *) + enable_fast_install=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, + for pkg in $enableval; do + IFS=$lt_save_ifs + if test "X$pkg" = "X$p"; then + enable_fast_install=yes + fi + done + IFS=$lt_save_ifs + ;; + esac], + [enable_fast_install=]_LT_ENABLE_FAST_INSTALL_DEFAULT) + +_LT_DECL([fast_install], [enable_fast_install], [0], + [Whether or not to optimize for fast installation])dnl +])# _LT_ENABLE_FAST_INSTALL + +LT_OPTION_DEFINE([LT_INIT], [fast-install], [_LT_ENABLE_FAST_INSTALL([yes])]) +LT_OPTION_DEFINE([LT_INIT], [disable-fast-install], [_LT_ENABLE_FAST_INSTALL([no])]) + +# Old names: +AU_DEFUN([AC_ENABLE_FAST_INSTALL], +[_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[fast-install]) +AC_DIAGNOSE([obsolete], +[$0: Remove this warning and the call to _LT_SET_OPTION when you put +the 'fast-install' option into LT_INIT's first parameter.]) +]) + +AU_DEFUN([AC_DISABLE_FAST_INSTALL], +[_LT_SET_OPTION([LT_INIT], [disable-fast-install]) +AC_DIAGNOSE([obsolete], +[$0: Remove this warning and the call to _LT_SET_OPTION when you put +the 'disable-fast-install' option into LT_INIT's first parameter.]) +]) + +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_ENABLE_FAST_INSTALL], []) +dnl AC_DEFUN([AM_DISABLE_FAST_INSTALL], []) + + +# _LT_WITH_AIX_SONAME([DEFAULT]) +# ---------------------------------- +# implement the --with-aix-soname flag, and support the `aix-soname=aix' +# and `aix-soname=both' and `aix-soname=svr4' LT_INIT options. DEFAULT +# is either `aix', `both' or `svr4'. If omitted, it defaults to `aix'. +m4_define([_LT_WITH_AIX_SONAME], +[m4_define([_LT_WITH_AIX_SONAME_DEFAULT], [m4_if($1, svr4, svr4, m4_if($1, both, both, aix))])dnl +shared_archive_member_spec= +case $host,$enable_shared in +power*-*-aix[[5-9]]*,yes) + AC_MSG_CHECKING([which variant of shared library versioning to provide]) + AC_ARG_WITH([aix-soname], + [AS_HELP_STRING([--with-aix-soname=aix|svr4|both], + [shared library versioning (aka "SONAME") variant to provide on AIX, @<:@default=]_LT_WITH_AIX_SONAME_DEFAULT[@:>@.])], + [case $withval in + aix|svr4|both) + ;; + *) + AC_MSG_ERROR([Unknown argument to --with-aix-soname]) + ;; + esac + lt_cv_with_aix_soname=$with_aix_soname], + [AC_CACHE_VAL([lt_cv_with_aix_soname], + [lt_cv_with_aix_soname=]_LT_WITH_AIX_SONAME_DEFAULT) + with_aix_soname=$lt_cv_with_aix_soname]) + AC_MSG_RESULT([$with_aix_soname]) + if test aix != "$with_aix_soname"; then + # For the AIX way of multilib, we name the shared archive member + # based on the bitwidth used, traditionally 'shr.o' or 'shr_64.o', + # and 'shr.imp' or 'shr_64.imp', respectively, for the Import File. + # Even when GNU compilers ignore OBJECT_MODE but need '-maix64' flag, + # the AIX toolchain works better with OBJECT_MODE set (default 32). + if test 64 = "${OBJECT_MODE-32}"; then + shared_archive_member_spec=shr_64 + else + shared_archive_member_spec=shr + fi + fi + ;; +*) + with_aix_soname=aix + ;; +esac + +_LT_DECL([], [shared_archive_member_spec], [0], + [Shared archive member basename, for filename based shared library versioning on AIX])dnl +])# _LT_WITH_AIX_SONAME + +LT_OPTION_DEFINE([LT_INIT], [aix-soname=aix], [_LT_WITH_AIX_SONAME([aix])]) +LT_OPTION_DEFINE([LT_INIT], [aix-soname=both], [_LT_WITH_AIX_SONAME([both])]) +LT_OPTION_DEFINE([LT_INIT], [aix-soname=svr4], [_LT_WITH_AIX_SONAME([svr4])]) + + +# _LT_WITH_PIC([MODE]) +# -------------------- +# implement the --with-pic flag, and support the 'pic-only' and 'no-pic' +# LT_INIT options. +# MODE is either 'yes' or 'no'. If omitted, it defaults to 'both'. +m4_define([_LT_WITH_PIC], +[AC_ARG_WITH([pic], + [AS_HELP_STRING([--with-pic@<:@=PKGS@:>@], + [try to use only PIC/non-PIC objects @<:@default=use both@:>@])], + [lt_p=${PACKAGE-default} + case $withval in + yes|no) pic_mode=$withval ;; + *) + pic_mode=default + # Look at the argument we got. We use all the common list separators. + lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, + for lt_pkg in $withval; do + IFS=$lt_save_ifs + if test "X$lt_pkg" = "X$lt_p"; then + pic_mode=yes + fi + done + IFS=$lt_save_ifs + ;; + esac], + [pic_mode=m4_default([$1], [default])]) + +_LT_DECL([], [pic_mode], [0], [What type of objects to build])dnl +])# _LT_WITH_PIC + +LT_OPTION_DEFINE([LT_INIT], [pic-only], [_LT_WITH_PIC([yes])]) +LT_OPTION_DEFINE([LT_INIT], [no-pic], [_LT_WITH_PIC([no])]) + +# Old name: +AU_DEFUN([AC_LIBTOOL_PICMODE], +[_LT_SET_OPTION([LT_INIT], [pic-only]) +AC_DIAGNOSE([obsolete], +[$0: Remove this warning and the call to _LT_SET_OPTION when you +put the 'pic-only' option into LT_INIT's first parameter.]) +]) + +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_PICMODE], []) + +## ----------------- ## +## LTDL_INIT Options ## +## ----------------- ## + +m4_define([_LTDL_MODE], []) +LT_OPTION_DEFINE([LTDL_INIT], [nonrecursive], + [m4_define([_LTDL_MODE], [nonrecursive])]) +LT_OPTION_DEFINE([LTDL_INIT], [recursive], + [m4_define([_LTDL_MODE], [recursive])]) +LT_OPTION_DEFINE([LTDL_INIT], [subproject], + [m4_define([_LTDL_MODE], [subproject])]) + +m4_define([_LTDL_TYPE], []) +LT_OPTION_DEFINE([LTDL_INIT], [installable], + [m4_define([_LTDL_TYPE], [installable])]) +LT_OPTION_DEFINE([LTDL_INIT], [convenience], + [m4_define([_LTDL_TYPE], [convenience])]) diff --git a/m4/ltsugar.m4 b/m4/ltsugar.m4 new file mode 100644 index 0000000..48bc934 --- /dev/null +++ b/m4/ltsugar.m4 @@ -0,0 +1,124 @@ +# ltsugar.m4 -- libtool m4 base layer. -*-Autoconf-*- +# +# Copyright (C) 2004-2005, 2007-2008, 2011-2015 Free Software +# Foundation, Inc. +# Written by Gary V. Vaughan, 2004 +# +# This file is free software; the Free Software Foundation gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. + +# serial 6 ltsugar.m4 + +# This is to help aclocal find these macros, as it can't see m4_define. +AC_DEFUN([LTSUGAR_VERSION], [m4_if([0.1])]) + + +# lt_join(SEP, ARG1, [ARG2...]) +# ----------------------------- +# Produce ARG1SEPARG2...SEPARGn, omitting [] arguments and their +# associated separator. +# Needed until we can rely on m4_join from Autoconf 2.62, since all earlier +# versions in m4sugar had bugs. +m4_define([lt_join], +[m4_if([$#], [1], [], + [$#], [2], [[$2]], + [m4_if([$2], [], [], [[$2]_])$0([$1], m4_shift(m4_shift($@)))])]) +m4_define([_lt_join], +[m4_if([$#$2], [2], [], + [m4_if([$2], [], [], [[$1$2]])$0([$1], m4_shift(m4_shift($@)))])]) + + +# lt_car(LIST) +# lt_cdr(LIST) +# ------------ +# Manipulate m4 lists. +# These macros are necessary as long as will still need to support +# Autoconf-2.59, which quotes differently. +m4_define([lt_car], [[$1]]) +m4_define([lt_cdr], +[m4_if([$#], 0, [m4_fatal([$0: cannot be called without arguments])], + [$#], 1, [], + [m4_dquote(m4_shift($@))])]) +m4_define([lt_unquote], $1) + + +# lt_append(MACRO-NAME, STRING, [SEPARATOR]) +# ------------------------------------------ +# Redefine MACRO-NAME to hold its former content plus 'SEPARATOR''STRING'. +# Note that neither SEPARATOR nor STRING are expanded; they are appended +# to MACRO-NAME as is (leaving the expansion for when MACRO-NAME is invoked). +# No SEPARATOR is output if MACRO-NAME was previously undefined (different +# than defined and empty). +# +# This macro is needed until we can rely on Autoconf 2.62, since earlier +# versions of m4sugar mistakenly expanded SEPARATOR but not STRING. +m4_define([lt_append], +[m4_define([$1], + m4_ifdef([$1], [m4_defn([$1])[$3]])[$2])]) + + + +# lt_combine(SEP, PREFIX-LIST, INFIX, SUFFIX1, [SUFFIX2...]) +# ---------------------------------------------------------- +# Produce a SEP delimited list of all paired combinations of elements of +# PREFIX-LIST with SUFFIX1 through SUFFIXn. Each element of the list +# has the form PREFIXmINFIXSUFFIXn. +# Needed until we can rely on m4_combine added in Autoconf 2.62. +m4_define([lt_combine], +[m4_if(m4_eval([$# > 3]), [1], + [m4_pushdef([_Lt_sep], [m4_define([_Lt_sep], m4_defn([lt_car]))])]]dnl +[[m4_foreach([_Lt_prefix], [$2], + [m4_foreach([_Lt_suffix], + ]m4_dquote(m4_dquote(m4_shift(m4_shift(m4_shift($@)))))[, + [_Lt_sep([$1])[]m4_defn([_Lt_prefix])[$3]m4_defn([_Lt_suffix])])])])]) + + +# lt_if_append_uniq(MACRO-NAME, VARNAME, [SEPARATOR], [UNIQ], [NOT-UNIQ]) +# ----------------------------------------------------------------------- +# Iff MACRO-NAME does not yet contain VARNAME, then append it (delimited +# by SEPARATOR if supplied) and expand UNIQ, else NOT-UNIQ. +m4_define([lt_if_append_uniq], +[m4_ifdef([$1], + [m4_if(m4_index([$3]m4_defn([$1])[$3], [$3$2$3]), [-1], + [lt_append([$1], [$2], [$3])$4], + [$5])], + [lt_append([$1], [$2], [$3])$4])]) + + +# lt_dict_add(DICT, KEY, VALUE) +# ----------------------------- +m4_define([lt_dict_add], +[m4_define([$1($2)], [$3])]) + + +# lt_dict_add_subkey(DICT, KEY, SUBKEY, VALUE) +# -------------------------------------------- +m4_define([lt_dict_add_subkey], +[m4_define([$1($2:$3)], [$4])]) + + +# lt_dict_fetch(DICT, KEY, [SUBKEY]) +# ---------------------------------- +m4_define([lt_dict_fetch], +[m4_ifval([$3], + m4_ifdef([$1($2:$3)], [m4_defn([$1($2:$3)])]), + m4_ifdef([$1($2)], [m4_defn([$1($2)])]))]) + + +# lt_if_dict_fetch(DICT, KEY, [SUBKEY], VALUE, IF-TRUE, [IF-FALSE]) +# ----------------------------------------------------------------- +m4_define([lt_if_dict_fetch], +[m4_if(lt_dict_fetch([$1], [$2], [$3]), [$4], + [$5], + [$6])]) + + +# lt_dict_filter(DICT, [SUBKEY], VALUE, [SEPARATOR], KEY, [...]) +# -------------------------------------------------------------- +m4_define([lt_dict_filter], +[m4_if([$5], [], [], + [lt_join(m4_quote(m4_default([$4], [[, ]])), + lt_unquote(m4_split(m4_normalize(m4_foreach(_Lt_key, lt_car([m4_shiftn(4, $@)]), + [lt_if_dict_fetch([$1], _Lt_key, [$2], [$3], [_Lt_key ])])))))])[]dnl +]) diff --git a/m4/ltversion.m4 b/m4/ltversion.m4 new file mode 100644 index 0000000..fa04b52 --- /dev/null +++ b/m4/ltversion.m4 @@ -0,0 +1,23 @@ +# ltversion.m4 -- version numbers -*- Autoconf -*- +# +# Copyright (C) 2004, 2011-2015 Free Software Foundation, Inc. +# Written by Scott James Remnant, 2004 +# +# This file is free software; the Free Software Foundation gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. + +# @configure_input@ + +# serial 4179 ltversion.m4 +# This file is part of GNU Libtool + +m4_define([LT_PACKAGE_VERSION], [2.4.6]) +m4_define([LT_PACKAGE_REVISION], [2.4.6]) + +AC_DEFUN([LTVERSION_VERSION], +[macro_version='2.4.6' +macro_revision='2.4.6' +_LT_DECL(, macro_version, 0, [Which release of libtool.m4 was used?]) +_LT_DECL(, macro_revision, 0) +]) diff --git a/m4/lt~obsolete.m4 b/m4/lt~obsolete.m4 new file mode 100644 index 0000000..c6b26f8 --- /dev/null +++ b/m4/lt~obsolete.m4 @@ -0,0 +1,99 @@ +# lt~obsolete.m4 -- aclocal satisfying obsolete definitions. -*-Autoconf-*- +# +# Copyright (C) 2004-2005, 2007, 2009, 2011-2015 Free Software +# Foundation, Inc. +# Written by Scott James Remnant, 2004. +# +# This file is free software; the Free Software Foundation gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. + +# serial 5 lt~obsolete.m4 + +# These exist entirely to fool aclocal when bootstrapping libtool. +# +# In the past libtool.m4 has provided macros via AC_DEFUN (or AU_DEFUN), +# which have later been changed to m4_define as they aren't part of the +# exported API, or moved to Autoconf or Automake where they belong. +# +# The trouble is, aclocal is a bit thick. It'll see the old AC_DEFUN +# in /usr/share/aclocal/libtool.m4 and remember it, then when it sees us +# using a macro with the same name in our local m4/libtool.m4 it'll +# pull the old libtool.m4 in (it doesn't see our shiny new m4_define +# and doesn't know about Autoconf macros at all.) +# +# So we provide this file, which has a silly filename so it's always +# included after everything else. This provides aclocal with the +# AC_DEFUNs it wants, but when m4 processes it, it doesn't do anything +# because those macros already exist, or will be overwritten later. +# We use AC_DEFUN over AU_DEFUN for compatibility with aclocal-1.6. +# +# Anytime we withdraw an AC_DEFUN or AU_DEFUN, remember to add it here. +# Yes, that means every name once taken will need to remain here until +# we give up compatibility with versions before 1.7, at which point +# we need to keep only those names which we still refer to. + +# This is to help aclocal find these macros, as it can't see m4_define. +AC_DEFUN([LTOBSOLETE_VERSION], [m4_if([1])]) + +m4_ifndef([AC_LIBTOOL_LINKER_OPTION], [AC_DEFUN([AC_LIBTOOL_LINKER_OPTION])]) +m4_ifndef([AC_PROG_EGREP], [AC_DEFUN([AC_PROG_EGREP])]) +m4_ifndef([_LT_AC_PROG_ECHO_BACKSLASH], [AC_DEFUN([_LT_AC_PROG_ECHO_BACKSLASH])]) +m4_ifndef([_LT_AC_SHELL_INIT], [AC_DEFUN([_LT_AC_SHELL_INIT])]) +m4_ifndef([_LT_AC_SYS_LIBPATH_AIX], [AC_DEFUN([_LT_AC_SYS_LIBPATH_AIX])]) +m4_ifndef([_LT_PROG_LTMAIN], [AC_DEFUN([_LT_PROG_LTMAIN])]) +m4_ifndef([_LT_AC_TAGVAR], [AC_DEFUN([_LT_AC_TAGVAR])]) +m4_ifndef([AC_LTDL_ENABLE_INSTALL], [AC_DEFUN([AC_LTDL_ENABLE_INSTALL])]) +m4_ifndef([AC_LTDL_PREOPEN], [AC_DEFUN([AC_LTDL_PREOPEN])]) +m4_ifndef([_LT_AC_SYS_COMPILER], [AC_DEFUN([_LT_AC_SYS_COMPILER])]) +m4_ifndef([_LT_AC_LOCK], [AC_DEFUN([_LT_AC_LOCK])]) +m4_ifndef([AC_LIBTOOL_SYS_OLD_ARCHIVE], [AC_DEFUN([AC_LIBTOOL_SYS_OLD_ARCHIVE])]) +m4_ifndef([_LT_AC_TRY_DLOPEN_SELF], [AC_DEFUN([_LT_AC_TRY_DLOPEN_SELF])]) +m4_ifndef([AC_LIBTOOL_PROG_CC_C_O], [AC_DEFUN([AC_LIBTOOL_PROG_CC_C_O])]) +m4_ifndef([AC_LIBTOOL_SYS_HARD_LINK_LOCKS], [AC_DEFUN([AC_LIBTOOL_SYS_HARD_LINK_LOCKS])]) +m4_ifndef([AC_LIBTOOL_OBJDIR], [AC_DEFUN([AC_LIBTOOL_OBJDIR])]) +m4_ifndef([AC_LTDL_OBJDIR], [AC_DEFUN([AC_LTDL_OBJDIR])]) +m4_ifndef([AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH], [AC_DEFUN([AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH])]) +m4_ifndef([AC_LIBTOOL_SYS_LIB_STRIP], [AC_DEFUN([AC_LIBTOOL_SYS_LIB_STRIP])]) +m4_ifndef([AC_PATH_MAGIC], [AC_DEFUN([AC_PATH_MAGIC])]) +m4_ifndef([AC_PROG_LD_GNU], [AC_DEFUN([AC_PROG_LD_GNU])]) +m4_ifndef([AC_PROG_LD_RELOAD_FLAG], [AC_DEFUN([AC_PROG_LD_RELOAD_FLAG])]) +m4_ifndef([AC_DEPLIBS_CHECK_METHOD], [AC_DEFUN([AC_DEPLIBS_CHECK_METHOD])]) +m4_ifndef([AC_LIBTOOL_PROG_COMPILER_NO_RTTI], [AC_DEFUN([AC_LIBTOOL_PROG_COMPILER_NO_RTTI])]) +m4_ifndef([AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE], [AC_DEFUN([AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE])]) +m4_ifndef([AC_LIBTOOL_PROG_COMPILER_PIC], [AC_DEFUN([AC_LIBTOOL_PROG_COMPILER_PIC])]) +m4_ifndef([AC_LIBTOOL_PROG_LD_SHLIBS], [AC_DEFUN([AC_LIBTOOL_PROG_LD_SHLIBS])]) +m4_ifndef([AC_LIBTOOL_POSTDEP_PREDEP], [AC_DEFUN([AC_LIBTOOL_POSTDEP_PREDEP])]) +m4_ifndef([LT_AC_PROG_EGREP], [AC_DEFUN([LT_AC_PROG_EGREP])]) +m4_ifndef([LT_AC_PROG_SED], [AC_DEFUN([LT_AC_PROG_SED])]) +m4_ifndef([_LT_CC_BASENAME], [AC_DEFUN([_LT_CC_BASENAME])]) +m4_ifndef([_LT_COMPILER_BOILERPLATE], [AC_DEFUN([_LT_COMPILER_BOILERPLATE])]) +m4_ifndef([_LT_LINKER_BOILERPLATE], [AC_DEFUN([_LT_LINKER_BOILERPLATE])]) +m4_ifndef([_AC_PROG_LIBTOOL], [AC_DEFUN([_AC_PROG_LIBTOOL])]) +m4_ifndef([AC_LIBTOOL_SETUP], [AC_DEFUN([AC_LIBTOOL_SETUP])]) +m4_ifndef([_LT_AC_CHECK_DLFCN], [AC_DEFUN([_LT_AC_CHECK_DLFCN])]) +m4_ifndef([AC_LIBTOOL_SYS_DYNAMIC_LINKER], [AC_DEFUN([AC_LIBTOOL_SYS_DYNAMIC_LINKER])]) +m4_ifndef([_LT_AC_TAGCONFIG], [AC_DEFUN([_LT_AC_TAGCONFIG])]) +m4_ifndef([AC_DISABLE_FAST_INSTALL], [AC_DEFUN([AC_DISABLE_FAST_INSTALL])]) +m4_ifndef([_LT_AC_LANG_CXX], [AC_DEFUN([_LT_AC_LANG_CXX])]) +m4_ifndef([_LT_AC_LANG_F77], [AC_DEFUN([_LT_AC_LANG_F77])]) +m4_ifndef([_LT_AC_LANG_GCJ], [AC_DEFUN([_LT_AC_LANG_GCJ])]) +m4_ifndef([AC_LIBTOOL_LANG_C_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_C_CONFIG])]) +m4_ifndef([_LT_AC_LANG_C_CONFIG], [AC_DEFUN([_LT_AC_LANG_C_CONFIG])]) +m4_ifndef([AC_LIBTOOL_LANG_CXX_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_CXX_CONFIG])]) +m4_ifndef([_LT_AC_LANG_CXX_CONFIG], [AC_DEFUN([_LT_AC_LANG_CXX_CONFIG])]) +m4_ifndef([AC_LIBTOOL_LANG_F77_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_F77_CONFIG])]) +m4_ifndef([_LT_AC_LANG_F77_CONFIG], [AC_DEFUN([_LT_AC_LANG_F77_CONFIG])]) +m4_ifndef([AC_LIBTOOL_LANG_GCJ_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_GCJ_CONFIG])]) +m4_ifndef([_LT_AC_LANG_GCJ_CONFIG], [AC_DEFUN([_LT_AC_LANG_GCJ_CONFIG])]) +m4_ifndef([AC_LIBTOOL_LANG_RC_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_RC_CONFIG])]) +m4_ifndef([_LT_AC_LANG_RC_CONFIG], [AC_DEFUN([_LT_AC_LANG_RC_CONFIG])]) +m4_ifndef([AC_LIBTOOL_CONFIG], [AC_DEFUN([AC_LIBTOOL_CONFIG])]) +m4_ifndef([_LT_AC_FILE_LTDLL_C], [AC_DEFUN([_LT_AC_FILE_LTDLL_C])]) +m4_ifndef([_LT_REQUIRED_DARWIN_CHECKS], [AC_DEFUN([_LT_REQUIRED_DARWIN_CHECKS])]) +m4_ifndef([_LT_AC_PROG_CXXCPP], [AC_DEFUN([_LT_AC_PROG_CXXCPP])]) +m4_ifndef([_LT_PREPARE_SED_QUOTE_VARS], [AC_DEFUN([_LT_PREPARE_SED_QUOTE_VARS])]) +m4_ifndef([_LT_PROG_ECHO_BACKSLASH], [AC_DEFUN([_LT_PROG_ECHO_BACKSLASH])]) +m4_ifndef([_LT_PROG_F77], [AC_DEFUN([_LT_PROG_F77])]) +m4_ifndef([_LT_PROG_FC], [AC_DEFUN([_LT_PROG_FC])]) +m4_ifndef([_LT_PROG_CXX], [AC_DEFUN([_LT_PROG_CXX])]) diff --git a/m4/pkgconfig.m4 b/m4/pkgconfig.m4 new file mode 100644 index 0000000..a0b9cd4 --- /dev/null +++ b/m4/pkgconfig.m4 @@ -0,0 +1,155 @@ +# pkg.m4 - Macros to locate and utilise pkg-config. -*- Autoconf -*- +# +# Copyright © 2004 Scott James Remnant . +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# PKG_PROG_PKG_CONFIG([MIN-VERSION]) +# ---------------------------------- +AC_DEFUN([PKG_PROG_PKG_CONFIG], +[m4_pattern_forbid([^_?PKG_[A-Z_]+$]) +m4_pattern_allow([^PKG_CONFIG(_PATH)?$]) +AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility])dnl +if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then + AC_PATH_TOOL([PKG_CONFIG], [pkg-config]) +fi +if test -n "$PKG_CONFIG"; then + _pkg_min_version=m4_default([$1], [0.9.0]) + AC_MSG_CHECKING([pkg-config is at least version $_pkg_min_version]) + if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + PKG_CONFIG="" + fi + +fi[]dnl +])# PKG_PROG_PKG_CONFIG + +# PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +# +# Check to see whether a particular set of modules exists. Similar +# to PKG_CHECK_MODULES(), but does not set variables or print errors. +# +# +# Similar to PKG_CHECK_MODULES, make sure that the first instance of +# this or PKG_CHECK_MODULES is called, or make sure to call +# PKG_CHECK_EXISTS manually +# -------------------------------------------------------------- +AC_DEFUN([PKG_CHECK_EXISTS], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl +if test -n "$PKG_CONFIG" && \ + AC_RUN_LOG([$PKG_CONFIG --exists --print-errors "$1"]); then + m4_ifval([$2], [$2], [:]) +m4_ifvaln([$3], [else + $3])dnl +fi]) + + +# _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES]) +# --------------------------------------------- +m4_define([_PKG_CONFIG], +[if test -n "$$1"; then + pkg_cv_[]$1="$$1" + elif test -n "$PKG_CONFIG"; then + PKG_CHECK_EXISTS([$3], + [pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null`], + [pkg_failed=yes]) + else + pkg_failed=untried +fi[]dnl +])# _PKG_CONFIG + +# _PKG_SHORT_ERRORS_SUPPORTED +# ----------------------------- +AC_DEFUN([_PKG_SHORT_ERRORS_SUPPORTED], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG]) +if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then + _pkg_short_errors_supported=yes +else + _pkg_short_errors_supported=no +fi[]dnl +])# _PKG_SHORT_ERRORS_SUPPORTED + + +# PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND], +# [ACTION-IF-NOT-FOUND]) +# +# +# Note that if there is a possibility the first call to +# PKG_CHECK_MODULES might not happen, you should be sure to include an +# explicit call to PKG_PROG_PKG_CONFIG in your configure.ac +# +# +# -------------------------------------------------------------- +AC_DEFUN([PKG_CHECK_MODULES], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl +AC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl +AC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl + +pkg_failed=no +AC_MSG_CHECKING([for $1]) + +_PKG_CONFIG([$1][_CFLAGS], [cflags], [$2]) +_PKG_CONFIG([$1][_LIBS], [libs], [$2]) + +m4_define([_PKG_TEXT], [Alternatively, you may set the environment variables $1[]_CFLAGS +and $1[]_LIBS to avoid the need to call pkg-config. +See the pkg-config man page for more details.]) + +if test $pkg_failed = yes; then + _PKG_SHORT_ERRORS_SUPPORTED + if test $_pkg_short_errors_supported = yes; then + $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors "$2" 2>&1` + else + $1[]_PKG_ERRORS=`$PKG_CONFIG --print-errors "$2" 2>&1` + fi + # Put the nasty error message in config.log where it belongs + echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD + + ifelse([$4], , [AC_MSG_ERROR(dnl +[Package requirements ($2) were not met: + +$$1_PKG_ERRORS + +Consider adjusting the PKG_CONFIG_PATH environment variable if you +installed software in a non-standard prefix. + +_PKG_TEXT +])], + [AC_MSG_RESULT([no]) + $4]) +elif test $pkg_failed = untried; then + ifelse([$4], , [AC_MSG_FAILURE(dnl +[The pkg-config script could not be found or is too old. Make sure it +is in your PATH or set the PKG_CONFIG environment variable to the full +path to pkg-config. + +_PKG_TEXT + +To get pkg-config, see .])], + [$4]) +else + $1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS + $1[]_LIBS=$pkg_cv_[]$1[]_LIBS + AC_MSG_RESULT([yes]) + ifelse([$3], , :, [$3]) +fi[]dnl +])# PKG_CHECK_MODULES diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..06f12e0 --- /dev/null +++ b/setup.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python + +import os +os.chdir('bindings/python') +execfile('setup.py') diff --git a/src/ConvertUTF.cpp b/src/ConvertUTF.cpp new file mode 100644 index 0000000..2a4bb69 --- /dev/null +++ b/src/ConvertUTF.cpp @@ -0,0 +1,539 @@ +/* + * Copyright 2001-2004 Unicode, Inc. + * + * Disclaimer + * + * This source code is provided as is by Unicode, Inc. No claims are + * made as to fitness for any particular purpose. No warranties of any + * kind are expressed or implied. The recipient agrees to determine + * applicability of information provided. If this file has been + * purchased on magnetic or optical media from Unicode, Inc., the + * sole remedy for any claim will be exchange of defective media + * within 90 days of receipt. + * + * Limitations on Rights to Redistribute This Code + * + * Unicode, Inc. hereby grants the right to freely use the information + * supplied in this file in the creation of products supporting the + * Unicode Standard, and to make copies of this file in any form + * for internal or external distribution as long as this notice + * remains attached. + */ + +/* --------------------------------------------------------------------- + + Conversions between UTF32, UTF-16, and UTF-8. Source code file. + Author: Mark E. Davis, 1994. + Rev History: Rick McGowan, fixes & updates May 2001. + Sept 2001: fixed const & error conditions per + mods suggested by S. Parent & A. Lillich. + June 2002: Tim Dodd added detection and handling of incomplete + source sequences, enhanced error detection, added casts + to eliminate compiler warnings. + July 2003: slight mods to back out aggressive FFFE detection. + Jan 2004: updated switches in from-UTF8 conversions. + Oct 2004: updated to use UNI_MAX_LEGAL_UTF32 in UTF-32 conversions. + + See the header file "ConvertUTF.h" for complete documentation. + +------------------------------------------------------------------------ */ + +// ignore warnings in this file +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include "libtorrent/ConvertUTF.h" +#ifdef CVTUTF_DEBUG +#include +#endif + +static const int halfShift = 10; /* used for shifting by 10 bits */ + +static const UTF32 halfBase = 0x0010000UL; +static const UTF32 halfMask = 0x3FFUL; + +#define UNI_SUR_HIGH_START (UTF32)0xD800 +#define UNI_SUR_HIGH_END (UTF32)0xDBFF +#define UNI_SUR_LOW_START (UTF32)0xDC00 +#define UNI_SUR_LOW_END (UTF32)0xDFFF + +/* --------------------------------------------------------------------- */ + +ConversionResult ConvertUTF32toUTF16 ( + const UTF32** sourceStart, const UTF32* sourceEnd, + UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF32* source = *sourceStart; + UTF16* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch; + if (target >= targetEnd) { + result = targetExhausted; break; + } + ch = *source++; + if (ch <= UNI_MAX_BMP) { /* Target is a character <= 0xFFFF */ + /* UTF-16 surrogate values are illegal in UTF-32; 0xffff or 0xfffe are both reserved values */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + if (flags == strictConversion) { + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } else { + *target++ = (UTF16)ch; /* normal case */ + } + } else if (ch > UNI_MAX_LEGAL_UTF32) { + if (flags == strictConversion) { + result = sourceIllegal; + } else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } else { + /* target is a character in range 0xFFFF - 0x10FFFF. */ + if (target + 1 >= targetEnd) { + --source; /* Back up source pointer! */ + result = targetExhausted; break; + } + ch -= halfBase; + *target++ = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START); + *target++ = (UTF16)((ch & halfMask) + UNI_SUR_LOW_START); + } + } + *sourceStart = source; + *targetStart = target; + return result; +} + +/* --------------------------------------------------------------------- */ + +ConversionResult ConvertUTF16toUTF32 ( + const UTF16** sourceStart, const UTF16* sourceEnd, + UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF16* source = *sourceStart; + UTF32* target = *targetStart; + UTF32 ch, ch2; + while (source < sourceEnd) { + const UTF16* oldSource = source; /* In case we have to back up because of target overflow. */ + ch = *source++; + /* If we have a surrogate pair, convert to UTF32 first. */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END) { + /* If the 16 bits following the high surrogate are in the source buffer... */ + if (source < sourceEnd) { + ch2 = *source; + /* If it's a low surrogate, convert to UTF32. */ + if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) { + ch = ((ch - UNI_SUR_HIGH_START) << halfShift) + + (ch2 - UNI_SUR_LOW_START) + halfBase; + ++source; + } else if (flags == strictConversion) { /* it's an unpaired high surrogate */ + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } else { /* We don't have the 16 bits following the high surrogate. */ + --source; /* return to the high surrogate */ + result = sourceExhausted; + break; + } + } else if (flags == strictConversion) { + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END) { + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } + if (target >= targetEnd) { + source = oldSource; /* Back up source pointer! */ + result = targetExhausted; break; + } + *target++ = ch; + } + *sourceStart = source; + *targetStart = target; +#ifdef CVTUTF_DEBUG +if (result == sourceIllegal) { + fprintf(stderr, "ConvertUTF16toUTF32 illegal seq 0x%04x,%04x\n", ch, ch2); + fflush(stderr); +} +#endif + return result; +} + +/* --------------------------------------------------------------------- */ + +/* + * Index into the table below with the first byte of a UTF-8 sequence to + * get the number of trailing bytes that are supposed to follow it. + * Note that *legal* UTF-8 values can't have 4 or 5-bytes. The table is + * left as-is for anyone who may want to do such conversion, which was + * allowed in earlier algorithms. + */ +static const char trailingBytesForUTF8[256] = { + 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, + 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, + 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, + 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, + 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, + 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, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 +}; + +/* + * Magic values subtracted from a buffer value during UTF8 conversion. + * This table contains as many values as there might be trailing bytes + * in a UTF-8 sequence. + */ +static const UTF32 offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL, + 0x03C82080UL, 0xFA082080UL, 0x82082080UL }; + +/* + * Once the bits are split out into bytes of UTF-8, this is a mask OR-ed + * into the first byte, depending on how many bytes follow. There are + * as many entries in this table as there are UTF-8 sequence types. + * (I.e., one byte sequence, two byte... etc.). Remember that sequencs + * for *legal* UTF-8 will be 4 or fewer bytes total. + */ +static const UTF8 firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; + +/* --------------------------------------------------------------------- */ + +/* The interface converts a whole buffer to avoid function-call overhead. + * Constants have been gathered. Loops & conditionals have been removed as + * much as possible for efficiency, in favor of drop-through switches. + * (See "Note A" at the bottom of the file for equivalent code.) + * If your compiler supports it, the "isLegalUTF8" call can be turned + * into an inline function. + */ + +/* --------------------------------------------------------------------- */ + +ConversionResult ConvertUTF16toUTF8 ( + const UTF16** sourceStart, const UTF16* sourceEnd, + UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF16* source = *sourceStart; + UTF8* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch; + unsigned short bytesToWrite = 0; + const UTF32 byteMask = 0xBF; + const UTF32 byteMark = 0x80; + const UTF16* oldSource = source; /* In case we have to back up because of target overflow. */ + ch = *source++; + /* If we have a surrogate pair, convert to UTF32 first. */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END) { + /* If the 16 bits following the high surrogate are in the source buffer... */ + if (source < sourceEnd) { + UTF32 ch2 = *source; + /* If it's a low surrogate, convert to UTF32. */ + if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) { + ch = ((ch - UNI_SUR_HIGH_START) << halfShift) + + (ch2 - UNI_SUR_LOW_START) + halfBase; + ++source; + } else if (flags == strictConversion) { /* it's an unpaired high surrogate */ + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } else { /* We don't have the 16 bits following the high surrogate. */ + --source; /* return to the high surrogate */ + result = sourceExhausted; + break; + } + } else if (flags == strictConversion) { + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END) { + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } + /* Figure out how many bytes the result will require */ + if (ch < (UTF32)0x80) { bytesToWrite = 1; + } else if (ch < (UTF32)0x800) { bytesToWrite = 2; + } else if (ch < (UTF32)0x10000) { bytesToWrite = 3; + } else if (ch < (UTF32)0x110000) { bytesToWrite = 4; + } else { bytesToWrite = 3; + ch = UNI_REPLACEMENT_CHAR; + } + + target += bytesToWrite; + if (target > targetEnd) { + source = oldSource; /* Back up source pointer! */ + target -= bytesToWrite; result = targetExhausted; break; + } + switch (bytesToWrite) { /* note: everything falls through. */ + case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 1: *--target = (UTF8)(ch | firstByteMark[bytesToWrite]); + } + target += bytesToWrite; + } + *sourceStart = source; + *targetStart = target; + return result; +} + +/* --------------------------------------------------------------------- */ + +/* + * Utility routine to tell whether a sequence of bytes is legal UTF-8. + * This must be called with the length pre-determined by the first byte. + * If not calling this from ConvertUTF8to*, then the length can be set by: + * length = trailingBytesForUTF8[*source]+1; + * and the sequence is illegal right away if there aren't that many bytes + * available. + * If presented with a length > 4, this returns false. The Unicode + * definition of UTF-8 goes up to 4-byte sequences. + */ + +static Boolean isLegalUTF8(const UTF8 *source, int length) { + UTF8 a; + const UTF8 *srcptr = source+length; + switch (length) { + default: return false; + /* Everything else falls through when "true"... */ + case 4: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; + case 3: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; + case 2: if ((a = (*--srcptr)) > 0xBF) return false; + + switch (*source) { + /* no fall-through in this inner switch */ + case 0xE0: if (a < 0xA0) return false; break; + case 0xED: if (a > 0x9F) return false; break; + case 0xF0: if (a < 0x90) return false; break; + case 0xF4: if (a > 0x8F) return false; break; + default: if (a < 0x80) return false; + } + + case 1: if (*source >= 0x80 && *source < 0xC2) return false; + } + if (*source > 0xF4) return false; + return true; +} + +/* --------------------------------------------------------------------- */ + +/* + * Exported function to return whether a UTF-8 sequence is legal or not. + * This is not used here; it's just exported. + */ +Boolean isLegalUTF8Sequence(const UTF8 *source, const UTF8 *sourceEnd) { + int length = trailingBytesForUTF8[*source]+1; + if (source+length > sourceEnd) { + return false; + } + return isLegalUTF8(source, length); +} + +/* --------------------------------------------------------------------- */ + +ConversionResult ConvertUTF8toUTF16 ( + const UTF8** sourceStart, const UTF8* sourceEnd, + UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF8* source = *sourceStart; + UTF16* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch = 0; + unsigned short extraBytesToRead = trailingBytesForUTF8[*source]; + if (source + extraBytesToRead >= sourceEnd) { + result = sourceExhausted; break; + } + /* Do this check whether lenient or strict */ + if (! isLegalUTF8(source, extraBytesToRead+1)) { + result = sourceIllegal; + break; + } + /* + * The cases all fall through. See "Note A" below. + */ + switch (extraBytesToRead) { + case 5: ch += *source++; ch <<= 6; /* remember, illegal UTF-8 */ + case 4: ch += *source++; ch <<= 6; /* remember, illegal UTF-8 */ + case 3: ch += *source++; ch <<= 6; + case 2: ch += *source++; ch <<= 6; + case 1: ch += *source++; ch <<= 6; + case 0: ch += *source++; + } + ch -= offsetsFromUTF8[extraBytesToRead]; + + if (target >= targetEnd) { + source -= (extraBytesToRead+1); /* Back up source pointer! */ + result = targetExhausted; break; + } + if (ch <= UNI_MAX_BMP) { /* Target is a character <= 0xFFFF */ + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + if (flags == strictConversion) { + source -= (extraBytesToRead+1); /* return to the illegal value itself */ + result = sourceIllegal; + break; + } else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } else { + *target++ = (UTF16)ch; /* normal case */ + } + } else if (ch > UNI_MAX_UTF16) { + if (flags == strictConversion) { + result = sourceIllegal; + source -= (extraBytesToRead+1); /* return to the start */ + break; /* Bail out; shouldn't continue */ + } else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } else { + /* target is a character in range 0xFFFF - 0x10FFFF. */ + if (target + 1 >= targetEnd) { + source -= (extraBytesToRead+1); /* Back up source pointer! */ + result = targetExhausted; break; + } + ch -= halfBase; + *target++ = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START); + *target++ = (UTF16)((ch & halfMask) + UNI_SUR_LOW_START); + } + } + *sourceStart = source; + *targetStart = target; + return result; +} + +/* --------------------------------------------------------------------- */ + +ConversionResult ConvertUTF32toUTF8 ( + const UTF32** sourceStart, const UTF32* sourceEnd, + UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF32* source = *sourceStart; + UTF8* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch; + unsigned short bytesToWrite = 0; + const UTF32 byteMask = 0xBF; + const UTF32 byteMark = 0x80; + ch = *source++; + if (flags == strictConversion ) { + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } + /* + * Figure out how many bytes the result will require. Turn any + * illegally large UTF32 things (> Plane 17) into replacement chars. + */ + if (ch < (UTF32)0x80) { bytesToWrite = 1; + } else if (ch < (UTF32)0x800) { bytesToWrite = 2; + } else if (ch < (UTF32)0x10000) { bytesToWrite = 3; + } else if (ch <= UNI_MAX_LEGAL_UTF32) { bytesToWrite = 4; + } else { bytesToWrite = 3; + ch = UNI_REPLACEMENT_CHAR; + result = sourceIllegal; + } + + target += bytesToWrite; + if (target > targetEnd) { + --source; /* Back up source pointer! */ + target -= bytesToWrite; result = targetExhausted; break; + } + switch (bytesToWrite) { /* note: everything falls through. */ + case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 1: *--target = (UTF8) (ch | firstByteMark[bytesToWrite]); + } + target += bytesToWrite; + } + *sourceStart = source; + *targetStart = target; + return result; +} + +/* --------------------------------------------------------------------- */ + +ConversionResult ConvertUTF8toUTF32 ( + const UTF8** sourceStart, const UTF8* sourceEnd, + UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF8* source = *sourceStart; + UTF32* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch = 0; + unsigned short extraBytesToRead = trailingBytesForUTF8[*source]; + if (source + extraBytesToRead >= sourceEnd) { + result = sourceExhausted; break; + } + /* Do this check whether lenient or strict */ + if (! isLegalUTF8(source, extraBytesToRead+1)) { + result = sourceIllegal; + break; + } + /* + * The cases all fall through. See "Note A" below. + */ + switch (extraBytesToRead) { + case 5: ch += *source++; ch <<= 6; + case 4: ch += *source++; ch <<= 6; + case 3: ch += *source++; ch <<= 6; + case 2: ch += *source++; ch <<= 6; + case 1: ch += *source++; ch <<= 6; + case 0: ch += *source++; + } + ch -= offsetsFromUTF8[extraBytesToRead]; + + if (target >= targetEnd) { + source -= (extraBytesToRead+1); /* Back up the source pointer! */ + result = targetExhausted; break; + } + if (ch <= UNI_MAX_LEGAL_UTF32) { + /* + * UTF-16 surrogate values are illegal in UTF-32, and anything + * over Plane 17 (> 0x10FFFF) is illegal. + */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + if (flags == strictConversion) { + source -= (extraBytesToRead+1); /* return to the illegal value itself */ + result = sourceIllegal; + break; + } else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } else { + *target++ = ch; + } + } else { /* i.e., ch > UNI_MAX_LEGAL_UTF32 */ + result = sourceIllegal; + *target++ = UNI_REPLACEMENT_CHAR; + } + } + *sourceStart = source; + *targetStart = target; + return result; +} + +/* --------------------------------------------------------------------- + + Note A. + The fall-through switches in UTF-8 reading code save a + temp variable, some decrements & conditionals. The switches + are equivalent to the following loop: + { + int tmpBytesToRead = extraBytesToRead+1; + do { + ch += *source++; + --tmpBytesToRead; + if (tmpBytesToRead) ch <<= 6; + } while (tmpBytesToRead > 0); + } + In UTF-8 writing code, the switches on "bytesToWrite" are + similarly unrolled loops. + + --------------------------------------------------------------------- */ diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..4c3496a --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,160 @@ +AUTOMAKE_OPTIONS = subdir-objects + +lib_LTLIBRARIES = libtorrent-rasterbar.la + +if ENABLE_DHT +KADEMLIA_SOURCES = \ + kademlia/dht_storage.cpp \ + kademlia/dht_tracker.cpp \ + kademlia/find_data.cpp \ + kademlia/put_data.cpp \ + kademlia/msg.cpp \ + kademlia/node.cpp \ + kademlia/node_entry.cpp \ + kademlia/node_id.cpp \ + kademlia/refresh.cpp \ + kademlia/routing_table.cpp \ + kademlia/rpc_manager.cpp \ + kademlia/traversal_algorithm.cpp \ + kademlia/dos_blocker.cpp \ + kademlia/get_peers.cpp \ + kademlia/get_item.cpp \ + kademlia/item.cpp \ + ../ed25519/src/add_scalar.cpp \ + ../ed25519/src/fe.cpp \ + ../ed25519/src/ge.cpp \ + ../ed25519/src/key_exchange.cpp \ + ../ed25519/src/keypair.cpp \ + ../ed25519/src/sc.cpp \ + ../ed25519/src/seed.cpp \ + ../ed25519/src/sha512.cpp \ + ../ed25519/src/sign.cpp \ + ../ed25519/src/verify.cpp +endif + +libtorrent_rasterbar_la_SOURCES = \ + web_connection_base.cpp \ + alert.cpp \ + alert_manager.cpp \ + allocator.cpp \ + announce_entry.cpp \ + assert.cpp \ + bandwidth_limit.cpp \ + bandwidth_manager.cpp \ + bandwidth_queue_entry.cpp \ + bdecode.cpp \ + bitfield.cpp \ + bloom_filter.cpp \ + broadcast_socket.cpp \ + block_cache.cpp \ + bt_peer_connection.cpp \ + chained_buffer.cpp \ + choker.cpp \ + close_reason.cpp \ + ConvertUTF.cpp \ + cpuid.cpp \ + crc32c.cpp \ + create_torrent.cpp \ + disk_buffer_holder.cpp \ + disk_buffer_pool.cpp \ + disk_io_job.cpp \ + disk_io_thread.cpp \ + disk_job_pool.cpp \ + entry.cpp \ + enum_net.cpp \ + error_code.cpp \ + escape_string.cpp \ + file.cpp \ + file_pool.cpp \ + file_storage.cpp \ + gzip.cpp \ + hasher.cpp \ + hex.cpp \ + http_connection.cpp \ + http_parser.cpp \ + http_seed_connection.cpp \ + http_stream.cpp \ + http_tracker_connection.cpp \ + i2p_stream.cpp \ + identify_client.cpp \ + instantiate_connection.cpp \ + ip_filter.cpp \ + ip_voter.cpp \ + lazy_bdecode.cpp \ + lsd.cpp \ + lt_trackers.cpp \ + magnet_uri.cpp \ + merkle.cpp \ + metadata_transfer.cpp \ + mpi.cpp \ + natpmp.cpp \ + parse_url.cpp \ + part_file.cpp \ + pe_crypto.cpp \ + performance_counters.cpp \ + peer_connection.cpp \ + peer_connection_handle.cpp \ + peer_class.cpp \ + peer_class_set.cpp \ + piece_picker.cpp \ + platform_util.cpp \ + packet_buffer.cpp \ + proxy_base.cpp \ + peer_list.cpp \ + puff.cpp \ + random.cpp \ + receive_buffer.cpp \ + request_blocks.cpp \ + resolve_links.cpp \ + resolver.cpp \ + rss.cpp \ + session.cpp \ + session_call.cpp \ + session_handle.cpp \ + session_impl.cpp \ + session_settings.cpp \ + proxy_settings.cpp \ + settings_pack.cpp \ + sha1.cpp \ + smart_ban.cpp \ + socket_io.cpp \ + socket_type.cpp \ + socks5_stream.cpp \ + stat.cpp \ + stat_cache.cpp \ + storage.cpp \ + session_stats.cpp \ + string_util.cpp \ + thread.cpp \ + torrent.cpp \ + torrent_handle.cpp \ + torrent_info.cpp \ + torrent_peer.cpp \ + torrent_peer_allocator.cpp \ + torrent_status.cpp \ + time.cpp \ + timestamp_history.cpp \ + tracker_manager.cpp \ + udp_socket.cpp \ + udp_tracker_connection.cpp \ + upnp.cpp \ + ut_metadata.cpp \ + ut_pex.cpp \ + utf8.cpp \ + utp_socket_manager.cpp \ + utp_stream.cpp \ + web_peer_connection.cpp \ + xml_parse.cpp \ + version.cpp \ + file_progress.cpp \ + \ + $(KADEMLIA_SOURCES) + +libtorrent_rasterbar_la_LDFLAGS = -version-info $(INTERFACE_VERSION_INFO) +libtorrent_rasterbar_la_LIBADD = @BOOST_SYSTEM_LIB@ @OPENSSL_LIBS@ + +AM_CPPFLAGS = -DTORRENT_BUILDING_LIBRARY -I$(top_srcdir)/include -I$(top_srcdir)/ed25519/src @DEBUGFLAGS@ @OPENSSL_INCLUDES@ +AM_CFLAGS = -I$(top_srcdir)/ed25519/src -std=c99 + +AM_LDFLAGS = @OPENSSL_LDFLAGS@ + diff --git a/src/Makefile.in b/src/Makefile.in new file mode 100644 index 0000000..7f44b12 --- /dev/null +++ b/src/Makefile.in @@ -0,0 +1,1160 @@ +# Makefile.in generated by automake 1.15 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2014 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +target_triplet = @target@ +subdir = src +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_boost_base.m4 \ + $(top_srcdir)/m4/ax_boost_chrono.m4 \ + $(top_srcdir)/m4/ax_boost_python.m4 \ + $(top_srcdir)/m4/ax_boost_random.m4 \ + $(top_srcdir)/m4/ax_boost_system.m4 \ + $(top_srcdir)/m4/ax_check_openssl.m4 \ + $(top_srcdir)/m4/ax_pthread.m4 \ + $(top_srcdir)/m4/ax_python_devel.m4 \ + $(top_srcdir)/m4/gettext-lib.m4 $(top_srcdir)/m4/iconv.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/pkgconfig.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(libdir)" +LTLIBRARIES = $(lib_LTLIBRARIES) +libtorrent_rasterbar_la_DEPENDENCIES = +am__libtorrent_rasterbar_la_SOURCES_DIST = web_connection_base.cpp \ + alert.cpp alert_manager.cpp allocator.cpp announce_entry.cpp \ + assert.cpp bandwidth_limit.cpp bandwidth_manager.cpp \ + bandwidth_queue_entry.cpp bdecode.cpp bitfield.cpp \ + bloom_filter.cpp broadcast_socket.cpp block_cache.cpp \ + bt_peer_connection.cpp chained_buffer.cpp choker.cpp \ + close_reason.cpp ConvertUTF.cpp cpuid.cpp crc32c.cpp \ + create_torrent.cpp disk_buffer_holder.cpp disk_buffer_pool.cpp \ + disk_io_job.cpp disk_io_thread.cpp disk_job_pool.cpp entry.cpp \ + enum_net.cpp error_code.cpp escape_string.cpp file.cpp \ + file_pool.cpp file_storage.cpp gzip.cpp hasher.cpp hex.cpp \ + http_connection.cpp http_parser.cpp http_seed_connection.cpp \ + http_stream.cpp http_tracker_connection.cpp i2p_stream.cpp \ + identify_client.cpp instantiate_connection.cpp ip_filter.cpp \ + ip_voter.cpp lazy_bdecode.cpp lsd.cpp lt_trackers.cpp \ + magnet_uri.cpp merkle.cpp metadata_transfer.cpp mpi.cpp \ + natpmp.cpp parse_url.cpp part_file.cpp pe_crypto.cpp \ + performance_counters.cpp peer_connection.cpp \ + peer_connection_handle.cpp peer_class.cpp peer_class_set.cpp \ + piece_picker.cpp platform_util.cpp packet_buffer.cpp \ + proxy_base.cpp peer_list.cpp puff.cpp random.cpp \ + receive_buffer.cpp request_blocks.cpp resolve_links.cpp \ + resolver.cpp rss.cpp session.cpp session_call.cpp \ + session_handle.cpp session_impl.cpp session_settings.cpp \ + proxy_settings.cpp settings_pack.cpp sha1.cpp smart_ban.cpp \ + socket_io.cpp socket_type.cpp socks5_stream.cpp stat.cpp \ + stat_cache.cpp storage.cpp session_stats.cpp string_util.cpp \ + thread.cpp torrent.cpp torrent_handle.cpp torrent_info.cpp \ + torrent_peer.cpp torrent_peer_allocator.cpp torrent_status.cpp \ + time.cpp timestamp_history.cpp tracker_manager.cpp \ + udp_socket.cpp udp_tracker_connection.cpp upnp.cpp \ + ut_metadata.cpp ut_pex.cpp utf8.cpp utp_socket_manager.cpp \ + utp_stream.cpp web_peer_connection.cpp xml_parse.cpp \ + version.cpp file_progress.cpp kademlia/dht_storage.cpp \ + kademlia/dht_tracker.cpp kademlia/find_data.cpp \ + kademlia/put_data.cpp kademlia/msg.cpp kademlia/node.cpp \ + kademlia/node_entry.cpp kademlia/node_id.cpp \ + kademlia/refresh.cpp kademlia/routing_table.cpp \ + kademlia/rpc_manager.cpp kademlia/traversal_algorithm.cpp \ + kademlia/dos_blocker.cpp kademlia/get_peers.cpp \ + kademlia/get_item.cpp kademlia/item.cpp \ + ../ed25519/src/add_scalar.cpp ../ed25519/src/fe.cpp \ + ../ed25519/src/ge.cpp ../ed25519/src/key_exchange.cpp \ + ../ed25519/src/keypair.cpp ../ed25519/src/sc.cpp \ + ../ed25519/src/seed.cpp ../ed25519/src/sha512.cpp \ + ../ed25519/src/sign.cpp ../ed25519/src/verify.cpp +am__dirstamp = $(am__leading_dot)dirstamp +@ENABLE_DHT_TRUE@am__objects_1 = kademlia/dht_storage.lo \ +@ENABLE_DHT_TRUE@ kademlia/dht_tracker.lo kademlia/find_data.lo \ +@ENABLE_DHT_TRUE@ kademlia/put_data.lo kademlia/msg.lo \ +@ENABLE_DHT_TRUE@ kademlia/node.lo kademlia/node_entry.lo \ +@ENABLE_DHT_TRUE@ kademlia/node_id.lo kademlia/refresh.lo \ +@ENABLE_DHT_TRUE@ kademlia/routing_table.lo \ +@ENABLE_DHT_TRUE@ kademlia/rpc_manager.lo \ +@ENABLE_DHT_TRUE@ kademlia/traversal_algorithm.lo \ +@ENABLE_DHT_TRUE@ kademlia/dos_blocker.lo kademlia/get_peers.lo \ +@ENABLE_DHT_TRUE@ kademlia/get_item.lo kademlia/item.lo \ +@ENABLE_DHT_TRUE@ ../ed25519/src/add_scalar.lo \ +@ENABLE_DHT_TRUE@ ../ed25519/src/fe.lo ../ed25519/src/ge.lo \ +@ENABLE_DHT_TRUE@ ../ed25519/src/key_exchange.lo \ +@ENABLE_DHT_TRUE@ ../ed25519/src/keypair.lo \ +@ENABLE_DHT_TRUE@ ../ed25519/src/sc.lo ../ed25519/src/seed.lo \ +@ENABLE_DHT_TRUE@ ../ed25519/src/sha512.lo \ +@ENABLE_DHT_TRUE@ ../ed25519/src/sign.lo \ +@ENABLE_DHT_TRUE@ ../ed25519/src/verify.lo +am_libtorrent_rasterbar_la_OBJECTS = web_connection_base.lo alert.lo \ + alert_manager.lo allocator.lo announce_entry.lo assert.lo \ + bandwidth_limit.lo bandwidth_manager.lo \ + bandwidth_queue_entry.lo bdecode.lo bitfield.lo \ + bloom_filter.lo broadcast_socket.lo block_cache.lo \ + bt_peer_connection.lo chained_buffer.lo choker.lo \ + close_reason.lo ConvertUTF.lo cpuid.lo crc32c.lo \ + create_torrent.lo disk_buffer_holder.lo disk_buffer_pool.lo \ + disk_io_job.lo disk_io_thread.lo disk_job_pool.lo entry.lo \ + enum_net.lo error_code.lo escape_string.lo file.lo \ + file_pool.lo file_storage.lo gzip.lo hasher.lo hex.lo \ + http_connection.lo http_parser.lo http_seed_connection.lo \ + http_stream.lo http_tracker_connection.lo i2p_stream.lo \ + identify_client.lo instantiate_connection.lo ip_filter.lo \ + ip_voter.lo lazy_bdecode.lo lsd.lo lt_trackers.lo \ + magnet_uri.lo merkle.lo metadata_transfer.lo mpi.lo natpmp.lo \ + parse_url.lo part_file.lo pe_crypto.lo performance_counters.lo \ + peer_connection.lo peer_connection_handle.lo peer_class.lo \ + peer_class_set.lo piece_picker.lo platform_util.lo \ + packet_buffer.lo proxy_base.lo peer_list.lo puff.lo random.lo \ + receive_buffer.lo request_blocks.lo resolve_links.lo \ + resolver.lo rss.lo session.lo session_call.lo \ + session_handle.lo session_impl.lo session_settings.lo \ + proxy_settings.lo settings_pack.lo sha1.lo smart_ban.lo \ + socket_io.lo socket_type.lo socks5_stream.lo stat.lo \ + stat_cache.lo storage.lo session_stats.lo string_util.lo \ + thread.lo torrent.lo torrent_handle.lo torrent_info.lo \ + torrent_peer.lo torrent_peer_allocator.lo torrent_status.lo \ + time.lo timestamp_history.lo tracker_manager.lo udp_socket.lo \ + udp_tracker_connection.lo upnp.lo ut_metadata.lo ut_pex.lo \ + utf8.lo utp_socket_manager.lo utp_stream.lo \ + web_peer_connection.lo xml_parse.lo version.lo \ + file_progress.lo $(am__objects_1) +libtorrent_rasterbar_la_OBJECTS = \ + $(am_libtorrent_rasterbar_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +libtorrent_rasterbar_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \ + $(AM_CXXFLAGS) $(CXXFLAGS) $(libtorrent_rasterbar_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ +depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) +LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CXXFLAGS) $(CXXFLAGS) +AM_V_CXX = $(am__v_CXX_@AM_V@) +am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@) +am__v_CXX_0 = @echo " CXX " $@; +am__v_CXX_1 = +CXXLD = $(CXX) +CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \ + $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CXXLD = $(am__v_CXXLD_@AM_V@) +am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@) +am__v_CXXLD_0 = @echo " CXXLD " $@; +am__v_CXXLD_1 = +SOURCES = $(libtorrent_rasterbar_la_SOURCES) +DIST_SOURCES = $(am__libtorrent_rasterbar_la_SOURCES_DIST) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in \ + $(top_srcdir)/build-aux/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BOOST_CHRONO_LIB = @BOOST_CHRONO_LIB@ +BOOST_CPPFLAGS = @BOOST_CPPFLAGS@ +BOOST_LDFLAGS = @BOOST_LDFLAGS@ +BOOST_PYTHON_LIB = @BOOST_PYTHON_LIB@ +BOOST_RANDOM_LIB = @BOOST_RANDOM_LIB@ +BOOST_SYSTEM_LIB = @BOOST_SYSTEM_LIB@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +COMPILETIME_OPTIONS = @COMPILETIME_OPTIONS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEBUGFLAGS = @DEBUGFLAGS@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GREP = @GREP@ +ICONV_LIBS = @ICONV_LIBS@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INTERFACE_VERSION_INFO = @INTERFACE_VERSION_INFO@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBICONV = @LIBICONV@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OPENSSL_INCLUDES = @OPENSSL_INCLUDES@ +OPENSSL_LDFLAGS = @OPENSSL_LDFLAGS@ +OPENSSL_LIBS = @OPENSSL_LIBS@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PTHREAD_CC = @PTHREAD_CC@ +PTHREAD_CFLAGS = @PTHREAD_CFLAGS@ +PTHREAD_LIBS = @PTHREAD_LIBS@ +PYTHON = @PYTHON@ +PYTHON_CPPFLAGS = @PYTHON_CPPFLAGS@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_EXTRA_LDFLAGS = @PYTHON_EXTRA_LDFLAGS@ +PYTHON_EXTRA_LIBS = @PYTHON_EXTRA_LIBS@ +PYTHON_INSTALL_PARAMS = @PYTHON_INSTALL_PARAMS@ +PYTHON_LIBS = @PYTHON_LIBS@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_SITE_PKG = @PYTHON_SITE_PKG@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +ax_pthread_config = @ax_pthread_config@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target = @target@ +target_alias = @target_alias@ +target_cpu = @target_cpu@ +target_os = @target_os@ +target_vendor = @target_vendor@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AUTOMAKE_OPTIONS = subdir-objects +lib_LTLIBRARIES = libtorrent-rasterbar.la +@ENABLE_DHT_TRUE@KADEMLIA_SOURCES = \ +@ENABLE_DHT_TRUE@ kademlia/dht_storage.cpp \ +@ENABLE_DHT_TRUE@ kademlia/dht_tracker.cpp \ +@ENABLE_DHT_TRUE@ kademlia/find_data.cpp \ +@ENABLE_DHT_TRUE@ kademlia/put_data.cpp \ +@ENABLE_DHT_TRUE@ kademlia/msg.cpp \ +@ENABLE_DHT_TRUE@ kademlia/node.cpp \ +@ENABLE_DHT_TRUE@ kademlia/node_entry.cpp \ +@ENABLE_DHT_TRUE@ kademlia/node_id.cpp \ +@ENABLE_DHT_TRUE@ kademlia/refresh.cpp \ +@ENABLE_DHT_TRUE@ kademlia/routing_table.cpp \ +@ENABLE_DHT_TRUE@ kademlia/rpc_manager.cpp \ +@ENABLE_DHT_TRUE@ kademlia/traversal_algorithm.cpp \ +@ENABLE_DHT_TRUE@ kademlia/dos_blocker.cpp \ +@ENABLE_DHT_TRUE@ kademlia/get_peers.cpp \ +@ENABLE_DHT_TRUE@ kademlia/get_item.cpp \ +@ENABLE_DHT_TRUE@ kademlia/item.cpp \ +@ENABLE_DHT_TRUE@ ../ed25519/src/add_scalar.cpp \ +@ENABLE_DHT_TRUE@ ../ed25519/src/fe.cpp \ +@ENABLE_DHT_TRUE@ ../ed25519/src/ge.cpp \ +@ENABLE_DHT_TRUE@ ../ed25519/src/key_exchange.cpp \ +@ENABLE_DHT_TRUE@ ../ed25519/src/keypair.cpp \ +@ENABLE_DHT_TRUE@ ../ed25519/src/sc.cpp \ +@ENABLE_DHT_TRUE@ ../ed25519/src/seed.cpp \ +@ENABLE_DHT_TRUE@ ../ed25519/src/sha512.cpp \ +@ENABLE_DHT_TRUE@ ../ed25519/src/sign.cpp \ +@ENABLE_DHT_TRUE@ ../ed25519/src/verify.cpp + +libtorrent_rasterbar_la_SOURCES = \ + web_connection_base.cpp \ + alert.cpp \ + alert_manager.cpp \ + allocator.cpp \ + announce_entry.cpp \ + assert.cpp \ + bandwidth_limit.cpp \ + bandwidth_manager.cpp \ + bandwidth_queue_entry.cpp \ + bdecode.cpp \ + bitfield.cpp \ + bloom_filter.cpp \ + broadcast_socket.cpp \ + block_cache.cpp \ + bt_peer_connection.cpp \ + chained_buffer.cpp \ + choker.cpp \ + close_reason.cpp \ + ConvertUTF.cpp \ + cpuid.cpp \ + crc32c.cpp \ + create_torrent.cpp \ + disk_buffer_holder.cpp \ + disk_buffer_pool.cpp \ + disk_io_job.cpp \ + disk_io_thread.cpp \ + disk_job_pool.cpp \ + entry.cpp \ + enum_net.cpp \ + error_code.cpp \ + escape_string.cpp \ + file.cpp \ + file_pool.cpp \ + file_storage.cpp \ + gzip.cpp \ + hasher.cpp \ + hex.cpp \ + http_connection.cpp \ + http_parser.cpp \ + http_seed_connection.cpp \ + http_stream.cpp \ + http_tracker_connection.cpp \ + i2p_stream.cpp \ + identify_client.cpp \ + instantiate_connection.cpp \ + ip_filter.cpp \ + ip_voter.cpp \ + lazy_bdecode.cpp \ + lsd.cpp \ + lt_trackers.cpp \ + magnet_uri.cpp \ + merkle.cpp \ + metadata_transfer.cpp \ + mpi.cpp \ + natpmp.cpp \ + parse_url.cpp \ + part_file.cpp \ + pe_crypto.cpp \ + performance_counters.cpp \ + peer_connection.cpp \ + peer_connection_handle.cpp \ + peer_class.cpp \ + peer_class_set.cpp \ + piece_picker.cpp \ + platform_util.cpp \ + packet_buffer.cpp \ + proxy_base.cpp \ + peer_list.cpp \ + puff.cpp \ + random.cpp \ + receive_buffer.cpp \ + request_blocks.cpp \ + resolve_links.cpp \ + resolver.cpp \ + rss.cpp \ + session.cpp \ + session_call.cpp \ + session_handle.cpp \ + session_impl.cpp \ + session_settings.cpp \ + proxy_settings.cpp \ + settings_pack.cpp \ + sha1.cpp \ + smart_ban.cpp \ + socket_io.cpp \ + socket_type.cpp \ + socks5_stream.cpp \ + stat.cpp \ + stat_cache.cpp \ + storage.cpp \ + session_stats.cpp \ + string_util.cpp \ + thread.cpp \ + torrent.cpp \ + torrent_handle.cpp \ + torrent_info.cpp \ + torrent_peer.cpp \ + torrent_peer_allocator.cpp \ + torrent_status.cpp \ + time.cpp \ + timestamp_history.cpp \ + tracker_manager.cpp \ + udp_socket.cpp \ + udp_tracker_connection.cpp \ + upnp.cpp \ + ut_metadata.cpp \ + ut_pex.cpp \ + utf8.cpp \ + utp_socket_manager.cpp \ + utp_stream.cpp \ + web_peer_connection.cpp \ + xml_parse.cpp \ + version.cpp \ + file_progress.cpp \ + \ + $(KADEMLIA_SOURCES) + +libtorrent_rasterbar_la_LDFLAGS = -version-info $(INTERFACE_VERSION_INFO) +libtorrent_rasterbar_la_LIBADD = @BOOST_SYSTEM_LIB@ @OPENSSL_LIBS@ +AM_CPPFLAGS = -DTORRENT_BUILDING_LIBRARY -I$(top_srcdir)/include -I$(top_srcdir)/ed25519/src @DEBUGFLAGS@ @OPENSSL_INCLUDES@ +AM_CFLAGS = -I$(top_srcdir)/ed25519/src -std=c99 +AM_LDFLAGS = @OPENSSL_LDFLAGS@ +all: all-am + +.SUFFIXES: +.SUFFIXES: .cpp .lo .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +install-libLTLIBRARIES: $(lib_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(libdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(libdir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(libdir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(libdir)"; \ + } + +uninstall-libLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$f"; \ + done + +clean-libLTLIBRARIES: + -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES) + @list='$(lib_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } +kademlia/$(am__dirstamp): + @$(MKDIR_P) kademlia + @: > kademlia/$(am__dirstamp) +kademlia/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) kademlia/$(DEPDIR) + @: > kademlia/$(DEPDIR)/$(am__dirstamp) +kademlia/dht_storage.lo: kademlia/$(am__dirstamp) \ + kademlia/$(DEPDIR)/$(am__dirstamp) +kademlia/dht_tracker.lo: kademlia/$(am__dirstamp) \ + kademlia/$(DEPDIR)/$(am__dirstamp) +kademlia/find_data.lo: kademlia/$(am__dirstamp) \ + kademlia/$(DEPDIR)/$(am__dirstamp) +kademlia/put_data.lo: kademlia/$(am__dirstamp) \ + kademlia/$(DEPDIR)/$(am__dirstamp) +kademlia/msg.lo: kademlia/$(am__dirstamp) \ + kademlia/$(DEPDIR)/$(am__dirstamp) +kademlia/node.lo: kademlia/$(am__dirstamp) \ + kademlia/$(DEPDIR)/$(am__dirstamp) +kademlia/node_entry.lo: kademlia/$(am__dirstamp) \ + kademlia/$(DEPDIR)/$(am__dirstamp) +kademlia/node_id.lo: kademlia/$(am__dirstamp) \ + kademlia/$(DEPDIR)/$(am__dirstamp) +kademlia/refresh.lo: kademlia/$(am__dirstamp) \ + kademlia/$(DEPDIR)/$(am__dirstamp) +kademlia/routing_table.lo: kademlia/$(am__dirstamp) \ + kademlia/$(DEPDIR)/$(am__dirstamp) +kademlia/rpc_manager.lo: kademlia/$(am__dirstamp) \ + kademlia/$(DEPDIR)/$(am__dirstamp) +kademlia/traversal_algorithm.lo: kademlia/$(am__dirstamp) \ + kademlia/$(DEPDIR)/$(am__dirstamp) +kademlia/dos_blocker.lo: kademlia/$(am__dirstamp) \ + kademlia/$(DEPDIR)/$(am__dirstamp) +kademlia/get_peers.lo: kademlia/$(am__dirstamp) \ + kademlia/$(DEPDIR)/$(am__dirstamp) +kademlia/get_item.lo: kademlia/$(am__dirstamp) \ + kademlia/$(DEPDIR)/$(am__dirstamp) +kademlia/item.lo: kademlia/$(am__dirstamp) \ + kademlia/$(DEPDIR)/$(am__dirstamp) +../ed25519/src/$(am__dirstamp): + @$(MKDIR_P) ../ed25519/src + @: > ../ed25519/src/$(am__dirstamp) +../ed25519/src/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) ../ed25519/src/$(DEPDIR) + @: > ../ed25519/src/$(DEPDIR)/$(am__dirstamp) +../ed25519/src/add_scalar.lo: ../ed25519/src/$(am__dirstamp) \ + ../ed25519/src/$(DEPDIR)/$(am__dirstamp) +../ed25519/src/fe.lo: ../ed25519/src/$(am__dirstamp) \ + ../ed25519/src/$(DEPDIR)/$(am__dirstamp) +../ed25519/src/ge.lo: ../ed25519/src/$(am__dirstamp) \ + ../ed25519/src/$(DEPDIR)/$(am__dirstamp) +../ed25519/src/key_exchange.lo: ../ed25519/src/$(am__dirstamp) \ + ../ed25519/src/$(DEPDIR)/$(am__dirstamp) +../ed25519/src/keypair.lo: ../ed25519/src/$(am__dirstamp) \ + ../ed25519/src/$(DEPDIR)/$(am__dirstamp) +../ed25519/src/sc.lo: ../ed25519/src/$(am__dirstamp) \ + ../ed25519/src/$(DEPDIR)/$(am__dirstamp) +../ed25519/src/seed.lo: ../ed25519/src/$(am__dirstamp) \ + ../ed25519/src/$(DEPDIR)/$(am__dirstamp) +../ed25519/src/sha512.lo: ../ed25519/src/$(am__dirstamp) \ + ../ed25519/src/$(DEPDIR)/$(am__dirstamp) +../ed25519/src/sign.lo: ../ed25519/src/$(am__dirstamp) \ + ../ed25519/src/$(DEPDIR)/$(am__dirstamp) +../ed25519/src/verify.lo: ../ed25519/src/$(am__dirstamp) \ + ../ed25519/src/$(DEPDIR)/$(am__dirstamp) + +libtorrent-rasterbar.la: $(libtorrent_rasterbar_la_OBJECTS) $(libtorrent_rasterbar_la_DEPENDENCIES) $(EXTRA_libtorrent_rasterbar_la_DEPENDENCIES) + $(AM_V_CXXLD)$(libtorrent_rasterbar_la_LINK) -rpath $(libdir) $(libtorrent_rasterbar_la_OBJECTS) $(libtorrent_rasterbar_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + -rm -f ../ed25519/src/*.$(OBJEXT) + -rm -f ../ed25519/src/*.lo + -rm -f kademlia/*.$(OBJEXT) + -rm -f kademlia/*.lo + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@../ed25519/src/$(DEPDIR)/add_scalar.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@../ed25519/src/$(DEPDIR)/fe.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@../ed25519/src/$(DEPDIR)/ge.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@../ed25519/src/$(DEPDIR)/key_exchange.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@../ed25519/src/$(DEPDIR)/keypair.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@../ed25519/src/$(DEPDIR)/sc.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@../ed25519/src/$(DEPDIR)/seed.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@../ed25519/src/$(DEPDIR)/sha512.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@../ed25519/src/$(DEPDIR)/sign.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@../ed25519/src/$(DEPDIR)/verify.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ConvertUTF.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/alert.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/alert_manager.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/allocator.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/announce_entry.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/assert.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bandwidth_limit.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bandwidth_manager.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bandwidth_queue_entry.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bdecode.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bitfield.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/block_cache.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bloom_filter.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/broadcast_socket.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bt_peer_connection.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/chained_buffer.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/choker.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/close_reason.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cpuid.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/crc32c.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/create_torrent.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/disk_buffer_holder.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/disk_buffer_pool.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/disk_io_job.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/disk_io_thread.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/disk_job_pool.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/entry.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/enum_net.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/error_code.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/escape_string.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file_pool.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file_progress.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file_storage.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gzip.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hasher.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hex.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/http_connection.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/http_parser.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/http_seed_connection.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/http_stream.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/http_tracker_connection.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/i2p_stream.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/identify_client.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/instantiate_connection.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ip_filter.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ip_voter.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lazy_bdecode.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lsd.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lt_trackers.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/magnet_uri.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/merkle.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/metadata_transfer.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mpi.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/natpmp.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/packet_buffer.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/parse_url.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/part_file.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pe_crypto.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/peer_class.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/peer_class_set.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/peer_connection.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/peer_connection_handle.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/peer_list.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/performance_counters.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/piece_picker.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/platform_util.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/proxy_base.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/proxy_settings.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/puff.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/random.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/receive_buffer.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/request_blocks.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/resolve_links.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/resolver.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rss.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/session.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/session_call.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/session_handle.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/session_impl.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/session_settings.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/session_stats.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/settings_pack.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sha1.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/smart_ban.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/socket_io.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/socket_type.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/socks5_stream.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stat.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stat_cache.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/storage.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/string_util.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/thread.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/time.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/timestamp_history.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/torrent.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/torrent_handle.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/torrent_info.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/torrent_peer.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/torrent_peer_allocator.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/torrent_status.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tracker_manager.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/udp_socket.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/udp_tracker_connection.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/upnp.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ut_metadata.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ut_pex.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/utf8.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/utp_socket_manager.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/utp_stream.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/version.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/web_connection_base.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/web_peer_connection.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/xml_parse.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@kademlia/$(DEPDIR)/dht_storage.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@kademlia/$(DEPDIR)/dht_tracker.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@kademlia/$(DEPDIR)/dos_blocker.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@kademlia/$(DEPDIR)/find_data.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@kademlia/$(DEPDIR)/get_item.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@kademlia/$(DEPDIR)/get_peers.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@kademlia/$(DEPDIR)/item.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@kademlia/$(DEPDIR)/msg.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@kademlia/$(DEPDIR)/node.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@kademlia/$(DEPDIR)/node_entry.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@kademlia/$(DEPDIR)/node_id.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@kademlia/$(DEPDIR)/put_data.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@kademlia/$(DEPDIR)/refresh.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@kademlia/$(DEPDIR)/routing_table.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@kademlia/$(DEPDIR)/rpc_manager.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@kademlia/$(DEPDIR)/traversal_algorithm.Plo@am__quote@ + +.cpp.o: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ +@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $< + +.cpp.obj: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\ +@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\ +@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.cpp.lo: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\ +@am__fastdepCXX_TRUE@ $(LTCXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + -rm -rf ../ed25519/src/.libs ../ed25519/src/_libs + -rm -rf kademlia/.libs kademlia/_libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) +installdirs: + for dir in "$(DESTDIR)$(libdir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + -rm -f ../ed25519/src/$(DEPDIR)/$(am__dirstamp) + -rm -f ../ed25519/src/$(am__dirstamp) + -rm -f kademlia/$(DEPDIR)/$(am__dirstamp) + -rm -f kademlia/$(am__dirstamp) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \ + mostlyclean-am + +distclean: distclean-am + -rm -rf ../ed25519/src/$(DEPDIR) ./$(DEPDIR) kademlia/$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-libLTLIBRARIES + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ../ed25519/src/$(DEPDIR) ./$(DEPDIR) kademlia/$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-libLTLIBRARIES + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am check check-am clean clean-generic \ + clean-libLTLIBRARIES clean-libtool cscopelist-am ctags \ + ctags-am distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-libLTLIBRARIES install-man install-pdf \ + install-pdf-am install-ps install-ps-am install-strip \ + installcheck installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am uninstall-libLTLIBRARIES + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/alert.cpp b/src/alert.cpp new file mode 100644 index 0000000..4652681 --- /dev/null +++ b/src/alert.cpp @@ -0,0 +1,2053 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg, Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/io_service.hpp" +#include "libtorrent/socket_io.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/extensions.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/aux_/time.hpp" +#include "libtorrent/performance_counters.hpp" +#include "libtorrent/stack_allocator.hpp" +#include "libtorrent/piece_picker.hpp" // for piece_block + +#include "libtorrent/aux_/escape_string.hpp" // for convert_from_native + +#include + +namespace libtorrent { + + alert::alert() : m_timestamp(clock_type::now()) {} + alert::~alert() {} + 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) + { + boost::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 + { + char msg[41]; + to_hex(t->info_hash().data(), 20, msg); + m_name_idx = alloc.copy_string(msg); + } + } + else + { + m_name_idx = alloc.copy_string(""); + } + +#ifndef TORRENT_NO_DEPRECATE + name = torrent_name(); +#endif + } + + char const* torrent_alert::torrent_name() const + { +#ifndef TORRENT_NO_DEPRECATE + return name.c_str(); +#else + return m_alloc.ptr(m_name_idx); +#endif + } + + std::string torrent_alert::message() const + { + if (!handle.is_valid()) return " - "; + return torrent_name(); + } + + peer_alert::peer_alert(aux::stack_allocator& alloc + , torrent_handle const& h + , tcp::endpoint const& i + , peer_id const& pi) + : torrent_alert(alloc, h) + , ip(i) + , pid(pi) + {} + + std::string peer_alert::message() const + { + error_code ec; + return torrent_alert::message() + " peer (" + print_endpoint(ip) + + ", " + identify_client(pid) + ")"; + } + + tracker_alert::tracker_alert(aux::stack_allocator& alloc + , torrent_handle const& h + , std::string const& u) + : torrent_alert(alloc, h) +#ifndef TORRENT_NO_DEPRECATE + , url(u) +#endif + , m_url_idx(alloc.copy_string(u)) + {} + + char const* tracker_alert::tracker_url() const + { +#ifndef TORRENT_NO_DEPRECATE + return url.c_str(); +#else + return m_alloc.ptr(m_url_idx); +#endif + } + + std::string tracker_alert::message() const + { + return torrent_alert::message() + " (" + tracker_url() + ")"; + } + + read_piece_alert::read_piece_alert(aux::stack_allocator& alloc + , torrent_handle const& h + , int p, boost::shared_array d, int s) + : torrent_alert(alloc, h) + , buffer(d) + , piece(p) + , size(s) + {} + + read_piece_alert::read_piece_alert(aux::stack_allocator& alloc + , torrent_handle h, int p, error_code e) + : torrent_alert(alloc, h) + , ec(e) + , piece(p) + , size(0) + {} + + std::string read_piece_alert::message() const + { + char msg[200]; + if (ec) + { + snprintf(msg, sizeof(msg), "%s: read_piece %u failed: %s" + , torrent_alert::message().c_str() , piece + , convert_from_native(ec.message()).c_str()); + } + else + { + snprintf(msg, sizeof(msg), "%s: read_piece %u successful" + , torrent_alert::message().c_str() , piece); + } + return msg; + } + + file_completed_alert::file_completed_alert(aux::stack_allocator& alloc + , torrent_handle const& h + , int idx) + : torrent_alert(alloc, h) + , index(idx) + {} + + std::string file_completed_alert::message() const + { + char msg[200 + TORRENT_MAX_PATH]; + snprintf(msg, sizeof(msg), "%s: file %d finished downloading" + , torrent_alert::message().c_str(), index); + return msg; + } + + file_renamed_alert::file_renamed_alert(aux::stack_allocator& alloc + , torrent_handle const& h + , std::string const& n + , int idx) + : torrent_alert(alloc, h) +#ifndef TORRENT_NO_DEPRECATE + , name(n) +#endif + , index(idx) + , m_name_idx(alloc.copy_string(n)) + {} + + char const* file_renamed_alert::new_name() const + { +#ifndef TORRENT_NO_DEPRECATE + return name.c_str(); +#else + return m_alloc.ptr(m_name_idx); +#endif + } + + std::string file_renamed_alert::message() const + { + char msg[200 + TORRENT_MAX_PATH * 2]; + snprintf(msg, sizeof(msg), "%s: file %d renamed to %s" + , torrent_alert::message().c_str(), index, new_name()); + return msg; + } + + file_rename_failed_alert::file_rename_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h + , int idx + , error_code ec) + : torrent_alert(alloc, h) + , index(idx) + , error(ec) + {} + + std::string file_rename_failed_alert::message() const + { + char ret[200 + TORRENT_MAX_PATH * 2]; + snprintf(ret, sizeof(ret), "%s: failed to rename file %d: %s" + , torrent_alert::message().c_str(), index, convert_from_native(error.message()).c_str()); + return ret; + } + + performance_alert::performance_alert(aux::stack_allocator& alloc + , torrent_handle const& h + , performance_warning_t w) + : torrent_alert(alloc, h) + , warning_code(w) + {} + + std::string performance_alert::message() const + { + static char 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", + "using bittyrant unchoker with no upload rate limit set", + "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" + }; + + return torrent_alert::message() + ": performance warning: " + + warning_str[warning_code]; + } + + state_changed_alert::state_changed_alert(aux::stack_allocator& alloc + , torrent_handle const& h + , torrent_status::state_t st + , torrent_status::state_t prev_st) + : torrent_alert(alloc, h) + , state(st) + , prev_state(prev_st) + {} + + std::string state_changed_alert::message() const + { + static char const* state_str[] = + {"checking (q)", "checking", "dl metadata" + , "downloading", "finished", "seeding", "allocating" + , "checking (r)"}; + + return torrent_alert::message() + ": state changed to: " + + state_str[state]; + } + + tracker_error_alert::tracker_error_alert(aux::stack_allocator& alloc + , torrent_handle const& h + , int times + , int status + , std::string const& u + , error_code const& e + , std::string const& m) + : tracker_alert(alloc, h, u) + , times_in_row(times) + , status_code(status) + , error(e) +#ifndef TORRENT_NO_DEPRECATE + , msg(m) +#endif + , m_msg_idx(alloc.copy_string(m)) + { + TORRENT_ASSERT(!u.empty()); + } + + char const* tracker_error_alert::error_message() const + { +#ifndef TORRENT_NO_DEPRECATE + return msg.c_str(); +#else + return m_alloc.ptr(m_msg_idx); +#endif + } + + std::string tracker_error_alert::message() const + { + char ret[400]; + snprintf(ret, sizeof(ret), "%s (%d) %s \"%s\" (%d)" + , tracker_alert::message().c_str(), status_code + , convert_from_native(error.message()).c_str(), error_message() + , times_in_row); + return ret; + } + + tracker_warning_alert::tracker_warning_alert(aux::stack_allocator& alloc + , torrent_handle const& h + , std::string const& u + , std::string const& m) + : tracker_alert(alloc, h, u) +#ifndef TORRENT_NO_DEPRECATE + , msg(m) +#endif + , m_msg_idx(alloc.copy_string(m)) + { + TORRENT_ASSERT(!u.empty()); + } + + char const* tracker_warning_alert::warning_message() const + { +#ifndef TORRENT_NO_DEPRECATE + return msg.c_str(); +#else + return m_alloc.ptr(m_msg_idx); +#endif + } + + std::string tracker_warning_alert::message() const + { + return tracker_alert::message() + " warning: " + warning_message(); + } + + scrape_reply_alert::scrape_reply_alert(aux::stack_allocator& alloc + , torrent_handle const& h + , int incomp + , int comp + , std::string const& u) + : tracker_alert(alloc, h, u) + , incomplete(incomp) + , complete(comp) + { + TORRENT_ASSERT(!u.empty()); + } + + std::string scrape_reply_alert::message() const + { + char ret[400]; + snprintf(ret, sizeof(ret), "%s scrape reply: %u %u" + , tracker_alert::message().c_str(), incomplete, complete); + return ret; + } + + scrape_failed_alert::scrape_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h + , std::string const& u + , error_code const& e) + : tracker_alert(alloc, h, u) +#ifndef TORRENT_NO_DEPRECATE + , msg(convert_from_native(e.message())) +#endif + , error(e) + , m_msg_idx(-1) + { + TORRENT_ASSERT(!u.empty()); + } + + scrape_failed_alert::scrape_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h + , std::string const& u + , std::string const& m) + : tracker_alert(alloc, h, u) +#ifndef TORRENT_NO_DEPRECATE + , msg(m) +#endif + , error(errors::tracker_failure) + , m_msg_idx(alloc.copy_string(m)) + { + TORRENT_ASSERT(!u.empty()); + } + + char const* scrape_failed_alert::error_message() const + { +#ifndef TORRENT_NO_DEPRECATE + return msg.c_str(); +#else + if (m_msg_idx == -1) return ""; + else return m_alloc.ptr(m_msg_idx); +#endif + } + + std::string scrape_failed_alert::message() const + { + return tracker_alert::message() + " scrape failed: " + error_message(); + } + + tracker_reply_alert::tracker_reply_alert(aux::stack_allocator& alloc + , torrent_handle const& h + , int np + , std::string const& u) + : tracker_alert(alloc, h, u) + , num_peers(np) + { + TORRENT_ASSERT(!u.empty()); + } + + std::string tracker_reply_alert::message() const + { + char ret[400]; + snprintf(ret, sizeof(ret), "%s received peers: %u" + , tracker_alert::message().c_str(), num_peers); + return ret; + } + + dht_reply_alert::dht_reply_alert(aux::stack_allocator& alloc + , torrent_handle const& h + , int np) + : tracker_alert(alloc, h, "") + , num_peers(np) + {} + + std::string dht_reply_alert::message() const + { + char ret[400]; + snprintf(ret, sizeof(ret), "%s received DHT peers: %u" + , tracker_alert::message().c_str(), num_peers); + return ret; + } + + tracker_announce_alert::tracker_announce_alert(aux::stack_allocator& alloc + , torrent_handle const& h + , std::string const& u, int e) + : tracker_alert(alloc, h, u) + , event(e) + { + TORRENT_ASSERT(!u.empty()); + } + + std::string tracker_announce_alert::message() const + { + static const char* event_str[] = {"none", "completed", "started", "stopped", "paused"}; + TORRENT_ASSERT_VAL(event < int(sizeof(event_str)/sizeof(event_str[0])), event); + return tracker_alert::message() + " sending announce (" + event_str[event] + ")"; + } + + hash_failed_alert::hash_failed_alert( + aux::stack_allocator& alloc + , torrent_handle const& h + , int index) + : torrent_alert(alloc, h) + , piece_index(index) + + { + TORRENT_ASSERT(index >= 0); + } + + std::string hash_failed_alert::message() const + { + char ret[400]; + snprintf(ret, sizeof(ret), "%s hash for piece %u failed" + , torrent_alert::message().c_str(), piece_index); + return ret; + } + + peer_ban_alert::peer_ban_alert(aux::stack_allocator& alloc + , torrent_handle h, tcp::endpoint const& ep + , peer_id const& peer_id) + : peer_alert(alloc, h, ep, peer_id) + {} + + std::string peer_ban_alert::message() const + { + return peer_alert::message() + " banned peer"; + } + + peer_unsnubbed_alert::peer_unsnubbed_alert(aux::stack_allocator& alloc + , torrent_handle h, tcp::endpoint const& ep + , peer_id const& peer_id) + : peer_alert(alloc, h, ep, peer_id) + {} + + std::string peer_unsnubbed_alert::message() const + { + return peer_alert::message() + " peer unsnubbed"; + } + + peer_snubbed_alert::peer_snubbed_alert(aux::stack_allocator& alloc + , torrent_handle h, tcp::endpoint const& ep + , peer_id const& peer_id) + : peer_alert(alloc, h, ep, peer_id) + {} + + std::string peer_snubbed_alert::message() const + { + return peer_alert::message() + " peer snubbed"; + } + + invalid_request_alert::invalid_request_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , peer_id const& peer_id, peer_request const& r + , bool _have, bool _peer_interested, bool _withheld) + : peer_alert(alloc, h, ep, peer_id) + , request(r) + , we_have(_have) + , peer_interested(_peer_interested) + , withheld(_withheld) + {} + + std::string invalid_request_alert::message() const + { + char ret[200]; + snprintf(ret, sizeof(ret), "%s peer sent an invalid piece request " + "(piece: %u start: %u len: %u)%s" + , peer_alert::message().c_str(), request.piece, request.start + , request.length + , withheld ? ": super seeding withheld piece" + : !we_have ? ": we don't have piece" + : !peer_interested ? ": peer is not interested" + : ""); + return ret; + } + + torrent_finished_alert::torrent_finished_alert(aux::stack_allocator& alloc + , torrent_handle h) + : torrent_alert(alloc, h) + {} + + std::string torrent_finished_alert::message() const + { + return torrent_alert::message() + " torrent finished downloading"; + } + + piece_finished_alert::piece_finished_alert(aux::stack_allocator& alloc + , torrent_handle const& h, int piece_num) + : torrent_alert(alloc, h) + , piece_index(piece_num) + { + TORRENT_ASSERT(piece_index >= 0); + } + + std::string piece_finished_alert::message() const + { + char ret[200]; + snprintf(ret, sizeof(ret), "%s piece: %u finished downloading" + , torrent_alert::message().c_str(), piece_index); + return ret; + } + + request_dropped_alert::request_dropped_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id, int block_num + , int piece_num) + : peer_alert(alloc, h, ep, peer_id) + , block_index(block_num) + , piece_index(piece_num) + { + TORRENT_ASSERT(block_index >= 0 && piece_index >= 0); + } + + std::string request_dropped_alert::message() const + { + char ret[200]; + snprintf(ret, sizeof(ret), "%s peer dropped block ( piece: %u block: %u)" + , torrent_alert::message().c_str(), piece_index, block_index); + return ret; + } + + block_timeout_alert::block_timeout_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id, int block_num + , int piece_num) + : peer_alert(alloc, h, ep, peer_id) + , block_index(block_num) + , piece_index(piece_num) + { + TORRENT_ASSERT(block_index >= 0 && piece_index >= 0); + } + + std::string block_timeout_alert::message() const + { + char ret[200]; + snprintf(ret, sizeof(ret), "%s peer timed out request ( piece: %u block: %u)" + , torrent_alert::message().c_str(), piece_index, block_index); + return ret; + } + + block_finished_alert::block_finished_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id, int block_num + , int piece_num) + : peer_alert(alloc, h, ep, peer_id) + , block_index(block_num) + , piece_index(piece_num) + { + TORRENT_ASSERT(block_index >= 0 && piece_index >= 0); + } + + std::string block_finished_alert::message() const + { + char ret[200]; + snprintf(ret, sizeof(ret), "%s block finished downloading (piece: %u block: %u)" + , torrent_alert::message().c_str(), piece_index, block_index); + return ret; + } + + block_downloading_alert::block_downloading_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep + , peer_id const& peer_id, int block_num, int piece_num) + : peer_alert(alloc, h, ep, peer_id) +#ifndef TORRENT_NO_DEPRECATE + , peer_speedmsg("") +#endif + , block_index(block_num) + , piece_index(piece_num) + { + TORRENT_ASSERT(block_index >= 0 && piece_index >= 0); + } + + std::string block_downloading_alert::message() const + { + char ret[200]; + snprintf(ret, sizeof(ret), "%s requested block (piece: %u block: %u)" + , torrent_alert::message().c_str(), piece_index, block_index); + return ret; + } + + unwanted_block_alert::unwanted_block_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep + , peer_id const& peer_id, int block_num, int piece_num) + : peer_alert(alloc, h, ep, peer_id) + , block_index(block_num) + , piece_index(piece_num) + { + TORRENT_ASSERT(block_index >= 0 && piece_index >= 0); + } + + std::string unwanted_block_alert::message() const + { + char ret[200]; + snprintf(ret, sizeof(ret), "%s received block not in download queue (piece: %u block: %u)" + , torrent_alert::message().c_str(), piece_index, block_index); + return ret; + } + + storage_moved_alert::storage_moved_alert(aux::stack_allocator& alloc + , torrent_handle const& h, std::string const& p) + : torrent_alert(alloc, h) +#ifndef TORRENT_NO_DEPRECATE + , path(p) +#endif + , m_path_idx(alloc.copy_string(p)) + {} + + std::string storage_moved_alert::message() const + { + return torrent_alert::message() + " moved storage to: " + + storage_path(); + } + + char const* storage_moved_alert::storage_path() const + { +#ifndef TORRENT_NO_DEPRECATE + return path.c_str(); +#else + return m_alloc.ptr(m_path_idx); +#endif + } + + storage_moved_failed_alert::storage_moved_failed_alert( + aux::stack_allocator& alloc + , torrent_handle const& h + , error_code const& e + , std::string const& f + , char const* op) + : torrent_alert(alloc, h) + , error(e) +#ifndef TORRENT_NO_DEPRECATE + , file(f) +#endif + , operation(op) + , m_file_idx(alloc.copy_string(f)) + {} + + char const* storage_moved_failed_alert::file_path() const + { +#ifndef TORRENT_NO_DEPRECATE + return file.c_str(); +#else + return m_alloc.ptr(m_file_idx); +#endif + } + + std::string storage_moved_failed_alert::message() const + { + return torrent_alert::message() + " storage move failed. " + + (operation?operation:"") + " (" + file_path() + "): " + + convert_from_native(error.message()); + } + + torrent_deleted_alert::torrent_deleted_alert(aux::stack_allocator& alloc + , torrent_handle const& h, sha1_hash const& ih) + : torrent_alert(alloc, h) + , info_hash(ih) + {} + + std::string torrent_deleted_alert::message() const + { + return torrent_alert::message() + " deleted"; + } + + torrent_delete_failed_alert::torrent_delete_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, error_code const& e, sha1_hash const& ih) + : torrent_alert(alloc, h) + , error(e) + , info_hash(ih) + { +#ifndef TORRENT_NO_DEPRECATE + msg = convert_from_native(error.message()); +#endif + } + + std::string torrent_delete_failed_alert::message() const + { + return torrent_alert::message() + " torrent deletion failed: " + +convert_from_native(error.message()); + } + + save_resume_data_alert::save_resume_data_alert(aux::stack_allocator& alloc + , boost::shared_ptr const& rd + , torrent_handle const& h) + : torrent_alert(alloc, h) + , resume_data(rd) + {} + + std::string save_resume_data_alert::message() const + { + return torrent_alert::message() + " resume data generated"; + } + + save_resume_data_failed_alert::save_resume_data_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, error_code const& e) + : torrent_alert(alloc, h) + , error(e) + { +#ifndef TORRENT_NO_DEPRECATE + msg = convert_from_native(error.message()); +#endif + } + + std::string save_resume_data_failed_alert::message() const + { + return torrent_alert::message() + " resume data was not generated: " + + convert_from_native(error.message()); + } + + torrent_paused_alert::torrent_paused_alert(aux::stack_allocator& alloc + , torrent_handle const& h) + : torrent_alert(alloc, h) + {} + + std::string torrent_paused_alert::message() const + { + return torrent_alert::message() + " paused"; + } + + torrent_resumed_alert::torrent_resumed_alert(aux::stack_allocator& alloc + , torrent_handle const& h) + : torrent_alert(alloc, h) + {} + + std::string torrent_resumed_alert::message() const + { + return torrent_alert::message() + " resumed"; + } + + torrent_checked_alert::torrent_checked_alert(aux::stack_allocator& alloc + , torrent_handle const& h) + : torrent_alert(alloc, h) + {} + + std::string torrent_checked_alert::message() const + { + return torrent_alert::message() + " checked"; + } + + namespace + { + static char const* const sock_type_str[] = + { + "TCP", "TCP/SSL", "UDP", "I2P", "Socks5", "uTP/SSL" + }; + + static char const* const nat_type_str[] = {"NAT-PMP", "UPnP"}; + + static char const* const protocol_str[] = {"TCP", "UDP"}; + + static char const* const socket_type_str[] = { + "null", + "TCP", + "Socks5/TCP", + "HTTP", + "uTP", + "i2p", + "SSL/TCP", + "SSL/Socks5", + "HTTPS", + "SSL/uTP" + }; + + tcp::endpoint parse_interface(std::string const& iface, int port) + { + // ignore errors + error_code ec; + return tcp::endpoint(address::from_string(iface, ec), port); + } + } + + listen_failed_alert::listen_failed_alert( + aux::stack_allocator& alloc + , std::string const& iface + , int prt + , int op + , error_code const& ec + , socket_type_t t) + : error(ec) + , operation(op) + , sock_type(t) + , endpoint(parse_interface(iface, prt)) + , m_alloc(alloc) + , m_interface_idx(alloc.copy_string(iface)) + {} + + char const* listen_failed_alert::listen_interface() const + { + return m_alloc.ptr(m_interface_idx); + } + + std::string listen_failed_alert::message() const + { + static char const* op_str[] = + { + "parse_addr", + "open", + "bind", + "listen", + "get_peer_name", + "accept" + }; + char ret[300]; + snprintf(ret, sizeof(ret), "listening on %s : %s failed: [%s] [%s] %s" + , listen_interface() + , print_endpoint(endpoint).c_str() + , op_str[operation] + , sock_type_str[sock_type] + , convert_from_native(error.message()).c_str()); + return ret; + } + + metadata_failed_alert::metadata_failed_alert(aux::stack_allocator& alloc + , const torrent_handle& h, error_code const& e) + : torrent_alert(alloc, h) + , error(e) + {} + + std::string metadata_failed_alert::message() const + { + return torrent_alert::message() + " invalid metadata received"; + } + + metadata_received_alert::metadata_received_alert(aux::stack_allocator& alloc + , const torrent_handle& h) + : torrent_alert(alloc, h) + {} + + std::string metadata_received_alert::message() const + { + return torrent_alert::message() + " metadata successfully received"; + } + + udp_error_alert::udp_error_alert( + aux::stack_allocator& + , udp::endpoint const& ep + , error_code const& ec) + : endpoint(ep) + , error(ec) + {} + + std::string udp_error_alert::message() const + { + error_code ec; + return "UDP error: " + convert_from_native(error.message()) + " from: " + endpoint.address().to_string(ec); + } + + external_ip_alert::external_ip_alert(aux::stack_allocator& + , address const& ip) + : external_address(ip) + {} + + std::string external_ip_alert::message() const + { + error_code ec; + return "external IP received: " + external_address.to_string(ec); + } + + listen_succeeded_alert::listen_succeeded_alert(aux::stack_allocator& + , tcp::endpoint const& ep, socket_type_t t) + : endpoint(ep) + , sock_type(t) + {} + + std::string listen_succeeded_alert::message() const + { + char const* type_str[] = { "TCP", "SSL/TCP", "UDP", "i2p", "socks5", "SSL/uTP" }; + char ret[200]; + snprintf(ret, sizeof(ret), "successfully listening on [%s] %s" + , type_str[sock_type], print_endpoint(endpoint).c_str()); + return ret; + } + + portmap_error_alert::portmap_error_alert(aux::stack_allocator& + , int i, int t, error_code const& e) + : mapping(i), map_type(t), error(e) + { +#ifndef TORRENT_NO_DEPRECATE + msg = convert_from_native(error.message()); +#endif + } + + std::string portmap_error_alert::message() const + { + return std::string("could not map port using ") + nat_type_str[map_type] + + ": " + convert_from_native(error.message()); + } + + portmap_alert::portmap_alert(aux::stack_allocator&, int i, int port, int t + , int proto) + : mapping(i), external_port(port), map_type(t), protocol(proto) + {} + + std::string portmap_alert::message() const + { + char ret[200]; + snprintf(ret, sizeof(ret), "successfully mapped port using %s. external port: %s/%u" + , nat_type_str[map_type], protocol_str[protocol], external_port); + return ret; + } + +#ifndef TORRENT_DISABLE_LOGGING + + portmap_log_alert::portmap_log_alert(aux::stack_allocator& alloc, int t, const char* m) + : map_type(t) +#ifndef TORRENT_NO_DEPRECATE + , msg(m) +#endif + , m_alloc(alloc) + , m_log_idx(alloc.copy_string(m)) + {} + + char const* portmap_log_alert::log_message() const + { +#ifndef TORRENT_NO_DEPRECATE + return msg.c_str(); +#else + return m_alloc.ptr(m_log_idx); +#endif + } + + std::string portmap_log_alert::message() const + { + char ret[600]; + snprintf(ret, sizeof(ret), "%s: %s", nat_type_str[map_type] + , log_message()); + return ret; + } + +#endif + + fastresume_rejected_alert::fastresume_rejected_alert( + aux::stack_allocator& alloc + , torrent_handle const& h + , error_code const& ec + , std::string const& f + , char const* op) + : torrent_alert(alloc, h) + , error(ec) +#ifndef TORRENT_NO_DEPRECATE + , file(f) +#endif + , operation(op) + , m_path_idx(alloc.copy_string(f)) + { +#ifndef TORRENT_NO_DEPRECATE + msg = convert_from_native(error.message()); +#endif + } + + std::string fastresume_rejected_alert::message() const + { + return torrent_alert::message() + " fast resume rejected. " + + (operation?operation:"") + "(" + file_path() + "): " + + convert_from_native(error.message()); + } + + char const* fastresume_rejected_alert::file_path() const + { +#ifndef TORRENT_NO_DEPRECATE + return file.c_str(); +#else + return m_alloc.ptr(m_path_idx); +#endif + } + + peer_blocked_alert::peer_blocked_alert(aux::stack_allocator& alloc + , torrent_handle const& h, address const& i + , int r) + : torrent_alert(alloc, h) + , ip(i) + , reason(r) + {} + + std::string peer_blocked_alert::message() const + { + error_code ec; + char ret[600]; + char const* reason_str[] = + { + "ip_filter", + "port_filter", + "i2p_mixed", + "privileged_ports", + "utp_disabled", + "tcp_disabled", + "invalid_local_interface" + }; + + snprintf(ret, sizeof(ret), "%s: blocked peer: %s [%s]" + , torrent_alert::message().c_str(), ip.to_string(ec).c_str() + , reason_str[reason]); + return ret; + } + + dht_announce_alert::dht_announce_alert(aux::stack_allocator& + , address const& i, int p + , sha1_hash const& ih) + : ip(i) + , port(p) + , info_hash(ih) + {} + + std::string dht_announce_alert::message() const + { + error_code ec; + char ih_hex[41]; + to_hex(info_hash.data(), 20, ih_hex); + char msg[200]; + snprintf(msg, sizeof(msg), "incoming dht announce: %s:%u (%s)" + , ip.to_string(ec).c_str(), port, ih_hex); + return msg; + } + + dht_get_peers_alert::dht_get_peers_alert(aux::stack_allocator& + , sha1_hash const& ih) + : info_hash(ih) + {} + + std::string dht_get_peers_alert::message() const + { + char ih_hex[41]; + to_hex(info_hash.data(), 20, ih_hex); + char msg[200]; + snprintf(msg, sizeof(msg), "incoming dht get_peers: %s", ih_hex); + return msg; + } + + stats_alert::stats_alert(aux::stack_allocator& alloc + , torrent_handle const& h, int in, stat const& s) + : torrent_alert(alloc, h) + , interval(in) + { + transferred[upload_payload] = s[stat::upload_payload].counter(); + transferred[upload_protocol] = s[stat::upload_protocol].counter(); + transferred[download_payload] = s[stat::download_payload].counter(); + transferred[download_protocol] = s[stat::download_protocol].counter(); + transferred[upload_ip_protocol] = s[stat::upload_ip_protocol].counter(); + transferred[download_ip_protocol] = s[stat::download_ip_protocol].counter(); + +#ifndef TORRENT_NO_DEPRECATE + transferred[upload_dht_protocol] = 0; + transferred[upload_tracker_protocol] = 0; + transferred[download_dht_protocol] = 0; + transferred[download_tracker_protocol] = 0; +#else + transferred[deprecated1] = 0; + transferred[deprecated2] = 0; + transferred[deprecated3] = 0; + transferred[deprecated4] = 0; +#endif + } + + std::string stats_alert::message() const + { + char msg[200]; + snprintf(msg, sizeof(msg), "%s: [%d] %d %d %d %d %d %d" +#ifndef TORRENT_NO_DEPRECATE + " %d %d %d %d" +#endif + , torrent_alert::message().c_str() + , interval + , transferred[0] + , transferred[1] + , transferred[2] + , transferred[3] + , transferred[4] + , transferred[5] +#ifndef TORRENT_NO_DEPRECATE + , transferred[6] + , transferred[7] + , transferred[8] + , transferred[9] +#endif + ); + return msg; + } + + cache_flushed_alert::cache_flushed_alert(aux::stack_allocator& alloc + , torrent_handle const& h) + : torrent_alert(alloc, h) {} + + anonymous_mode_alert::anonymous_mode_alert(aux::stack_allocator& alloc + , torrent_handle const& h, int k, std::string const& s) + : torrent_alert(alloc, h) + , kind(k) + , str(s) + {} + + std::string anonymous_mode_alert::message() const + { + char msg[200]; + char const* msgs[] = { + "tracker is not anonymous, set a proxy" + }; + snprintf(msg, sizeof(msg), "%s: %s: %s" + , torrent_alert::message().c_str() + , msgs[kind], str.c_str()); + return msg; + } + + 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(0)) + {} + + std::string lsd_peer_alert::message() const + { + char msg[200]; + snprintf(msg, sizeof(msg), "%s: received peer from local service discovery" + , peer_alert::message().c_str()); + return msg; + } + + trackerid_alert::trackerid_alert( + aux::stack_allocator& alloc + , torrent_handle const& h + , std::string const& u + , const std::string& id) + : tracker_alert(alloc, h, u) +#ifndef TORRENT_NO_DEPRECATE + , trackerid(id) +#endif + , m_tracker_idx(alloc.copy_string(id)) + {} + + char const* trackerid_alert::tracker_id() const + { +#ifndef TORRENT_NO_DEPRECATE + return trackerid.c_str(); +#else + return m_alloc.ptr(m_tracker_idx); +#endif + } + + std::string trackerid_alert::message() const + { + return std::string("trackerid received: ") + tracker_id(); + } + + dht_bootstrap_alert::dht_bootstrap_alert(aux::stack_allocator&) + {} + + std::string dht_bootstrap_alert::message() const + { + return "DHT bootstrap complete"; + } + +#ifndef TORRENT_NO_DEPRECATE + rss_alert::rss_alert(aux::stack_allocator&, feed_handle h + , std::string const& u, int s, error_code const& ec) + : handle(h), url(u), state(s), error(ec) + {} + + std::string rss_alert::message() const + { + char msg[600]; + char const* state_msg[] = {"updating", "updated", "error"}; + snprintf(msg, sizeof(msg), "RSS feed %s: %s (%s)" + , url.c_str(), state_msg[state], convert_from_native(error.message()).c_str()); + return msg; + } +#endif + + torrent_error_alert::torrent_error_alert( + aux::stack_allocator& alloc + , torrent_handle const& h + , error_code const& e, std::string const& f) + : torrent_alert(alloc, h) + , error(e) +#ifndef TORRENT_NO_DEPRECATE + , error_file(f) +#endif + , m_file_idx(alloc.copy_string(f)) + {} + + std::string torrent_error_alert::message() const + { + char msg[200]; + snprintf(msg, sizeof(msg), " ERROR: %s", convert_from_native(error.message()).c_str()); + return torrent_alert::message() + msg; + } + + char const* torrent_error_alert::filename() const + { + return m_alloc.ptr(m_file_idx); + } + + torrent_added_alert::torrent_added_alert(aux::stack_allocator& alloc + , torrent_handle const& h) + : torrent_alert(alloc, h) + {} + + std::string torrent_added_alert::message() const + { + return torrent_alert::message() + " added"; + } + + torrent_removed_alert::torrent_removed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, sha1_hash const& ih) + : torrent_alert(alloc, h) + , info_hash(ih) + {} + + std::string torrent_removed_alert::message() const + { + return torrent_alert::message() + " removed"; + } + + torrent_need_cert_alert::torrent_need_cert_alert(aux::stack_allocator& alloc + , torrent_handle const& h) + : torrent_alert(alloc, h) + {} + + std::string torrent_need_cert_alert::message() const + { + return torrent_alert::message() + " needs SSL certificate"; + } + + incoming_connection_alert::incoming_connection_alert(aux::stack_allocator&, int t + , tcp::endpoint const& i) + : socket_type(t) + , ip(i) + {} + + std::string incoming_connection_alert::message() const + { + char msg[600]; + error_code ec; + snprintf(msg, sizeof(msg), "incoming connection from %s (%s)" + , print_endpoint(ip).c_str(), socket_type_str[socket_type]); + return msg; + } + + peer_connect_alert::peer_connect_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id, int type) + : peer_alert(alloc, h, ep, peer_id) + , socket_type(type) + {} + + std::string peer_connect_alert::message() const + { + char msg[600]; + error_code ec; + snprintf(msg, sizeof(msg), "%s connecting to peer (%s)" + , peer_alert::message().c_str(), socket_type_str[socket_type]); + return msg; + } + + add_torrent_alert::add_torrent_alert(aux::stack_allocator& alloc, torrent_handle h + , add_torrent_params const& p, error_code ec) + : torrent_alert(alloc, h) + , params(p) + , error(ec) + {} + + std::string add_torrent_alert::message() const + { + char msg[600]; + char info_hash[41]; + char const* torrent_name = info_hash; + if (params.ti) torrent_name = params.ti->name().c_str(); + else if (!params.name.empty()) torrent_name = params.name.c_str(); + else if (!params.url.empty()) torrent_name = params.url.c_str(); + else to_hex(params.info_hash.data(), 20, info_hash); + + if (error) + { + snprintf(msg, sizeof(msg), "failed to add torrent \"%s\": [%s] %s" + , torrent_name, error.category().name() + , convert_from_native(error.message()).c_str()); + } + else + { + snprintf(msg, sizeof(msg), "added torrent: %s", torrent_name); + } + return msg; + } + + state_update_alert::state_update_alert(aux::stack_allocator& + , std::vector st) + : status(st) + {} + + std::string state_update_alert::message() const + { + char msg[600]; + snprintf(msg, sizeof(msg), "state updates for %d torrents", int(status.size())); + return msg; + } + + mmap_cache_alert::mmap_cache_alert(aux::stack_allocator& + , error_code const& ec): error(ec) + {} + + std::string mmap_cache_alert::message() const + { + char msg[600]; + snprintf(msg, sizeof(msg), "mmap cache failed: (%d) %s", error.value() + , convert_from_native(error.message()).c_str()); + return msg; + } + + peer_error_alert::peer_error_alert(aux::stack_allocator& alloc, torrent_handle const& h + , tcp::endpoint const& ep, peer_id const& peer_id, int op + , error_code const& e) + : peer_alert(alloc, h, ep, peer_id) + , operation(op) + , error(e) + { +#ifndef TORRENT_NO_DEPRECATE + msg = convert_from_native(error.message()); +#endif + } + + std::string peer_error_alert::message() const + { + char buf[200]; + snprintf(buf, sizeof(buf), "%s peer error [%s] [%s]: %s" + , peer_alert::message().c_str() + , operation_name(operation), error.category().name() + , convert_from_native(error.message()).c_str()); + return buf; + } + + char const* operation_name(int op) + { + static char const* names[] = { + "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", + }; + + if (op < 0 || op >= int(sizeof(names)/sizeof(names[0]))) + return "unknown operation"; + + return names[op]; + } + + torrent_update_alert::torrent_update_alert(aux::stack_allocator& alloc, torrent_handle h + , sha1_hash const& old_hash, sha1_hash const& new_hash) + : torrent_alert(alloc, h) + , old_ih(old_hash) + , new_ih(new_hash) + {} + + std::string torrent_update_alert::message() const + { + char msg[200]; + snprintf(msg, sizeof(msg), " torrent changed info-hash from: %s to %s" + , to_hex(old_ih.to_string()).c_str() + , to_hex(new_ih.to_string()).c_str()); + return torrent_alert::message() + msg; + } + +#ifndef TORRENT_NO_DEPRECATE + rss_item_alert::rss_item_alert(aux::stack_allocator&, feed_handle h + , feed_item const& i) + : handle(h) + , item(i) + {} + + std::string rss_item_alert::message() const + { + char msg[500]; + snprintf(msg, sizeof(msg), "feed [%s] has new RSS item %s" + , handle.get_feed_status().title.c_str() + , item.title.empty() ? item.url.c_str() : item.title.c_str()); + return msg; + } +#endif + + peer_disconnected_alert::peer_disconnected_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , peer_id const& peer_id, operation_t op, int type, error_code const& e + , close_reason_t r) + : peer_alert(alloc, h, ep, peer_id) + , socket_type(type) + , operation(op) + , error(e) + , reason(r) + { +#ifndef TORRENT_NO_DEPRECATE + msg = convert_from_native(error.message()); +#endif + } + + std::string peer_disconnected_alert::message() const + { + char buf[600]; + snprintf(buf, sizeof(buf), "%s disconnecting (%s) [%s] [%s]: %s (reason: %d)" + , peer_alert::message().c_str() + , socket_type_str[socket_type] + , operation_name(operation), error.category().name() + , convert_from_native(error.message()).c_str() + , int(reason)); + return buf; + } + + dht_error_alert::dht_error_alert(aux::stack_allocator&, int op + , error_code const& ec) + : error(ec), operation(op_t(op)) + {} + + std::string dht_error_alert::message() const + { + static const char* const operation_names[] = + { + "unknown", + "hostname lookup" + }; + + int op = operation; + if (op < 0 || op >= int(sizeof(operation_names)/sizeof(operation_names[0]))) + op = 0; + + char msg[600]; + snprintf(msg, sizeof(msg), "DHT error [%s] (%d) %s" + , operation_names[op] + , error.value() + , convert_from_native(error.message()).c_str()); + return msg; + } + + dht_immutable_item_alert::dht_immutable_item_alert(aux::stack_allocator& + , sha1_hash const& t, entry const& i) + : target(t), item(i) + {} + + std::string dht_immutable_item_alert::message() const + { + char msg[1050]; + snprintf(msg, sizeof(msg), "DHT immutable item %s [ %s ]" + , to_hex(target.to_string()).c_str() + , item.to_string().c_str()); + return msg; + } + + // TODO: 2 the salt here is allocated on the heap. It would be nice to + // allocate in in the stack_allocator + dht_mutable_item_alert::dht_mutable_item_alert(aux::stack_allocator& + , boost::array k + , boost::array sig + , boost::uint64_t sequence + , std::string const& s + , entry const& i + , bool a) + : key(k), signature(sig), seq(sequence), salt(s), item(i), authoritative(a) + {} + + std::string dht_mutable_item_alert::message() const + { + char msg[1050]; + snprintf(msg, sizeof(msg), "DHT mutable item (key=%s salt=%s seq=%" PRId64 " %s) [ %s ]" + , to_hex(std::string(&key[0], 32)).c_str() + , salt.c_str() + , seq + , authoritative ? "auth" : "non-auth" + , item.to_string().c_str()); + return msg; + } + + dht_put_alert::dht_put_alert(aux::stack_allocator&, sha1_hash const& t, int n) + : target(t) + , seq(0) + , num_success(n) + {} + + dht_put_alert::dht_put_alert(aux::stack_allocator& + , boost::array key + , boost::array sig + , std::string s + , boost::uint64_t sequence_number + , int n) + : target(0) + , public_key(key) + , signature(sig) + , salt(s) + , seq(sequence_number) + , num_success(n) + {} + + std::string dht_put_alert::message() const + { + char msg[1050]; + if (target.is_all_zeros()) + { + snprintf(msg, sizeof(msg), "DHT put complete (success=%d key=%s sig=%s salt=%s seq=%" PRId64 ")" + , num_success + , to_hex(std::string(&public_key[0], 32)).c_str() + , to_hex(std::string(&signature[0], 64)).c_str() + , salt.c_str() + , seq); + return msg; + } + + snprintf(msg, sizeof(msg), "DHT put commplete (success=%d hash=%s)" + , num_success + , to_hex(target.to_string()).c_str()); + return msg; + } + + i2p_alert::i2p_alert(aux::stack_allocator&, error_code const& ec) + : error(ec) + {} + + std::string i2p_alert::message() const + { + char msg[600]; + snprintf(msg, sizeof(msg), "i2p_error: [%s] %s" + , error.category().name(), convert_from_native(error.message()).c_str()); + return msg; + } + + dht_outgoing_get_peers_alert::dht_outgoing_get_peers_alert(aux::stack_allocator& + , sha1_hash const& ih, sha1_hash const& obfih + , udp::endpoint ep) + : info_hash(ih) + , obfuscated_info_hash(obfih) + , ip(ep) + {} + + std::string dht_outgoing_get_peers_alert::message() const + { + char msg[600]; + char obf[70]; + obf[0] = '\0'; + if (obfuscated_info_hash != info_hash) + { + snprintf(obf, sizeof(obf), " [obfuscated: %s]" + , to_hex(obfuscated_info_hash.to_string()).c_str()); + } + snprintf(msg, sizeof(msg), "outgoing dht get_peers : %s%s -> %s" + , to_hex(info_hash.to_string()).c_str() + , obf + , print_endpoint(ip).c_str()); + return msg; + } + +#ifndef TORRENT_DISABLE_LOGGING + + log_alert::log_alert(aux::stack_allocator& alloc, char const* log) + : m_alloc(alloc) + , m_str_idx(alloc.copy_string(log)) + {} + + char const* log_alert::msg() const + { + return m_alloc.ptr(m_str_idx); + } + + std::string log_alert::message() const + { + return msg(); + } + + torrent_log_alert::torrent_log_alert(aux::stack_allocator& alloc, torrent_handle const& h + , char const* log) + : torrent_alert(alloc, h) + , m_str_idx(alloc.copy_string(log)) + {} + + char const* torrent_log_alert::msg() const + { + return m_alloc.ptr(m_str_idx); + } + + std::string torrent_log_alert::message() const + { + return torrent_alert::message() + ": " + msg(); + } + + peer_log_alert::peer_log_alert(aux::stack_allocator& alloc + , torrent_handle const& h + , tcp::endpoint const& i + , peer_id const& pi + , direction_t dir + , char const* event + , char const* log) + : peer_alert(alloc, h, i, pi) + , event_type(event) + , direction(dir) + , m_str_idx(alloc.copy_string(log)) + {} + + char const* peer_log_alert::msg() const + { + return m_alloc.ptr(m_str_idx); + } + + std::string peer_log_alert::message() const + { + static char const* mode[] = + { "<==", "==>", "<<<", ">>>", "***" }; + return torrent_alert::message() + " [" + print_endpoint(ip) + "] " + + mode[direction] + " " + event_type + " [ " + msg() + " ]"; + } + +#endif + + lsd_error_alert::lsd_error_alert(aux::stack_allocator&, error_code const& ec) + : alert() + , error(ec) + {} + + std::string lsd_error_alert::message() const + { + return "Local Service Discovery error: " + error.message(); + } + + session_stats_alert::session_stats_alert(aux::stack_allocator&, counters const& cnt) + { + for (int i = 0; i < counters::num_counters; ++i) + values[i] = cnt[i]; + } + + std::string session_stats_alert::message() const + { + // this specific output is parsed by tools/parse_session_stats.py + // if this is changed, that parser should also be changed + char msg[100]; + snprintf(msg, sizeof(msg), "session stats (%d values): " + , int(sizeof(values)/sizeof(values[0]))); + std::string ret = msg; + bool first = true; + for (int i = 0; i < sizeof(values)/sizeof(values[0]); ++i) + { + snprintf(msg, sizeof(msg), first ? "%" PRIu64 : ", %" PRIu64, values[i]); + first = false; + ret += msg; + } + return ret; + } + + dht_stats_alert::dht_stats_alert(aux::stack_allocator& + , std::vector const& table + , std::vector const& requests) + : alert() + , active_requests(requests) + , routing_table(table) + {} + + std::string dht_stats_alert::message() const + { + char buf[2048]; + snprintf(buf, sizeof(buf), "DHT stats: reqs: %d buckets: %d" + , int(active_requests.size()) + , int(routing_table.size())); + return buf; + } + + url_seed_alert::url_seed_alert(aux::stack_allocator& alloc, torrent_handle const& h + , std::string const& u, error_code const& e) + : torrent_alert(alloc, h) +#ifndef TORRENT_NO_DEPRECATE + , url(u) + , msg(convert_from_native(e.message())) +#endif + , error(e) + , m_url_idx(alloc.copy_string(u)) + , m_msg_idx(-1) + {} + + url_seed_alert::url_seed_alert(aux::stack_allocator& alloc, torrent_handle const& h + , std::string const& u, std::string const& m) + : torrent_alert(alloc, h) +#ifndef TORRENT_NO_DEPRECATE + , url(u) + , msg(m) +#endif + , m_url_idx(alloc.copy_string(u)) + , m_msg_idx(alloc.copy_string(m)) + {} + + std::string url_seed_alert::message() const + { + return torrent_alert::message() + " url seed (" + + server_url() + ") failed: " + convert_from_native(error.message()); + } + + char const* url_seed_alert::server_url() const + { + return m_alloc.ptr(m_url_idx); + } + + char const* url_seed_alert::error_message() const + { +#ifndef TORRENT_NO_DEPRECATE + return msg.c_str(); +#else + if (m_msg_idx == -1) return ""; + return m_alloc.ptr(m_msg_idx); +#endif + } + + file_error_alert::file_error_alert(aux::stack_allocator& alloc + , error_code const& ec + , std::string const& f + , char const* op + , torrent_handle const& h) + : torrent_alert(alloc, h) +#ifndef TORRENT_NO_DEPRECATE + , file(f) +#endif + , error(ec) + , operation(op) + , m_file_idx(alloc.copy_string(f)) + { +#ifndef TORRENT_NO_DEPRECATE + msg = convert_from_native(error.message()); +#endif + } + + char const* file_error_alert::filename() const + { + return m_alloc.ptr(m_file_idx); + } + + std::string file_error_alert::message() const + { + return torrent_alert::message() + " " + + (operation?operation:"") + " (" + filename() + + ") error: " + convert_from_native(error.message()); + } + + incoming_request_alert::incoming_request_alert(aux::stack_allocator& alloc + , peer_request r, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id) + : peer_alert(alloc, h, ep, peer_id) + , req(r) + {} + + std::string incoming_request_alert::message() const + { + char msg[1024]; + snprintf(msg, sizeof(msg), "%s: incoming request [ piece: %d start: %d length: %d ]" + , peer_alert::message().c_str(), req.piece, req.start, req.length); + return msg; + } + + dht_log_alert::dht_log_alert(aux::stack_allocator& alloc + , dht_log_alert::dht_module_t m, const char* msg) + : module(m) + , m_alloc(alloc) + , m_msg_idx(alloc.copy_string(msg)) + {} + + char const* dht_log_alert::log_message() const + { + return m_alloc.ptr(m_msg_idx); + } + + std::string dht_log_alert::message() const + { + static char const* const dht_modules[] = + { + "tracker", + "node", + "routing_table", + "rpc_manager", + "traversal" + }; + + char ret[900]; + snprintf(ret, sizeof(ret), "DHT %s: %s", dht_modules[module] + , log_message()); + return ret; + } + + dht_pkt_alert::dht_pkt_alert(aux::stack_allocator& alloc + , char const* buf, int size, dht_pkt_alert::direction_t d, udp::endpoint ep) + : dir(d) + , node(ep) + , m_alloc(alloc) + , m_msg_idx(alloc.copy_buffer(buf, size)) + , m_size(size) + {} + + char const* dht_pkt_alert::pkt_buf() const + { + return m_alloc.ptr(m_msg_idx); + } + + int dht_pkt_alert::pkt_size() const + { + return m_size; + } + + std::string dht_pkt_alert::message() const + { + bdecode_node print; + error_code ec; + + // ignore errors here. This is best-effort. It may be a broken encoding + // but at least we'll print the valid parts + bdecode(pkt_buf(), pkt_buf() + pkt_size(), print, ec, NULL, 100, 100); + + std::string msg = print_entry(print, true); + + char const* prefix[2] = { "<==", "==>"}; + char buf[1024]; + snprintf(buf, sizeof(buf), "%s [%s] %s", prefix[dir] + , print_endpoint(node).c_str(), msg.c_str()); + + return buf; + } + + dht_get_peers_reply_alert::dht_get_peers_reply_alert(aux::stack_allocator& alloc + , sha1_hash const& ih + , std::vector const& peers) + : info_hash(ih) + , m_alloc(alloc) + , m_num_peers(peers.size()) + { + std::size_t total_size = m_num_peers; // num bytes for sizes + for (int i = 0; i < m_num_peers; i++) { + total_size += peers[i].size(); + } + + m_peers_idx = alloc.allocate(total_size); + + char *ptr = alloc.ptr(m_peers_idx); + for (int i = 0; i < m_num_peers; i++) { + tcp::endpoint endp = peers[i]; + std::size_t size = endp.size(); + + detail::write_uint8(size, ptr); + memcpy(ptr, endp.data(), size); + ptr += size; + } + } + + std::string dht_get_peers_reply_alert::message() const + { + char ih_hex[41]; + to_hex(info_hash.data(), 20, ih_hex); + char msg[200]; + snprintf(msg, sizeof(msg), "incoming dht get_peers reply: %s, peers %d", ih_hex, m_num_peers); + return msg; + } + + int dht_get_peers_reply_alert::num_peers() const + { + return m_num_peers; + } + +#ifndef TORRENT_NO_DEPRECATE + 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 { + std::vector peers(m_num_peers); + + const char *ptr = m_alloc.ptr(m_peers_idx); + for (int i = 0; i < m_num_peers; i++) { + std::size_t size = detail::read_uint8(ptr); + memcpy(peers[i].data(), ptr, size); + ptr += size; + } + + return peers; + } + + dht_direct_response_alert::dht_direct_response_alert( + aux::stack_allocator& alloc, void* userdata_ + , udp::endpoint const& addr_, bdecode_node const& response) + : userdata(userdata_), addr(addr_), m_alloc(alloc) + , m_response_idx(alloc.copy_buffer(response.data_section().first, response.data_section().second)) + , m_response_size(response.data_section().second) + {} + + dht_direct_response_alert::dht_direct_response_alert( + aux::stack_allocator& alloc + , void* userdata_ + , udp::endpoint const& addr_) + : userdata(userdata_), addr(addr_), m_alloc(alloc) + , m_response_idx(-1), m_response_size(0) + {} + + std::string dht_direct_response_alert::message() const + { + char msg[1050]; + snprintf(msg, sizeof(msg), "DHT direct response (address=%s) [ %s ]" + , addr.address().to_string().c_str() + , m_response_size ? std::string(m_alloc.ptr(m_response_idx), m_response_size).c_str() : ""); + return msg; + } + + bdecode_node dht_direct_response_alert::response() const + { + if (m_response_size == 0) return bdecode_node(); + char const* start = m_alloc.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; + } + +#ifndef TORRENT_DISABLE_LOGGING + + picker_log_alert::picker_log_alert(aux::stack_allocator& alloc, torrent_handle const& h + , tcp::endpoint const& ep, peer_id const& peer_id, boost::uint32_t flags + , piece_block const* blocks, int num_blocks) + : peer_alert(alloc, h, ep, peer_id) + , picker_flags(flags) + , m_array_idx(alloc.copy_buffer(reinterpret_cast(blocks) + , num_blocks * sizeof(piece_block))) + , m_num_blocks(num_blocks) + {} + + std::vector picker_log_alert::blocks() const + { + // we need to copy this array to make sure the structures are properly + // aigned, not just to have a nice API + std::vector ret; + ret.resize(m_num_blocks); + + char const* start = m_alloc.ptr(m_array_idx); + memcpy(&ret[0], start, m_num_blocks * sizeof(piece_block)); + + return ret; + } + + std::string picker_log_alert::message() const + { + static char const* const flag_names[] = + { + "partial_ratio ", + "prioritize_partials ", + "rarest_first_partials ", + "rarest_first ", + "reverse_rarest_first ", + "suggested_pieces ", + "prio_sequential_pieces ", + "sequential_pieces ", + "reverse_pieces ", + "time_critical ", + "random_pieces ", + "prefer_contiguous ", + "reverse_sequential ", + "backup1 ", + "backup2 ", + "end_game " + }; + + std::string ret = peer_alert::message(); + + boost::uint32_t flags = 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 (int i = 0; i < int(b.size()); ++i) + { + char buf[50]; + snprintf(buf, sizeof(buf), "(%d,%d) " + , b[i].piece_index, b[i].block_index); + ret += buf; + } + return ret; + } + +#endif // TORRENT_DISABLE_LOGGING + +} // namespace libtorrent + diff --git a/src/alert_manager.cpp b/src/alert_manager.cpp new file mode 100644 index 0000000..ddb6619 --- /dev/null +++ b/src/alert_manager.cpp @@ -0,0 +1,205 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg, Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/alert_manager.hpp" +#include "libtorrent/alert_types.hpp" + +#ifndef TORRENT_DISABLE_EXTENSIONS +#include "libtorrent/extensions.hpp" +#endif + +namespace libtorrent +{ + + alert_manager::alert_manager(int queue_limit, boost::uint32_t alert_mask) + : m_alert_mask(alert_mask) + , m_queue_size_limit(queue_limit) + , m_num_queued_resume(0) + , m_generation(0) + {} + + alert_manager::~alert_manager() {} + + int alert_manager::num_queued_resume() const + { + mutex::scoped_lock lock(m_mutex); + return m_num_queued_resume; + } + + alert* alert_manager::wait_for_alert(time_duration max_wait) + { + mutex::scoped_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 NULL; + } + + void alert_manager::maybe_notify(alert* a, mutex::scoped_lock& lock) + { + if (a->type() == save_resume_data_failed_alert::alert_type + || a->type() == save_resume_data_alert::alert_type) + ++m_num_queued_resume; + + if (m_alerts[m_generation].size() == 1) + { + lock.unlock(); + + // 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(); + } + else + { + lock.unlock(); + } + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (ses_extension_list_t::iterator i = m_ses_extensions.begin() + , end(m_ses_extensions.end()); i != end; ++i) + { + (*i)->on_alert(a); + } +#endif + } + +#ifndef TORRENT_NO_DEPRECATE + + bool alert_manager::maybe_dispatch(alert const& a) + { + if (m_dispatch) + { + m_dispatch(a.clone()); + return true; + } + return false; + } + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + + void alert_manager::set_dispatch_function( + boost::function)> const& fun) + { + mutex::scoped_lock lock(m_mutex); + + m_dispatch = fun; + + heterogeneous_queue storage; + m_alerts[m_generation].swap(storage); + lock.unlock(); + + std::vector alerts; + storage.get_pointers(alerts); + + for (std::vector::iterator i = alerts.begin() + , end(alerts.end()); i != end; ++i) + { + m_dispatch((*i)->clone()); + } + } + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +#endif + + void alert_manager::set_notify_function(boost::function const& fun) + { + mutex::scoped_lock lock(m_mutex); + m_notify = fun; + if (!m_alerts[m_generation].empty()) + { + // never call a callback with the lock held! + lock.unlock(); + if (m_notify) m_notify(); + } + } + +#ifndef TORRENT_DISABLE_EXTENSIONS + void alert_manager::add_extension(boost::shared_ptr ext) + { + m_ses_extensions.push_back(ext); + } +#endif + + void alert_manager::get_all(std::vector& alerts, int& num_resume) + { + mutex::scoped_lock lock(m_mutex); + TORRENT_ASSERT(m_num_queued_resume <= m_alerts[m_generation].size()); + + alerts.clear(); + if (m_alerts[m_generation].empty()) return; + + m_alerts[m_generation].get_pointers(alerts); + num_resume = m_num_queued_resume; + m_num_queued_resume = 0; + + // 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 + { + mutex::scoped_lock lock(m_mutex); + return !m_alerts[m_generation].empty(); + } + + int alert_manager::set_alert_queue_size_limit(int queue_size_limit_) + { + mutex::scoped_lock lock(m_mutex); + + std::swap(m_queue_size_limit, queue_size_limit_); + return queue_size_limit_; + } + +} + diff --git a/src/allocator.cpp b/src/allocator.cpp new file mode 100644 index 0000000..a21ac74 --- /dev/null +++ b/src/allocator.cpp @@ -0,0 +1,211 @@ +/* + +Copyright (c) 2009-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/allocator.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" // for print_backtrace +#include + +#if defined TORRENT_BEOS +#include +#include // malloc/free +#elif !defined TORRENT_WINDOWS +#include // posix_memalign/free +#include // _SC_PAGESIZE +#endif + +#if TORRENT_USE_MEMALIGN || TORRENT_USE_POSIX_MEMALIGN || defined TORRENT_WINDOWS +#include // memalign and _aligned_malloc +#include // _aligned_malloc on mingw +#endif + +#ifdef TORRENT_WINDOWS +// windows.h must be included after stdlib.h under mingw +#include +#endif + +#ifdef TORRENT_MINGW +#define _aligned_malloc __mingw_aligned_malloc +#define _aligned_free __mingw_aligned_free +#endif + +#ifdef TORRENT_DEBUG_BUFFERS +#ifndef TORRENT_WINDOWS +#include +#endif + +struct alloc_header +{ + boost::int64_t size; + int magic; + char stack[3072]; +}; + +#endif + +namespace libtorrent +{ + + int page_size() + { + static int s = 0; + if (s != 0) return s; + +#ifdef TORRENT_BUILD_SIMULATOR + s = 4096; +#elif defined TORRENT_WINDOWS + SYSTEM_INFO si; + GetSystemInfo(&si); + s = si.dwPageSize; +#elif defined TORRENT_BEOS + s = B_PAGE_SIZE; +#else + s = int(sysconf(_SC_PAGESIZE)); +#endif + // assume the page size is 4 kiB if we + // fail to query it + if (s <= 0) s = 4096; + return s; + } + + char* page_aligned_allocator::malloc(page_aligned_allocator::size_type bytes) + { + TORRENT_ASSERT(bytes > 0); + // just sanity check (this needs to be pretty high + // for cases where the cache size is several gigabytes) + TORRENT_ASSERT(bytes < 0x30000000); + + TORRENT_ASSERT(int(bytes) >= page_size()); +#ifdef TORRENT_DEBUG_BUFFERS + const int page = page_size(); + const int num_pages = (bytes + (page-1)) / page + 2; + const int orig_bytes = bytes; + bytes = num_pages * page; +#endif + + void* ret; +#if TORRENT_USE_POSIX_MEMALIGN + if (posix_memalign(&ret, page_size(), bytes) + != 0) ret = NULL; +#elif TORRENT_USE_MEMALIGN + ret = memalign(page_size(), bytes); +#elif defined TORRENT_WINDOWS + ret = _aligned_malloc(bytes, page_size()); +#elif defined TORRENT_BEOS + area_id id = create_area("", &ret, B_ANY_ADDRESS + , (bytes + page_size() - 1) & (page_size()-1), B_NO_LOCK, B_READ_AREA | B_WRITE_AREA); + if (id < B_OK) return NULL; +#else + ret = valloc(size_t(bytes)); +#endif + if (ret == NULL) return NULL; + +#ifdef TORRENT_DEBUG_BUFFERS + // make the two surrounding pages non-readable and -writable + alloc_header* h = static_cast(ret); + h->size = orig_bytes; + h->magic = 0x1337; + print_backtrace(h->stack, sizeof(h->stack)); + +#ifdef TORRENT_WINDOWS +#define mprotect(buf, size, prot) VirtualProtect(buf, size, prot, NULL) +#define PROT_READ PAGE_READONLY +#endif + mprotect(ret, page, PROT_READ); + mprotect(static_cast(ret) + (num_pages-1) * page, page, PROT_READ); + +#ifdef TORRENT_WINDOWS +#undef mprotect +#undef PROT_READ +#endif +// fprintf(stderr, "malloc: %p head: %p tail: %p size: %d\n", ret + page, ret, ret + page + bytes, int(bytes)); + + return static_cast(ret) + page; +#else + return static_cast(ret); +#endif // TORRENT_DEBUG_BUFFERS + } + + void page_aligned_allocator::free(char* block) + { + if (block == 0) return; + +#ifdef TORRENT_DEBUG_BUFFERS + +#ifdef TORRENT_WINDOWS +#define mprotect(buf, size, prot) VirtualProtect(buf, size, prot, NULL) +#define PROT_READ PAGE_READONLY +#define PROT_WRITE PAGE_READWRITE +#endif + const int page = page_size(); + // make the two surrounding pages non-readable and -writable + mprotect(block - page, page, PROT_READ | PROT_WRITE); + alloc_header* h = reinterpret_cast(block - page); + const int num_pages = (h->size + (page-1)) / page + 2; + TORRENT_ASSERT(h->magic == 0x1337); + mprotect(block + (num_pages-2) * page, page, PROT_READ | PROT_WRITE); +// fprintf(stderr, "free: %p head: %p tail: %p size: %d\n", block, block - page, block + h->size, int(h->size)); + h->magic = 0; + block -= page; + +#ifdef TORRENT_WINDOWS +#undef mprotect +#undef PROT_READ +#undef PROT_WRITE +#endif + + print_backtrace(h->stack, sizeof(h->stack)); + +#endif // TORRENT_DEBUG_BUFFERS + +#ifdef TORRENT_WINDOWS + _aligned_free(block); +#elif defined TORRENT_BEOS + area_id id = area_for(block); + if (id < B_OK) return; + delete_area(id); +#else + ::free(block); +#endif // TORRENT_WINDOWS + } + +#ifdef TORRENT_DEBUG_BUFFERS + bool page_aligned_allocator::in_use(char const* block) + { + const int page = page_size(); + alloc_header const* h = reinterpret_cast(block - page); + return h->magic == 0x1337; + } +#endif + +} + diff --git a/src/announce_entry.cpp b/src/announce_entry.cpp new file mode 100644 index 0000000..d94e17f --- /dev/null +++ b/src/announce_entry.cpp @@ -0,0 +1,134 @@ +/* + +Copyright (c) 2015-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/announce_entry.hpp" +#include "libtorrent/aux_/time.hpp" +#include "libtorrent/aux_/session_settings.hpp" + +namespace libtorrent +{ + enum + { + // wait at least 5 seconds before retrying a failed tracker + tracker_retry_delay_min = 5 + // when tracker_failed_max trackers + // has failed, wait 60 minutes instead + , tracker_retry_delay_max = 60 * 60 + }; + + announce_entry::announce_entry(std::string const& u) + : url(u) + , next_announce(min_time()) + , min_announce(min_time()) + , scrape_incomplete(-1) + , scrape_complete(-1) + , scrape_downloaded(-1) + , tier(0) + , fail_limit(0) + , fails(0) + , updating(false) + , source(0) + , verified(false) + , start_sent(false) + , complete_sent(false) + , send_stats(true) + , triggered_manually(false) + {} + + announce_entry::announce_entry() + : next_announce(min_time()) + , min_announce(min_time()) + , scrape_incomplete(-1) + , scrape_complete(-1) + , scrape_downloaded(-1) + , tier(0) + , fail_limit(0) + , fails(0) + , updating(false) + , source(0) + , verified(false) + , start_sent(false) + , complete_sent(false) + , send_stats(true) + , triggered_manually(false) + {} + + announce_entry::~announce_entry() {} + + int announce_entry::next_announce_in() const + { return total_seconds(next_announce - aux::time_now()); } + + int announce_entry::min_announce_in() const + { return total_seconds(min_announce - aux::time_now()); } + + void announce_entry::reset() + { + start_sent = false; + next_announce = min_time(); + min_announce = min_time(); + } + + void announce_entry::failed(aux::session_settings const& sett, int retry_interval) + { + ++fails; + // the exponential back-off ends up being: + // 7, 15, 27, 45, 95, 127, 165, ... seconds + // with the default tracker_backoff of 250 + int delay = (std::min)(tracker_retry_delay_min + int(fails) * int(fails) + * tracker_retry_delay_min * sett.get_int(settings_pack::tracker_backoff) / 100 + , int(tracker_retry_delay_max)); + delay = (std::max)(delay, retry_interval); + next_announce = aux::time_now() + seconds(delay); + updating = false; + } + + bool announce_entry::can_announce(time_point now, bool is_seed) const + { + // if we're a seed and we haven't sent a completed + // event, we need to let this announce through + bool need_send_complete = is_seed && !complete_sent; + + return now >= next_announce + && (now >= min_announce || need_send_complete) + && (fails < fail_limit || fail_limit == 0) + && !updating; + } + + void announce_entry::trim() + { + while (!url.empty() && is_space(url[0])) + url.erase(url.begin()); + } + +} + diff --git a/src/assert.cpp b/src/assert.cpp new file mode 100644 index 0000000..5b54dd3 --- /dev/null +++ b/src/assert.cpp @@ -0,0 +1,377 @@ +/* + +Copyright (c) 2007-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/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 + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +// uClibc++ doesn't have cxxabi.h +#if defined __GNUC__ && __GNUC__ >= 3 \ + && !defined __UCLIBCXX_MAJOR__ + +#include + +std::string demangle(char const* name) +{ +// in case this string comes + // this is needed on linux + char const* start = strchr(name, '('); + if (start != NULL) + { + ++start; + } + else + { + // this is needed on macos x + start = strstr(name, "0x"); + if (start != NULL) + { + start = strchr(start, ' '); + if (start != NULL) ++start; + else start = name; + } + else start = name; + } + + char const* end = strchr(start, '+'); + if (end) while (*(end-1) == ' ') --end; + + std::string in; + if (end == NULL) in.assign(start); + else in.assign(start, end); + + size_t len; + int status; + char* unmangled = ::abi::__cxa_demangle(in.c_str(), NULL, &len, &status); + if (unmangled == NULL) return in; + std::string ret(unmangled); + free(unmangled); + return ret; +} +#elif defined _WIN32 + +#include "windows.h" +#include "dbghelp.h" + +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 +std::string demangle(char const* name) { return name; } +#endif + +#include +#include +#include +#include "libtorrent/version.hpp" + +#if TORRENT_USE_EXECINFO +#include + +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 = snprintf(out, len, "%d: %s\n", i, demangle(symbols[i]).c_str()); + out += ret; + len -= ret; + if (i - 1 == max_depth && max_depth > 0) break; + } + + free(symbols); +} + +#elif defined _WIN32 + +#include "windows.h" +#include "libtorrent/utf8.hpp" +#include "libtorrent/thread.hpp" + +#include "winbase.h" +#include "dbghelp.h" + +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 libtorrent::mutex dbghlp_mutex; + libtorrent::mutex::scoped_lock 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; + boost::array stack; + + STACKFRAME64 stack_frame; + memset(&stack_frame, 0, sizeof(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, + NULL, + &SymFunctionTableAccess64, + &SymGetModuleBase64, + NULL) && size < 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, NULL, 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 = snprintf(out, len, "%2d: %p", i, stack[i]); + out += ret; len -= ret; if (len <= 0) break; + + if (has_symbol) + { + ret = snprintf(out, len, " %s +%-4" PRId64 + , demangle(symbol.Name).c_str(), displacement); + out += ret; len -= ret; if (len <= 0) break; + } + + if (has_line) + { + ret = snprintf(out, len, " %s:%d" + , line.FileName, line.LineNumber); + out += ret; len -= ret; if (len <= 0) break; + } + + + ret = snprintf(out, len, "\n"); + out += ret; + len -= ret; + + if (i == max_depth && max_depth > 0) break; + } +} + +#else + +TORRENT_EXPORT void print_backtrace(char* out, int len, int /*max_depth*/, void* /* ctx */) +{ + out[0] = 0; + strncat(out, "", len); +} + +#endif + +#endif + +#if TORRENT_USE_ASSERTS || defined TORRENT_ASIO_DEBUGGING + +#ifdef TORRENT_PRODUCTION_ASSERTS +char const* libtorrent_assert_log = "asserts.log"; +namespace { +// the number of asserts we've printed to the log +boost::atomic assert_counter(0); +} +#endif + +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 == 0) out = stderr; +#else + FILE* out = stderr; +#endif + va_list va; + va_start(va, fmt); + 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 + // send SIGINT to the current process + // to break into the debugger + raise(SIGINT); + 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 + diff --git a/src/bandwidth_limit.cpp b/src/bandwidth_limit.cpp new file mode 100644 index 0000000..ea1cd33 --- /dev/null +++ b/src/bandwidth_limit.cpp @@ -0,0 +1,94 @@ +/* + +Copyright (c) 2009-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/bandwidth_limit.hpp" +#include + +namespace libtorrent +{ + bandwidth_channel::bandwidth_channel() + : tmp(0) + , distribute_quota(0) + , m_quota_left(0) + , m_limit(0) + {} + + // 0 means infinite + void bandwidth_channel::throttle(int limit) + { + TORRENT_ASSERT(limit >= 0); + // if the throttle is more than this, we might overflow + TORRENT_ASSERT(limit < INT_MAX); + 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 dt_milliseconds) + { + if (m_limit == 0) return; + m_quota_left += (m_limit * dt_milliseconds + 500) / 1000; + if (m_quota_left > m_limit * 3) m_quota_left = m_limit * 3; + distribute_quota = int((std::max)(m_quota_left, boost::int64_t(0))); +// fprintf(stderr, "%p: [%d]: + %"PRId64" limit: %"PRId64" quota_left: %"PRId64"\n", this +// , dt_milliseconds, (m_limit * dt_milliseconds + 500) / 1000, m_limit +// , m_quota_left); + } + + // this is used when connections disconnect with + // some quota left. It's returned to its bandwidth + // channels. + void bandwidth_channel::return_quota(int 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 amount) + { + TORRENT_ASSERT(amount >= 0); + TORRENT_ASSERT(m_limit >= 0); + if (m_limit == 0) return; + +// fprintf(stderr, "%p: - %"PRId64" limit: %"PRId64" quota_left: %"PRId64"\n", this +// , amount, m_limit, m_quota_left); + m_quota_left -= amount; + } + +} + diff --git a/src/bandwidth_manager.cpp b/src/bandwidth_manager.cpp new file mode 100644 index 0000000..25cad66 --- /dev/null +++ b/src/bandwidth_manager.cpp @@ -0,0 +1,226 @@ +/* + +Copyright (c) 2009-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/bandwidth_manager.hpp" +#include "libtorrent/time.hpp" + +namespace libtorrent +{ + + bandwidth_manager::bandwidth_manager(int channel) + : m_queued_bytes(0) + , m_channel(channel) + , m_abort(false) + { + } + + void bandwidth_manager::close() + { + m_abort = true; + + queue_t tm; + tm.swap(m_queue); + m_queued_bytes = 0; + + while (!tm.empty()) + { + bw_request& bwr = tm.back(); + bwr.peer->assign_bandwidth(m_channel, bwr.assigned); + tm.pop_back(); + } + } + +#if TORRENT_USE_ASSERTS + bool bandwidth_manager::is_queued(bandwidth_socket const* peer) const + { + for (queue_t::const_iterator i = m_queue.begin() + , end(m_queue.end()); i != end; ++i) + { + if (i->peer.get() == peer) return true; + } + return false; + } +#endif + + int bandwidth_manager::queue_size() const + { + return m_queue.size(); + } + + boost::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(boost::shared_ptr const& peer + , int blk, int priority, bandwidth_channel** chan, int 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(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(bwr); + return 0; + } + +#if TORRENT_USE_INVARIANT_CHECKS + void bandwidth_manager::check_invariant() const + { + boost::int64_t queued = 0; + for (queue_t::const_iterator i = m_queue.begin() + , end(m_queue.end()); i != end; ++i) + { + queued += i->request_size - i->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; + + boost::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; + + queue_t tm; + + for (queue_t::iterator 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; + tm.push_back(*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 (queue_t::iterator i = m_queue.begin() + , end(m_queue.end()); i != end; ++i) + { + for (int j = 0; j < bw_request::max_bandwidth_channels && i->channel[j]; ++j) + { + bandwidth_channel* bwc = i->channel[j]; + if (bwc->tmp == 0) channels.push_back(bwc); + TORRENT_ASSERT(INT_MAX - bwc->tmp > i->priority); + bwc->tmp += i->priority; + } + } + + for (std::vector::iterator i = channels.begin() + , end(channels.end()); i != end; ++i) + { + (*i)->update_quota(int(dt_milliseconds)); + } + + for (queue_t::iterator 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); + tm.push_back(*i); + i = m_queue.erase(i); + } + else + { + ++i; + } + m_queued_bytes -= a; + } + + while (!tm.empty()) + { + bw_request& bwr = tm.back(); + bwr.peer->assign_bandwidth(m_channel, bwr.assigned); + tm.pop_back(); + } + } +} + diff --git a/src/bandwidth_queue_entry.cpp b/src/bandwidth_queue_entry.cpp new file mode 100644 index 0000000..5086bdd --- /dev/null +++ b/src/bandwidth_queue_entry.cpp @@ -0,0 +1,79 @@ +/* + +Copyright (c) 2009-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/bandwidth_queue_entry.hpp" +#include +#include + +namespace libtorrent +{ + bw_request::bw_request(boost::shared_ptr const& pe + , int blk, int prio) + : peer(pe) + , priority(prio) + , assigned(0) + , request_size(blk) + , ttl(20) + { + TORRENT_ASSERT(priority > 0); + std::memset(channel, 0, sizeof(channel)); + } + + 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(boost::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; + } +} + diff --git a/src/bdecode.cpp b/src/bdecode.cpp new file mode 100644 index 0000000..e5bee9b --- /dev/null +++ b/src/bdecode.cpp @@ -0,0 +1,1086 @@ +/* + +Copyright (c) 2015-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/bdecode.hpp" +#include "libtorrent/alloca.hpp" +#include +#include +#include // for memset + +#ifndef BOOST_SYSTEM_NOEXCEPT +#define BOOST_SYSTEM_NOEXCEPT throw() +#endif + +namespace libtorrent +{ + using detail::bdecode_token; + + namespace + { + bool numeric(char c) { return c >= '0' && c <= '9'; } + + // finds the end of an integer and verifies that it looks valid this does + // not detect all overflows, just the ones that are an order of magnitued + // 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(int t): token(t), state(0) {} + // this is an index into m_tokens + boost::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 + boost::uint32_t state:1; + }; + + // str1 is null-terminated + // str2 is not, str2 is len2 chars + bool string_equal(char const* str1, char const* str2, int len2) + { + while (len2 > 0) + { + if (*str1 != *str2) return false; + if (*str1 == 0) return false; + ++str1; + ++str2; + --len2; + } + return *str1 == 0; + } + + } // anonymous namespace + + + // fills in 'val' with what the string between start and the + // first occurance of the delimiter is interpreted as an int. + // return the pointer to the delimiter, or 0 if there is a + // parse error. val should be initialized to zero + char const* parse_int(char const* start, char const* end, char delimiter + , boost::int64_t& val, bdecode_errors::error_code_enum& ec) + { + while (start < end && *start != delimiter) + { + if (!numeric(*start)) + { + ec = bdecode_errors::expected_digit; + return start; + } + if (val > (std::numeric_limits::max)() / 10) + { + ec = bdecode_errors::overflow; + return start; + } + val *= 10; + int digit = *start - '0'; + if (val > (std::numeric_limits::max)() - digit) + { + ec = bdecode_errors::overflow; + return start; + } + val += digit; + ++start; + } + if (*start != delimiter) + ec = bdecode_errors::expected_colon; + return start; + } + + + struct bdecode_error_category : boost::system::error_category + { + virtual const char* name() const BOOST_SYSTEM_NOEXCEPT; + virtual std::string message(int ev) const BOOST_SYSTEM_NOEXCEPT; + virtual boost::system::error_condition default_error_condition( + int ev) const BOOST_SYSTEM_NOEXCEPT + { return boost::system::error_condition(ev, *this); } + }; + + const char* bdecode_error_category::name() const BOOST_SYSTEM_NOEXCEPT + { + return "bdecode error"; + } + + std::string bdecode_error_category::message(int ev) const BOOST_SYSTEM_NOEXCEPT + { + 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& get_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 boost::system::error_code(e, get_bdecode_category()); + } + } + + + bdecode_node::bdecode_node() + : m_root_tokens(0) + , m_buffer(NULL) + , m_buffer_size(0) + , m_token_idx(-1) + , m_last_index(-1) + , m_last_token(-1) + , m_size(-1) + {} + + 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) + { + 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_token const* tokens, char const* buf + , int len, int idx) + : m_root_tokens(tokens) + , m_buffer(buf) + , m_buffer_size(len) + , m_token_idx(idx) + , m_last_index(-1) + , m_last_token(-1) + , m_size(-1) + { + TORRENT_ASSERT(tokens != NULL); + 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 = NULL; + m_token_idx = -1; + m_size = -1; + m_last_index = -1; + m_last_token = -1; + } + + void bdecode_node::switch_underlying_buffer(char const* buf) + { + TORRENT_ASSERT(!m_tokens.empty()); + if (m_tokens.empty()) return; + + m_buffer = buf; + } + + bdecode_node::type_t bdecode_node::type() const + { + if (m_token_idx == -1) return none_t; + return static_cast(m_root_tokens[m_token_idx].type); + } + + bdecode_node::operator bool() const + { return m_token_idx != -1; } + + std::pair bdecode_node::data_section() const + { + if (m_token_idx == -1) return std::make_pair(m_buffer, 0); + + 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 std::make_pair(m_buffer + t.offset, next.offset - t.offset); + } + + bdecode_node bdecode_node::list_at(int i) const + { + TORRENT_ASSERT(type() == list_t); + TORRENT_ASSERT(i >= 0); + + // make sure this is a list. + bdecode_token const* tokens = m_root_tokens; + + // this is the first item + int token = m_token_idx + 1; + int item = 0; + + // do we have a lookup cached? + if (m_last_index <= i && m_last_index != -1) + { + token = m_last_token; + item = m_last_index; + } + + while (item < i) + { + token += tokens[token].next_item; + ++item; + + // index 'i' out of range + TORRENT_ASSERT(tokens[token].type != bdecode_token::end); + } + + m_last_token = token; + m_last_index = i; + + return bdecode_node(tokens, m_buffer, m_buffer_size, token); + } + + std::string bdecode_node::list_string_value_at(int i + , char const* default_val) + { + bdecode_node n = list_at(i); + if (n.type() != bdecode_node::string_t) return default_val; + return n.string_value(); + } + + boost::int64_t bdecode_node::list_int_value_at(int i + , boost::int64_t default_val) + { + bdecode_node n = list_at(i); + if (n.type() != bdecode_node::int_t) return default_val; + return n.int_value(); + } + + int bdecode_node::list_size() const + { + TORRENT_ASSERT(type() == list_t); + + if (m_size != -1) return m_size; + + // make sure this is a list. + bdecode_token const* tokens = m_root_tokens; + TORRENT_ASSERT(tokens[m_token_idx].type == bdecode_token::list); + + // this is the first item + int token = m_token_idx + 1; + int ret = 0; + + // do we have a lookup cached? + if (m_last_index != -1) + { + token = m_last_token; + ret = m_last_index; + } + while (tokens[token].type != bdecode_token::end) + { + token += tokens[token].next_item; + ++ret; + } + + m_size = ret; + + return ret; + } + + std::pair bdecode_node::dict_at(int i) const + { + TORRENT_ASSERT(type() == dict_t); + TORRENT_ASSERT(m_token_idx != -1); + + bdecode_token const* tokens = m_root_tokens; + TORRENT_ASSERT(tokens[m_token_idx].type == bdecode_token::dict); + + int token = m_token_idx + 1; + int item = 0; + + // do we have a lookup cached? + if (m_last_index <= i && m_last_index != -1) + { + token = m_last_token; + item = m_last_index; + } + + while (item < i) + { + TORRENT_ASSERT(tokens[token].type == bdecode_token::string); + + // skip the key + token += tokens[token].next_item; + TORRENT_ASSERT(tokens[token].type != bdecode_token::end); + + // skip the value + token += tokens[token].next_item; + + ++item; + + // index 'i' out of range + TORRENT_ASSERT(tokens[token].type != bdecode_token::end); + } + + // there's no point in caching the first item + if (i > 0) + { + m_last_token = token; + m_last_index = i; + } + + int value_token = token + tokens[token].next_item; + TORRENT_ASSERT(tokens[token].type != bdecode_token::end); + + return std::make_pair( + bdecode_node(tokens, m_buffer, m_buffer_size, token).string_value() + , bdecode_node(tokens, m_buffer, m_buffer_size, value_token)); + } + + int bdecode_node::dict_size() const + { + TORRENT_ASSERT(type() == dict_t); + TORRENT_ASSERT(m_token_idx != -1); + + if (m_size != -1) return m_size; + + bdecode_token const* tokens = m_root_tokens; + TORRENT_ASSERT(tokens[m_token_idx].type == bdecode_token::dict); + + // this is the first item + int token = m_token_idx + 1; + int ret = 0; + + if (m_last_index != -1) + { + ret = m_last_index * 2; + token = m_last_token; + } + + while (tokens[token].type != bdecode_token::end) + { + token += tokens[token].next_item; + ++ret; + } + + // a dictionary must contain full key-value pairs. which means + // the number of entries is divisible by 2 + TORRENT_ASSERT((ret % 2) == 0); + + // each item is one key and one value, so divide by 2 + ret /= 2; + + m_size = ret; + + return ret; + } + + bdecode_node bdecode_node::dict_find(std::string key) const + { + TORRENT_ASSERT(type() == dict_t); + + bdecode_token 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 size = m_root_tokens[token + 1].offset - t.offset - t.start_offset(); + if (int(key.size()) == size + && std::equal(key.c_str(), key.c_str() + 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(char const* 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(std::string 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_dict(char const* 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(char const* 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(char const* key) const + { + bdecode_node ret = dict_find(key); + if (ret.type() == bdecode_node::int_t) + return ret; + return bdecode_node(); + } + + + bdecode_node bdecode_node::dict_find(char const* key) const + { + TORRENT_ASSERT(type() == dict_t); + + bdecode_token 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 size = m_root_tokens[token + 1].offset - t.offset - t.start_offset(); + if (string_equal(key, m_buffer + t.offset + t.start_offset(), size)) + { + // 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(); + } + + std::string bdecode_node::dict_find_string_value(char const* key + , char const* default_value) const + { + bdecode_node n = dict_find(key); + if (n.type() != bdecode_node::string_t) return default_value; + return n.string_value(); + } + + boost::int64_t bdecode_node::dict_find_int_value(char const* key + , boost::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(); + } + + boost::int64_t bdecode_node::int_value() const + { + TORRENT_ASSERT(type() == int_t); + bdecode_token const& t = m_root_tokens[m_token_idx]; + int size = m_root_tokens[m_token_idx + 1].offset - t.offset; + TORRENT_ASSERT(t.type == bdecode_token::integer); + + // +1 is to skip the 'i' + char const* ptr = m_buffer + t.offset + 1; + boost::int64_t val = 0; + bool negative = false; + if (*ptr == '-') negative = true; + bdecode_errors::error_code_enum ec = bdecode_errors::no_error; + parse_int(ptr + negative + , ptr + size, 'e', val, ec); + if (ec) return 0; + if (negative) val = -val; + return val; + } + + std::string bdecode_node::string_value() const + { + TORRENT_ASSERT(type() == string_t); + bdecode_token const& t = m_root_tokens[m_token_idx]; + int size = m_root_tokens[m_token_idx + 1].offset - t.offset - t.start_offset(); + TORRENT_ASSERT(t.type == bdecode_token::string); + + return std::string(m_buffer + t.offset + t.start_offset(), size); + } + + char const* bdecode_node::string_ptr() const + { + TORRENT_ASSERT(type() == string_t); + bdecode_token const& t = m_root_tokens[m_token_idx]; + TORRENT_ASSERT(t.type == bdecode_token::string); + return m_buffer + t.offset + t.start_offset(); + } + + int bdecode_node::string_length() const + { + TORRENT_ASSERT(type() == string_t); + bdecode_token const& t = m_root_tokens[m_token_idx]; + TORRENT_ASSERT(t.type == bdecode_token::string); + return m_root_tokens[m_token_idx + 1].offset - t.offset - t.start_offset(); + } + + void bdecode_node::reserve(int tokens) + { m_tokens.reserve(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 = make_error_code(code); \ + if (error_pos) *error_pos = 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 depth_limit, int token_limit) + { + ec.clear(); + ret.clear(); + + if (end - start > bdecode_token::max_offset) + { + if (error_pos) *error_pos = 0; + ec = make_error_code(bdecode_errors::limit_exceeded); + return -1; + } + + // 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; + stack_frame* stack = TORRENT_ALLOCA(stack_frame, depth_limit); + + 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 + const char t = *start; + + const int 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++] = 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(bdecode_token(start - orig_start + , bdecode_token::dict)); + ++start; + break; + case 'l': + stack[sp++] = 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(bdecode_token(start - orig_start + , bdecode_token::list)); + ++start; + break; + case 'i': + { + char 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 = start - orig_start; + error_pos = NULL; + start = int_start; + TORRENT_FAIL_BDECODE(e); + } + ret.m_tokens.push_back(bdecode_token(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(bdecode_token(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 top = stack[sp-1].token; + // subtract the token's own index, since this is a relative + // offset + if (ret.m_tokens.size() - top > bdecode_token::max_next_item) + TORRENT_FAIL_BDECODE(bdecode_errors::limit_exceeded); + + ret.m_tokens[top].next_item = ret.m_tokens.size() - top; + + // and pop it from the stack. + --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); + + boost::int64_t len = t - '0'; + char const* str_start = start; + ++start; + bdecode_errors::error_code_enum e = bdecode_errors::no_error; + start = parse_int(start, end, ':', len, e); + if (e) + TORRENT_FAIL_BDECODE(e); + + // remaining buffer size excluding ':' + const ptrdiff_t 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; + if (start >= end) TORRENT_FAIL_BDECODE(bdecode_errors::unexpected_eof); + + // the bdecode_token only has 8 bits to keep the header size + // in. If it overflows, fail! + if (start - str_start - 2 > detail::bdecode_token::max_header) + TORRENT_FAIL_BDECODE(bdecode_errors::limit_exceeded); + + ret.m_tokens.push_back(bdecode_token(str_start - orig_start + , 1, bdecode_token::string, 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 + stack[current_frame-1].state = ~stack[current_frame-1].state; + } + + // 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(bdecode_token(start - orig_start + , 2, bdecode_token::dict)); + ret.m_tokens.push_back(bdecode_token(start - orig_start + , bdecode_token::end)); + } + + int top = stack[sp].token; + TORRENT_ASSERT(ret.m_tokens.size() - top <= bdecode_token::max_next_item); + ret.m_tokens[top].next_item = ret.m_tokens.size() - top; + ret.m_tokens.push_back(bdecode_token(start - orig_start, 1, bdecode_token::end)); + } + + ret.m_tokens.push_back(bdecode_token(start - orig_start, 0 + , bdecode_token::end)); + + ret.m_token_idx = 0; + ret.m_buffer = orig_start; + ret.m_buffer_size = start - orig_start; + ret.m_root_tokens = &ret.m_tokens[0]; + + return ec ? -1 : 0; + } + + 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 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 + e.dict_at(i).first.size(); + if (line_len > limit) return -1; + int ret = line_longer_than(e.dict_at(i).second, limit - line_len); + if (ret == -1) return -1; + line_len += ret + 1; + } + break; + case bdecode_node::string_t: + line_len += 3 + e.string_length(); + break; + case bdecode_node::int_t: + { + boost::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 escape_string(std::string& ret, char const* str, int len) + { + for (int i = 0; i < len; ++i) + { + if (str[i] >= 32 && str[i] < 127) + { + ret += str[i]; + } + else + { + char tmp[5]; + snprintf(tmp, sizeof(tmp), "\\x%02x", boost::uint8_t(str[i])); + ret += tmp; + } + } + } + + void print_string(std::string& ret, char const* str, int len, bool single_line) + { + bool printable = true; + for (int i = 0; i < len; ++i) + { + char c = str[i]; + if (c >= 32 && c < 127) continue; + printable = false; + break; + } + ret += "'"; + if (printable) + { + if (single_line && len > 30) + { + ret.append(str, 14); + ret += "..."; + ret.append(str + len-14, 14); + } + else + ret.append(str, len); + ret += "'"; + return; + } + if (single_line && len > 20) + { + escape_string(ret, str, 9); + ret += "..."; + escape_string(ret, str + len - 9, 9); + } + else + { + escape_string(ret, str, 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]; + snprintf(str, sizeof(str), "%" PRId64, e.int_value()); + return str; + } + case bdecode_node::string_t: + { + print_string(ret, e.string_ptr(), e.string_length(), 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.c_str(), ent.first.size(), true); + ret += ": "; + ret += print_entry(ent.second, single_line, indent + 2); + if (i < e.dict_size() - 1) ret += (one_liner?", ":indent_str); + else ret += (one_liner?" ":indent_str+1); + } + ret += "}"; + return ret; + } + } + return ret; + } +} + diff --git a/src/bitfield.cpp b/src/bitfield.cpp new file mode 100644 index 0000000..dab1d22 --- /dev/null +++ b/src/bitfield.cpp @@ -0,0 +1,163 @@ +/* + +Copyright (c) 2008-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/bitfield.hpp" +#include "libtorrent/aux_/cpuid.hpp" + +#ifdef _MSC_VER +#include +#endif + +namespace libtorrent +{ + bool bitfield::all_set() const + { + const int words = size() / 32; + for (int i = 0; i < words; ++i) + { + if (m_buf[i] != 0xffffffff) return false; + } + int rest = size() & 31; + if (rest > 0) + { + boost::uint32_t mask = aux::host_to_network(0xffffffff << (32-rest)); + if ((m_buf[words] & mask) != mask) return false; + } + return true; + } + + int bitfield::count() const + { + int ret = 0; + const int words = num_words(); +#if TORRENT_HAS_SSE + if (aux::mmx_support) + { + for (int i = 0; i < words; ++i) + { +#ifdef __GNUC__ + boost::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 + } + + return ret; + } +#endif // TORRENT_HAS_SSE + + for (int i = 0; i < words; ++i) + { + boost::uint32_t 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 int B[] = {0x55555555, 0x33333333, 0x0F0F0F0F, 0x00FF00FF, 0x0000FFFF}; + + boost::uint32_t c = v - ((v >> 1) & B[0]); + c = ((c >> S[1]) & B[1]) + (c & B[1]); + c = ((c >> S[2]) + c) & B[2]; + c = ((c >> S[3]) + c) & B[3]; + c = ((c >> S[4]) + c) & B[4]; + ret += c; + } + + TORRENT_ASSERT(ret <= size()); + TORRENT_ASSERT(ret >= 0); + return ret; + } + + void bitfield::resize(int bits, bool val) + { + if (bits == size()) return; + + int s = size(); + int b = size() & 31; + resize(bits); + if (s >= size()) return; + int old_size_words = (s + 31) / 32; + int new_size_words = num_words(); + if (val) + { + if (old_size_words && b) m_buf[old_size_words - 1] |= aux::host_to_network((0xffffffff >> b)); + if (old_size_words < new_size_words) + std::memset(m_buf + old_size_words, 0xff + , size_t((new_size_words - old_size_words) * 4)); + clear_trailing_bits(); + } + else + { + if (old_size_words < new_size_words) + std::memset(m_buf + old_size_words, 0x00 + , size_t((new_size_words - old_size_words) * 4)); + } + TORRENT_ASSERT(size() == bits); + } + + void bitfield::resize(int bits) + { + if (bits == size()) return; + + TORRENT_ASSERT(bits >= 0); + // +1 because the first word is the size (in bits) + const int b = (bits + 31) / 32; + if (m_buf) + { + boost::uint32_t* tmp = static_cast(std::realloc(m_buf-1, (b+1) * 4)); +#ifndef BOOST_NO_EXCEPTIONS + if (tmp == NULL) throw std::bad_alloc(); +#endif + m_buf = tmp + 1; + m_buf[-1] = bits; + } + else if (bits > 0) + { + boost::uint32_t* tmp = static_cast(std::malloc((b+1) * 4)); +#ifndef BOOST_NO_EXCEPTIONS + if (tmp == NULL) throw std::bad_alloc(); +#endif + m_buf = tmp + 1; + m_buf[-1] = bits; + } + else if (m_buf != NULL) + { + std::free(m_buf-1); + m_buf = NULL; + } + clear_trailing_bits(); + TORRENT_ASSERT(size() == bits); + } +} diff --git a/src/block_cache.cpp b/src/block_cache.cpp new file mode 100644 index 0000000..3bd0757 --- /dev/null +++ b/src/block_cache.cpp @@ -0,0 +1,1915 @@ +/* + +Copyright (c) 2010-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/block_cache.hpp" +#include "libtorrent/disk_buffer_pool.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/disk_io_job.hpp" +#include "libtorrent/storage.hpp" +#include "libtorrent/io_service_fwd.hpp" +#include "libtorrent/error.hpp" +#include "libtorrent/disk_io_thread.hpp" // disk_operation_failed +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/alloca.hpp" +#include "libtorrent/performance_counters.hpp" +#include "libtorrent/aux_/time.hpp" + +#ifdef TORRENT_DEBUG +#include "libtorrent/random.hpp" +#endif + +/* + + The disk cache mimics ARC (adaptive replacement cache). + See paper: http://dbs.uni-leipzig.de/file/ARC.pdf + See slides: http://www-vlsi.stanford.edu/smart_memories/protected/meetings/spring2004/arc-fast.pdf + + This cache has a few modifications to make it fit the bittorrent use + case better. It has a few more lists and it defers the eviction + of pieces. + + read_lru1 + This is a plain LRU for items that have been requested once. If a piece + in this list gets accessed again, by someone other than the first + accessor, the piece is promoted into LRU2. which holds pieces that are + more frequently used, and more important to keep around as this LRU list + takes churn. + + read_lru1_ghost + This is a list of pieces that were least recently evicted from read_lru1. + These pieces don't hold any actual blocks in the cache, they are just + here to extend the reach and probability for pieces to be promoted into + read_lru2. Any piece in this list that get one more access is promoted to + read_lru2. This is technically a cache-miss, since there's no cached + blocks here, but for the purposes of promoting the piece from + infrequently used to frequently used), it's considered a cache-hit. + + read_lru2 + TODO + + read_lru2_ghost + TODO + + volatile_read_lru + TODO + + write_lru + TODO + + Cache hits + .......... + + When a piece get a cache hit, it's promoted, either to the beginning of the + lru2 or into lru2. Since this ARC implementation operates on pieces instead + of blocks, any one peer requesting blocks from one piece would essentially + always produce a "cache hit" the second block it requests. In order to make + the promotions make more sense, and be more in the spirit of the ARC + algorithm, each access contains a token, unique to each peer. If any access + has a different token than the last one, it's considered a cache hit. This + is because at least two peers requested blocks from the same piece. + + Deferred evictions + .................. + + Since pieces and blocks can be pinned in the cache, and it's not always + practical, or possible, to evict a piece at the point where a new block is + allocated (because it's not known what the block will be used for), + evictions are not done at the time of allocating blocks. Instead, whenever + an operation requires to add a new piece to the cache, it also records the + cache event leading to it, in m_last_cache_op. This is one of cache_miss + (piece did not exist in cache), lru1_ghost_hit (the piece was found in + lru1_ghost and it was promoted) or lru2_ghost_hit (the piece was found in + lru2_ghost and it was promoted). This cache operation then guides the cache + eviction algorithm to know which list to evict from. The volatile list is + always the first one to be evicted however. + + Write jobs + .......... + + When the write cache is enabled, write jobs are not issued via the normal + job queue. They are just hung on its corresponding cached piece entry, and a + flush_hashed job is issued. This job will inspect the current state of the + cached piece and determine if any of the blocks should be flushed. It also + kicks the hasher, i.e. progresses the SHA1 context, which calculates the + SHA-1 hash of the piece. This job flushed blocks that have been hashed and + also form a contiguous block run of at least the write cache line size. + + Read jobs + ......... + + The data blocks pulled in from disk by read jobs, are hung on the + corresponding cache piece (cached_piece_entry) once the operation completes. + Read operations typically pulls in an entire read cache stripe, and not just + the one block that was requested. When adjacent blocks are requested to be + read in quick succession, there is a risk that each block would pull in more + blocks (read ahead) and potentially read the same blocks several times, if + the original requests were serviced by different disk thread. This is + because all the read operation may start before any of them has completed, + hanging the resulting blocks in the cache. i.e. they would all be cache + misses, even though all but the first should be cache hits in the first's + read ahead. + + In order to solve this problem, there is only a single outstanding read job + at any given time per piece. When there is an outstanding read job on a + piece, the *outstanding_read* member is set to 1. This indicates that the + job should be hung on the piece for later processing, instead of being + issued into the main job queue. There is a tailqueue on each piece entry + called read_jobs where these jobs are added. + + At the end of every read job, this job list is inspected, any job in it is + tried against the cache to see if it's a cache hit now. If it is, complete + it right away. If it isn't, put it back in the read_jobs list except for + one, which is issued into the regular job queue. +*/ + +#define DEBUG_CACHE 0 + +#if __cplusplus >= 201103L || defined __clang__ + +#if DEBUG_CACHE +#define DLOG(...) fprintf(__VA_ARGS__) +#else +#define DLOG(...) do {} while (false) +#endif + +#else // cplusplus + +#if DEBUG_CACHE +#define DLOG fprintf +#else +#define DLOG TORRENT_WHILE_0 fprintf +#endif + +#endif // cplusplus + +namespace libtorrent { + +#if DEBUG_CACHE +void log_refcounts(cached_piece_entry const* pe) +{ + char out[4096]; + char* ptr = out; + char* end = ptr + sizeof(out); + ptr += snprintf(ptr, end - ptr, "piece: %d [ ", int(pe->piece)); + for (int i = 0; i < pe->blocks_in_piece; ++i) + { + ptr += snprintf(ptr, end - ptr, "%d ", int(pe->blocks[i].refcount)); + } + strncpy(ptr, "]\n", end - ptr); + DLOG(stderr, out); +} +#endif + +const char* const job_action_name[] = +{ + "read", + "write", + "hash", + "move_storage", + "release_files", + "delete_files", + "check_fastresume", + "save_resume_data", + "rename_file", + "stop_torrent", +#ifndef TORRENT_NO_DEPRECATE + "cache_piece", + "finalize_file", +#endif + "flush_piece", + "flush_hashed", + "flush_storage", + "trim_cache", + "set_file_priority", + "load_torrent", + "clear_piece", + "tick_storage", + "resolve_links" +}; + +#if __cplusplus >= 201103L +// make sure the job names array covers all the job IDs +static_assert(sizeof(job_action_name)/sizeof(job_action_name[0]) + == disk_io_job::num_job_ids, "disk-job-action and action-name-array mismatch"); +#endif + +#if TORRENT_USE_ASSERTS + + char const* const piece_log_t::job_names[7] = + { + "flushing", + "flush_expired", + "try_flush_write_blocks", + "try_flush_write_blocks2", + "flush_range", + "clear_outstanding_jobs", + "set_outstanding_jobs", + }; + + char const* job_name(int j) + { + if (j < 0 || j >= piece_log_t::last_job) + return "unknown"; + + if (j < piece_log_t::flushing) + return job_action_name[j]; + return piece_log_t::job_names[j - piece_log_t::flushing]; + } + + void print_piece_log(std::vector const& piece_log) + { + for (int i = 0; i < int(piece_log.size()); ++i) + { + if (piece_log[i].block == -1) + { + printf("%d: %s\n", i, job_name(piece_log[i].job)); + } + else + { + printf("%d: %s %d\n", i, job_name(piece_log[i].job), piece_log[i].block); + } + } + } + + void assert_print_piece(cached_piece_entry const* pe) + { + static const char* const cache_state[] = + { + "write", "volatile-read", "read-lru", "read-lru-ghost", "read-lfu", "read-lfu-ghost" + }; + + if (pe == NULL) + { + assert_print("piece: NULL\n"); + } + else + { + assert_print("piece: %d\nrefcount: %d\npiece_refcount: %d\n" + "num_blocks: %d\nhashing: %d\n\nhash: %p\nhash_offset: %d\n" + "cache_state: (%d) %s\noutstanding_flush: %d\npiece: %d\n" + "num_dirty: %d\nnum_blocks: %d\nblocks_in_piece: %d\n" + "hashing_done: %d\nmarked_for_deletion: %d\nneed_readback: %d\n" + "hash_passed: %d\nread_jobs: %d\njobs: %d\n" + "piece_log:\n" + , int(pe->piece), pe->refcount, pe->piece_refcount, int(pe->num_blocks) + , int(pe->hashing), static_cast(pe->hash), pe->hash ? pe->hash->offset : -1 + , int(pe->cache_state) + , pe->cache_state < cached_piece_entry::num_lrus ? cache_state[pe->cache_state] : "" + , int(pe->outstanding_flush), int(pe->piece), int(pe->num_dirty) + , int(pe->num_blocks), int(pe->blocks_in_piece), int(pe->hashing_done) + , int(pe->marked_for_deletion), int(pe->need_readback), pe->hash_passes + , int(pe->read_jobs.size()), int(pe->jobs.size())); + for (int i = 0; i < pe->piece_log.size(); ++i) + { + assert_print("%s %s (%d)", (i==0?"":",") + , job_name(pe->piece_log[i].job), pe->piece_log[i].block); + } + } + assert_print("\n"); + } + + +#define TORRENT_PIECE_ASSERT(cond, piece) \ + do { if (!(cond)) { assert_print_piece(piece); assert_fail(#cond, __LINE__, __FILE__, TORRENT_FUNCTION, 0); } } TORRENT_WHILE_0 + +#else +#define TORRENT_PIECE_ASSERT(cond, piece) do {} TORRENT_WHILE_0 +#endif + +cached_piece_entry::cached_piece_entry() + : storage() + , hash(0) + , last_requester(NULL) + , blocks() + , expire(min_time()) + , piece(0) + , num_dirty(0) + , num_blocks(0) + , blocks_in_piece(0) + , hashing(0) + , hashing_done(0) + , marked_for_deletion(false) + , need_readback(false) + , cache_state(read_lru1) + , piece_refcount(0) + , outstanding_flush(0) + , outstanding_read(0) + , pinned(0) + , refcount(0) +#if TORRENT_USE_ASSERTS + , hash_passes(0) + , in_storage(false) + , in_use(true) +#endif +{} + +cached_piece_entry::~cached_piece_entry() +{ + TORRENT_ASSERT(piece_refcount == 0); + TORRENT_ASSERT(jobs.size() == 0); + TORRENT_ASSERT(read_jobs.size() == 0); +#if TORRENT_USE_ASSERTS + for (int i = 0; i < blocks_in_piece; ++i) + { + TORRENT_ASSERT(blocks[i].buf == 0); + TORRENT_ASSERT(!blocks[i].pending); + TORRENT_ASSERT(blocks[i].refcount == 0); + TORRENT_ASSERT(blocks[i].hashing_count == 0); + TORRENT_ASSERT(blocks[i].flushing_count == 0); + } + in_use = false; +#endif + delete hash; +} + +block_cache::block_cache(int block_size, io_service& ios + , boost::function const& trigger_trim) + : disk_buffer_pool(block_size, ios, trigger_trim) + , m_last_cache_op(cache_miss) + , m_ghost_size(8) + , m_max_volatile_blocks(100) + , m_volatile_size(0) + , m_read_cache_size(0) + , m_write_cache_size(0) + , m_send_buffer_blocks(0) + , m_pinned_blocks(0) +{ +} + +// returns: +// -1: not in cache +// -2: no memory +int block_cache::try_read(disk_io_job* j, bool expect_no_fail) +{ + INVARIANT_CHECK; + + TORRENT_ASSERT(j->buffer.disk_block == 0); + +#if TORRENT_USE_ASSERTS + // we're not allowed to add dirty blocks + // for a deleted storage! + TORRENT_ASSERT(std::find(m_deleted_storages.begin(), m_deleted_storages.end() + , std::make_pair(j->storage->files()->name() + , reinterpret_cast(j->storage->files()))) + == m_deleted_storages.end()); +#endif + + cached_piece_entry* p = find_piece(j); + + int ret = 0; + + // if the piece cannot be found in the cache, + // it's a cache miss + TORRENT_ASSERT(!expect_no_fail || p != NULL); + if (p == 0) return -1; + +#if TORRENT_USE_ASSERTS + p->piece_log.push_back(piece_log_t(j->action, j->d.io.offset / 0x4000)); +#endif + cache_hit(p, j->requester, j->flags & disk_io_job::volatile_read); + + ret = copy_from_piece(p, j, expect_no_fail); + if (ret < 0) return ret; + + ret = j->d.io.buffer_size; + return ret; +} + +void block_cache::bump_lru(cached_piece_entry* p) +{ + // move to the top of the LRU list + TORRENT_PIECE_ASSERT(p->cache_state == cached_piece_entry::write_lru, p); + linked_list* lru_list = &m_lru[p->cache_state]; + + // move to the back (MRU) of the list + lru_list->erase(p); + lru_list->push_back(p); + p->expire = aux::time_now(); +} + +// this is called for pieces that we're reading from, when they +// are in the cache (including the ghost lists) +void block_cache::cache_hit(cached_piece_entry* p, void* requester, bool volatile_read) +{ +// this can be pretty expensive +// INVARIANT_CHECK; + + TORRENT_ASSERT(p); + TORRENT_ASSERT(p->in_use); + + // move the piece into this queue. Whenever we have a cache + // hit, we move the piece into the lru2 queue (i.e. the most + // frequently used piece). However, we only do that if the + // requester is different than the last one. This is to + // avoid a single requester making it look like a piece is + // frequently requested, when in fact it's only a single peer + int target_queue = cached_piece_entry::read_lru2; + + if (p->last_requester == requester || requester == NULL) + { + // if it's the same requester and the piece isn't in + // any of the ghost lists, ignore it + if (p->cache_state == cached_piece_entry::read_lru1 + || p->cache_state == cached_piece_entry::read_lru2 + || p->cache_state == cached_piece_entry::write_lru + || p->cache_state == cached_piece_entry::volatile_read_lru) + return; + + if (p->cache_state == cached_piece_entry::read_lru1_ghost) + target_queue = cached_piece_entry::read_lru1; + } + + if (p->cache_state == cached_piece_entry::volatile_read_lru) + { + // a volatile read hit on a volatile piece doesn't do anything + if (volatile_read) return; + + // however, if this is a proper read on a volatile piece + // we need to promote it to lru1 + target_queue = cached_piece_entry::read_lru1; + } + + if (requester != NULL) + p->last_requester = requester; + + // if we have this piece anywhere in L1 or L2, it's a "hit" + // and it should be bumped to the highest priority in L2 + // i.e. "frequently used" + if (p->cache_state < cached_piece_entry::read_lru1 + || p->cache_state > cached_piece_entry::read_lru2_ghost) + return; + + // if we got a cache hit in a ghost list, that indicates the proper + // list is too small. Record which ghost list we got the hit in and + // it will be used to determine which end of the cache we'll evict + // from, next time we need to reclaim blocks + if (p->cache_state == cached_piece_entry::read_lru1_ghost) + { + m_last_cache_op = ghost_hit_lru1; + p->storage->add_piece(p); + } + else if (p->cache_state == cached_piece_entry::read_lru2_ghost) + { + m_last_cache_op = ghost_hit_lru2; + p->storage->add_piece(p); + } + + // move into L2 (frequently used) + m_lru[p->cache_state].erase(p); + m_lru[target_queue].push_back(p); + p->cache_state = target_queue; + p->expire = aux::time_now(); +#if TORRENT_USE_ASSERTS + switch (p->cache_state) + { + case cached_piece_entry::write_lru: + case cached_piece_entry::volatile_read_lru: + case cached_piece_entry::read_lru1: + case cached_piece_entry::read_lru2: + TORRENT_ASSERT(p->in_storage == true); + break; + default: + TORRENT_ASSERT(p->in_storage == false); + break; + } +#endif +} + +// this is used to move pieces primarily from the write cache +// to the read cache. Technically it can move from read to write +// cache as well, it's unclear if that ever happens though +void block_cache::update_cache_state(cached_piece_entry* p) +{ + int state = p->cache_state; + int desired_state = p->cache_state; + if (p->num_dirty > 0 || p->hash != 0) + desired_state = cached_piece_entry::write_lru; + else if (p->cache_state == cached_piece_entry::write_lru) + desired_state = cached_piece_entry::read_lru1; + + if (desired_state == state) return; + + TORRENT_PIECE_ASSERT(state < cached_piece_entry::num_lrus, p); + TORRENT_PIECE_ASSERT(desired_state < cached_piece_entry::num_lrus, p); + linked_list* src = &m_lru[state]; + linked_list* dst = &m_lru[desired_state]; + + src->erase(p); + dst->push_back(p); + p->expire = aux::time_now(); + p->cache_state = desired_state; +#if TORRENT_USE_ASSERTS + switch (p->cache_state) + { + case cached_piece_entry::write_lru: + case cached_piece_entry::volatile_read_lru: + case cached_piece_entry::read_lru1: + case cached_piece_entry::read_lru2: + TORRENT_ASSERT(p->in_storage == true); + break; + default: + TORRENT_ASSERT(p->in_storage == false); + break; + } +#endif +} + +void block_cache::try_evict_one_volatile() +{ + INVARIANT_CHECK; + + DLOG(stderr, "[%p] try_evict_one_volatile\n", static_cast(this)); + + if (m_volatile_size < m_max_volatile_blocks) return; + + linked_list* piece_list = &m_lru[cached_piece_entry::volatile_read_lru]; + + for (list_iterator i = piece_list->iterate(); i.get();) + { + cached_piece_entry* pe = reinterpret_cast(i.get()); + TORRENT_PIECE_ASSERT(pe->in_use, pe); + i.next(); + + if (pe->ok_to_evict()) + { +#ifdef TORRENT_DEBUG + for (int j = 0; j < pe->blocks_in_piece; ++j) + TORRENT_PIECE_ASSERT(pe->blocks[j].buf == 0, pe); +#endif + TORRENT_PIECE_ASSERT(pe->refcount == 0, pe); + move_to_ghost(pe); + continue; + } + + TORRENT_PIECE_ASSERT(pe->num_dirty == 0, pe); + + // someone else is using this piece + if (pe->refcount > 0) continue; + + // some blocks are pinned in this piece, skip it + if (pe->pinned > 0) continue; + + char** to_delete = TORRENT_ALLOCA(char*, pe->blocks_in_piece); + int num_to_delete = 0; + + // go through the blocks and evict the ones that are not dirty and not + // referenced + for (int j = 0; j < pe->blocks_in_piece; ++j) + { + cached_block_entry& b = pe->blocks[j]; + + TORRENT_PIECE_ASSERT(b.dirty == false, pe); + TORRENT_PIECE_ASSERT(b.pending == false, pe); + + if (b.buf == 0 || b.refcount > 0 || b.dirty || b.pending) continue; + + to_delete[num_to_delete++] = b.buf; + b.buf = NULL; + TORRENT_PIECE_ASSERT(pe->num_blocks > 0, pe); + --pe->num_blocks; + TORRENT_PIECE_ASSERT(m_read_cache_size > 0, pe); + --m_read_cache_size; + TORRENT_PIECE_ASSERT(m_volatile_size > 0, pe); + --m_volatile_size; + } + + if (pe->ok_to_evict()) + { +#ifdef TORRENT_DEBUG + for (int j = 0; j < pe->blocks_in_piece; ++j) + TORRENT_PIECE_ASSERT(pe->blocks[j].buf == 0, pe); +#endif + move_to_ghost(pe); + } + + if (num_to_delete == 0) return; + + DLOG(stderr, "[%p] removed %d blocks\n", static_cast(this) + , num_to_delete); + + free_multiple_buffers(to_delete, num_to_delete); + return; + } +} + +cached_piece_entry* block_cache::allocate_piece(disk_io_job const* j, int cache_state) +{ +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + INVARIANT_CHECK; +#endif + + TORRENT_ASSERT(cache_state < cached_piece_entry::num_lrus); + + // we're assuming we're not allocating a ghost piece + // a bit further down + TORRENT_ASSERT(cache_state != cached_piece_entry::read_lru1_ghost + && cache_state != cached_piece_entry::read_lru2_ghost); + + cached_piece_entry* p = find_piece(j); + if (p == 0) + { + int const piece_size = j->storage->files()->piece_size(j->piece); + int const blocks_in_piece = (piece_size + block_size() - 1) / block_size(); + + cached_piece_entry pe; + pe.piece = j->piece; + pe.storage = j->storage; + pe.expire = aux::time_now(); + pe.blocks_in_piece = blocks_in_piece; + pe.blocks.reset(new (std::nothrow) cached_block_entry[blocks_in_piece]); + pe.cache_state = cache_state; + pe.last_requester = j->requester; + TORRENT_PIECE_ASSERT(pe.blocks, &pe); + if (!pe.blocks) return 0; + p = const_cast(&*m_pieces.insert(pe).first); + + j->storage->add_piece(p); + + TORRENT_PIECE_ASSERT(p->cache_state < cached_piece_entry::num_lrus, p); + linked_list* lru_list = &m_lru[p->cache_state]; + lru_list->push_back(p); + + // this piece is part of the ARC cache (as opposed to + // the write cache). Allocating a new read piece indicates + // that we just got a cache miss. Record this to determine + // which end to evict blocks from next time we need to + // evict blocks + if (cache_state == cached_piece_entry::read_lru1) + m_last_cache_op = cache_miss; + +#if TORRENT_USE_ASSERTS + switch (p->cache_state) + { + case cached_piece_entry::write_lru: + case cached_piece_entry::volatile_read_lru: + case cached_piece_entry::read_lru1: + case cached_piece_entry::read_lru2: + TORRENT_ASSERT(p->in_storage == true); + break; + default: + TORRENT_ASSERT(p->in_storage == false); + break; + } +#endif + } + else + { + TORRENT_PIECE_ASSERT(p->in_use, p); + + // we want to retain the piece now + p->marked_for_deletion = false; + + // only allow changing the cache state downwards. i.e. turn a ghost + // piece into a non-ghost, or a read piece into a write piece + if (p->cache_state > cache_state) + { + // this can happen for instance if a piece fails the hash check + // first it's in the write cache, then it completes and is moved + // into the read cache, but fails and is cleared (into the ghost list) + // then we want to add new dirty blocks to it and we need to move + // it back into the write cache + + // it also happens when pulling a ghost piece back into the proper cache + + if (p->cache_state == cached_piece_entry::read_lru1_ghost + || p->cache_state == cached_piece_entry::read_lru2_ghost) + { + // since it used to be a ghost piece, but no more, + // we need to add it back to the storage + p->storage->add_piece(p); + } + m_lru[p->cache_state].erase(p); + p->cache_state = cache_state; + m_lru[p->cache_state].push_back(p); + p->expire = aux::time_now(); +#if TORRENT_USE_ASSERTS + switch (p->cache_state) + { + case cached_piece_entry::write_lru: + case cached_piece_entry::volatile_read_lru: + case cached_piece_entry::read_lru1: + case cached_piece_entry::read_lru2: + TORRENT_ASSERT(p->in_storage == true); + break; + default: + TORRENT_ASSERT(p->in_storage == false); + break; + } +#endif + } + } + + return p; +} + +#if TORRENT_USE_ASSERTS +void block_cache::mark_deleted(file_storage const& fs) +{ + m_deleted_storages.push_back(std::make_pair(fs.name() + , reinterpret_cast(&fs))); + if (m_deleted_storages.size() > 100) + m_deleted_storages.erase(m_deleted_storages.begin()); +} +#endif + +cached_piece_entry* block_cache::add_dirty_block(disk_io_job* j) +{ +#if !defined TORRENT_DISABLE_POOL_ALLOCATOR + TORRENT_ASSERT(is_disk_buffer(j->buffer.disk_block)); +#endif +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + INVARIANT_CHECK; +#endif + +#if TORRENT_USE_ASSERTS + // we're not allowed to add dirty blocks + // for a deleted storage! + TORRENT_ASSERT(std::find(m_deleted_storages.begin(), m_deleted_storages.end() + , std::make_pair(j->storage->files()->name() + , static_cast(j->storage->files()))) + == m_deleted_storages.end()); +#endif + + TORRENT_ASSERT(j->buffer.disk_block); + TORRENT_ASSERT(m_write_cache_size + m_read_cache_size + 1 <= in_use()); + + cached_piece_entry* pe = allocate_piece(j, cached_piece_entry::write_lru); + TORRENT_ASSERT(pe); + if (pe == 0) return pe; + + TORRENT_PIECE_ASSERT(pe->in_use, pe); + + int block = j->d.io.offset / block_size(); + TORRENT_ASSERT((j->d.io.offset % block_size()) == 0); + + // we should never add a new dirty block on a piece + // that has checked the hash. Before we add it, the + // piece need to be cleared (with async_clear_piece) + TORRENT_PIECE_ASSERT(pe->hashing_done == 0, pe); + + // this only evicts read blocks + + int evict = num_to_evict(1); + if (evict > 0) try_evict_blocks(evict, pe); + + TORRENT_PIECE_ASSERT(block < pe->blocks_in_piece, pe); + TORRENT_PIECE_ASSERT(j->piece == pe->piece, pe); + TORRENT_PIECE_ASSERT(!pe->marked_for_deletion, pe); + + TORRENT_PIECE_ASSERT(pe->blocks[block].refcount == 0, pe); + + cached_block_entry& b = pe->blocks[block]; + + TORRENT_PIECE_ASSERT(b.buf != j->buffer.disk_block, pe); + + // we might have a left-over read block from + // hash checking + // we might also have a previous dirty block which + // we're still waiting for to be written + if (b.buf != 0 && b.buf != j->buffer.disk_block) + { + TORRENT_PIECE_ASSERT(b.refcount == 0 && !b.pending, pe); + free_block(pe, block); + TORRENT_PIECE_ASSERT(b.dirty == 0, pe); + } + + b.buf = j->buffer.disk_block; + + b.dirty = true; + ++pe->num_blocks; + ++pe->num_dirty; + ++m_write_cache_size; + j->buffer.disk_block = 0; + TORRENT_PIECE_ASSERT(j->piece == pe->piece, pe); + TORRENT_PIECE_ASSERT(j->flags & disk_io_job::in_progress, pe); + TORRENT_PIECE_ASSERT(j->piece == pe->piece, pe); + pe->jobs.push_back(j); + + if (block == 0 && pe->hash == NULL && pe->hashing_done == false) + pe->hash = new partial_hash; + + update_cache_state(pe); + + bump_lru(pe); + + return pe; +} + +// flushed is an array of num_flushed integers. Each integer is the block index +// that was flushed. This function marks those blocks as not pending and not +// dirty. It also adjusts its understanding of the read vs. write cache size +// (since these blocks now are part of the read cache) the refcounts of the +// blocks are also decremented by this function. They are expected to have been +// incremented by the caller. +void block_cache::blocks_flushed(cached_piece_entry* pe, int const* flushed, int num_flushed) +{ + TORRENT_PIECE_ASSERT(pe->in_use, pe); + + for (int i = 0; i < num_flushed; ++i) + { + int block = flushed[i]; + TORRENT_PIECE_ASSERT(block >= 0, pe); + TORRENT_PIECE_ASSERT(block < pe->blocks_in_piece, pe); + TORRENT_PIECE_ASSERT(pe->blocks[block].dirty, pe); + TORRENT_PIECE_ASSERT(pe->blocks[block].pending, pe); + pe->blocks[block].pending = false; + // it's important to mark it as non-dirty before decrementing the + // refcount because the buffer may be marked as discardable/volatile it + // this is the last reference to it + pe->blocks[block].dirty = false; + dec_block_refcount(pe, block, block_cache::ref_flushing); + } + + m_write_cache_size -= num_flushed; + m_read_cache_size += num_flushed; + pe->num_dirty -= num_flushed; + + update_cache_state(pe); +} + +std::pair block_cache::all_pieces() const +{ + return std::make_pair(m_pieces.begin(), m_pieces.end()); +} + +void block_cache::free_block(cached_piece_entry* pe, int block) +{ + TORRENT_ASSERT(pe != 0); + TORRENT_PIECE_ASSERT(pe->in_use, pe); + TORRENT_PIECE_ASSERT(block < pe->blocks_in_piece, pe); + TORRENT_PIECE_ASSERT(block >= 0, pe); + + cached_block_entry& b = pe->blocks[block]; + + TORRENT_PIECE_ASSERT(b.refcount == 0, pe); + TORRENT_PIECE_ASSERT(!b.pending, pe); + TORRENT_PIECE_ASSERT(b.buf, pe); + + if (b.dirty) + { + --pe->num_dirty; + b.dirty = false; + TORRENT_PIECE_ASSERT(m_write_cache_size > 0, pe); + --m_write_cache_size; + } + else + { + TORRENT_PIECE_ASSERT(m_read_cache_size > 0, pe); + --m_read_cache_size; + if (pe->cache_state == cached_piece_entry::volatile_read_lru) + { + --m_volatile_size; + } + } + + + TORRENT_PIECE_ASSERT(pe->num_blocks > 0, pe); + --pe->num_blocks; + free_buffer(b.buf); + b.buf = NULL; +} + +bool block_cache::evict_piece(cached_piece_entry* pe, tailqueue& jobs) +{ + INVARIANT_CHECK; + + TORRENT_PIECE_ASSERT(pe->in_use, pe); + + char** to_delete = TORRENT_ALLOCA(char*, pe->blocks_in_piece); + int num_to_delete = 0; + for (int i = 0; i < pe->blocks_in_piece; ++i) + { + if (pe->blocks[i].buf == 0 || pe->blocks[i].refcount > 0) continue; + TORRENT_PIECE_ASSERT(!pe->blocks[i].pending, pe); + TORRENT_PIECE_ASSERT(pe->blocks[i].buf != 0, pe); + TORRENT_PIECE_ASSERT(num_to_delete < pe->blocks_in_piece, pe); + to_delete[num_to_delete++] = pe->blocks[i].buf; + pe->blocks[i].buf = NULL; + TORRENT_PIECE_ASSERT(pe->num_blocks > 0, pe); + --pe->num_blocks; + if (!pe->blocks[i].dirty) + { + TORRENT_PIECE_ASSERT(m_read_cache_size > 0, pe); + --m_read_cache_size; + } + else + { + TORRENT_PIECE_ASSERT(pe->num_dirty > 0, pe); + --pe->num_dirty; + pe->blocks[i].dirty = false; + TORRENT_PIECE_ASSERT(m_write_cache_size > 0, pe); + --m_write_cache_size; + } + if (pe->num_blocks == 0) break; + } + + if (pe->cache_state == cached_piece_entry::volatile_read_lru) + { + m_volatile_size -= num_to_delete; + } + + if (num_to_delete) free_multiple_buffers(to_delete, num_to_delete); + + if (pe->ok_to_evict(true)) + { + delete pe->hash; + pe->hash = NULL; + + // append will move the items from pe->jobs onto the end of jobs + jobs.append(pe->jobs); + TORRENT_ASSERT(pe->jobs.size() == 0); + + if (pe->cache_state == cached_piece_entry::read_lru1_ghost + || pe->cache_state == cached_piece_entry::read_lru2_ghost) + return true; + + if (pe->cache_state == cached_piece_entry::write_lru + || pe->cache_state == cached_piece_entry::volatile_read_lru) + erase_piece(pe); + else + move_to_ghost(pe); + return true; + } + + return false; +} + +void block_cache::mark_for_deletion(cached_piece_entry* p) +{ + INVARIANT_CHECK; + + DLOG(stderr, "[%p] block_cache mark-for-deletion " + "piece: %d\n", static_cast(this), int(p->piece)); + + TORRENT_PIECE_ASSERT(p->jobs.empty(), p); + tailqueue jobs; + if (!evict_piece(p, jobs)) + { + p->marked_for_deletion = true; + } +} + +void block_cache::erase_piece(cached_piece_entry* pe) +{ + INVARIANT_CHECK; + + TORRENT_PIECE_ASSERT(pe->ok_to_evict(), pe); + TORRENT_PIECE_ASSERT(pe->cache_state < cached_piece_entry::num_lrus, pe); + TORRENT_PIECE_ASSERT(pe->jobs.empty(), pe); + linked_list* lru_list = &m_lru[pe->cache_state]; + if (pe->hash) + { + TORRENT_PIECE_ASSERT(pe->hash->offset == 0, pe); + delete pe->hash; + pe->hash = NULL; + } + if (pe->cache_state != cached_piece_entry::read_lru1_ghost + && pe->cache_state != cached_piece_entry::read_lru2_ghost) + pe->storage->remove_piece(pe); + lru_list->erase(pe); + m_pieces.erase(*pe); +} + +// this only evicts read blocks. For write blocks, see +// try_flush_write_blocks in disk_io_thread.cpp +int block_cache::try_evict_blocks(int num, cached_piece_entry* ignore) +{ + INVARIANT_CHECK; + + if (num <= 0) return 0; + + DLOG(stderr, "[%p] try_evict_blocks: %d\n", static_cast(this), num); + + char** to_delete = TORRENT_ALLOCA(char*, num); + int num_to_delete = 0; + + // There are two ends of the ARC cache we can evict from. There's L1 and L2. + // The last cache operation determines which end we'll evict from. If we go + // through the entire list from the preferred end, and still need to evict + // more blocks, we'll go to the other end and start evicting from there. The + // lru_list is an array of two lists, these are the two ends to evict from, + // ordered by preference. + + linked_list* lru_list[3]; + + // however, before we consider any of the proper LRU lists, we evict pieces + // from the volatile list. These are low priority pieces that were + // specifically marked as to not survive long in the cache. These are the + // first pieces to go when evicting + lru_list[0] = &m_lru[cached_piece_entry::volatile_read_lru]; + + if (m_last_cache_op == cache_miss) + { + // when there was a cache miss, evict from the largest list, to tend to + // keep the lists of equal size when we don't know which one is + // performing better + if (m_lru[cached_piece_entry::read_lru2].size() + > m_lru[cached_piece_entry::read_lru1].size()) + { + lru_list[1] = &m_lru[cached_piece_entry::read_lru2]; + lru_list[2] = &m_lru[cached_piece_entry::read_lru1]; + } + else + { + lru_list[1] = &m_lru[cached_piece_entry::read_lru1]; + lru_list[2] = &m_lru[cached_piece_entry::read_lru2]; + } + } + else if (m_last_cache_op == ghost_hit_lru1) + { + // when we insert new items or move things from L1 to L2 + // evict blocks from L2 + lru_list[1] = &m_lru[cached_piece_entry::read_lru2]; + lru_list[2] = &m_lru[cached_piece_entry::read_lru1]; + } + else + { + // when we get cache hits in L2 evict from L1 + lru_list[1] = &m_lru[cached_piece_entry::read_lru1]; + lru_list[2] = &m_lru[cached_piece_entry::read_lru2]; + } + + // end refers to which end of the ARC cache we're evicting + // from. The LFU or the LRU end + for (int end = 0; num > 0 && end < 3; ++end) + { + // iterate over all blocks in order of last being used (oldest first) and + // as long as we still have blocks to evict TODO: it's somewhat expensive + // to iterate over this linked list. Presumably because of the random + // access of memory. It would be nice if pieces with no evictable blocks + // weren't in this list + for (list_iterator i = lru_list[end]->iterate(); i.get() && num > 0;) + { + cached_piece_entry* pe = reinterpret_cast(i.get()); + TORRENT_PIECE_ASSERT(pe->in_use, pe); + i.next(); + + if (pe == ignore) + continue; + + if (pe->ok_to_evict()) + { +#ifdef TORRENT_DEBUG + for (int j = 0; j < pe->blocks_in_piece; ++j) + TORRENT_PIECE_ASSERT(pe->blocks[j].buf == 0, pe); +#endif + TORRENT_PIECE_ASSERT(pe->refcount == 0, pe); + move_to_ghost(pe); + continue; + } + + TORRENT_PIECE_ASSERT(pe->num_dirty == 0, pe); + + // all blocks are pinned in this piece, skip it + if (pe->num_blocks <= pe->pinned) continue; + + // go through the blocks and evict the ones that are not dirty and not + // referenced + int removed = 0; + for (int j = 0; j < pe->blocks_in_piece && num > 0; ++j) + { + cached_block_entry& b = pe->blocks[j]; + + if (b.buf == 0 || b.refcount > 0 || b.dirty || b.pending) continue; + + to_delete[num_to_delete++] = b.buf; + b.buf = NULL; + TORRENT_PIECE_ASSERT(pe->num_blocks > 0, pe); + --pe->num_blocks; + ++removed; + --num; + } + + TORRENT_PIECE_ASSERT(m_read_cache_size >= removed, pe); + m_read_cache_size -= removed; + if (pe->cache_state == cached_piece_entry::volatile_read_lru) + { + m_volatile_size -= removed; + } + + if (pe->ok_to_evict()) + { +#ifdef TORRENT_DEBUG + for (int j = 0; j < pe->blocks_in_piece; ++j) + TORRENT_PIECE_ASSERT(pe->blocks[j].buf == 0, pe); +#endif + move_to_ghost(pe); + } + } + } + + // if we can't evict enough blocks from the read cache, also look at write + // cache pieces for blocks that have already been written to disk and can be + // evicted the first pass, we only evict blocks that have been hashed, the + // second pass we flush anything this is potentially a very expensive + // operation, since we're likely to have iterate every single block in the + // cache, and we might not get to evict anything. + + // TODO: this should probably only be done every n:th time + if (num > 0 && m_read_cache_size > m_pinned_blocks) + { + for (int pass = 0; pass < 2 && num > 0; ++pass) + { + for (list_iterator i = m_lru[cached_piece_entry::write_lru].iterate(); i.get() && num > 0;) + { + cached_piece_entry* pe = reinterpret_cast(i.get()); + TORRENT_PIECE_ASSERT(pe->in_use, pe); + + i.next(); + + if (pe == ignore) + continue; + + if (pe->ok_to_evict()) + { +#ifdef TORRENT_DEBUG + for (int j = 0; j < pe->blocks_in_piece; ++j) + TORRENT_PIECE_ASSERT(pe->blocks[j].buf == 0, pe); +#endif + TORRENT_PIECE_ASSERT(pe->refcount == 0, pe); + erase_piece(pe); + continue; + } + + // all blocks in this piece are dirty + if (pe->num_dirty == pe->num_blocks) + continue; + + int end = pe->blocks_in_piece; + + // the first pass, only evict blocks that have been + // hashed + if (pass == 0 && pe->hash) + end = pe->hash->offset / block_size(); + + // go through the blocks and evict the ones + // that are not dirty and not referenced + int removed = 0; + for (int j = 0; j < end && num > 0; ++j) + { + cached_block_entry& b = pe->blocks[j]; + + if (b.buf == 0 || b.refcount > 0 || b.dirty || b.pending) continue; + + to_delete[num_to_delete++] = b.buf; + b.buf = NULL; + TORRENT_PIECE_ASSERT(pe->num_blocks > 0, pe); + --pe->num_blocks; + ++removed; + --num; + } + + TORRENT_PIECE_ASSERT(m_read_cache_size >= removed, pe); + m_read_cache_size -= removed; + if (pe->cache_state == cached_piece_entry::volatile_read_lru) + { + m_volatile_size -= removed; + } + + if (pe->ok_to_evict()) + { +#ifdef TORRENT_DEBUG + for (int j = 0; j < pe->blocks_in_piece; ++j) + TORRENT_PIECE_ASSERT(pe->blocks[j].buf == 0, pe); +#endif + erase_piece(pe); + } + } + } + } + + if (num_to_delete == 0) return num; + + DLOG(stderr, "[%p] removed %d blocks\n", static_cast(this) + , num_to_delete); + + free_multiple_buffers(to_delete, num_to_delete); + + return num; +} + +void block_cache::clear(tailqueue& jobs) +{ + INVARIANT_CHECK; + + // this holds all the block buffers we want to free + // at the end + std::vector bufs; + + for (iterator p = m_pieces.begin() + , end(m_pieces.end()); p != end; ++p) + { + cached_piece_entry& pe = const_cast(*p); +#if TORRENT_USE_ASSERTS + for (tailqueue_iterator i = pe.jobs.iterate(); i.get(); i.next()) + TORRENT_PIECE_ASSERT((static_cast(i.get()))->piece == pe.piece, &pe); + for (tailqueue_iterator i = pe.read_jobs.iterate(); i.get(); i.next()) + TORRENT_PIECE_ASSERT((static_cast(i.get()))->piece == pe.piece, &pe); +#endif + // this also removes the jobs from the piece + jobs.append(pe.jobs); + jobs.append(pe.read_jobs); + + drain_piece_bufs(pe, bufs); + } + + if (!bufs.empty()) free_multiple_buffers(&bufs[0], bufs.size()); + + // clear lru lists + for (int i = 0; i < cached_piece_entry::num_lrus; ++i) + m_lru[i].get_all(); + + m_pieces.clear(); +} + +void block_cache::move_to_ghost(cached_piece_entry* pe) +{ + TORRENT_PIECE_ASSERT(pe->refcount == 0, pe); + TORRENT_PIECE_ASSERT(pe->piece_refcount == 0, pe); + TORRENT_PIECE_ASSERT(pe->num_blocks == 0, pe); + TORRENT_PIECE_ASSERT(pe->in_use, pe); + + if (pe->cache_state == cached_piece_entry::volatile_read_lru) + { + erase_piece(pe); + return; + } + + TORRENT_PIECE_ASSERT(pe->cache_state == cached_piece_entry::read_lru1 + || pe->cache_state == cached_piece_entry::read_lru2, pe); + + // if the piece is in L1 or L2, move it into the ghost list + // i.e. recently evicted + if (pe->cache_state != cached_piece_entry::read_lru1 + && pe->cache_state != cached_piece_entry::read_lru2) + return; + + // if the ghost list is growing too big, remove the oldest entry + linked_list* ghost_list = &m_lru[pe->cache_state + 1]; + while (ghost_list->size() >= m_ghost_size) + { + cached_piece_entry* p = static_cast(ghost_list->front()); + TORRENT_PIECE_ASSERT(p != pe, p); + TORRENT_PIECE_ASSERT(p->num_blocks == 0, p); + TORRENT_PIECE_ASSERT(p->refcount == 0, p); + TORRENT_PIECE_ASSERT(p->piece_refcount == 0, p); + erase_piece(p); + } + + pe->storage->remove_piece(pe); + m_lru[pe->cache_state].erase(pe); + pe->cache_state += 1; + ghost_list->push_back(pe); +} + +int block_cache::pad_job(disk_io_job const* j, int blocks_in_piece + , int read_ahead) const +{ + int block_offset = j->d.io.offset & (block_size()-1); + int start = j->d.io.offset / block_size(); + int end = block_offset > 0 && (read_ahead > block_size() - block_offset) ? start + 2 : start + 1; + + // take the read-ahead into account + // make sure to not overflow in this case + if (read_ahead == INT_MAX) end = blocks_in_piece; + else end = (std::min)(blocks_in_piece, (std::max)(start + read_ahead, end)); + + return end - start; +} + +void block_cache::insert_blocks(cached_piece_entry* pe, int block, file::iovec_t *iov + , int iov_len, disk_io_job* j, int flags) +{ +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + INVARIANT_CHECK; +#endif + + TORRENT_ASSERT(pe); + TORRENT_ASSERT(pe->in_use); + TORRENT_PIECE_ASSERT(iov_len > 0, pe); + +#if TORRENT_USE_ASSERTS + // we're not allowed to add dirty blocks + // for a deleted storage! + TORRENT_ASSERT(std::find(m_deleted_storages.begin(), m_deleted_storages.end() + , std::make_pair(j->storage->files()->name(), static_cast(j->storage->files()))) + == m_deleted_storages.end()); +#endif + + cache_hit(pe, j->requester, (j->flags & disk_io_job::volatile_read) != 0); + + TORRENT_ASSERT(pe->in_use); + + for (int i = 0; i < iov_len; ++i, ++block) + { + // each iovec buffer has to be the size of a block (or the size of the last block) + TORRENT_PIECE_ASSERT(iov[i].iov_len == (std::min)(block_size() + , pe->storage->files()->piece_size(pe->piece) - block * block_size()), pe); + + // no NULL pointers allowed + TORRENT_ASSERT(iov[i].iov_base); + +#ifdef TORRENT_DEBUG_BUFFERS + TORRENT_PIECE_ASSERT(is_disk_buffer(static_cast(iov[i].iov_base)), pe); +#endif + + if (pe->blocks[block].buf && (flags & blocks_inc_refcount)) + { + inc_block_refcount(pe, block, ref_reading); + } + + // either free the block or insert it. Never replace a block + if (pe->blocks[block].buf) + { + free_buffer(static_cast(iov[i].iov_base)); + } + else + { + pe->blocks[block].buf = static_cast(iov[i].iov_base); + + TORRENT_PIECE_ASSERT(iov[i].iov_base != NULL, pe); + TORRENT_PIECE_ASSERT(pe->blocks[block].dirty == false, pe); + ++pe->num_blocks; + ++m_read_cache_size; + if (j->flags & disk_io_job::volatile_read) ++m_volatile_size; + + if (flags & blocks_inc_refcount) + { + bool ret = inc_block_refcount(pe, block, ref_reading); + TORRENT_UNUSED(ret); // suppress warning + TORRENT_ASSERT(ret); + } + } + + TORRENT_ASSERT(pe->blocks[block].buf != NULL); + } + + TORRENT_PIECE_ASSERT(pe->cache_state != cached_piece_entry::read_lru1_ghost, pe); + TORRENT_PIECE_ASSERT(pe->cache_state != cached_piece_entry::read_lru2_ghost, pe); +} + +// return false if the memory was purged +bool block_cache::inc_block_refcount(cached_piece_entry* pe, int block, int reason) +{ + TORRENT_PIECE_ASSERT(pe->in_use, pe); + TORRENT_PIECE_ASSERT(block < pe->blocks_in_piece, pe); + TORRENT_PIECE_ASSERT(block >= 0, pe); + if (pe->blocks[block].buf == NULL) return false; + TORRENT_PIECE_ASSERT(pe->blocks[block].refcount < cached_block_entry::max_refcount, pe); + if (pe->blocks[block].refcount == 0) + { + ++pe->pinned; + ++m_pinned_blocks; + } + ++pe->blocks[block].refcount; + ++pe->refcount; +#if TORRENT_USE_ASSERTS + switch (reason) + { + case ref_hashing: ++pe->blocks[block].hashing_count; break; + case ref_reading: ++pe->blocks[block].reading_count; break; + case ref_flushing: ++pe->blocks[block].flushing_count; break; + }; + TORRENT_ASSERT(pe->blocks[block].refcount >= pe->blocks[block].hashing_count + + pe->blocks[block].reading_count + pe->blocks[block].flushing_count); +#else + TORRENT_UNUSED(reason); +#endif + return true; +} + +void block_cache::dec_block_refcount(cached_piece_entry* pe, int block, int reason) +{ + TORRENT_PIECE_ASSERT(pe->in_use, pe); + TORRENT_PIECE_ASSERT(block < pe->blocks_in_piece, pe); + TORRENT_PIECE_ASSERT(block >= 0, pe); + + TORRENT_PIECE_ASSERT(pe->blocks[block].buf != NULL, pe); + TORRENT_PIECE_ASSERT(pe->blocks[block].refcount > 0, pe); + --pe->blocks[block].refcount; + TORRENT_PIECE_ASSERT(pe->refcount > 0, pe); + --pe->refcount; + if (pe->blocks[block].refcount == 0) + { + TORRENT_PIECE_ASSERT(pe->pinned > 0, pe); + --pe->pinned; + TORRENT_PIECE_ASSERT(m_pinned_blocks > 0, pe); + --m_pinned_blocks; + } +#if TORRENT_USE_ASSERTS + switch (reason) + { + case ref_hashing: --pe->blocks[block].hashing_count; break; + case ref_reading: --pe->blocks[block].reading_count; break; + case ref_flushing: --pe->blocks[block].flushing_count; break; + }; + TORRENT_PIECE_ASSERT(pe->blocks[block].refcount >= pe->blocks[block].hashing_count + + pe->blocks[block].reading_count + pe->blocks[block].flushing_count, pe); +#else + TORRENT_UNUSED(reason); +#endif +} + +void block_cache::abort_dirty(cached_piece_entry* pe) +{ + INVARIANT_CHECK; + + TORRENT_PIECE_ASSERT(pe->in_use, pe); + + char** to_delete = TORRENT_ALLOCA(char*, pe->blocks_in_piece); + int num_to_delete = 0; + for (int i = 0; i < pe->blocks_in_piece; ++i) + { + if (!pe->blocks[i].dirty + || pe->blocks[i].refcount > 0 + || pe->blocks[i].buf == NULL) continue; + + TORRENT_PIECE_ASSERT(!pe->blocks[i].pending, pe); + TORRENT_PIECE_ASSERT(pe->blocks[i].dirty, pe); + to_delete[num_to_delete++] = pe->blocks[i].buf; + pe->blocks[i].buf = NULL; + pe->blocks[i].dirty = false; + TORRENT_PIECE_ASSERT(pe->num_blocks > 0, pe); + --pe->num_blocks; + TORRENT_PIECE_ASSERT(m_write_cache_size > 0, pe); + --m_write_cache_size; + TORRENT_PIECE_ASSERT(pe->num_dirty > 0, pe); + --pe->num_dirty; + } + if (num_to_delete) free_multiple_buffers(to_delete, num_to_delete); + + update_cache_state(pe); +} + +// frees all buffers associated with this piece. May only +// be called for pieces with a refcount of 0 +void block_cache::free_piece(cached_piece_entry* pe) +{ + INVARIANT_CHECK; + + TORRENT_PIECE_ASSERT(pe->in_use, pe); + + TORRENT_PIECE_ASSERT(pe->refcount == 0, pe); + TORRENT_PIECE_ASSERT(pe->piece_refcount == 0, pe); + TORRENT_PIECE_ASSERT(pe->outstanding_read == 0, pe); + + // build a vector of all the buffers we need to free + // and free them all in one go + char** to_delete = TORRENT_ALLOCA(char*, pe->blocks_in_piece); + int num_to_delete = 0; + int removed_clean = 0; + for (int i = 0; i < pe->blocks_in_piece; ++i) + { + if (pe->blocks[i].buf == 0) continue; + TORRENT_PIECE_ASSERT(pe->blocks[i].pending == false, pe); + TORRENT_PIECE_ASSERT(pe->blocks[i].refcount == 0, pe); + TORRENT_PIECE_ASSERT(num_to_delete < pe->blocks_in_piece, pe); + to_delete[num_to_delete++] = pe->blocks[i].buf; + pe->blocks[i].buf = NULL; + TORRENT_PIECE_ASSERT(pe->num_blocks > 0, pe); + --pe->num_blocks; + if (pe->blocks[i].dirty) + { + TORRENT_PIECE_ASSERT(m_write_cache_size > 0, pe); + --m_write_cache_size; + TORRENT_PIECE_ASSERT(pe->num_dirty > 0, pe); + --pe->num_dirty; + } + else + { + ++removed_clean; + } + } + + TORRENT_PIECE_ASSERT(m_read_cache_size >= removed_clean, pe); + m_read_cache_size -= removed_clean; + if (pe->cache_state == cached_piece_entry::volatile_read_lru) + { + m_volatile_size -= num_to_delete; + } + if (num_to_delete) free_multiple_buffers(to_delete, num_to_delete); + update_cache_state(pe); +} + +int block_cache::drain_piece_bufs(cached_piece_entry& p, std::vector& buf) +{ + int const piece_size = p.storage->files()->piece_size(p.piece); + int const blocks_in_piece = (piece_size + block_size() - 1) / block_size(); + int ret = 0; + + TORRENT_PIECE_ASSERT(p.in_use, &p); + + int removed_clean = 0; + for (int i = 0; i < blocks_in_piece; ++i) + { + if (p.blocks[i].buf == 0) continue; + TORRENT_PIECE_ASSERT(p.blocks[i].refcount == 0, &p); + buf.push_back(p.blocks[i].buf); + ++ret; + p.blocks[i].buf = NULL; + TORRENT_PIECE_ASSERT(p.num_blocks > 0, &p); + --p.num_blocks; + + if (p.blocks[i].dirty) + { + TORRENT_ASSERT(m_write_cache_size > 0); + --m_write_cache_size; + TORRENT_PIECE_ASSERT(p.num_dirty > 0, &p); + --p.num_dirty; + } + else + { + ++removed_clean; + } + } + + TORRENT_ASSERT(m_read_cache_size >= removed_clean); + m_read_cache_size -= removed_clean; + if (p.cache_state == cached_piece_entry::volatile_read_lru) + { + m_volatile_size -= removed_clean; + } + + update_cache_state(&p); + return ret; +} + +void block_cache::update_stats_counters(counters& c) const +{ + c.set_value(counters::write_cache_blocks, m_write_cache_size); + c.set_value(counters::read_cache_blocks, m_read_cache_size); + c.set_value(counters::pinned_blocks, m_pinned_blocks); + + c.set_value(counters::arc_mru_size, m_lru[cached_piece_entry::read_lru1].size()); + c.set_value(counters::arc_mru_ghost_size, m_lru[cached_piece_entry::read_lru1_ghost].size()); + c.set_value(counters::arc_mfu_size, m_lru[cached_piece_entry::read_lru2].size()); + c.set_value(counters::arc_mfu_ghost_size, m_lru[cached_piece_entry::read_lru2_ghost].size()); + c.set_value(counters::arc_write_size, m_lru[cached_piece_entry::write_lru].size()); + c.set_value(counters::arc_volatile_size, m_lru[cached_piece_entry::volatile_read_lru].size()); +} + +#ifndef TORRENT_NO_DEPRECATE +void block_cache::get_stats(cache_status* ret) const +{ + ret->write_cache_size = m_write_cache_size; + ret->read_cache_size = m_read_cache_size; + ret->pinned_blocks = m_pinned_blocks; + ret->cache_size = m_read_cache_size + m_write_cache_size; + + ret->arc_mru_size = m_lru[cached_piece_entry::read_lru1].size(); + ret->arc_mru_ghost_size = m_lru[cached_piece_entry::read_lru1_ghost].size(); + ret->arc_mfu_size = m_lru[cached_piece_entry::read_lru2].size(); + ret->arc_mfu_ghost_size = m_lru[cached_piece_entry::read_lru2_ghost].size(); + ret->arc_write_size = m_lru[cached_piece_entry::write_lru].size(); + ret->arc_volatile_size = m_lru[cached_piece_entry::volatile_read_lru].size(); +} +#endif + +void block_cache::set_settings(aux::session_settings const& sett, error_code& ec) +{ + // the ghost size is the number of pieces to keep track of + // after they are evicted. Since cache_size is blocks, the + // assumption is that there are about 128 blocks per piece, + // and there are two ghost lists, so divide by 2. + + m_ghost_size = (std::max)(8, sett.get_int(settings_pack::cache_size) + / (std::max)(sett.get_int(settings_pack::read_cache_line_size), 4) / 2); + + m_max_volatile_blocks = sett.get_int(settings_pack::cache_size_volatile); + disk_buffer_pool::set_settings(sett, ec); +} + +#if TORRENT_USE_INVARIANT_CHECKS +void block_cache::check_invariant() const +{ + int cached_write_blocks = 0; + int cached_read_blocks = 0; + int num_pinned = 0; + + std::set storages; + + for (int i = 0; i < cached_piece_entry::num_lrus; ++i) + { + time_point timeout = min_time(); + + for (list_iterator p = m_lru[i].iterate(); p.get(); p.next()) + { + cached_piece_entry* pe = static_cast(p.get()); + TORRENT_PIECE_ASSERT(pe->cache_state == i, pe); + if (pe->num_dirty > 0) + TORRENT_PIECE_ASSERT(i == cached_piece_entry::write_lru, pe); + +// if (i == cached_piece_entry::write_lru) +// TORRENT_ASSERT(pe->num_dirty > 0); + for (tailqueue_iterator j = pe->jobs.iterate(); j.get(); j.next()) + { + disk_io_job const* job = static_cast(j.get()); + TORRENT_PIECE_ASSERT(job->piece == pe->piece, pe); + TORRENT_PIECE_ASSERT(job->in_use, pe); + TORRENT_PIECE_ASSERT(!job->callback_called, pe); + } + + if (i != cached_piece_entry::read_lru1_ghost + && i != cached_piece_entry::read_lru2_ghost) + { + TORRENT_PIECE_ASSERT(pe->storage->has_piece(pe), pe); + TORRENT_PIECE_ASSERT(pe->expire >= timeout, pe); + timeout = pe->expire; + TORRENT_PIECE_ASSERT(pe->in_storage, pe); + TORRENT_PIECE_ASSERT(pe->storage->has_piece(pe), pe); + } + else + { + // pieces in the ghost lists should never have any blocks + TORRENT_PIECE_ASSERT(pe->num_blocks == 0, pe); + TORRENT_PIECE_ASSERT(pe->storage->has_piece(pe) == false, pe); + } + + storages.insert(pe->storage.get()); + } + } + + for (std::set::iterator i = storages.begin() + , end(storages.end()); i != end; ++i) + { + for (boost::unordered_set::iterator j = (*i)->cached_pieces().begin() + , end2((*i)->cached_pieces().end()); j != end2; ++j) + { + cached_piece_entry* pe = *j; + TORRENT_PIECE_ASSERT(pe->storage.get() == *i, pe); + } + } + + boost::unordered_set buffers; + for (iterator i = m_pieces.begin(), end(m_pieces.end()); i != end; ++i) + { + cached_piece_entry const& p = *i; + TORRENT_PIECE_ASSERT(p.blocks, &p); + + TORRENT_PIECE_ASSERT(p.storage, &p); + int num_blocks = 0; + int num_dirty = 0; + int num_pending = 0; + int num_refcount = 0; + + bool in_storage = p.storage->has_piece(&p); + switch (p.cache_state) + { + case cached_piece_entry::write_lru: + case cached_piece_entry::volatile_read_lru: + case cached_piece_entry::read_lru1: + case cached_piece_entry::read_lru2: + TORRENT_ASSERT(in_storage == true); + break; + default: + TORRENT_ASSERT(in_storage == false); + break; + } + + for (int k = 0; k < p.blocks_in_piece; ++k) + { + if (p.blocks[k].buf) + { +#if !defined TORRENT_DISABLE_POOL_ALLOCATOR && defined TORRENT_EXPENSIVE_INVARIANT_CHECKS + TORRENT_PIECE_ASSERT(is_disk_buffer(p.blocks[k].buf), &p); + + // make sure we don't have the same buffer + // in the cache twice + TORRENT_PIECE_ASSERT(buffers.count(p.blocks[k].buf) == 0, &p); + buffers.insert(p.blocks[k].buf); +#endif + ++num_blocks; + if (p.blocks[k].dirty) + { + ++num_dirty; + ++cached_write_blocks; + } + else + { + ++cached_read_blocks; + } + if (p.blocks[k].pending) ++num_pending; + if (p.blocks[k].refcount > 0) ++num_pinned; + + TORRENT_PIECE_ASSERT(p.blocks[k].refcount >= + p.blocks[k].hashing_count + + p.blocks[k].reading_count + + p.blocks[k].flushing_count, &p); + + } + else + { + TORRENT_PIECE_ASSERT(!p.blocks[k].dirty, &p); + TORRENT_PIECE_ASSERT(!p.blocks[k].pending, &p); + TORRENT_PIECE_ASSERT(p.blocks[k].refcount == 0, &p); + } + num_refcount += p.blocks[k].refcount; + } + TORRENT_PIECE_ASSERT(num_blocks == p.num_blocks, &p); + TORRENT_PIECE_ASSERT(num_pending <= p.refcount, &p); + TORRENT_PIECE_ASSERT(num_refcount == p.refcount, &p); + TORRENT_PIECE_ASSERT(num_dirty == p.num_dirty, &p); + } + TORRENT_ASSERT(m_read_cache_size == cached_read_blocks); + TORRENT_ASSERT(m_write_cache_size == cached_write_blocks); + TORRENT_ASSERT(m_pinned_blocks == num_pinned); + TORRENT_ASSERT(m_write_cache_size + m_read_cache_size <= in_use()); +} +#endif + +// TODO: 2 turn these return values into enums +// returns +// -1: block not in cache +// -2: out of memory + +int block_cache::copy_from_piece(cached_piece_entry* const pe + , disk_io_job* const j + , bool const expect_no_fail) +{ + INVARIANT_CHECK; + TORRENT_UNUSED(expect_no_fail); + + TORRENT_PIECE_ASSERT(j->buffer.disk_block == 0, pe); + TORRENT_PIECE_ASSERT(pe->in_use, pe); + + // copy from the cache and update the last use timestamp + int block = j->d.io.offset / block_size(); + int block_offset = j->d.io.offset & (block_size()-1); + int buffer_offset = 0; + int size = j->d.io.buffer_size; + int const blocks_to_read = block_offset > 0 && (size > block_size() - block_offset) ? 2 : 1; + TORRENT_PIECE_ASSERT(size <= block_size(), pe); + int const start_block = block; + +#if TORRENT_USE_ASSERTS + int const piece_size = j->storage->files()->piece_size(j->piece); + int const blocks_in_piece = (piece_size + block_size() - 1) / block_size(); + TORRENT_PIECE_ASSERT(start_block < blocks_in_piece, pe); +#endif + + // if there's no buffer, we don't have this block in + // the cache, and we're not currently reading it in either + // since it's not pending + + if (inc_block_refcount(pe, start_block, ref_reading) == false) + { + TORRENT_ASSERT(!expect_no_fail); + return -1; + } + + // if block_offset > 0, we need to read two blocks, and then + // copy parts of both, because it's not aligned to the block + // boundaries + if (blocks_to_read == 1 && (j->flags & disk_io_job::force_copy) == 0) + { + // special case for block aligned request + // don't actually copy the buffer, just reference + // the existing block. Which means we don't want to decrement the + // refcount, we're handing the ownership of the reference to the calling + // thread. + cached_block_entry& bl = pe->blocks[start_block]; + + // make sure it didn't wrap + TORRENT_PIECE_ASSERT(pe->refcount > 0, pe); + j->d.io.ref.storage = j->storage.get(); + j->d.io.ref.piece = pe->piece; + j->d.io.ref.block = start_block; + j->buffer.disk_block = bl.buf + (j->d.io.offset & (block_size()-1)); + ++m_send_buffer_blocks; + return j->d.io.buffer_size; + } + + // if we don't have the second block, it's a cache miss + if (blocks_to_read == 2 && inc_block_refcount(pe, start_block + 1, ref_reading) == false) + { + TORRENT_ASSERT(!expect_no_fail); + dec_block_refcount(pe, start_block, ref_reading); + return -1; + } + + j->buffer.disk_block = allocate_buffer("send buffer"); + if (j->buffer.disk_block == 0) return -2; + + while (size > 0) + { + TORRENT_PIECE_ASSERT(pe->blocks[block].buf, pe); + int to_copy = (std::min)(block_size() + - block_offset, size); + std::memcpy(j->buffer.disk_block + buffer_offset + , pe->blocks[block].buf + block_offset + , to_copy); + size -= to_copy; + block_offset = 0; + buffer_offset += to_copy; + ++block; + } + // we incremented the refcount for both of these blocks. + // now decrement it. + // TODO: create a holder for refcounts that automatically decrement + dec_block_refcount(pe, start_block, ref_reading); + if (blocks_to_read == 2) dec_block_refcount(pe, start_block + 1, ref_reading); + return j->d.io.buffer_size; +} + +void block_cache::reclaim_block(block_cache_reference const& ref) +{ + cached_piece_entry* pe = find_piece(ref); + TORRENT_ASSERT(pe); + if (pe == NULL) return; + + TORRENT_PIECE_ASSERT(pe->in_use, pe); + + TORRENT_PIECE_ASSERT(pe->blocks[ref.block].buf, pe); + dec_block_refcount(pe, ref.block, block_cache::ref_reading); + + TORRENT_PIECE_ASSERT(m_send_buffer_blocks > 0, pe); + --m_send_buffer_blocks; + + maybe_free_piece(pe); +} + +bool block_cache::maybe_free_piece(cached_piece_entry* pe) +{ + if (!pe->ok_to_evict() + || !pe->marked_for_deletion + || !pe->jobs.empty()) + return false; + + DLOG(stderr, "[%p] block_cache maybe_free_piece " + "piece: %d refcount: %d marked_for_deletion: %d\n" + , static_cast(this) + , int(pe->piece), int(pe->refcount), int(pe->marked_for_deletion)); + + tailqueue jobs; + bool removed = evict_piece(pe, jobs); + TORRENT_UNUSED(removed); // suppress warning + TORRENT_PIECE_ASSERT(removed, pe); + TORRENT_PIECE_ASSERT(jobs.empty(), pe); + + return true; +} + +cached_piece_entry* block_cache::find_piece(block_cache_reference const& ref) +{ + return find_piece(static_cast(ref.storage), ref.piece); +} + +cached_piece_entry* block_cache::find_piece(disk_io_job const* j) +{ + return find_piece(j->storage.get(), j->piece); +} + +cached_piece_entry* block_cache::find_piece(piece_manager* st, int piece) +{ + cached_piece_entry model; + model.storage = st->shared_from_this(); + model.piece = piece; + iterator i = m_pieces.find(model); + TORRENT_ASSERT(i == m_pieces.end() || (i->storage.get() == st && i->piece == piece)); + if (i == m_pieces.end()) return 0; + TORRENT_PIECE_ASSERT(i->in_use, &*i); + +#if TORRENT_USE_ASSERTS + for (tailqueue_iterator j = i->jobs.iterate(); j.get(); j.next()) + { + disk_io_job const* job = static_cast(j.get()); + TORRENT_PIECE_ASSERT(job->piece == piece, &*i); + } +#endif + + return const_cast(&*i); +} + +} + diff --git a/src/bloom_filter.cpp b/src/bloom_filter.cpp new file mode 100644 index 0000000..29a0da1 --- /dev/null +++ b/src/bloom_filter.cpp @@ -0,0 +1,76 @@ +/* + +Copyright (c) 2010-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/bloom_filter.hpp" + +namespace libtorrent +{ + bool has_bits(boost::uint8_t const* k, boost::uint8_t const* bits, int len) + { + boost::uint32_t idx1 = boost::uint32_t(k[0]) | (boost::uint32_t(k[1]) << 8); + boost::uint32_t idx2 = boost::uint32_t(k[2]) | (boost::uint32_t(k[3]) << 8); + idx1 %= len * 8; + idx2 %= len * 8; + return (bits[idx1/8] & (1 << (idx1 & 7))) != 0 + && (bits[idx2/8] & (1 << (idx2 & 7))) != 0; + } + + void set_bits(boost::uint8_t const* k, boost::uint8_t* bits, int len) + { + boost::uint32_t idx1 = boost::uint32_t(k[0]) | (boost::uint32_t(k[1]) << 8); + boost::uint32_t idx2 = boost::uint32_t(k[2]) | (boost::uint32_t(k[3]) << 8); + idx1 %= len * 8; + idx2 %= len * 8; + bits[idx1/8] |= (1 << (idx1 & 7)); + bits[idx2/8] |= (1 << (idx2 & 7)); + } + + int count_zero_bits(boost::uint8_t const* bits, int len) + { + // number of bits _not_ set in a nibble + boost::uint8_t bitcount[16] = + { + // 0000, 0001, 0010, 0011, 0100, 0101, 0110, 0111, + // 1000, 1001, 1010, 1011, 1100, 1101, 1110, 1111 + 4, 3, 3, 2, 3, 2, 2, 1, + 3, 2, 2, 1, 2, 1, 1, 0 + }; + int ret = 0; + for (int i = 0; i < len; ++i) + { + ret += bitcount[bits[i] & 0xf]; + ret += bitcount[(bits[i] >> 4) & 0xf]; + } + return ret; + } +} + diff --git a/src/broadcast_socket.cpp b/src/broadcast_socket.cpp new file mode 100644 index 0000000..007808a --- /dev/null +++ b/src/broadcast_socket.cpp @@ -0,0 +1,438 @@ +/* + +Copyright (c) 2007-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 "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#if defined TORRENT_OS2 +#include +#endif + +#include +#include +#include + +#ifdef TORRENT_WINDOWS +#include // for if_nametoindex +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/socket.hpp" +#include "libtorrent/enum_net.hpp" +#include "libtorrent/broadcast_socket.hpp" +#include "libtorrent/assert.hpp" + +#if defined TORRENT_ASIO_DEBUGGING +#include "libtorrent/debug.hpp" +#endif + +#ifdef TORRENT_DEBUG +#include "libtorrent/socket_io.hpp" +#endif + +namespace libtorrent +{ + bool is_ip_address(char const* host) + { + error_code ec; + address::from_string(host, ec); + return !ec; + } + + bool is_local(address const& a) + { + TORRENT_TRY { +#if TORRENT_USE_IPV6 + if (a.is_v6()) + { + return a.to_v6().is_loopback() + || a.to_v6().is_link_local() + || a.to_v6().is_multicast_link_local(); + } +#endif + address_v4 a4 = a.to_v4(); + unsigned long ip = a4.to_ulong(); + return ((ip & 0xff000000) == 0x0a000000 // 10.x.x.x + || (ip & 0xfff00000) == 0xac100000 // 172.16.x.x + || (ip & 0xffff0000) == 0xc0a80000 // 192.168.x.x + || (ip & 0xffff0000) == 0xa9fe0000 // 169.254.x.x + || (ip & 0xff000000) == 0x7f000000); // 127.x.x.x + } TORRENT_CATCH(std::exception&) { return false; } + } + + bool is_loopback(address const& addr) + { +#if TORRENT_USE_IPV6 + TORRENT_TRY { + if (addr.is_v4()) + return addr.to_v4() == address_v4::loopback(); + else + return addr.to_v6() == address_v6::loopback(); + } TORRENT_CATCH(std::exception&) { return false; } +#else + return addr.to_v4() == address_v4::loopback(); +#endif + } + + bool is_multicast(address const& addr) + { +#if TORRENT_USE_IPV6 + TORRENT_TRY { + if (addr.is_v4()) + return addr.to_v4().is_multicast(); + else + return addr.to_v6().is_multicast(); + } TORRENT_CATCH(std::exception&) { return false; } +#else + return addr.to_v4().is_multicast(); +#endif + } + + bool is_any(address const& addr) + { + TORRENT_TRY { +#if TORRENT_USE_IPV6 + if (addr.is_v4()) + return addr.to_v4() == address_v4::any(); + else if (addr.to_v6().is_v4_mapped()) + return (addr.to_v6().to_v4() == address_v4::any()); + else + return addr.to_v6() == address_v6::any(); +#else + return addr.to_v4() == address_v4::any(); +#endif + } TORRENT_CATCH(std::exception&) { return false; } + } + + bool is_teredo(address const& addr) + { +#if TORRENT_USE_IPV6 + TORRENT_TRY { + if (!addr.is_v6()) return false; + boost::uint8_t teredo_prefix[] = {0x20, 0x01, 0, 0}; + address_v6::bytes_type b = addr.to_v6().to_bytes(); + return memcmp(&b[0], teredo_prefix, 4) == 0; + } TORRENT_CATCH(std::exception&) { return false; } +#else + TORRENT_UNUSED(addr); + return false; +#endif + } + + bool supports_ipv6() + { +#if !TORRENT_USE_IPV6 + return false; +#elif defined TORRENT_BUILD_SIMULATOR + return true; +#elif defined TORRENT_WINDOWS + TORRENT_TRY { + error_code ec; + address::from_string("::1", ec); + return !ec; + } TORRENT_CATCH(std::exception&) { return false; } +#else + io_service ios; + tcp::socket test(ios); + error_code ec; + test.open(tcp::v6(), ec); + return !bool(ec); +#endif + } + + // count the length of the common bit prefix + int common_bits(unsigned char const* b1 + , unsigned char const* b2, int n) + { + for (int i = 0; i < n; ++i, ++b1, ++b2) + { + unsigned char a = *b1 ^ *b2; + if (a == 0) continue; + int ret = i * 8 + 8; + for (; a > 0; a >>= 1) --ret; + return ret; + } + return n * 8; + } + + // returns the number of bits in that differ from the right + // between the addresses. The larger number, the further apart + // the IPs are + int cidr_distance(address const& a1, address const& a2) + { +#if TORRENT_USE_IPV6 + if (a1.is_v4() && a2.is_v4()) + { +#endif + // both are v4 + address_v4::bytes_type b1 = a1.to_v4().to_bytes(); + address_v4::bytes_type b2 = a2.to_v4().to_bytes(); + return address_v4::bytes_type().size() * 8 + - common_bits(b1.data(), b2.data(), b1.size()); +#if TORRENT_USE_IPV6 + } + + address_v6::bytes_type b1; + address_v6::bytes_type b2; + if (a1.is_v4()) b1 = address_v6::v4_mapped(a1.to_v4()).to_bytes(); + else b1 = a1.to_v6().to_bytes(); + if (a2.is_v4()) b2 = address_v6::v4_mapped(a2.to_v4()).to_bytes(); + else b2 = a2.to_v6().to_bytes(); + return address_v6::bytes_type().size() * 8 + - common_bits(b1.data(), b2.data(), b1.size()); +#endif + } + + broadcast_socket::broadcast_socket( + udp::endpoint const& multicast_endpoint) + : m_multicast_endpoint(multicast_endpoint) + , m_outstanding_operations(0) + , m_abort(false) + { + TORRENT_ASSERT(is_multicast(m_multicast_endpoint.address())); + + using namespace boost::asio::ip::multicast; + } + + void broadcast_socket::open(receive_handler_t const& handler + , io_service& ios, error_code& ec, bool loopback) + { + m_on_receive = handler; + + std::vector interfaces = enum_net_interfaces(ios, ec); + +#if TORRENT_USE_IPV6 + if (m_multicast_endpoint.address().is_v6()) + open_multicast_socket(ios, address_v6::any(), loopback, ec); + else +#endif + open_multicast_socket(ios, address_v4::any(), loopback, ec); + + for (std::vector::const_iterator i = interfaces.begin() + , end(interfaces.end()); i != end; ++i) + { + // only multicast on compatible networks + if (i->interface_address.is_v4() != m_multicast_endpoint.address().is_v4()) continue; + // ignore any loopback interface + if (!loopback && is_loopback(i->interface_address)) continue; + + ec = error_code(); + + // if_nametoindex was introduced in vista +#if TORRENT_USE_IPV6 \ + && (!defined TORRENT_WINDOWS || _WIN32_WINNT >= 0x0600) \ + && !defined TORRENT_MINGW + + if (i->interface_address.is_v6() && + i->interface_address.to_v6().is_link_local()) + { + address_v6 addr6 = i->interface_address.to_v6(); + addr6.scope_id(if_nametoindex(i->name)); + open_multicast_socket(ios, addr6, loopback, ec); + + address_v4 const& mask = i->netmask.is_v4() + ? i->netmask.to_v4() : address_v4(); + open_unicast_socket(ios, addr6, mask); + continue; + } + +#endif + open_multicast_socket(ios, i->interface_address, loopback, ec); +#ifdef TORRENT_DEBUG + fprintf(stderr, "broadcast socket [ if: %s group: %s mask: %s ] %s\n" + , i->interface_address.to_string().c_str() + , m_multicast_endpoint.address().to_string().c_str() + , i->netmask.to_string().c_str() + , ec.message().c_str()); +#endif + open_unicast_socket(ios, i->interface_address + , i->netmask.is_v4() ? i->netmask.to_v4() : address_v4()); + } + } + + void broadcast_socket::open_multicast_socket(io_service& ios + , address const& addr, bool loopback, error_code& ec) + { + using namespace boost::asio::ip::multicast; + + boost::shared_ptr s(new udp::socket(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.push_back(socket_entry(s)); + socket_entry& se = m_sockets.back(); +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("broadcast_socket::on_receive"); +#endif + s->async_receive_from(boost::asio::buffer(se.buffer, sizeof(se.buffer)) + , se.remote, boost::bind(&broadcast_socket::on_receive, this, &se, _1, _2)); + ++m_outstanding_operations; + } + + void broadcast_socket::open_unicast_socket(io_service& ios, address const& addr + , address_v4 const& mask) + { + using namespace boost::asio::ip::multicast; + error_code ec; + boost::shared_ptr s(new udp::socket(ios)); + s->open(addr.is_v4() ? udp::v4() : udp::v6(), ec); + if (ec) return; + s->bind(udp::endpoint(addr, 0), ec); + if (ec) return; + + m_unicast_sockets.push_back(socket_entry(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; + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("broadcast_socket::on_receive"); +#endif + s->async_receive_from(boost::asio::buffer(se.buffer, sizeof(se.buffer)) + , se.remote, boost::bind(&broadcast_socket::on_receive, this, &se, _1, _2)); + ++m_outstanding_operations; + } + + void broadcast_socket::send(char const* buffer, int size, error_code& ec, int flags) + { + bool all_fail = true; + error_code e; + + for (std::list::iterator i = m_unicast_sockets.begin() + , end(m_unicast_sockets.end()); i != end; ++i) + { + if (!i->socket) continue; + i->socket->send_to(boost::asio::buffer(buffer, 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::broadcast) && i->can_broadcast()) + i->socket->send_to(boost::asio::buffer(buffer, size) + , udp::endpoint(i->broadcast_address(), m_multicast_endpoint.port()), 0, e); + + if (e) + { + i->socket->close(e); + i->socket.reset(); + } + else + { + all_fail = false; + } + } + + for (std::list::iterator i = m_sockets.begin() + , end(m_sockets.end()); i != end; ++i) + { + if (!i->socket) continue; + i->socket->send_to(boost::asio::buffer(buffer, size), m_multicast_endpoint, 0, e); + if (e) + { + i->socket->close(e); + i->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) + { +#if defined TORRENT_ASIO_DEBUGGING + complete_async("broadcast_socket::on_receive"); +#endif + 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, bytes_transferred); + + if (maybe_abort()) return; + if (!s->socket) return; +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("broadcast_socket::on_receive"); +#endif + s->socket->async_receive_from(boost::asio::buffer(s->buffer, sizeof(s->buffer)) + , s->remote, boost::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(), boost::bind(&socket_entry::close, _1)); + std::for_each(m_unicast_sockets.begin(), m_unicast_sockets.end(), boost::bind(&socket_entry::close, _1)); + + m_abort = true; + maybe_abort(); + } +} + + diff --git a/src/bt_peer_connection.cpp b/src/bt_peer_connection.cpp new file mode 100644 index 0000000..cd55acb --- /dev/null +++ b/src/bt_peer_connection.cpp @@ -0,0 +1,3669 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +Copyright (c) 2007-2016, Arvid Norberg, Un Shyam +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include + +#ifdef TORRENT_USE_OPENSSL +#include // autp_ptr +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/bt_peer_connection.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/identify_client.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/socket_io.hpp" +#include "libtorrent/version.hpp" +#include "libtorrent/extensions.hpp" +#include "libtorrent/aux_/session_interface.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/broadcast_socket.hpp" +#include "libtorrent/peer_info.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/alloca.hpp" +#include "libtorrent/socket_type.hpp" +#include "libtorrent/performance_counters.hpp" // for counters + +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) +#include "libtorrent/pe_crypto.hpp" +#include "libtorrent/hasher.hpp" +#endif + +using boost::shared_ptr; + +namespace libtorrent +{ + const bt_peer_connection::message_handler + bt_peer_connection::m_message_handler[] = + { + &bt_peer_connection::on_choke, + &bt_peer_connection::on_unchoke, + &bt_peer_connection::on_interested, + &bt_peer_connection::on_not_interested, + &bt_peer_connection::on_have, + &bt_peer_connection::on_bitfield, + &bt_peer_connection::on_request, + &bt_peer_connection::on_piece, + &bt_peer_connection::on_cancel, + &bt_peer_connection::on_dht_port, + 0, 0, 0, + // FAST extension messages + &bt_peer_connection::on_suggest_piece, + &bt_peer_connection::on_have_all, + &bt_peer_connection::on_have_none, + &bt_peer_connection::on_reject_request, + &bt_peer_connection::on_allowed_fast, +#ifndef TORRENT_DISABLE_EXTENSIONS + 0, 0, + &bt_peer_connection::on_extended +#endif + }; + + + bt_peer_connection::bt_peer_connection(peer_connection_args const& pack + , peer_id const& pid) + : peer_connection(pack) + , m_state(read_protocol_identifier) + , 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) && !defined(TORRENT_DISABLE_EXTENSIONS) + , m_encrypted(false) + , m_rc4_encrypted(false) + , m_recv_buffer(peer_connection::m_recv_buffer) +#endif + , m_our_peer_id(pid) +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + , m_sync_bytes_read(0) +#endif +#ifndef TORRENT_DISABLE_EXTENSIONS + , m_upload_only_id(0) + , m_holepunch_id(0) +#endif +#ifndef TORRENT_DISABLE_EXTENSIONS + , m_dont_have_id(0) + , m_share_mode_id(0) +#endif +#if defined TORRENT_DEBUG || defined TORRENT_RELEASE_ASSERTS + , m_in_constructor(true) +#endif + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "CONSTRUCT", "bt_peer_connection"); +#endif + +#if TORRENT_USE_ASSERTS + m_in_constructor = false; +#endif +#ifndef TORRENT_DISABLE_EXTENSIONS + memset(m_reserved_bits, 0, sizeof(m_reserved_bits)); +#endif + } + + 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() + { + } + +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + void bt_peer_connection::switch_send_crypto(boost::shared_ptr crypto) + { + if (m_enc_handler.switch_send_crypto(crypto, send_buffer_size() - get_send_barrier())) + set_send_barrier(send_buffer_size()); + } + + void bt_peer_connection::switch_recv_crypto(boost::shared_ptr crypto) + { + m_enc_handler.switch_recv_crypto(crypto, m_recv_buffer); + } +#endif + + void bt_peer_connection::on_connected() + { + if (is_disconnecting()) return; + + boost::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(error_code(errors::torrent_paused), op_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) && !defined(TORRENT_DISABLE_EXTENSIONS) + + boost::uint8_t out_policy = m_settings.get_int(settings_pack::out_enc_policy); + +#ifdef TORRENT_USE_OPENSSL + // never try an encrypted connection when already using SSL + if (is_ssl(*get_socket())) + out_policy = settings_pack::pe_disabled; +#endif +#ifndef TORRENT_DISABLE_LOGGING + 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 = 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 = 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 haven't gotten far enough on the incoming handshake to be able to + // send the bitfield yet + if (m_state < read_packet_size) return; + + // connections that are still in the handshake + // will send their bitfield when the handshake + // is done +#ifndef TORRENT_DISABLE_EXTENSIONS + write_upload_only(); +#endif + + if (m_sent_bitfield) return; + boost::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + write_bitfield(); + TORRENT_ASSERT(m_sent_bitfield); +#ifndef TORRENT_DISABLE_DHT + if (m_supports_dht_port && m_ses.has_dht()) + write_dht_port(m_ses.external_udp_port()); +#endif + } + + void bt_peer_connection::write_dht_port(int listen_port) + { + INVARIANT_CHECK; + + TORRENT_ASSERT(m_sent_handshake); + TORRENT_ASSERT(m_sent_bitfield); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "DHT_PORT", "%d", listen_port); +#endif + char msg[] = {0,0,0,3, msg_dht_port, 0, 0}; + char* ptr = msg + 5; + detail::write_uint16(listen_port, ptr); + send_buffer(msg, sizeof(msg)); + + stats_counters().inc_stats_counter(counters::num_outgoing_dht_port); + } + + void bt_peer_connection::write_have_all() + { + INVARIANT_CHECK; + TORRENT_ASSERT(m_sent_handshake); + m_sent_bitfield = true; +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "HAVE_ALL"); +#endif + char msg[] = {0,0,0,1, msg_have_all}; + send_buffer(msg, sizeof(msg)); + + stats_counters().inc_stats_counter(counters::num_outgoing_have_all); + } + + void bt_peer_connection::write_have_none() + { + INVARIANT_CHECK; + TORRENT_ASSERT(m_sent_handshake); + m_sent_bitfield = true; +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "HAVE_NONE"); +#endif + char msg[] = {0,0,0,1, msg_have_none}; + send_buffer(msg, sizeof(msg)); + + stats_counters().inc_stats_counter(counters::num_outgoing_have_none); + } + + void bt_peer_connection::write_reject_request(peer_request const& r) + { + INVARIANT_CHECK; + + stats_counters().inc_stats_counter(counters::piece_rejects); + + if (!m_supports_fast) return; + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "REJECT_PIECE" + , "piece: %d | s: %d | l: %d", r.piece, r.start, r.length); +#endif + TORRENT_ASSERT(m_sent_handshake); + TORRENT_ASSERT(m_sent_bitfield); + TORRENT_ASSERT(associated_torrent().lock()->valid_metadata()); + + char msg[] = {0,0,0,13, msg_reject_request,0,0,0,0, 0,0,0,0, 0,0,0,0}; + char* ptr = msg + 5; + detail::write_int32(r.piece, ptr); // index + detail::write_int32(r.start, ptr); // begin + detail::write_int32(r.length, ptr); // length + send_buffer(msg, sizeof(msg)); + + stats_counters().inc_stats_counter(counters::num_outgoing_reject); + } + + void bt_peer_connection::write_allow_fast(int piece) + { + INVARIANT_CHECK; + + if (!m_supports_fast) return; + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "ALLOWED_FAST", "%d", piece); +#endif + + TORRENT_ASSERT(m_sent_handshake); + TORRENT_ASSERT(m_sent_bitfield); + TORRENT_ASSERT(associated_torrent().lock()->valid_metadata()); + + char msg[] = {0,0,0,5, msg_allowed_fast, 0, 0, 0, 0}; + char* ptr = msg + 5; + detail::write_int32(piece, ptr); + send_buffer(msg, sizeof(msg)); + + stats_counters().inc_stats_counter(counters::num_outgoing_allowed_fast); + } + + void bt_peer_connection::write_suggest(int piece) + { + INVARIANT_CHECK; + + if (!m_supports_fast) return; + + TORRENT_ASSERT(m_sent_handshake); + TORRENT_ASSERT(m_sent_bitfield); + + boost::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + TORRENT_ASSERT(t->valid_metadata()); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "SUGGEST" + , "piece: %d num_peers: %d", piece + , t->has_picker() ? t->picker().get_availability(piece) : -1); +#endif + + char msg[] = {0,0,0,5, msg_suggest_piece, 0, 0, 0, 0}; + char* ptr = msg + 5; + detail::write_int32(piece, ptr); + send_buffer(msg, sizeof(msg)); + + stats_counters().inc_stats_counter(counters::num_outgoing_suggest); + } + + 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) && !defined(TORRENT_DISABLE_EXTENSIONS) + 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 + { + return m_state < read_packet_size; + } + +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + + namespace { + char random_byte() + { return random() & 0xff; } + } + + void bt_peer_connection::write_pe1_2_dhkey() + { + INVARIANT_CHECK; + + TORRENT_ASSERT(!m_encrypted); + TORRENT_ASSERT(!m_rc4_encrypted); + TORRENT_ASSERT(!m_dh_key_exchange.get()); + TORRENT_ASSERT(!m_sent_handshake); + +#ifndef TORRENT_DISABLE_LOGGING + if (is_outgoing()) + peer_log(peer_log_alert::info, "ENCRYPTION", "initiating encrypted handshake"); +#endif + + m_dh_key_exchange.reset(new (std::nothrow) dh_key_exchange); + if (!m_dh_key_exchange || !m_dh_key_exchange->good()) + { + disconnect(errors::no_memory, op_encryption); + return; + } + + int pad_size = 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 buf_size = dh_key_len + pad_size; + + memcpy(ptr, m_dh_key_exchange->get_local_key(), dh_key_len); + ptr += dh_key_len; + + std::generate(ptr, ptr + pad_size, random_byte); + 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); + + boost::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + + hasher h; + sha1_hash const& info_hash = t->torrent_file().info_hash(); + char const* const secret = m_dh_key_exchange->get_secret(); + + int pad_size = random() % 512; + + // synchash,skeyhash,vc,crypto_provide,len(pad),pad,len(ia) + char msg[20 + 20 + 8 + 4 + 2 + 512 + 2]; + char* ptr = msg; + + // sync hash (hash('req1',S)) + h.reset(); + h.update("req1",4); + h.update(secret, dh_key_len); + sha1_hash sync_hash = h.final(); + + memcpy(ptr, &sync_hash[0], 20); + ptr += 20; + + // stream key obfuscated hash [ hash('req2',SKEY) xor hash('req3',S) ] + h.reset(); + h.update("req2",4); + h.update(info_hash.data(), 20); + sha1_hash streamkey_hash = h.final(); + + h.reset(); + h.update("req3",4); + h.update(secret, dh_key_len); + sha1_hash obfsc_hash = h.final(); + obfsc_hash ^= streamkey_hash; + + memcpy(ptr, &obfsc_hash[0], 20); + ptr += 20; + + // Discard DH key exchange data, setup RC4 keys + init_pe_rc4_handler(secret, info_hash); + m_dh_key_exchange.reset(); // secret should be invalid at this point + + // write the verification constant and crypto field + int encrypt_size = sizeof(msg) - 512 + pad_size - 40; + + boost::uint8_t crypto_provide = m_settings.get_int(settings_pack::allowed_enc_level); + + // this is an invalid setting, but let's just make the best of the situation + if ((crypto_provide & settings_pack::pe_both) == 0) + crypto_provide = settings_pack::pe_both; + +#ifndef TORRENT_DISABLE_LOGGING + 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); + std::vector vec; + vec.push_back(boost::asio::mutable_buffer(ptr, encrypt_size)); + m_rc4->encrypt(vec); + send_buffer(msg, sizeof(msg) - 512 + pad_size); + } + + void bt_peer_connection::write_pe4_sync(int 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 pad_size = random() % 512; + + const int buf_size = 8 + 4 + 2 + pad_size; + char msg[512 + 8 + 4 + 2]; + write_pe_vc_cryptofield(msg, sizeof(msg), crypto_select, pad_size); + + std::vector vec; + vec.push_back(boost::asio::mutable_buffer(msg, buf_size)); + m_rc4->encrypt(vec); + send_buffer(msg, buf_size); + + // 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(char* write_buf, int len + , int crypto_field, int pad_size) + { + INVARIANT_CHECK; +#if !TORRENT_USE_ASSERTS + TORRENT_UNUSED(len); +#endif + + TORRENT_ASSERT(crypto_field <= 0x03 && crypto_field > 0); + // vc,crypto_field,len(pad),pad, (len(ia)) + TORRENT_ASSERT((len >= 8+4+2+pad_size+2 && is_outgoing()) + || (len >= 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 + memset(write_buf, 0, 8); + write_buf += 8; + + detail::write_uint32(crypto_field, write_buf); + detail::write_uint16(pad_size, write_buf); // len (pad) + + // fill pad with zeroes + std::generate(write_buf, write_buf + pad_size, random_byte); + write_buf += pad_size; + + // append len(ia) if we are initiating + if (is_outgoing()) + detail::write_uint16(handshake_len, write_buf); // len(IA) + } + + void bt_peer_connection::init_pe_rc4_handler(char const* secret + , sha1_hash const& stream_key) + { + INVARIANT_CHECK; + + TORRENT_ASSERT(secret); + + hasher h; + static const char keyA[] = "keyA"; + static const char keyB[] = "keyB"; + + // encryption rc4 longkeys + // outgoing connection : hash ('keyA',S,SKEY) + // incoming connection : hash ('keyB',S,SKEY) + + if (is_outgoing()) h.update(keyA, 4); else h.update(keyB, 4); + h.update(secret, dh_key_len); + h.update(stream_key.data(), 20); + const sha1_hash local_key = h.final(); + + h.reset(); + + // decryption rc4 longkeys + // outgoing connection : hash ('keyB',S,SKEY) + // incoming connection : hash ('keyA',S,SKEY) + + if (is_outgoing()) h.update(keyB, 4); else h.update(keyA, 4); + h.update(secret, dh_key_len); + h.update(stream_key.data(), 20); + const sha1_hash remote_key = h.final(); + + TORRENT_ASSERT(!m_rc4.get()); + m_rc4 = boost::make_shared(); + + if (!m_rc4) + { + disconnect(errors::no_memory, op_encryption); + return; + } + + m_rc4->set_incoming_key(&remote_key[0], 20); + m_rc4->set_outgoing_key(&local_key[0], 20); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ENCRYPTION", "computed RC4 keys"); +#endif + } + + int bt_peer_connection::get_syncoffset(char const* src, int src_size, + char const* target, int target_size) const + { + TORRENT_ASSERT(target_size >= src_size); + TORRENT_ASSERT(src_size > 0); + TORRENT_ASSERT(src); + TORRENT_ASSERT(target); + + int traverse_limit = target_size - src_size; + + // TODO: this could be optimized using knuth morris pratt + for (int i = 0; i < traverse_limit; ++i) + { + char const* target_ptr = target + i; + if (std::equal(src, src+src_size, target_ptr)) + return i; + } + + // Partial sync +// for (int i = 0; i < target_size; ++i) +// { +// // first is iterator in src[] at which mismatch occurs +// // second is iterator in target[] at which mismatch occurs +// std::pair ret; +// int src_sync_size; +// if (i > traverse_limit) // partial sync test +// { +// ret = std::mismatch(src, src + src_size - (i - traverse_limit), &target[i]); +// src_sync_size = ret.first - src; +// if (src_sync_size == (src_size - (i - traverse_limit))) +// return i; +// } +// else // complete sync test +// { +// ret = std::mismatch(src, src + src_size, &target[i]); +// src_sync_size = ret.first - src; +// if (src_sync_size == src_size) +// return i; +// } +// } + + // no complete sync + return -1; + } + + void bt_peer_connection::rc4_decrypt(char* pos, int len) + { + std::vector vec; + vec.push_back(boost::asio::mutable_buffer(pos, len)); + int consume = 0; + int produce = len; + int packet_size = 0; + m_rc4->decrypt(vec, consume, produce, packet_size); + } + + namespace { + void regular_c_free(char* buf, void* /* userdata */ + , block_cache_reference /* ref */) + { + ::free(buf); + } + } + +#endif // #if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + + void bt_peer_connection::append_const_send_buffer(char const* buffer, int size + , chained_buffer::free_buffer_fun destructor, void* userdata + , block_cache_reference ref) + { +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + if (!m_enc_handler.is_send_plaintext()) + { + // if we're encrypting this buffer, we need to make a copy + // since we'll mutate it + char* buf = static_cast(malloc(size)); + memcpy(buf, buffer, size); + append_send_buffer(buf, size, ®ular_c_free, NULL); + destructor(const_cast(buffer), userdata, ref); + } + else +#endif + { + peer_connection::append_const_send_buffer(buffer, size, destructor + , userdata, ref); + } + } + + void bt_peer_connection::write_handshake() + { + INVARIANT_CHECK; + + TORRENT_ASSERT(!m_sent_handshake); + m_sent_handshake = true; + + boost::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + + // add handshake to the send buffer + const char version_string[] = "BitTorrent protocol"; + const int string_len = sizeof(version_string)-1; + + char handshake[1 + string_len + 8 + 20 + 20]; + char* ptr = handshake; + // length of version string + detail::write_uint8(string_len, ptr); + // protocol identifier + memcpy(ptr, version_string, string_len); + ptr += string_len; + // 8 zeroes + memset(ptr, 0, 8); + +#ifndef TORRENT_DISABLE_DHT + // indicate that we support the DHT messages + *(ptr + 7) |= 0x01; +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + // we support extensions + *(ptr + 5) |= 0x10; +#endif + + if (m_settings.get_bool(settings_pack::support_merkle_torrents)) + { + // we support merkle torrents + *(ptr + 5) |= 0x08; + } + + // we support FAST extension + *(ptr + 7) |= 0x04; + +#ifndef TORRENT_DISABLE_LOGGING + std::string bitmask; + for (int k = 0; k < 8; ++k) + { + for (int j = 0; j < 8; ++j) + { + if (ptr[k] & (0x80 >> j)) bitmask += '1'; + else bitmask += '0'; + } + } + peer_log(peer_log_alert::outgoing_message, "EXTENSIONS" + , "%s", bitmask.c_str()); +#endif + ptr += 8; + + // info hash + sha1_hash const& ih = t->torrent_file().info_hash(); + memcpy(ptr, &ih[0], 20); + ptr += 20; + + // peer id + if (m_settings.get_bool(settings_pack::anonymous_mode)) + { + // in anonymous mode, every peer connection + // has a unique peer-id + for (int i = 0; i < 20; ++i) + m_our_peer_id[i] = random() & 0xff; + } + + memcpy(ptr, &m_our_peer_id[0], 20); + ptr += 20; + +#ifndef TORRENT_DISABLE_LOGGING + { + char hex_pid[41]; + to_hex(m_our_peer_id.data(), 20, hex_pid); + hex_pid[40] = 0; + peer_log(peer_log_alert::outgoing, "HANDSHAKE" + , "sent peer_id: %s client: %s" + , hex_pid, identify_client(m_our_peer_id).c_str()); + } + peer_log(peer_log_alert::outgoing_message, "HANDSHAKE" + , "ih: %s", to_hex(ih.to_string()).c_str()); +#endif + send_buffer(handshake, sizeof(handshake)); + } + + boost::optional bt_peer_connection::downloading_piece_progress() const + { + boost::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + + buffer::const_interval recv_buffer = m_recv_buffer.get(); + // are we currently receiving a 'piece' message? + if (m_state != read_packet + || recv_buffer.left() <= 9 + || recv_buffer[0] != msg_piece) + return boost::optional(); + + const char* ptr = recv_buffer.begin + 1; + peer_request r; + r.piece = detail::read_int32(ptr); + r.start = detail::read_int32(ptr); + r.length = m_recv_buffer.packet_size() - 9; + + // is any of the piece message header data invalid? + if (!verify_piece(r)) + return boost::optional(); + + piece_block_progress p; + + p.piece_index = r.piece; + p.block_index = r.start / t->block_size(); + p.bytes_downloaded = recv_buffer.left() - 9; + p.full_block_bytes = r.length; + + return boost::optional(p); + } + + + // message handlers + + // ----------------------------- + // --------- KEEPALIVE --------- + // ----------------------------- + + void bt_peer_connection::on_keepalive() + { + INVARIANT_CHECK; + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "KEEPALIVE"); +#endif + incoming_keepalive(); + } + + // ----------------------------- + // ----------- 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, op_bittorrent, 2); + 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 + boost::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + while (!download_queue().empty()) + { + piece_block const& b = download_queue().front().block; + peer_request r; + r.piece = b.piece_index; + r.start = b.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_outsanding_bytes + if (r.piece == t->torrent_file().num_pieces() - 1) + { + 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, op_bittorrent, 2); + 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, op_bittorrent, 2); + 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, op_bittorrent, 2); + 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, op_bittorrent, 2); + return; + } + if (!m_recv_buffer.packet_finished()) return; + + buffer::const_interval recv_buffer = m_recv_buffer.get(); + + const char* ptr = recv_buffer.begin + 1; + int index = detail::read_int32(ptr); + + incoming_have(index); + } + + // ----------------------------- + // --------- BITFIELD ---------- + // ----------------------------- + + void bt_peer_connection::on_bitfield(int received) + { + INVARIANT_CHECK; + + TORRENT_ASSERT(received >= 0); + + boost::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + + received_bytes(0, received); + // if we don't have the metedata, we cannot + // verify the bitfield size + if (t->valid_metadata() + && m_recv_buffer.packet_size() - 1 != (t->torrent_file().num_pieces() + 7) / 8) + { + disconnect(errors::invalid_bitfield_size, op_bittorrent, 2); + return; + } + + if (!m_recv_buffer.packet_finished()) return; + + buffer::const_interval recv_buffer = m_recv_buffer.get(); + + bitfield bits; + bits.assign(recv_buffer.begin + 1 + , t->valid_metadata()?get_bitfield().size():(m_recv_buffer.packet_size()-1)*8); + + 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, op_bittorrent, 2); + return; + } + if (!m_recv_buffer.packet_finished()) return; + + buffer::const_interval recv_buffer = m_recv_buffer.get(); + + peer_request r; + const char* ptr = recv_buffer.begin + 1; + r.piece = detail::read_int32(ptr); + r.start = detail::read_int32(ptr); + r.length = detail::read_int32(ptr); + + incoming_request(r); + } + + // ----------------------------- + // ----------- PIECE ----------- + // ----------------------------- + + void bt_peer_connection::on_piece(int received) + { + INVARIANT_CHECK; + + TORRENT_ASSERT(received >= 0); + + buffer::const_interval recv_buffer = m_recv_buffer.get(); + int recv_pos = m_recv_buffer.pos(); // recv_buffer.end - recv_buffer.begin; + + boost::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + bool merkle = static_cast(recv_buffer.begin[0]) == 250; + if (merkle) + { + if (recv_pos == 1) + { + m_recv_buffer.set_soft_packet_size(13); + received_bytes(0, received); + return; + } + if (recv_pos < 13) + { + received_bytes(0, received); + return; + } + if (recv_pos == 13) + { + const char* ptr = recv_buffer.begin + 9; + int list_size = detail::read_int32(ptr); + // now we know how long the bencoded hash list is + // and we can allocate the disk buffer and receive + // into it + + if (list_size > m_recv_buffer.packet_size() - 13) + { + disconnect(errors::invalid_hash_list, op_bittorrent, 2); + return; + } + + if (m_recv_buffer.packet_size() - 13 - list_size > t->block_size()) + { + disconnect(errors::packet_too_large, op_bittorrent, 2); + return; + } + + m_recv_buffer.assert_no_disk_buffer(); + if (!m_settings.get_bool(settings_pack::contiguous_recv_buffer) && + m_recv_buffer.can_recv_contiguous(m_recv_buffer.packet_size() - 13 - list_size)) + { + if (!allocate_disk_receive_buffer(m_recv_buffer.packet_size() - 13 - list_size)) + { + received_bytes(0, received); + return; + } + } + } + } + else + { + if (recv_pos == 1) + { + m_recv_buffer.assert_no_disk_buffer(); + + if (m_recv_buffer.packet_size() - 9 > t->block_size()) + { + disconnect(errors::packet_too_large, op_bittorrent, 2); + return; + } + + if (!m_settings.get_bool(settings_pack::contiguous_recv_buffer) && + m_recv_buffer.can_recv_contiguous(m_recv_buffer.packet_size() - 9)) + { + if (!allocate_disk_receive_buffer(m_recv_buffer.packet_size() - 9)) + { + received_bytes(0, received); + return; + } + } + } + } + TORRENT_ASSERT(m_settings.get_bool(settings_pack::contiguous_recv_buffer) || m_recv_buffer.has_disk_buffer() || m_recv_buffer.packet_size() == 9); + // classify the received data as protocol chatter + // or data payload for the statistics + int piece_bytes = 0; + + int header_size = merkle?13:9; + + peer_request p; + int list_size = 0; + + if (recv_pos >= header_size) + { + const char* ptr = recv_buffer.begin + 1; + p.piece = detail::read_int32(ptr); + p.start = detail::read_int32(ptr); + + if (merkle) + { + list_size = detail::read_int32(ptr); + p.length = m_recv_buffer.packet_size() - list_size - header_size; + header_size += list_size; + } + else + { + p.length = m_recv_buffer.packet_size() - header_size; + } + } + + if (recv_pos <= header_size) + { + // only received protocol data + received_bytes(0, received); + } + else if (recv_pos - received >= header_size) + { + // only received payload data + received_bytes(received, 0); + piece_bytes = received; + } + else + { + // received a bit of both + TORRENT_ASSERT(recv_pos - received < header_size); + TORRENT_ASSERT(recv_pos > header_size); + TORRENT_ASSERT(header_size - (recv_pos - received) <= header_size); + received_bytes( + recv_pos - header_size + , header_size - (recv_pos - received)); + piece_bytes = recv_pos - header_size; + } + + if (recv_pos < header_size) return; + +#ifndef TORRENT_DISABLE_LOGGING +// peer_log(peer_log_alert::incoming_message, "PIECE_FRAGMENT", "p: %d start: %d length: %d" +// , p.piece, p.start, p.length); +#endif + + if (recv_pos - received < header_size && recv_pos >= header_size) + { + // call this once, the first time the entire header + // has been received + start_receive_piece(p); + if (is_disconnecting()) return; + } + + TORRENT_ASSERT(m_settings.get_bool(settings_pack::contiguous_recv_buffer) || m_recv_buffer.has_disk_buffer() || m_recv_buffer.packet_size() == header_size); + + incoming_piece_fragment(piece_bytes); + if (!m_recv_buffer.packet_finished()) return; + + if (merkle && list_size > 0) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "HASHPIECE" + , "piece: %d list: %d", p.piece, list_size); +#endif + bdecode_node hash_list; + error_code ec; + if (bdecode(recv_buffer.begin + 13, recv_buffer.begin+ 13 + list_size + , hash_list, ec) != 0) + { + disconnect(errors::invalid_hash_piece, op_bittorrent, 2); + return; + } + + // the list has this format: + // [ [node-index, hash], [node-index, hash], ... ] + if (hash_list.type() != bdecode_node::list_t) + { + disconnect(errors::invalid_hash_list, op_bittorrent, 2); + return; + } + + std::map nodes; + for (int i = 0; i < hash_list.list_size(); ++i) + { + bdecode_node e = hash_list.list_at(i); + if (e.type() != bdecode_node::list_t + || e.list_size() != 2 + || e.list_at(0).type() != bdecode_node::int_t + || e.list_at(1).type() != bdecode_node::string_t + || e.list_at(1).string_length() != 20) continue; + + nodes.insert(std::make_pair(int(e.list_int_value_at(0)) + , sha1_hash(e.list_at(1).string_ptr()))); + } + if (!nodes.empty() && !t->add_merkle_nodes(nodes, p.piece)) + { + disconnect(errors::invalid_hash_piece, op_bittorrent, 2); + return; + } + } + + char* disk_buffer = m_recv_buffer.release_disk_buffer(); + if (disk_buffer) + { + disk_buffer_holder holder(m_allocator, disk_buffer); + incoming_piece(p, holder); + } + else + { + incoming_piece(p, recv_buffer.begin + header_size); + } + } + + // ----------------------------- + // ---------- CANCEL ----------- + // ----------------------------- + + void bt_peer_connection::on_cancel(int received) + { + INVARIANT_CHECK; + + TORRENT_ASSERT(received >= 0); + received_bytes(0, received); + if (m_recv_buffer.packet_size() != 13) + { + disconnect(errors::invalid_cancel, op_bittorrent, 2); + return; + } + if (!m_recv_buffer.packet_finished()) return; + + buffer::const_interval recv_buffer = m_recv_buffer.get(); + + peer_request r; + const char* ptr = recv_buffer.begin + 1; + r.piece = detail::read_int32(ptr); + r.start = detail::read_int32(ptr); + r.length = detail::read_int32(ptr); + + incoming_cancel(r); + } + + // ----------------------------- + // --------- DHT PORT ---------- + // ----------------------------- + + void bt_peer_connection::on_dht_port(int received) + { + INVARIANT_CHECK; + + TORRENT_ASSERT(received >= 0); + received_bytes(0, received); + if (m_recv_buffer.packet_size() != 3) + { + disconnect(errors::invalid_dht_port, op_bittorrent, 2); + return; + } + if (!m_recv_buffer.packet_finished()) return; + + buffer::const_interval recv_buffer = m_recv_buffer.get(); + + const char* ptr = recv_buffer.begin + 1; + int listen_port = detail::read_uint16(ptr); + + incoming_dht_port(listen_port); + + if (!m_supports_dht_port) + { + m_supports_dht_port = true; +#ifndef TORRENT_DISABLE_DHT + if (m_supports_dht_port && m_ses.has_dht()) + write_dht_port(m_ses.external_udp_port()); +#endif + } + } + + void bt_peer_connection::on_suggest_piece(int received) + { + INVARIANT_CHECK; + + received_bytes(0, received); + if (!m_supports_fast) + { + disconnect(errors::invalid_suggest, op_bittorrent, 2); + return; + } + + if (!m_recv_buffer.packet_finished()) return; + + buffer::const_interval recv_buffer = m_recv_buffer.get(); + + const char* ptr = recv_buffer.begin + 1; + int piece = detail::read_uint32(ptr); + incoming_suggest(piece); + } + + void bt_peer_connection::on_have_all(int received) + { + INVARIANT_CHECK; + + received_bytes(0, received); + if (!m_supports_fast) + { + disconnect(errors::invalid_have_all, op_bittorrent, 2); + return; + } + incoming_have_all(); + } + + void bt_peer_connection::on_have_none(int received) + { + INVARIANT_CHECK; + + received_bytes(0, received); + if (!m_supports_fast) + { + disconnect(errors::invalid_have_none, op_bittorrent, 2); + return; + } + incoming_have_none(); + } + + void bt_peer_connection::on_reject_request(int received) + { + INVARIANT_CHECK; + + received_bytes(0, received); + if (!m_supports_fast) + { + disconnect(errors::invalid_reject, op_bittorrent, 2); + return; + } + + if (!m_recv_buffer.packet_finished()) return; + + buffer::const_interval recv_buffer = m_recv_buffer.get(); + + peer_request r; + const char* ptr = recv_buffer.begin + 1; + r.piece = detail::read_int32(ptr); + r.start = detail::read_int32(ptr); + r.length = detail::read_int32(ptr); + + incoming_reject_request(r); + } + + void bt_peer_connection::on_allowed_fast(int received) + { + INVARIANT_CHECK; + + received_bytes(0, received); + if (!m_supports_fast) + { + disconnect(errors::invalid_allow_fast, op_bittorrent, 2); + return; + } + + if (!m_recv_buffer.packet_finished()) return; + buffer::const_interval recv_buffer = m_recv_buffer.get(); + const char* ptr = recv_buffer.begin + 1; + int index = detail::read_int32(ptr); + + incoming_allowed_fast(index); + } + + // ----------------------------- + // -------- RENDEZVOUS --------- + // ----------------------------- + +#ifndef TORRENT_DISABLE_EXTENSIONS + 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; + + buffer::const_interval recv_buffer = m_recv_buffer.get(); + TORRENT_ASSERT(*recv_buffer.begin == msg_extended); + ++recv_buffer.begin; + TORRENT_ASSERT(*recv_buffer.begin == holepunch_msg); + ++recv_buffer.begin; + + const char* ptr = recv_buffer.begin; + + // ignore invalid messages + if (recv_buffer.left() < 2) return; + + int msg_type = detail::read_uint8(ptr); + int addr_type = detail::read_uint8(ptr); + + tcp::endpoint ep; + + if (addr_type == 0) + { + if (recv_buffer.left() < 2 + 4 + 2) return; + // IPv4 address + ep = detail::read_v4_endpoint(ptr); + } +#if TORRENT_USE_IPV6 + else if (addr_type == 1) + { + // IPv6 address + if (recv_buffer.left() < 2 + 18 + 2) return; + ep = detail::read_v6_endpoint(ptr); + } +#endif + else + { +#ifndef TORRENT_DISABLE_LOGGING + error_code ec; + 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" + , (msg_type >= 0 && msg_type < 3 ? hp_msg_name[msg_type] : "unknown message type") + , print_address(remote().address()).c_str()); +#endif + + return; // unknown address type + } + + boost::shared_ptr t = associated_torrent().lock(); + if (!t) return; + + switch (msg_type) + { + case hp_rendezvous: // rendezvous + { +#ifndef TORRENT_DISABLE_LOGGING + 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 == 0) + { + // we're not connected to this peer + write_holepunch_msg(hp_failed, ep, hp_not_connected); + break; + } + if (!p->supports_holepunch()) + { + write_holepunch_msg(hp_failed, ep, hp_no_support); + break; + } + if (p == this) + { + write_holepunch_msg(hp_failed, ep, hp_no_self); + break; + } + + write_holepunch_msg(hp_connect, ep, 0); + p->write_holepunch_msg(hp_connect, remote(), 0); + } break; + case hp_connect: + { + // add or find the peer with this endpoint + torrent_peer* p = t->add_peer(ep, peer_info::pex); + if (p == 0 || p->connection) + { +#ifndef TORRENT_DISABLE_LOGGING + 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 + 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 + peer_log(peer_log_alert::incoming_message, "HOLEPUNCH" + , "msg:connect to: %s" + , print_address(ep.address()).c_str()); +#endif + } break; + case hp_failed: + { + boost::uint32_t error = detail::read_uint32(ptr); +#ifndef TORRENT_DISABLE_LOGGING + error_code ec; + 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; +#ifndef TORRENT_DISABLE_LOGGING + default: + { + error_code ec; + peer_log(peer_log_alert::incoming_message, "HOLEPUNCH" + , "msg: unknown message type (%d) to: %s" + , msg_type, print_address(ep.address()).c_str()); + } +#endif + } + } + + void bt_peer_connection::write_holepunch_msg(int type, tcp::endpoint const& ep, int error) + { + char buf[35]; + char* ptr = buf + 6; + detail::write_uint8(type, ptr); + if (ep.address().is_v4()) detail::write_uint8(0, ptr); + else detail::write_uint8(1, ptr); + detail::write_endpoint(ep, ptr); + +#ifndef TORRENT_DISABLE_LOGGING + error_code ec; + 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" + , (type >= 0 && type < 3 ? hp_msg_name[type] : "unknown message type") + , print_address(ep.address()).c_str() + , hp_error_string[error]); +#endif + if (type == hp_failed) + { + detail::write_uint32(error, ptr); + } + + // write the packet length and type + char* hdr = buf; + detail::write_uint32(ptr - buf - 4, hdr); + detail::write_uint8(msg_extended, hdr); + detail::write_uint8(m_holepunch_id, hdr); + + TORRENT_ASSERT(ptr <= buf + sizeof(buf)); + + send_buffer(buf, ptr - buf); + + stats_counters().inc_stats_counter(counters::num_outgoing_extended); + } +#endif // TORRENT_DISABLE_EXTENSIONS + + // ----------------------------- + // --------- EXTENDED ---------- + // ----------------------------- + +#ifndef TORRENT_DISABLE_EXTENSIONS + 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, op_bittorrent, 2); + return; + } + + if (associated_torrent().expired()) + { + disconnect(errors::invalid_extended, op_bittorrent, 2); + return; + } + + buffer::const_interval recv_buffer = m_recv_buffer.get(); + if (recv_buffer.left() < 2) return; + + TORRENT_ASSERT(*recv_buffer.begin == msg_extended); + ++recv_buffer.begin; + + int extended_id = detail::read_uint8(recv_buffer.begin); + + 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 ul = detail::read_uint8(recv_buffer.begin) != 0; +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "UPLOAD_ONLY" + , "%s", (ul?"true":"false")); +#endif + set_upload_only(ul); + return; + } + + 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 = detail::read_uint8(recv_buffer.begin) != 0; +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "SHARE_MODE" + , "%s", (sm?"true":"false")); +#endif + set_share_mode(sm); + return; + } + + 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; + } + int piece = detail::read_uint32(recv_buffer.begin); + 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 + + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + if ((*i)->on_extended(m_recv_buffer.packet_size() - 2, extended_id + , recv_buffer)) + return; + } + + disconnect(errors::invalid_message, op_bittorrent, 2); + return; + } + + void bt_peer_connection::on_extended_handshake() + { + if (!m_recv_buffer.packet_finished()) return; + + boost::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + + buffer::const_interval recv_buffer = m_recv_buffer.get(); + + bdecode_node root; + error_code ec; + int pos; + int ret = bdecode(recv_buffer.begin + 2, recv_buffer.end, root, ec, &pos); + if (ret != 0 || ec || root.type() != bdecode_node::dict_t) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "EXTENSION_MESSAGE" + , "invalid extended handshake: %s pos: %d" + , ec.message().c_str(), pos); +#endif + return; + } + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "EXTENDED_HANDSHAKE" + , "%s", print_entry(root).c_str()); +#endif + + for (extension_list_t::iterator 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; + + // upload_only + if (bdecode_node m = root.dict_find_dict("m")) + { + m_upload_only_id = boost::uint8_t(m.dict_find_int_value("upload_only", 0)); + m_holepunch_id = boost::uint8_t(m.dict_find_int_value("ut_holepunch", 0)); + m_dont_have_id = boost::uint8_t(m.dict_find_int_value("lt_donthave", 0)); + } + + // there is supposed to be a remote listen port + int listen_port = int(root.dict_find_int_value("p")); + if (listen_port > 0 && peer_info_struct() != 0) + { + 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 last_seen_complete = boost::uint8_t(root.dict_find_int_value("complete_ago", -1)); + if (last_seen_complete >= 0) set_last_seen_complete(last_seen_complete); + + std::string client_info = root.dict_find_string_value("v"); + if (!client_info.empty()) m_client_version = client_info; + + int 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); + + if (m_settings.get_bool(settings_pack::support_share_mode) + && root.dict_find_int_value("share_mode", 0)) + set_share_mode(true); + + std::string myip = root.dict_find_string_value("yourip"); + if (!myip.empty()) + { + if (myip.size() == address_v4::bytes_type().size()) + { + address_v4::bytes_type bytes; + std::copy(myip.begin(), myip.end(), bytes.begin()); + m_ses.set_external_address(address_v4(bytes) + , aux::session_interface::source_peer, remote().address()); + } +#if TORRENT_USE_IPV6 + else if (myip.size() == address_v6::bytes_type().size()) + { + 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(ipv6_address.to_v4() + , aux::session_interface::source_peer, remote().address()); + else + m_ses.set_external_address(ipv6_address + , aux::session_interface::source_peer, remote().address()); + } +#endif + } + + // 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) + && !t->share_mode()) + disconnect(errors::upload_upload_connection, op_bittorrent); + + stats_counters().inc_stats_counter(counters::num_incoming_ext_handshake); + } +#endif // TORRENT_DISABLE_EXTENSIONS + + bool bt_peer_connection::dispatch_message(int 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; + } + + buffer::const_interval recv_buffer = m_recv_buffer.get(); + + TORRENT_ASSERT(recv_buffer.left() >= 1); + int packet_type = static_cast(recv_buffer[0]); + + if (m_settings.get_bool(settings_pack::support_merkle_torrents) + && packet_type == 250) packet_type = msg_piece; + + if (packet_type < 0 + || packet_type >= num_supported_messages + || m_message_handler[packet_type] == 0) + { +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + if ((*i)->on_unknown_message(m_recv_buffer.packet_size(), packet_type + , buffer::const_interval(recv_buffer.begin+1 + , recv_buffer.end))) + return m_recv_buffer.packet_finished(); + } +#endif + + received_bytes(0, received); + disconnect(errors::invalid_message, op_bittorrent); + return m_recv_buffer.packet_finished(); + } + + TORRENT_ASSERT(m_message_handler[packet_type] != 0); + +#if TORRENT_USE_ASSERTS + boost::int64_t cur_payload_dl = statistics().last_payload_downloaded(); + boost::int64_t cur_protocol_dl = statistics().last_protocol_downloaded(); +#endif + + // call the correct handler for this packet type + (this->*m_message_handler[packet_type])(received); + +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(statistics().last_payload_downloaded() - cur_payload_dl >= 0); + TORRENT_ASSERT(statistics().last_protocol_downloaded() - cur_protocol_dl >= 0); + boost::int64_t stats_diff = statistics().last_payload_downloaded() - cur_payload_dl + + statistics().last_protocol_downloaded() - cur_protocol_dl; + TORRENT_ASSERT(stats_diff == received); +#endif + + bool finished = m_recv_buffer.packet_finished(); + + if (finished) + { + // count this packet in the session stats counters + int counter = counters::num_incoming_extended; + if (packet_type <= msg_dht_port) + counter = counters::num_incoming_choke + packet_type; + else if (packet_type <= msg_allowed_fast) + counter = counters::num_incoming_suggest + packet_type; + else if (packet_type <= msg_extended) + counter = counters::num_incoming_extended; + else + TORRENT_ASSERT(false); + + stats_counters().inc_stats_counter(counter); + } + + return finished; + } + +#ifndef TORRENT_DISABLE_EXTENSIONS + void bt_peer_connection::write_upload_only() + { + INVARIANT_CHECK; + + boost::shared_ptr t = associated_torrent().lock(); + if (m_upload_only_id == 0) return; + if (t->share_mode()) 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; + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "UPLOAD_ONLY", "%d" + , int(t->is_upload_only() && !t->super_seeding())); +#endif + + char msg[7] = {0, 0, 0, 3, msg_extended}; + char* ptr = msg + 5; + detail::write_uint8(m_upload_only_id, ptr); + // 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 + detail::write_uint8(t->is_upload_only() && !t->super_seeding(), ptr); + send_buffer(msg, sizeof(msg)); + + stats_counters().inc_stats_counter(counters::num_outgoing_extended); + } + + void bt_peer_connection::write_share_mode() + { + INVARIANT_CHECK; + + boost::shared_ptr t = associated_torrent().lock(); + if (m_share_mode_id == 0) return; + + char msg[7] = {0, 0, 0, 3, msg_extended}; + char* ptr = msg + 5; + detail::write_uint8(m_share_mode_id, ptr); + detail::write_uint8(t->share_mode(), ptr); + send_buffer(msg, sizeof(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 timimg 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); + + char msg[] = {0,0,0,0}; + send_buffer(msg, sizeof(msg)); + } + + void bt_peer_connection::write_cancel(peer_request const& r) + { + INVARIANT_CHECK; + + TORRENT_ASSERT(m_sent_handshake); + TORRENT_ASSERT(m_sent_bitfield); + TORRENT_ASSERT(associated_torrent().lock()->valid_metadata()); + + char msg[17] = {0,0,0,13, msg_cancel}; + char* ptr = msg + 5; + detail::write_int32(r.piece, ptr); // index + detail::write_int32(r.start, ptr); // begin + detail::write_int32(r.length, ptr); // length + send_buffer(msg, sizeof(msg)); + + stats_counters().inc_stats_counter(counters::num_outgoing_cancel); + + if (!m_supports_fast) + incoming_reject_request(r); + } + + void bt_peer_connection::write_request(peer_request const& r) + { + INVARIANT_CHECK; + + TORRENT_ASSERT(m_sent_handshake); + TORRENT_ASSERT(m_sent_bitfield); + TORRENT_ASSERT(associated_torrent().lock()->valid_metadata()); + + char msg[17] = {0,0,0,13, msg_request}; + char* ptr = msg + 5; + + detail::write_int32(r.piece, ptr); // index + detail::write_int32(r.start, ptr); // begin + detail::write_int32(r.length, ptr); // length + send_buffer(msg, sizeof(msg), message_type_request); + + stats_counters().inc_stats_counter(counters::num_outgoing_request); + } + + 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 >= read_peer_id); + + boost::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + TORRENT_ASSERT(m_sent_handshake); + TORRENT_ASSERT(t->valid_metadata()); + + 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 superseeding by sending two have message + int piece = t->get_piece_to_super_seed(get_bitfield()); + if (piece >= 0) superseed_piece(-1, piece); + piece = t->get_piece_to_super_seed(get_bitfield()); + if (piece >= 0) superseed_piece(-1, piece); + return; + } + else if (m_supports_fast && t->is_seed() && !m_settings.get_bool(settings_pack::lazy_bitfields)) + { + 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); + if (num_pieces <= 0) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "BITFIELD", "not sending bitfield, num_pieces == 0"); +#endif + return; + } + + int lazy_pieces[50]; + int num_lazy_pieces = 0; + int lazy_piece = 0; + + if (t->is_seed() && m_settings.get_bool(settings_pack::lazy_bitfields) +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + && !m_encrypted +#endif + ) + { + num_lazy_pieces = (std::min)(50, num_pieces / 10); + if (num_lazy_pieces < 1) num_lazy_pieces = 1; + for (int i = 0; i < num_pieces; ++i) + { + if (int(random() % (num_pieces - i)) >= num_lazy_pieces - lazy_piece) continue; + lazy_pieces[lazy_piece++] = i; + } + TORRENT_ASSERT(lazy_piece == num_lazy_pieces); + } + + const int packet_size = (num_pieces + 7) / 8 + 5; + + boost::uint8_t* msg = TORRENT_ALLOCA(boost::uint8_t, packet_size); + if (msg == 0) return; // out of memory + unsigned char* ptr = msg; + + detail::write_int32(packet_size - 4, ptr); + detail::write_uint8(msg_bitfield, ptr); + + if (t->is_seed()) + { + memset(ptr, 0xff, packet_size - 5); + + // Clear trailing bits + unsigned char *p = msg + packet_size - 1; + *p = (0xff << ((8 - (num_pieces & 7)) & 7)) & 0xff; + } + else + { + memset(ptr, 0, packet_size - 5); + piece_picker const& p = t->picker(); + int mask = 0x80; + for (int i = 0; i < num_pieces; ++i) + { + if (p.have_piece(i)) *ptr |= mask; + mask >>= 1; + if (mask == 0) + { + mask = 0x80; + ++ptr; + } + } + } + for (int c = 0; c < num_lazy_pieces; ++c) + msg[5 + lazy_pieces[c] / 8] &= ~(0x80 >> (lazy_pieces[c] & 7)); + + // add predictive pieces to the bitfield as well, since we won't + // announce them again + for (std::vector::const_iterator i = t->predictive_pieces().begin() + , end(t->predictive_pieces().end()); i != end; ++i) + msg[5 + *i / 8] |= (0x80 >> (*i & 7)); + +#ifndef TORRENT_DISABLE_LOGGING + + std::string bitfield_string; + bitfield_string.resize(num_pieces); + for (int k = 0; k < num_pieces; ++k) + { + if (msg[5 + k / 8] & (0x80 >> (k % 8))) 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(reinterpret_cast(msg), packet_size); + + stats_counters().inc_stats_counter(counters::num_outgoing_bitfield); + + if (num_lazy_pieces > 0) + { + for (int i = 0; i < num_lazy_pieces; ++i) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "HAVE" + , "piece: %d", lazy_pieces[i]); +#endif + write_have(lazy_pieces[i]); + } + // TODO: if we're finished, send upload_only message + } + } + +#ifndef TORRENT_DISABLE_EXTENSIONS + 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(); + + // if we're using a proxy, our listen port won't be useful + // anyway. + if (!m_settings.get_bool(settings_pack::force_proxy) && is_outgoing()) + handshake["p"] = m_ses.listen_port(); + + // only send the port in case we bade the connection + // on incoming connections the other end already knows + // our listen port + if (!m_settings.get_bool(settings_pack::anonymous_mode)) + { + handshake["v"] = m_settings.get_str(settings_pack::handshake_client_version).empty() + ? m_settings.get_str(settings_pack::user_agent) + : m_settings.get_str(settings_pack::handshake_client_version); + } + + std::string remote_address; + std::back_insert_iterator out(remote_address); + detail::write_address(remote().address(), out); +#if TORRENT_USE_I2P + if (!is_i2p(*get_socket())) +#endif + handshake["yourip"] = remote_address; + handshake["reqq"] = m_settings.get_int(settings_pack::max_allowed_in_request_queue); + boost::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + + m["upload_only"] = upload_only_msg; + m["ut_holepunch"] = holepunch_msg; + if (m_settings.get_bool(settings_pack::support_share_mode)) + m["share_mode"] = share_mode_msg; + 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 using lazy bitfields or 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() + && !t->share_mode() + && t->valid_metadata() + && !t->super_seeding() + && (!m_settings.get_bool(settings_pack::lazy_bitfields) +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + || m_encrypted +#endif + )) + { + handshake["upload_only"] = 1; + } + + if (m_settings.get_bool(settings_pack::support_share_mode) + && t->share_mode()) + handshake["share_mode"] = 1; + + // loop backwards, to make the first extension be the last + // to fill in the handshake (i.e. give the first extensions priority) + for (extension_list_t::reverse_iterator i = m_extensions.rbegin() + , end(m_extensions.rend()); i != end; ++i) + { + (*i)->add_handshake(handshake); + } + +#ifndef NDEBUG + // make sure there are not conflicting extensions + std::set ext; + for (entry::dictionary_type::const_iterator i = m.begin() + , end(m.end()); i != end; ++i) + { + if (i->second.type() != entry::int_t) continue; + int val = int(i->second.integer()); + TORRENT_ASSERT(ext.find(val) == ext.end()); + ext.insert(val); + } +#endif + + std::vector dict_msg; + bencode(std::back_inserter(dict_msg), handshake); + + char msg[6]; + char* ptr = msg; + + // write the length of the message + detail::write_int32(int(dict_msg.size()) + 2, ptr); + detail::write_uint8(msg_extended, ptr); + // signal handshake message + detail::write_uint8(0, ptr); + send_buffer(msg, sizeof(msg)); + send_buffer(&dict_msg[0], dict_msg.size()); + + stats_counters().inc_stats_counter(counters::num_outgoing_ext_handshake); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "EXTENDED_HANDSHAKE" + , "%s", handshake.to_string().c_str()); +#endif + } +#endif + + void bt_peer_connection::write_choke() + { + INVARIANT_CHECK; + + TORRENT_ASSERT(m_sent_handshake); + TORRENT_ASSERT(m_sent_bitfield); + + if (is_choked()) return; + char msg[] = {0,0,0,1,msg_choke}; + send_buffer(msg, sizeof(msg)); + + stats_counters().inc_stats_counter(counters::num_outgoing_choke); + } + + void bt_peer_connection::write_unchoke() + { + INVARIANT_CHECK; + + TORRENT_ASSERT(m_sent_handshake); + TORRENT_ASSERT(m_sent_bitfield); + + char msg[] = {0,0,0,1,msg_unchoke}; + send_buffer(msg, sizeof(msg)); + + stats_counters().inc_stats_counter(counters::num_outgoing_unchoke); + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + (*i)->sent_unchoke(); + } +#endif + } + + void bt_peer_connection::write_interested() + { + INVARIANT_CHECK; + + TORRENT_ASSERT(m_sent_handshake); + TORRENT_ASSERT(m_sent_bitfield); + + char msg[] = {0,0,0,1,msg_interested}; + send_buffer(msg, sizeof(msg)); + + stats_counters().inc_stats_counter(counters::num_outgoing_interested); + } + + void bt_peer_connection::write_not_interested() + { + INVARIANT_CHECK; + + TORRENT_ASSERT(m_sent_handshake); + TORRENT_ASSERT(m_sent_bitfield); + + char msg[] = {0,0,0,1,msg_not_interested}; + send_buffer(msg, sizeof(msg)); + + stats_counters().inc_stats_counter(counters::num_outgoing_not_interested); + } + + void bt_peer_connection::write_have(int index) + { + INVARIANT_CHECK; + TORRENT_ASSERT(associated_torrent().lock()->valid_metadata()); + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < associated_torrent().lock()->torrent_file().num_pieces()); + TORRENT_ASSERT(m_sent_handshake); + TORRENT_ASSERT(m_sent_bitfield); + + char msg[] = {0,0,0,5,msg_have,0,0,0,0}; + char* ptr = msg + 5; + detail::write_int32(index, ptr); + send_buffer(msg, sizeof(msg)); + + stats_counters().inc_stats_counter(counters::num_outgoing_have); + } + + void bt_peer_connection::write_dont_have(int index) + { +#ifndef TORRENT_DISABLE_EXTENSIONS + INVARIANT_CHECK; + TORRENT_ASSERT(associated_torrent().lock()->valid_metadata()); + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < associated_torrent().lock()->torrent_file().num_pieces()); + + if (in_handshake()) return; + + TORRENT_ASSERT(m_sent_handshake); + TORRENT_ASSERT(m_sent_bitfield); + + if (!m_supports_extensions || m_dont_have_id == 0) return; + + char msg[] = {0,0,0,6,msg_extended,char(m_dont_have_id),0,0,0,0}; + char* ptr = msg + 6; + detail::write_int32(index, ptr); + send_buffer(msg, sizeof(msg)); + + stats_counters().inc_stats_counter(counters::num_outgoing_extended); +#else + TORRENT_UNUSED(index); +#endif + } + + namespace { + + void buffer_reclaim_block(char* /* buffer */, void* userdata + , block_cache_reference ref) + { + buffer_allocator_interface* buf = static_cast(userdata); + buf->reclaim_block(ref); + } + + void buffer_free_disk_buf(char* buffer, void* userdata + , block_cache_reference /* ref */) + { + buffer_allocator_interface* buf = static_cast(userdata); + buf->free_disk_buffer(buffer); + } + + } // anonymous namespace + + 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); + + boost::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + + bool merkle = t->torrent_file().is_merkle_torrent() && r.start == 0; + // the hash piece looks like this: + // uint8_t msg + // uint32_t piece index + // uint32_t start + // uint32_t list len + // var bencoded list + // var piece data + char msg[4 + 1 + 4 + 4 + 4]; + char* ptr = msg; + TORRENT_ASSERT(r.length <= 16 * 1024); + detail::write_int32(r.length + 1 + 4 + 4, ptr); + if (m_settings.get_bool(settings_pack::support_merkle_torrents) && merkle) + detail::write_uint8(250, ptr); + else + detail::write_uint8(msg_piece, ptr); + detail::write_int32(r.piece, ptr); + detail::write_int32(r.start, ptr); + + // if this is a merkle torrent and the start offset + // is 0, we need to include the merkle node hashes + if (merkle) + { + std::vector piece_list_buf; + entry piece_list; + entry::list_type& l = piece_list.list(); + std::map merkle_node_list = t->torrent_file().build_merkle_list(r.piece); + for (std::map::iterator i = merkle_node_list.begin() + , end(merkle_node_list.end()); i != end; ++i) + { + l.push_back(entry(entry::list_t)); + l.back().list().push_back(i->first); + l.back().list().push_back(i->second.to_string()); + } + bencode(std::back_inserter(piece_list_buf), piece_list); + detail::write_int32(piece_list_buf.size(), ptr); + + // back-patch the length field + char* ptr2 = msg; + detail::write_int32(r.length + 1 + 4 + 4 + 4 + piece_list_buf.size() + , ptr2); + + send_buffer(msg, 17); + send_buffer(&piece_list_buf[0], piece_list_buf.size()); + } + else + { + send_buffer(msg, 13); + } + + if (buffer.ref().storage == 0) + { + append_send_buffer(buffer.get(), r.length + , &buffer_free_disk_buf, &m_allocator); + } + else + { + append_const_send_buffer(buffer.get(), r.length + , &buffer_reclaim_block, &m_allocator, buffer.ref()); + } + buffer.release(); + + m_payloads.push_back(range(send_buffer_size() - r.length, r.length)); + setup_send(); + + stats_counters().inc_stats_counter(counters::num_outgoing_piece); + } + + // -------------------------- + // RECEIVE DATA + // -------------------------- + + void bt_peer_connection::on_receive(error_code const& error + , std::size_t bytes_transferred) + { + INVARIANT_CHECK; + + if (error) + { + received_bytes(0, 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) && !defined(TORRENT_DISABLE_EXTENSIONS) + if (!m_enc_handler.is_recv_plaintext()) + { + int consumed = m_enc_handler.decrypt(m_recv_buffer, bytes_transferred); + #ifndef TORRENT_DISABLE_LOGGING + if (consumed + bytes_transferred > 0) + peer_log(peer_log_alert::incoming_message, "ENCRYPTION" + , "decrypted block s = %d", int(consumed + bytes_transferred)); + #endif + if (bytes_transferred == SIZE_MAX) + { + disconnect(errors::parse_failed, op_encryption); + return; + } + received_bytes(0, consumed); + + int sub_transferred = 0; + while (bytes_transferred > 0 && + ((sub_transferred = m_recv_buffer.advance_pos(bytes_transferred)) > 0)) + { + #if TORRENT_USE_ASSERTS + boost::int64_t cur_payload_dl = m_statistics.last_payload_downloaded(); + boost::int64_t cur_protocol_dl = m_statistics.last_protocol_downloaded(); + #endif + on_receive_impl(sub_transferred); + bytes_transferred -= sub_transferred; + TORRENT_ASSERT(sub_transferred > 0); + + #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); + boost::int64_t stats_diff = m_statistics.last_payload_downloaded() - cur_payload_dl + + m_statistics.last_protocol_downloaded() - cur_protocol_dl; + TORRENT_ASSERT(stats_diff == int(sub_transferred)); + #endif + + if (m_disconnecting) return; + } + } + else +#endif + on_receive_impl(bytes_transferred); + } + + void bt_peer_connection::on_receive_impl(std::size_t bytes_transferred) + { + boost::shared_ptr t = associated_torrent().lock(); + + buffer::const_interval recv_buffer = m_recv_buffer.get(); + +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + // m_state is set to read_pe_dhkey in initial state + // (read_protocol_identifier) for incoming, or in constructor + // for outgoing + if (m_state == read_pe_dhkey) + { + received_bytes(0, 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 == m_recv_buffer.get()); + + 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 + if (m_dh_key_exchange->compute_secret(recv_buffer.begin) != 0) + { + disconnect(errors::no_memory, op_encryption); + return; + } + +#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 = 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(boost::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 = 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 == read_pe_synchash) + { + TORRENT_ASSERT(!m_encrypted); + TORRENT_ASSERT(!m_rc4_encrypted); + TORRENT_ASSERT(!is_outgoing()); + TORRENT_ASSERT(recv_buffer == m_recv_buffer.get()); + + if (recv_buffer.left() < 20) + { + received_bytes(0, bytes_transferred); + + if (m_recv_buffer.packet_finished()) + disconnect(errors::sync_hash_not_found, op_bittorrent, 1); + return; + } + + if (!m_sync_hash.get()) + { + TORRENT_ASSERT(m_sync_bytes_read == 0); + hasher h; + + // compute synchash (hash('req1',S)) + h.update("req1", 4); + h.update(m_dh_key_exchange->get_secret(), dh_key_len); + + m_sync_hash.reset(new (std::nothrow) sha1_hash(h.final())); + if (!m_sync_hash) + { + received_bytes(0, bytes_transferred); + disconnect(errors::no_memory, op_encryption); + return; + } + } + + int syncoffset = get_syncoffset(m_sync_hash->data(), 20 + , recv_buffer.begin, recv_buffer.left()); + + // No sync + if (syncoffset == -1) + { + received_bytes(0, bytes_transferred); + + std::size_t bytes_processed = recv_buffer.left() - 20; + m_sync_bytes_read += bytes_processed; + if (m_sync_bytes_read >= 512) + { + disconnect(errors::sync_hash_not_found, op_encryption, 1); + 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 + { + std::size_t bytes_processed = syncoffset + 20; +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ENCRYPTION" + , "sync point (hash) found at offset %d" + , int(m_sync_bytes_read + bytes_processed - 20)); +#endif + m_state = read_pe_skey_vc; + // skey,vc - 28 bytes + m_sync_hash.reset(); + int transferred_used = bytes_processed - recv_buffer.left() + bytes_transferred; + TORRENT_ASSERT(transferred_used <= int(bytes_transferred)); + received_bytes(0, transferred_used); + bytes_transferred -= transferred_used; + m_recv_buffer.cut(bytes_processed, 28); + } + } + + if (m_state == read_pe_skey_vc) + { + received_bytes(0, 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.begin); + 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); + } + + init_pe_rc4_handler(m_dh_key_exchange->get_secret(), ti->info_hash()); +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ENCRYPTION", "stream key found, torrent located"); +#endif + } + + if (!m_rc4.get()) + { + disconnect(errors::invalid_info_hash, op_bittorrent, 1); + return; + } + + // verify constant + buffer::interval wr_recv_buf = m_recv_buffer.mutable_buffer(); + rc4_decrypt(wr_recv_buf.begin + 20, 8); + wr_recv_buf.begin += 28; + + 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, op_encryption, 2); + return; + } + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ENCRYPTION", "verification constant found"); +#endif + m_state = read_pe_cryptofield; + m_recv_buffer.reset(4 + 2); + } + + // cannot fall through into + if (m_state == read_pe_syncvc) + { + TORRENT_ASSERT(is_outgoing()); + TORRENT_ASSERT(!m_encrypted); + TORRENT_ASSERT(!m_rc4_encrypted); + TORRENT_ASSERT(recv_buffer == m_recv_buffer.get()); + + if (recv_buffer.left() < 8) + { + received_bytes(0, bytes_transferred); + if (m_recv_buffer.packet_finished()) + disconnect(errors::invalid_encryption_constant, op_encryption, 2); + return; + } + + // generate the verification constant + if (!m_sync_vc.get()) + { + TORRENT_ASSERT(m_sync_bytes_read == 0); + + m_sync_vc.reset(new (std::nothrow) char[8]); + if (!m_sync_vc) + { + disconnect(errors::no_memory, op_encryption); + return; + } + std::fill(m_sync_vc.get(), m_sync_vc.get() + 8, 0); + rc4_decrypt(m_sync_vc.get(), 8); + } + + TORRENT_ASSERT(m_sync_vc.get()); + int syncoffset = get_syncoffset(m_sync_vc.get(), 8 + , recv_buffer.begin, recv_buffer.left()); + + // No sync + if (syncoffset == -1) + { + std::size_t bytes_processed = recv_buffer.left() - 8; + m_sync_bytes_read += bytes_processed; + received_bytes(0, bytes_transferred); + + if (m_sync_bytes_read >= 512) + { + disconnect(errors::invalid_encryption_constant, op_encryption, 2); + 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 + { + std::size_t bytes_processed = syncoffset + 8; +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ENCRYPTION" + , "sync point (verification constant) found at offset %d" + , int(m_sync_bytes_read + bytes_processed - 8)); +#endif + int transferred_used = bytes_processed - recv_buffer.left() + bytes_transferred; + TORRENT_ASSERT(transferred_used <= int(bytes_transferred)); + received_bytes(0, transferred_used); + bytes_transferred -= transferred_used; + + m_recv_buffer.cut(bytes_processed, 4 + 2); + + // delete verification constant + m_sync_vc.reset(); + m_state = read_pe_cryptofield; + // fall through + } + } + + if (m_state == 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, bytes_transferred); + bytes_transferred = 0; + + if (!m_recv_buffer.packet_finished()) return; + + buffer::interval wr_buf = m_recv_buffer.mutable_buffer(); + rc4_decrypt(wr_buf.begin, m_recv_buffer.packet_size()); + + recv_buffer = m_recv_buffer.get(); + + boost::uint32_t crypto_field = detail::read_uint32(recv_buffer.begin); + +#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); + boost::uint32_t crypto_select = crypto_field & 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)) + { + boost::uint32_t mask = (std::numeric_limits::max)(); + while (crypto_select & (mask << 1)) + { + mask <<= 1; + crypto_select = crypto_select & mask; + } + } + else + { + boost::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, op_encryption, 1); + return; + } + + // write the pe4 step + write_pe4_sync(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 &= allowed_encryption; + if (crypto_field == 0) + { + // we don't allow any of the offered encryption levels + disconnect(errors::unsupported_encryption_mode_selected, op_encryption, 2); + 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 = detail::read_int16(recv_buffer.begin); + if (len_pad < 0 || len_pad > 512) + { + disconnect(errors::invalid_pad_size, op_encryption, 2); + return; + } + + m_state = read_pe_pad; + if (!is_outgoing()) + m_recv_buffer.reset(len_pad + 2); // len(IA) at the end of pad + else + { + if (len_pad == 0) + { + m_encrypted = true; + if (m_rc4_encrypted) + { + switch_send_crypto(m_rc4); + switch_recv_crypto(m_rc4); + } + m_state = init_bt_handshake; + } + else + m_recv_buffer.reset(len_pad); + } + } + + if (m_state == read_pe_pad) + { + TORRENT_ASSERT(!m_encrypted); + received_bytes(0, bytes_transferred); + bytes_transferred = 0; + if (!m_recv_buffer.packet_finished()) return; + + int pad_size = is_outgoing() ? m_recv_buffer.packet_size() : m_recv_buffer.packet_size() - 2; + + buffer::interval wr_buf = m_recv_buffer.mutable_buffer(); + rc4_decrypt(wr_buf.begin, m_recv_buffer.packet_size()); + + recv_buffer = m_recv_buffer.get(); + + if (!is_outgoing()) + { + recv_buffer.begin += pad_size; + int len_ia = detail::read_int16(recv_buffer.begin); + + if (len_ia < 0) + { + disconnect(errors::invalid_encrypt_handshake, op_encryption, 2); + return; + } + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ENCRYPTION", "len(IA) : %d", len_ia); +#endif + if (len_ia == 0) + { + // everything after this is Encrypt2 + m_encrypted = true; + if (m_rc4_encrypted) + { + switch_send_crypto(m_rc4); + switch_recv_crypto(m_rc4); + } + m_state = init_bt_handshake; + } + else + { + m_state = read_pe_ia; + m_recv_buffer.reset(len_ia); + } + } + else // is_outgoing() + { + // everything that arrives after this is Encrypt2 + m_encrypted = true; + if (m_rc4_encrypted) + { + switch_send_crypto(m_rc4); + switch_recv_crypto(m_rc4); + } + m_state = init_bt_handshake; + } + } + + if (m_state == read_pe_ia) + { + received_bytes(0, bytes_transferred); + bytes_transferred = 0; + TORRENT_ASSERT(!is_outgoing()); + TORRENT_ASSERT(!m_encrypted); + + if (!m_recv_buffer.packet_finished()) return; + + // ia is always rc4, so decrypt it + buffer::interval wr_buf = m_recv_buffer.mutable_buffer(); + rc4_decrypt(wr_buf.begin, m_recv_buffer.packet_size()); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ENCRYPTION" + , "decrypted ia : %d bytes", m_recv_buffer.packet_size()); +#endif + + // everything that arrives after this is encrypted + m_encrypted = true; + if (m_rc4_encrypted) + { + switch_send_crypto(m_rc4); + switch_recv_crypto(m_rc4); + } + m_rc4.reset(); + + m_state = read_protocol_identifier; + m_recv_buffer.cut(0, 20); + } + + if (m_state == init_bt_handshake) + { + received_bytes(0, bytes_transferred); + bytes_transferred = 0; + TORRENT_ASSERT(m_encrypted); + + // decrypt remaining received bytes + if (m_rc4_encrypted) + { + buffer::interval wr_buf = m_recv_buffer.mutable_buffer(); + wr_buf.begin += m_recv_buffer.packet_size(); + rc4_decrypt(wr_buf.begin, wr_buf.left()); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ENCRYPTION" + , "decrypted remaining %d bytes", wr_buf.left()); +#endif + } + m_rc4.reset(); + + // payload stream, start with 20 handshake bytes + m_state = read_protocol_identifier; + m_recv_buffer.reset(20); + + // encrypted portion of handshake completed, toggle + // peer_info pe_support flag back to true + if (is_outgoing() && + m_settings.get_int(settings_pack::out_enc_policy) + == settings_pack::pe_enabled) + { + torrent_peer* pi = peer_info_struct(); + TORRENT_ASSERT(pi); + + pi->pe_support = true; + } + } + +#endif // #if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + + if (m_state == read_protocol_identifier) + { + received_bytes(0, 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 packet_size = recv_buffer[0]; + const char protocol_string[] = "\x13" "BitTorrent protocol"; + + if (packet_size != 19 || + memcmp(recv_buffer.begin, protocol_string, 20) != 0) + { +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ENCRYPTION" + , "unrecognized protocol header"); +#endif + +#ifdef TORRENT_USE_OPENSSL + if (is_ssl(*get_socket())) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ENCRYPTION" + , "SSL peers are not allowed to use any other encryption"); +#endif + disconnect(errors::invalid_info_hash, op_bittorrent, 1); + return; + } +#endif // TORRENT_USE_OPENSSL + + if (!is_outgoing() + && m_settings.get_int(settings_pack::in_enc_policy) + == settings_pack::pe_disabled) + { + disconnect(errors::no_incoming_encrypted, op_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, op_bittorrent, 1); + return; + } + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ENCRYPTION", "attempting encrypted connection"); +#endif + m_state = read_pe_dhkey; + m_recv_buffer.cut(0, dh_key_len); + TORRENT_ASSERT(!m_recv_buffer.packet_finished()); + return; +#else + disconnect(errors::invalid_info_hash, op_bittorrent, 1); + return; +#endif // TORRENT_DISABLE_ENCRYPTION + } + else + { +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + TORRENT_ASSERT(m_state != 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, op_bittorrent); + return; + } +#endif + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "HANDSHAKE", "BitTorrent protocol"); +#endif + } + + m_state = read_info_hash; + m_recv_buffer.reset(28); + } + + // fall through + if (m_state == read_info_hash) + { + received_bytes(0, 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.resize(8 * 8); + for (int i=0; i < 8; ++i) + { + for (int j=0; j < 8; ++j) + { + if (recv_buffer[i] & (0x80 >> j)) extensions[i*8+j] = '1'; + else extensions[i*8+j] = '0'; + } + } + peer_log(peer_log_alert::incoming_message, "EXTENSIONS", "%s ext: %s%s%s" + , extensions.c_str() + , (recv_buffer[7] & 0x01) ? "DHT " : "" + , (recv_buffer[7] & 0x04) ? "FAST " : "" + , (recv_buffer[5] & 0x10) ? "extension " : ""); +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + std::memcpy(m_reserved_bits, recv_buffer.begin, 8); + if ((recv_buffer[5] & 0x10)) + m_supports_extensions = true; +#endif + if (recv_buffer[7] & 0x01) + m_supports_dht_port = true; + + if (recv_buffer[7] & 0x04) + m_supports_fast = true; + + t = associated_torrent().lock(); + + // ok, now we have got enough of the handshake. Is this connection + // attached to a torrent? + if (!t) + { + // now, we have to see if there's a torrent with the + // info_hash we got from the peer + sha1_hash info_hash; + std::copy(recv_buffer.begin + 8, recv_buffer.begin + 28 + , info_hash.data()); + + attach_to_torrent(info_hash); + if (is_disconnecting()) return; + } + else + { + // verify info hash + if (!std::equal(recv_buffer.begin + 8, recv_buffer.begin + 28 + , t->torrent_file().info_hash().data())) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ERROR", "received invalid info_hash"); +#endif + disconnect(errors::invalid_info_hash, op_bittorrent, 1); + return; + } + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming, "HANDSHAKE", "info_hash received"); +#endif + } + + t = associated_torrent().lock(); + TORRENT_ASSERT(t); + + // if this is a local connection, we have already + // sent the handshake + if (!is_outgoing()) write_handshake(); + TORRENT_ASSERT(m_sent_handshake); + + if (is_disconnecting()) return; + + m_state = read_peer_id; + m_recv_buffer.reset(20); + } + + // fall through + if (m_state == read_peer_id) + { + TORRENT_ASSERT(m_sent_handshake); + received_bytes(0, 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 + { + char hex_pid[41]; + to_hex(recv_buffer.begin, 20, hex_pid); + hex_pid[40] = 0; + char ascii_pid[21]; + ascii_pid[20] = 0; + for (int i = 0; i != 20; ++i) + { + if (is_print(recv_buffer.begin[i])) ascii_pid[i] = recv_buffer.begin[i]; + else ascii_pid[i] = '.'; + } + peer_log(peer_log_alert::incoming, "HANDSHAKE", "received peer_id: %s client: %s ascii: \"%s\"" + , hex_pid, identify_client(peer_id(recv_buffer.begin)).c_str(), ascii_pid); + } +#endif + peer_id pid; + std::copy(recv_buffer.begin, recv_buffer.begin + 20, pid.data()); + + if (t->settings().get_bool(settings_pack::allow_multiple_connections_per_ip)) + { + // 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, op_bittorrent); + } + else + { + disconnect(errors::duplicate_peer_id, op_bittorrent); + return; + } + } + } + + set_pid(pid); + + // disconnect if the peer has the same peer-id as ourself + // since it most likely is ourself then + if (pid == m_our_peer_id) + { + if (peer_info_struct()) t->ban_peer(peer_info_struct()); + disconnect(errors::self_connection, op_bittorrent, 1); + return; + } + + m_client_version = identify_client(pid); + boost::optional f = client_fingerprint(pid); + if (f && std::equal(f->name, f->name + 2, "BC")) + { + // if this is a bitcomet client, lower the request queue size limit + if (max_out_request_queue() > 50) max_out_request_queue(50); + } + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator 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; + + if (m_supports_extensions) write_extensions(); +#endif + +#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) && !defined(TORRENT_DISABLE_EXTENSIONS) + // 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(); +#ifndef TORRENT_DISABLE_DHT + if (m_supports_dht_port && m_ses.has_dht()) + write_dht_port(m_ses.external_udp_port()); +#endif + + // 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 = read_packet_size; + m_recv_buffer.reset(5); + + TORRENT_ASSERT(!m_recv_buffer.packet_finished()); + return; + } + + // cannot fall through into + if (m_state == read_packet_size) + { + // Make sure this is not fallen though into + TORRENT_ASSERT(recv_buffer == m_recv_buffer.get()); + 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 = recv_buffer.left() > 4 ? bytes_transferred - 1: bytes_transferred; + received_bytes(0, used_bytes); + bytes_transferred -= used_bytes; + if (recv_buffer.left() < 4) return; + + TORRENT_ASSERT(bytes_transferred <= 1); + + const char* ptr = recv_buffer.begin; + int packet_size = detail::read_int32(ptr); + + // don't accept packets larger than 1 MB + if (packet_size > 1024*1024 || packet_size < 0) + { + // packet too large + received_bytes(0, bytes_transferred); + disconnect(errors::packet_too_large, op_bittorrent, 2); + return; + } + + if (packet_size == 0) + { + TORRENT_ASSERT(bytes_transferred <= 1); + received_bytes(0, bytes_transferred); + incoming_keepalive(); + if (is_disconnecting()) return; + // keepalive message + m_state = read_packet_size; + m_recv_buffer.cut(4, 5); + return; + } + if (recv_buffer.left() < 5) return; + + m_state = read_packet; + m_recv_buffer.cut(4, packet_size); + recv_buffer = m_recv_buffer.get(); + TORRENT_ASSERT(recv_buffer.left() == 1); + TORRENT_ASSERT(bytes_transferred == 1); + } + + if (m_state == read_packet) + { + TORRENT_ASSERT(recv_buffer == m_recv_buffer.get()); + if (!t) + { + received_bytes(0, bytes_transferred); + disconnect(errors::torrent_removed, op_bittorrent, 1); + return; + } +#if TORRENT_USE_ASSERTS + boost::int64_t cur_payload_dl = statistics().last_payload_downloaded(); + boost::int64_t cur_protocol_dl = statistics().last_protocol_downloaded(); +#endif + if (dispatch_message(bytes_transferred)) + { + m_state = 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); + boost::int64_t stats_diff = statistics().last_payload_downloaded() - cur_payload_dl + + statistics().last_protocol_downloaded() - cur_protocol_dl; + TORRENT_ASSERT(stats_diff == boost::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) && !defined(TORRENT_DISABLE_EXTENSIONS) + int bt_peer_connection::hit_send_barrier(std::vector& iovec) + { + int next_barrier = 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 next_barrier; + } +#endif + + // -------------------------- + // SEND DATA + // -------------------------- + + void bt_peer_connection::on_sent(error_code const& error + , std::size_t bytes_transferred) + { + INVARIANT_CHECK; + + if (error) + { + sent_bytes(0, 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 + std::vector::iterator first_to_keep = m_payloads.begin(); + + for (std::vector::iterator i = m_payloads.begin(); + i != m_payloads.end(); ++i) + { + i->start -= 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, bytes_transferred - amount_payload); + + if (amount_payload > 0) + { + boost::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 + { + boost::shared_ptr t = associated_torrent().lock(); + +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + TORRENT_ASSERT( (bool(m_state != 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..789cdb2 --- /dev/null +++ b/src/chained_buffer.cpp @@ -0,0 +1,202 @@ +/* + +Copyright (c) 2007-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/chained_buffer.hpp" +#include "libtorrent/assert.hpp" + +namespace libtorrent +{ + void chained_buffer::pop_front(int bytes_to_pop) + { + TORRENT_ASSERT(is_single_thread()); + 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.start += bytes_to_pop; + b.used_size -= 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.free_fun(b.buf, b.userdata, b.ref); + 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(); + } + } + + void chained_buffer::append_buffer(char* buffer, int s, int used_size + , free_buffer_fun destructor, void* userdata + , block_cache_reference ref) + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(s >= used_size); + buffer_t b; + b.buf = buffer; + b.size = s; + b.start = buffer; + b.used_size = used_size; + b.free_fun = destructor; + b.userdata = userdata; + b.ref = ref; + m_vec.push_back(b); + + m_bytes += used_size; + m_capacity += s; + TORRENT_ASSERT(m_bytes <= m_capacity); + } + + void chained_buffer::prepend_buffer(char* buffer, int s, int used_size + , free_buffer_fun destructor, void* userdata + , block_cache_reference ref) + { + TORRENT_ASSERT(s >= used_size); + buffer_t b; + b.buf = buffer; + b.size = s; + b.start = buffer; + b.used_size = used_size; + b.free_fun = destructor; + b.userdata = userdata; + b.ref = ref; + m_vec.push_front(b); + + m_bytes += used_size; + m_capacity += s; + TORRENT_ASSERT(m_bytes <= m_capacity); + } + + // 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()); + if (m_vec.empty()) return 0; + buffer_t& b = m_vec.back(); + return b.size - b.used_size - (b.start - b.buf); + } + + // tries to copy the given buffer to the end of the + // last chained buffer. If there's not enough room + // it returns false + char* chained_buffer::append(char const* buf, int s) + { + TORRENT_ASSERT(is_single_thread()); + char* insert = allocate_appendix(s); + if (insert == 0) return 0; + memcpy(insert, buf, s); + 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 s) + { + TORRENT_ASSERT(is_single_thread()); + if (m_vec.empty()) return 0; + buffer_t& b = m_vec.back(); + char* insert = b.start + b.used_size; + if (insert + s > b.buf + b.size) return 0; + b.used_size += s; + m_bytes += s; + TORRENT_ASSERT(m_bytes <= m_capacity); + return insert; + } + + std::vector const& chained_buffer::build_iovec(int to_send) + { + TORRENT_ASSERT(is_single_thread()); + 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) + { + build_vec(bytes, vec); + } + + template + void chained_buffer::build_vec(int bytes, std::vector &vec) + { + for (std::deque::iterator i = m_vec.begin() + , end(m_vec.end()); bytes > 0 && i != end; ++i) + { + if (i->used_size > bytes) + { + TORRENT_ASSERT(bytes > 0); + vec.push_back(Buffer(i->start, bytes)); + break; + } + TORRENT_ASSERT(i->used_size > 0); + vec.push_back(Buffer(i->start, i->used_size)); + bytes -= i->used_size; + } + } + + void chained_buffer::clear() + { + for (std::deque::iterator i = m_vec.begin() + , end(m_vec.end()); i != end; ++i) + { + i->free_fun(i->buf, i->userdata, i->ref); + } + m_bytes = 0; + m_capacity = 0; + m_vec.clear(); + } + + chained_buffer::~chained_buffer() + { +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(!m_destructed); + m_destructed = true; +#endif + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(m_bytes >= 0); + TORRENT_ASSERT(m_capacity >= 0); + clear(); + } + +} + diff --git a/src/choker.cpp b/src/choker.cpp new file mode 100644 index 0000000..783b004 --- /dev/null +++ b/src/choker.cpp @@ -0,0 +1,419 @@ +/* + +Copyright (c) 2014-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/choker.hpp" +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/torrent.hpp" + +#include + +namespace libtorrent +{ + + namespace { + + // 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) + { + // if one peer belongs to a higher priority torrent than the other one + // that one should be unchoked. + boost::shared_ptr t1 = lhs->associated_torrent().lock(); + TORRENT_ASSERT(t1); + boost::shared_ptr t2 = rhs->associated_torrent().lock(); + TORRENT_ASSERT(t2); + + int prio1 = lhs->get_priority(peer_connection::upload_channel); + int prio2 = rhs->get_priority(peer_connection::upload_channel); + + if (prio1 != prio2) + return prio1 > prio2; + + // compare how many bytes they've sent us + boost::int64_t c1; + boost::int64_t c2; + c1 = lhs->downloaded_in_last_round(); + c2 = rhs->downloaded_in_last_round(); + + if (c1 != c2) return c1 > c2; + + // when seeding, rotate which peer is unchoked in a round-robin fasion + + // the amount uploaded since unchoked (not just in the last round) + c1 = lhs->uploaded_since_unchoked(); + c2 = 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. + + // if a peer is already unchoked, and the number of bytes sent since it was unchoked + // is greater than the send quanta, then it's done with it' upload slot, and we + // can de-prioritize it + bool c1_quota_complete = !lhs->is_choked() && c1 + > (std::max)(t1->torrent_file().piece_length() * pieces, 256 * 1024); + bool c2_quota_complete = !rhs->is_choked() && c2 + > (std::max)(t2->torrent_file().piece_length() * pieces, 256 * 1024); + + // if c2 has completed a quanta, it should be de-prioritized + // and vice versa + if (c1_quota_complete < c2_quota_complete) return true; + if (c1_quota_complete > c2_quota_complete) return false; + + // if both peers have either completed a quanta, or not. + // keep unchoked peers prioritized over choked ones, to let + // peers keep working on uploading a full quanta + if (lhs->is_choked() < rhs->is_choked()) return true; + if (lhs->is_choked() > rhs->is_choked()) return false; + + // 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) + { + // if one peer belongs to a higher priority torrent than the other one + // that one should be unchoked. + boost::shared_ptr t1 = lhs->associated_torrent().lock(); + TORRENT_ASSERT(t1); + boost::shared_ptr t2 = rhs->associated_torrent().lock(); + TORRENT_ASSERT(t2); + + int prio1 = lhs->get_priority(peer_connection::upload_channel); + int prio2 = rhs->get_priority(peer_connection::upload_channel); + + if (prio1 != prio2) + return prio1 > prio2; + + // compare how many bytes they've sent us + boost::int64_t c1; + boost::int64_t c2; + c1 = lhs->downloaded_in_last_round(); + c2 = rhs->downloaded_in_last_round(); + + if (c1 != c2) return c1 > c2; + + // when seeding, prefer the peer we're uploading the fastest to + c1 = lhs->uploaded_in_last_round(); + c2 = rhs->uploaded_in_last_round(); + + // take torrent priority into account + c1 *= prio1; + c2 *= prio2; + + if (c1 > c2) return true; + if (c2 > c1) return false; + + // 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_anti_leech(peer_connection const* lhs + , peer_connection const* rhs) + { + // if one peer belongs to a higher priority torrent than the other one + // that one should be unchoked. + boost::shared_ptr t1 = lhs->associated_torrent().lock(); + TORRENT_ASSERT(t1); + boost::shared_ptr t2 = rhs->associated_torrent().lock(); + TORRENT_ASSERT(t2); + + int prio1 = lhs->get_priority(peer_connection::upload_channel); + int prio2 = rhs->get_priority(peer_connection::upload_channel); + + if (prio1 != prio2) + return prio1 > prio2; + + // compare how many bytes they've sent us + boost::int64_t c1; + boost::int64_t c2; + c1 = lhs->downloaded_in_last_round(); + c2 = rhs->downloaded_in_last_round(); + + if (c1 != c2) return c1 > c2; + + // 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% + int t1_total = t1->torrent_file().num_pieces(); + int t2_total = t2->torrent_file().num_pieces(); + int score1 = (lhs->num_have_pieces() < t1_total / 2 + ? t1_total - lhs->num_have_pieces() : lhs->num_have_pieces()) * 1000 / t1_total; + int score2 = (rhs->num_have_pieces() < t2_total / 2 + ? t2_total - rhs->num_have_pieces() : rhs->num_have_pieces()) * 1000 / t2_total; + if (score1 > score2) return true; + if (score2 > score1) return false; + + // 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) + { + boost::int64_t c1; + boost::int64_t c2; + + c1 = lhs->uploaded_in_last_round(); + c2 = rhs->uploaded_in_last_round(); + + // take torrent priority into account + c1 *= lhs->get_priority(peer_connection::upload_channel); + c2 *= rhs->get_priority(peer_connection::upload_channel); + + return c1 > c2; + } + + bool bittyrant_unchoke_compare(peer_connection const* lhs + , peer_connection const* rhs) + { + boost::int64_t d1, d2, u1, u2; + + // first compare how many bytes they've sent us + d1 = lhs->downloaded_in_last_round(); + d2 = rhs->downloaded_in_last_round(); + // divided by the number of bytes we've sent them + u1 = lhs->uploaded_in_last_round(); + u2 = rhs->uploaded_in_last_round(); + + // take torrent priority into account + d1 *= lhs->get_priority(peer_connection::upload_channel); + d2 *= rhs->get_priority(peer_connection::upload_channel); + + d1 = d1 * 1000 / (std::max)(boost::int64_t(1), u1); + d2 = d2 * 1000 / (std::max)(boost::int64_t(1), u2); + if (d1 > d2) return true; + if (d1 < d2) return false; + + // if both peers are still in their send quota or not in their send quota + // prioritize the one that has waited the longest to be unchoked + return lhs->time_of_last_unchoke() < rhs->time_of_last_unchoke(); + } + + } // anonymous namespace + + int unchoke_sort(std::vector& peers + , int max_upload_rate + , time_duration unchoke_interval + , aux::session_settings const& sett) + { +#if TORRENT_USE_ASSERTS + for (std::vector::iterator i = peers.begin() + , end(peers.end()); i != end; ++i) + { + TORRENT_ASSERT((*i)->self()); + TORRENT_ASSERT((*i)->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)(); + + // ==== BitTyrant ==== + // + // if we're using the bittyrant unchoker, go through all peers that + // we have unchoked already, and adjust our estimated reciprocation + // rate. If the peer has reciprocated, lower the estimate, if it hasn't, + // increase the estimate (this attempts to optimize "ROI" of upload + // capacity, by sending just enough to be reciprocated). + // For more information, see: http://bittyrant.cs.washington.edu/ + if (sett.get_int(settings_pack::choking_algorithm) + == settings_pack::bittyrant_choker) + { + for (std::vector::const_iterator i = peers.begin() + , end(peers.end()); i != end; ++i) + { + peer_connection* p = *i; + if (p->is_choked() || !p->is_interesting()) continue; + + if (!p->has_peer_choked()) + { + // we're unchoked, we may want to lower our estimated + // reciprocation rate + p->decrease_est_reciprocation_rate(); + } + else + { + // we've unchoked this peer, and it hasn't reciprocated + // we may want to increase our estimated reciprocation rate + p->increase_est_reciprocation_rate(); + } + } + + // if we're using the bittyrant choker, sort peers by their return + // on investment. i.e. download rate / upload rate + std::sort(peers.begin(), peers.end() + , boost::bind(&bittyrant_unchoke_compare, _1, _2)); + + int upload_capacity_left = max_upload_rate; + + // now, figure out how many peers should be unchoked. We deduct the + // estimated reciprocation rate from our upload_capacity estimate + // until there none left + upload_slots = 0; + + for (std::vector::iterator i = peers.begin() + , end(peers.end()); i != end; ++i) + { + peer_connection* p = *i; + TORRENT_ASSERT(p); + + if (p->est_reciprocation_rate() > upload_capacity_left) break; + + ++upload_slots; + upload_capacity_left -= p->est_reciprocation_rate(); + } + + return upload_slots; + } + + // ==== 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 our threshold + // by 1 kB/s. The first peer we get to 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; + + // TODO: optimize this using partial_sort or something. We don't need + // to sort the entire list + + // TODO: make the comparison function a free function and move it + // into this cpp file + std::sort(peers.begin(), peers.end() + , boost::bind(&upload_rate_compare, _1, _2)); + + // TODO: make configurable + int rate_threshold = 1024; + + for (std::vector::const_iterator i = peers.begin() + , end(peers.end()); i != end; ++i) + { + peer_connection const& p = **i; + int const rate = int(p.uploaded_in_last_round() + * 1000 / total_milliseconds(unchoke_interval)); + + if (rate < rate_threshold) break; + + ++upload_slots; + + // TODO: make configurable + rate_threshold += 1024; + } + ++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. + + 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::partial_sort(peers.begin(), peers.begin() + + (std::min)(upload_slots, int(peers.size())), peers.end() + , boost::bind(&unchoke_compare_rr, _1, _2, pieces)); + } + else if (sett.get_int(settings_pack::seed_choking_algorithm) + == settings_pack::fastest_upload) + { + std::partial_sort(peers.begin(), peers.begin() + + (std::min)(upload_slots, int(peers.size())), peers.end() + , boost::bind(&unchoke_compare_fastest_upload, _1, _2)); + } + else if (sett.get_int(settings_pack::seed_choking_algorithm) + == settings_pack::anti_leech) + { + std::partial_sort(peers.begin(), peers.begin() + + (std::min)(upload_slots, int(peers.size())), peers.end() + , boost::bind(&unchoke_compare_anti_leech, _1, _2)); + } + else + { + int const pieces = sett.get_int(settings_pack::seeding_piece_quota); + std::partial_sort(peers.begin(), peers.begin() + + (std::min)(upload_slots, int(peers.size())), peers.end() + , boost::bind(&unchoke_compare_rr, _1, _2, pieces)); + + TORRENT_ASSERT(false); + } + + return upload_slots; + } + +} diff --git a/src/close_reason.cpp b/src/close_reason.cpp new file mode 100644 index 0000000..bb4cbe5 --- /dev/null +++ b/src/close_reason.cpp @@ -0,0 +1,176 @@ +/* + +Copyright (c) 2015-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/close_reason.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/error_code.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent +{ + + close_reason_t error_to_close_reason(error_code const& ec) + { + if (ec.category() == get_libtorrent_category()) + { +#define TORRENT_MAP(error, close_reason) \ + case errors:: error : \ + return close_reason; + + switch (ec.value()) + { + TORRENT_MAP(invalid_swarm_metadata, close_invalid_metadata) + TORRENT_MAP(session_is_closing, close_torrent_removed) + TORRENT_MAP(peer_sent_empty_piece, close_invalid_piece_message) + TORRENT_MAP(mismatching_info_hash, close_invalid_info_hash) + TORRENT_MAP(port_blocked, close_port_blocked) + TORRENT_MAP(destructing_torrent, close_torrent_removed) + TORRENT_MAP(timed_out, close_timeout) + TORRENT_MAP(upload_upload_connection, close_upload_to_upload) + TORRENT_MAP(uninteresting_upload_peer, close_not_interested_upload_only) + TORRENT_MAP(invalid_info_hash, close_invalid_info_hash) + TORRENT_MAP(torrent_paused, close_torrent_removed) + TORRENT_MAP(invalid_have, close_invalid_have_message) + TORRENT_MAP(invalid_bitfield_size, close_invalid_bitfield_message) + TORRENT_MAP(too_many_requests_when_choked, close_request_when_choked) + TORRENT_MAP(invalid_piece, close_invalid_piece_message) + TORRENT_MAP(invalid_piece_size, close_invalid_piece_message) + TORRENT_MAP(no_memory, close_no_memory) + TORRENT_MAP(torrent_aborted, close_torrent_removed) + TORRENT_MAP(self_connection, close_self_connection) + TORRENT_MAP(timed_out_no_interest, close_timed_out_interest) + TORRENT_MAP(timed_out_inactivity, close_timed_out_activity) + TORRENT_MAP(timed_out_no_handshake, close_timed_out_handshake) + TORRENT_MAP(timed_out_no_request, close_timed_out_request) + TORRENT_MAP(invalid_choke, close_invalid_choke_message) + TORRENT_MAP(invalid_unchoke, close_invalid_unchoke_message) + TORRENT_MAP(invalid_interested, close_invalid_interested_message) + TORRENT_MAP(invalid_not_interested, close_invalid_not_interested_message) + TORRENT_MAP(invalid_request, close_invalid_request_message) + TORRENT_MAP(invalid_hash_list, close_invalid_message) + TORRENT_MAP(invalid_hash_piece, close_invalid_message) + TORRENT_MAP(invalid_cancel, close_invalid_cancel_message) + TORRENT_MAP(invalid_dht_port, close_invalid_dht_port_message) + TORRENT_MAP(invalid_suggest, close_invalid_suggest_message) + TORRENT_MAP(invalid_have_all, close_invalid_have_all_message) + TORRENT_MAP(invalid_have_none, close_invalid_have_none_message) + TORRENT_MAP(invalid_reject, close_invalid_reject_message) + TORRENT_MAP(invalid_allow_fast, close_invalid_allow_fast_message) + TORRENT_MAP(invalid_extended, close_invalid_extended_message) + TORRENT_MAP(invalid_message, close_invalid_message_id) + TORRENT_MAP(sync_hash_not_found, close_encryption_error) + TORRENT_MAP(invalid_encryption_constant, close_encryption_error) + TORRENT_MAP(no_plaintext_mode, close_protocol_blocked) + TORRENT_MAP(no_rc4_mode, close_protocol_blocked) + TORRENT_MAP(unsupported_encryption_mode_selected, close_protocol_blocked) + TORRENT_MAP(invalid_pad_size, close_encryption_error) + TORRENT_MAP(invalid_encrypt_handshake, close_encryption_error) + TORRENT_MAP(no_incoming_encrypted, close_protocol_blocked) + TORRENT_MAP(no_incoming_regular, close_protocol_blocked) + TORRENT_MAP(duplicate_peer_id, close_duplicate_peer_id) + TORRENT_MAP(torrent_removed, close_torrent_removed) + TORRENT_MAP(packet_too_large, close_message_too_big) + TORRENT_MAP(torrent_not_ready, close_torrent_removed) + TORRENT_MAP(session_closing, close_torrent_removed) + TORRENT_MAP(optimistic_disconnect, close_peer_churn) + TORRENT_MAP(torrent_finished, close_upload_to_upload) + TORRENT_MAP(too_many_corrupt_pieces, close_corrupt_pieces) + TORRENT_MAP(too_many_connections, close_too_many_connections) + TORRENT_MAP(peer_banned, close_blocked) + TORRENT_MAP(stopping_torrent, close_torrent_removed) + TORRENT_MAP(metadata_too_large, close_metadata_too_big) + TORRENT_MAP(invalid_metadata_size, close_metadata_too_big) + TORRENT_MAP(invalid_metadata_request, close_invalid_metadata_request_message) + TORRENT_MAP(invalid_metadata_offset, close_invalid_metadata_offset) + TORRENT_MAP(invalid_metadata_message, close_invalid_metadata_message) + TORRENT_MAP(pex_message_too_large, close_pex_message_too_big) + TORRENT_MAP(invalid_pex_message, close_invalid_pex_message) + TORRENT_MAP(invalid_lt_tracker_message, close_invalid_message) + TORRENT_MAP(too_frequent_pex, close_pex_too_frequent) + TORRENT_MAP(invalid_dont_have, close_invalid_dont_have_message) + TORRENT_MAP(requires_ssl_connection, close_protocol_blocked) + TORRENT_MAP(invalid_ssl_cert, close_blocked) + TORRENT_MAP(not_an_ssl_torrent, close_blocked) + TORRENT_MAP(banned_by_port_filter, close_port_blocked) + +#ifdef TORRENT_USE_ASSERTS + case errors::redirecting: + return close_no_reason; +#endif + + default: + return close_no_reason; + } + } + else if (ec.category() == boost::asio::error::get_misc_category()) + { + switch (ec.value()) + { + case boost::asio::error::eof: + return close_no_reason; + } + } + else if (ec.category() == boost::system::system_category()) + { + switch (ec.value()) + { +#ifdef TORRENT_USE_ASSERTS + case boost::system::errc::connection_reset: + case boost::system::errc::broken_pipe: + return close_no_reason; +#endif + case boost::system::errc::timed_out: + return close_timeout; + case boost::system::errc::too_many_files_open: + case boost::system::errc::too_many_files_open_in_system: + return close_too_many_files; + case boost::system::errc::not_enough_memory: + case boost::system::errc::no_buffer_space: + return close_no_memory; + } + } + else if (ec.category() == get_http_category()) + { + return close_no_memory; + } + + return close_no_reason; + } +} + diff --git a/src/cpuid.cpp b/src/cpuid.cpp new file mode 100644 index 0000000..c3869ae --- /dev/null +++ b/src/cpuid.cpp @@ -0,0 +1,93 @@ +/* + +Copyright (c) 2014-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include + +#include "libtorrent/aux_/cpuid.hpp" + +#if defined _MSC_VER && TORRENT_HAS_SSE +#include +#include +#endif + +#if TORRENT_HAS_SSE && defined __GNUC__ +#include +#endif + +namespace libtorrent { namespace aux +{ + namespace { + + // internal + void cpuid(unsigned int info[4], int type) + { +#if TORRENT_HAS_SSE && defined _MSC_VER + __cpuid((int*)info, type); + +#elif TORRENT_HAS_SSE && defined __GNUC__ + __get_cpuid(type, &info[0], &info[1], &info[2], &info[3]); +#else + // for non-x86 and non-amd64, just return zeroes + std::memset(&info[0], 0, sizeof(unsigned int) * 4); +#endif + } + + bool supports_sse42() + { +#if TORRENT_HAS_SSE + unsigned int cpui[4]; + cpuid(cpui, 1); + return cpui[2] & (1 << 20); +#else + return false; +#endif + } + + bool supports_mmx() + { +#if TORRENT_HAS_SSE + unsigned int cpui[4]; + cpuid(cpui, 1); + return cpui[2] & (1 << 23); +#else + return false; +#endif + } + + } // anonymous namespace + + bool sse42_support = supports_sse42(); + bool mmx_support = supports_mmx(); +} } + + diff --git a/src/crc32c.cpp b/src/crc32c.cpp new file mode 100644 index 0000000..087f9e3 --- /dev/null +++ b/src/crc32c.cpp @@ -0,0 +1,126 @@ +/* + +Copyright (c) 2014-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/crc32c.hpp" +#include "libtorrent/aux_/cpuid.hpp" +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#if (defined _MSC_VER && _MSC_VER >= 1600) +#include +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent +{ + boost::uint32_t crc32c_32(boost::uint32_t v) + { +#if TORRENT_HAS_SSE + if (aux::sse42_support) + { + boost::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 + + boost::crc_optimal<32, 0x1EDC6F41, 0xFFFFFFFF, 0xFFFFFFFF, true, true> crc; + crc.process_bytes(&v, 4); + return crc.checksum(); + } + + boost::uint32_t crc32c(boost::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__ + boost::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 boost::uint32_t(ret) ^ 0xffffffff; +#else + boost::uint32_t ret = 0xffffffff; + boost::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 + + boost::crc_optimal<32, 0x1EDC6F41, 0xFFFFFFFF, 0xFFFFFFFF, true, true> crc; + crc.process_bytes(buf, num_words * 8); + return crc.checksum(); + } +} + + diff --git a/src/create_torrent.cpp b/src/create_torrent.cpp new file mode 100644 index 0000000..dda3d47 --- /dev/null +++ b/src/create_torrent.cpp @@ -0,0 +1,748 @@ +/* + +Copyright (c) 2008-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/utf8.hpp" +#include "libtorrent/file_pool.hpp" +#include "libtorrent/storage.hpp" +#include "libtorrent/aux_/escape_string.hpp" // for convert_to_wstring +#include "libtorrent/disk_io_thread.hpp" +#include "libtorrent/aux_/merkle.hpp" // for merkle_*() +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/announce_entry.hpp" +#include "libtorrent/performance_counters.hpp" // for counters +#include "libtorrent/alert_manager.hpp" + +#include +#include +#include +#include + +#include +#include + +#define MAX_SYMLINK_PATH 200 + +namespace libtorrent +{ + + class alert; + + namespace + { + inline bool default_pred(std::string const&) { return true; } + + inline bool ignore_subdir(std::string const& leaf) + { return leaf == ".." || leaf == "."; } + + int get_file_attributes(std::string const& p) + { +#ifdef TORRENT_WINDOWS + WIN32_FILE_ATTRIBUTE_DATA attr; +#if TORRENT_USE_WSTRING + std::wstring path = convert_to_wstring(p); + GetFileAttributesExW(path.c_str(), GetFileExInfoStandard, &attr); +#else + std::string path = convert_to_native(p); + GetFileAttributesExA(path.c_str(), GetFileExInfoStandard, &attr); +#endif // TORRENT_USE_WSTRING + if (attr.dwFileAttributes == INVALID_FILE_ATTRIBUTES) return 0; + if (attr.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) return file_storage::attribute_hidden; + return 0; +#else + struct stat s; + if (lstat(convert_to_native(p).c_str(), &s) < 0) return 0; + int file_attr = 0; + if (s.st_mode & S_IXUSR) + file_attr += file_storage::attribute_executable; + if (S_ISLNK(s.st_mode)) + file_attr += file_storage::attribute_symlink; + return file_attr; +#endif + } + +#ifndef TORRENT_WINDOWS + std::string get_symlink_path_impl(char const* path) + { + char buf[MAX_SYMLINK_PATH]; + std::string f = convert_to_native(path); + int char_read = 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(buf); + } +#endif + + std::string get_symlink_path(std::string const& p) + { +#if defined TORRENT_WINDOWS + TORRENT_UNUSED(p); + return ""; +#else + std::string path = convert_to_native(p); + return get_symlink_path_impl(p.c_str()); +#endif + } + + void add_files_impl(file_storage& fs, std::string const& p + , std::string const& l, boost::function pred, boost::uint32_t flags) + { + std::string f = combine_path(p, l); + if (!pred(f)) return; + error_code ec; + file_status s; + stat_file(f, &s, ec, (flags & create_torrent::symlinks) ? dont_follow_links : 0); + if (ec) return; + + // recurse into directories + bool recurse = (s.mode & file_status::directory) != 0; + + // if the file is not a link or we're following links, and it's a directory + // only then should we recurse +#ifndef TORRENT_WINDOWS + if ((s.mode & file_status::link) && (flags & create_torrent::symlinks)) + recurse = false; +#endif + + if (recurse) + { + for (directory i(f, ec); !i.done(); i.next(ec)) + { + std::string 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 + int file_flags = get_file_attributes(f); + + // mask all bits to check if the file is a symlink + if ((file_flags & file_storage::attribute_symlink) + && (flags & create_torrent::symlinks)) + { + std::string sym_path = get_symlink_path(f); + fs.add_file(l, 0, file_flags, s.mtime, sym_path); + } + else + { + fs.add_file(l, s.file_size, file_flags, s.mtime); + } + } + } + + void on_hash(disk_io_job const* j, create_torrent* t + , boost::shared_ptr storage, disk_io_thread* iothread + , int* piece_counter, int* completed_piece + , boost::function const* f, error_code* ec) + { + if (j->ret != 0) + { + // on error + *ec = j->error.ec; + iothread->set_num_threads(0); + return; + } + t->set_hash(j->piece, sha1_hash(j->d.piece_hash)); + (*f)(*completed_piece); + ++(*completed_piece); + if (*piece_counter < t->num_pieces()) + { + iothread->async_hash(storage.get(), *piece_counter + , disk_io_job::sequential_access + , boost::bind(&on_hash, _1, t, storage, iothread + , piece_counter, completed_piece, f, ec), NULL); + ++(*piece_counter); + } + else + { + iothread->abort(true); + } + iothread->submit_jobs(); + } + + } // anonymous namespace + +#if TORRENT_USE_WSTRING +#ifndef TORRENT_NO_DEPRECATE + + void add_files(file_storage& fs, std::wstring const& wfile + , boost::function p, boost::uint32_t flags) + { + std::string utf8; + wchar_utf8(wfile, utf8); + add_files_impl(fs, parent_path(complete(utf8)) + , filename(utf8), p, flags); + } + + void add_files(file_storage& fs + , std::wstring const& wfile, boost::uint32_t flags) + { + std::string utf8; + wchar_utf8(wfile, utf8); + add_files_impl(fs, parent_path(complete(utf8)) + , filename(utf8), default_pred, flags); + } + + void set_piece_hashes(create_torrent& t, std::wstring const& p + , boost::function f, error_code& ec) + { + std::string utf8; + wchar_utf8(p, utf8); + set_piece_hashes(t, utf8, f, ec); + } + + void set_piece_hashes_deprecated(create_torrent& t, std::wstring const& p + , boost::function f, error_code& ec) + { + std::string utf8; + wchar_utf8(p, utf8); + set_piece_hashes(t, utf8, f, ec); + } +#endif +#endif + + void add_files(file_storage& fs, std::string const& file + , boost::function p, boost::uint32_t flags) + { + add_files_impl(fs, parent_path(complete(file)), filename(file), p, flags); + } + + void add_files(file_storage& fs, std::string const& file, boost::uint32_t flags) + { + add_files_impl(fs, parent_path(complete(file)), filename(file) + , default_pred, flags); + } + + void set_piece_hashes(create_torrent& t, std::string const& p + , boost::function const& f, error_code& ec) + { + // optimized path + io_service ios; + +#if TORRENT_USE_UNC_PATHS + std::string path = canonicalize_path(p); +#else + std::string const& path = p; +#endif + + if (t.files().num_files() == 0) + { + ec = error_code(errors::no_files_in_torrent, get_libtorrent_category()); + return; + } + + if (t.files().total_size() == 0) + { + ec = error_code(errors::torrent_invalid_length, get_libtorrent_category()); + return; + } + + // dummy torrent object pointer + boost::shared_ptr dummy; + counters cnt; + disk_io_thread disk_thread(ios, cnt, 0); + disk_thread.set_num_threads(1); + + storage_params params; + params.files = &t.files(); + params.mapped_files = NULL; + params.path = path; + params.pool = &disk_thread.files(); + params.mode = storage_mode_sparse; + + storage_interface* storage_impl = default_storage_constructor(params); + + boost::shared_ptr storage = boost::make_shared( + storage_impl, dummy, const_cast(&t.files())); + + settings_pack sett; + sett.set_int(settings_pack::cache_size, 0); + sett.set_int(settings_pack::aio_threads, 2); + + // TODO: this should probably be optional + alert_manager dummy2(0, 0); + disk_thread.set_settings(&sett, dummy2); + + int piece_counter = 0; + int completed_piece = 0; + int piece_read_ahead = 15 * 1024 * 1024 / t.piece_length(); + if (piece_read_ahead < 1) piece_read_ahead = 1; + + for (int i = 0; i < piece_read_ahead; ++i) + { + disk_thread.async_hash(storage.get(), i, disk_io_job::sequential_access + , boost::bind(&on_hash, _1, &t, storage, &disk_thread + , &piece_counter, &completed_piece, &f, &ec), NULL); + ++piece_counter; + if (piece_counter >= t.num_pieces()) break; + } + disk_thread.submit_jobs(); + ios.run(ec); + } + + create_torrent::~create_torrent() {} + + create_torrent::create_torrent(file_storage& fs, int piece_size + , int pad_file_limit, int flags, int alignment) + : m_files(fs) + , m_creation_date(time(0)) + , m_multifile(fs.num_files() > 1) + , m_private(false) + , m_merkle_torrent((flags & merkle) != 0) + , m_include_mtime((flags & modification_time) != 0) + , m_include_symlinks((flags & symlinks) != 0) + { + // 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(0))) m_multifile = true; + + // a piece_size of 0 means automatic + if (piece_size == 0 && !m_merkle_torrent) + { + const int target_size = 40 * 1024; + piece_size = int(fs.total_size() / (target_size / 20)); + + int i = 16*1024; + for (; i < 2*1024*1024; i *= 2) + { + if (piece_size > i) continue; + break; + } + piece_size = i; + } + else if (piece_size == 0 && m_merkle_torrent) + { + piece_size = 64*1024; + } + + // to support mutable torrents, alignment always has to be the piece size, + // because piece hashes are compared to determine whether files are + // identical + if (flags & mutable_torrent_support) + alignment = piece_size; + + // make sure the size is an even power of 2 +#ifndef NDEBUG + for (int i = 0; i < 32; ++i) + { + if (piece_size & (1 << i)) + { + TORRENT_ASSERT((piece_size & ~(1 << i)) == 0); + break; + } + } +#endif + m_files.set_piece_length(piece_size); + if (flags & (optimize_alignment | mutable_torrent_support)) + m_files.optimize(pad_file_limit, alignment, (flags & mutable_torrent_support) != 0); + + m_files.set_num_pieces(static_cast( + (m_files.total_size() + m_files.piece_length() - 1) / m_files.piece_length())); + m_piece_hash.resize(m_files.num_pieces()); + } + + create_torrent::create_torrent(torrent_info const& ti) + : m_files(const_cast(ti.files())) + , m_creation_date(time(0)) + , m_multifile(ti.num_files() > 1) + , m_private(ti.priv()) + , m_merkle_torrent(ti.is_merkle_torrent()) + , m_include_mtime(false) + , m_include_symlinks(false) + { + TORRENT_ASSERT(ti.is_valid()); + TORRENT_ASSERT(ti.num_pieces() > 0); + TORRENT_ASSERT(ti.num_files() > 0); + TORRENT_ASSERT(ti.total_size() > 0); + + if (ti.creation_date()) 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()); + + torrent_info::nodes_t const& nodes = ti.nodes(); + for (torrent_info::nodes_t::const_iterator i = nodes.begin() + , end(nodes.end()); i != end; ++i) + add_node(*i); + + std::vector const& trackers = ti.trackers(); + for (std::vector::const_iterator i = trackers.begin() + , end(trackers.end()); i != end; ++i) + add_tracker(i->url, i->tier); + + std::vector const& web_seeds = ti.web_seeds(); + for (std::vector::const_iterator i = web_seeds.begin() + , end(web_seeds.end()); i != end; ++i) + { + if (i->type == web_seed_entry::url_seed) + add_url_seed(i->url); + else if (i->type == web_seed_entry::http_seed) + add_http_seed(i->url); + } + + m_piece_hash.resize(m_files.num_pieces()); + for (int i = 0; i < num_pieces(); ++i) set_hash(i, ti.hash_for_piece(i)); + + boost::shared_array const info = ti.metadata(); + int const size = ti.metadata_size(); + m_info_dict.preformatted().assign(&info[0], &info[0] + size); + m_info_hash = ti.info_hash(); + } + + entry create_torrent::generate() const + { + TORRENT_ASSERT(m_files.piece_length() > 0); + + entry dict; + + if (m_files.num_files() == 0) + return 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 (nodes_t::const_iterator i = m_nodes.begin() + , end(m_nodes.end()); i != end; ++i) + { + entry::list_type node; + node.push_back(entry(i->first)); + node.push_back(entry(i->second)); + nodes_list.push_back(entry(node)); + } + } + + if (m_urls.size() > 1) + { + entry trackers(entry::list_t); + entry tier(entry::list_t); + int current_tier = m_urls.front().second; + for (std::vector::const_iterator i = m_urls.begin(); + i != m_urls.end(); ++i) + { + if (i->second != current_tier) + { + current_tier = i->second; + trackers.list().push_back(tier); + tier.list().clear(); + } + tier.list().push_back(entry(i->first)); + } + trackers.list().push_back(tier); + dict["announce-list"] = trackers; + } + + if (!m_comment.empty()) + dict["comment"] = m_comment; + + dict["creation date"] = m_creation_date; + + if (!m_created_by.empty()) + dict["created by"] = m_created_by; + + if (!m_url_seeds.empty()) + { + if (m_url_seeds.size() == 1) + { + dict["url-list"] = m_url_seeds.front(); + } + else + { + entry& list = dict["url-list"]; + for (std::vector::const_iterator i + = m_url_seeds.begin(); i != m_url_seeds.end(); ++i) + { + list.list().push_back(entry(*i)); + } + } + } + + if (!m_http_seeds.empty()) + { + if (m_http_seeds.size() == 1) + { + dict["httpseeds"] = m_http_seeds.front(); + } + else + { + entry& list = dict["httpseeds"]; + for (std::vector::const_iterator i + = m_http_seeds.begin(); i != m_http_seeds.end(); ++i) + { + list.list().push_back(entry(*i)); + } + } + } + + 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 (std::vector::const_iterator i + = m_collections.begin(); i != m_collections.end(); ++i) + { + list.list().push_back(entry(*i)); + } + } + + if (!m_similar.empty()) + { + entry& list = info["similar"]; + for (std::vector::const_iterator i + = m_similar.begin(); i != m_similar.end(); ++i) + { + list.list().push_back(entry(i->to_string())); + } + } + + info["name"] = m_files.name(); + + if (!m_root_cert.empty()) + info["ssl-cert"] = m_root_cert; + + if (m_private) info["private"] = 1; + + if (!m_multifile) + { + if (m_include_mtime) info["mtime"] = m_files.mtime(0); + info["length"] = m_files.file_size(0); + int const flags = m_files.file_flags(0); + if (flags & (file_storage::flag_pad_file + | file_storage::flag_hidden + | file_storage::flag_executable + | file_storage::flag_symlink)) + { + std::string& attr = info["attr"].string(); + if (flags & file_storage::flag_pad_file) attr += 'p'; + if (flags & file_storage::flag_hidden) attr += 'h'; + if (flags & file_storage::flag_executable) attr += 'x'; + if (m_include_symlinks && (flags & file_storage::flag_symlink)) attr += 'l'; + } + if (m_include_symlinks + && (flags & file_storage::flag_symlink)) + { + entry& sympath_e = info["symlink path"]; + + std::string split = split_path(m_files.symlink(0)); + for (char const* e = split.c_str(); e != 0; e = next_path_element(e)) + sympath_e.list().push_back(entry(e)); + } + if (!m_filehashes.empty()) + { + info["sha1"] = m_filehashes[0].to_string(); + } + } + else + { + if (!info.find_key("files")) + { + entry& files = info["files"]; + + for (int i = 0; i < m_files.num_files(); ++i) + { + files.list().push_back(entry()); + 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); + entry& path_e = file_e["path"]; + + TORRENT_ASSERT(has_parent_path(m_files.file_path(i))); + + { + std::string split = split_path(m_files.file_path(i)); + TORRENT_ASSERT(split.c_str() == m_files.name()); + + for (char const* e = next_path_element(split.c_str()); + e != 0; e = next_path_element(e)) + path_e.list().push_back(entry(e)); + } + + int flags = m_files.file_flags(i); + if (flags != 0) + { + std::string& attr = file_e["attr"].string(); + if (flags & file_storage::flag_pad_file) attr += 'p'; + if (flags & file_storage::flag_hidden) attr += 'h'; + if (flags & file_storage::flag_executable) attr += 'x'; + if (m_include_symlinks && (flags & file_storage::flag_symlink)) attr += 'l'; + } + + if (m_include_symlinks + && (flags & file_storage::flag_symlink)) + { + entry& sympath_e = file_e["symlink path"]; + + std::string split = split_path(m_files.symlink(i)); + for (char const* e = split.c_str(); e != 0; e = next_path_element(e)) + sympath_e.list().push_back(entry(e)); + } + if (!m_filehashes.empty() && m_filehashes[i] != sha1_hash()) + { + file_e["sha1"] = m_filehashes[i].to_string(); + } + } + } + } + + info["piece length"] = m_files.piece_length(); + if (m_merkle_torrent) + { + int num_leafs = merkle_num_leafs(m_files.num_pieces()); + int num_nodes = merkle_num_nodes(num_leafs); + int first_leaf = num_nodes - num_leafs; + m_merkle_tree.resize(num_nodes); + int num_pieces = m_piece_hash.size(); + for (int i = 0; i < num_pieces; ++i) + m_merkle_tree[first_leaf + i] = m_piece_hash[i]; + sha1_hash filler(0); + for (int i = num_pieces; i < num_leafs; ++i) + m_merkle_tree[first_leaf + i] = filler; + + // now that we have initialized all leaves, build + // each level bottom-up + int level_start = first_leaf; + int level_size = num_leafs; + while (level_start > 0) + { + int parent = merkle_get_parent(level_start); + for (int i = level_start; i < level_start + level_size; i += 2, ++parent) + { + hasher h; + h.update(m_merkle_tree[i].data(), 20); + h.update(m_merkle_tree[i+1].data(), 20); + m_merkle_tree[parent] = h.final(); + } + level_start = merkle_get_parent(level_start); + level_size /= 2; + } + TORRENT_ASSERT(level_size == 1); + std::string& p = info["root hash"].string(); + p.assign(m_merkle_tree[0].data(), 20); + } + else + { + std::string& p = info["pieces"].string(); + + for (std::vector::const_iterator i = m_piece_hash.begin(); + i != m_piece_hash.end(); ++i) + { + p.append(i->data(), sha1_hash::size); + } + } + + std::vector buf; + bencode(std::back_inserter(buf), info); + m_info_hash = hasher(&buf[0], buf.size()).final(); + + return dict; + } + + void create_torrent::add_tracker(std::string const& url, int tier) + { + m_urls.push_back(announce_entry(url, tier)); + + std::sort(m_urls.begin(), m_urls.end() + , boost::bind(&announce_entry::second, _1) < boost::bind(&announce_entry::second, _2)); + } + + void create_torrent::set_root_cert(std::string const& cert) + { + m_root_cert = cert; + } + + void create_torrent::add_similar_torrent(sha1_hash ih) + { + m_similar.push_back(ih); + } + + void create_torrent::add_collection(std::string c) + { + m_collections.push_back(c); + } + + void create_torrent::set_hash(int index, sha1_hash const& h) + { + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < int(m_piece_hash.size())); + m_piece_hash[index] = h; + } + + void create_torrent::set_file_hash(int index, sha1_hash const& h) + { + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < int(m_files.num_files())); + if (m_filehashes.empty()) m_filehashes.resize(m_files.num_files()); + m_filehashes[index] = h; + } + + void create_torrent::add_node(std::pair const& node) + { + m_nodes.push_back(node); + } + + void create_torrent::add_url_seed(std::string const& url) + { + m_url_seeds.push_back(url); + } + + void create_torrent::add_http_seed(std::string const& url) + { + m_http_seeds.push_back(url); + } + + void create_torrent::set_comment(char const* str) + { + if (str == 0) m_comment.clear(); + else m_comment = str; + } + + void create_torrent::set_creator(char const* str) + { + if (str == 0) m_created_by.clear(); + else m_created_by = str; + } + +} + diff --git a/src/disk_buffer_holder.cpp b/src/disk_buffer_holder.cpp new file mode 100644 index 0000000..83e7843 --- /dev/null +++ b/src/disk_buffer_holder.cpp @@ -0,0 +1,95 @@ +/* + +Copyright (c) 2008-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/disk_buffer_holder.hpp" +#include "libtorrent/aux_/session_impl.hpp" +#include "libtorrent/disk_io_thread.hpp" + +namespace libtorrent +{ + + disk_buffer_holder::disk_buffer_holder(buffer_allocator_interface& alloc, char* buf) + : m_allocator(alloc), m_buf(buf) + { + m_ref.storage = 0; + m_ref.piece = -1; + m_ref.block = -1; + } + + disk_buffer_holder::disk_buffer_holder(buffer_allocator_interface& alloc, disk_io_job const& j) + : m_allocator(alloc), m_buf(j.buffer.disk_block), m_ref(j.d.io.ref) + { + TORRENT_ASSERT(m_ref.storage == 0 || m_ref.piece >= 0); + TORRENT_ASSERT(m_ref.storage == 0 || m_ref.block >= 0); + TORRENT_ASSERT(m_ref.storage == 0 || m_ref.piece < static_cast(m_ref.storage)->files()->num_pieces()); + TORRENT_ASSERT(m_ref.storage == 0 || m_ref.block <= static_cast(m_ref.storage)->files()->piece_length() / 0x4000); + TORRENT_ASSERT(j.action != disk_io_job::save_resume_data); + TORRENT_ASSERT(j.action != disk_io_job::rename_file); + TORRENT_ASSERT(j.action != disk_io_job::move_storage); + } + + void disk_buffer_holder::reset(disk_io_job const& j) + { + if (m_ref.storage) m_allocator.reclaim_block(m_ref); + else if (m_buf) m_allocator.free_disk_buffer(m_buf); + m_buf = j.buffer.disk_block; + m_ref = j.d.io.ref; + + TORRENT_ASSERT(m_ref.piece >= 0); + TORRENT_ASSERT(m_ref.storage != 0); + TORRENT_ASSERT(m_ref.block >= 0); + TORRENT_ASSERT(m_ref.piece < static_cast(m_ref.storage)->files()->num_pieces()); + TORRENT_ASSERT(m_ref.block <= static_cast(m_ref.storage)->files()->piece_length() / 0x4000); + TORRENT_ASSERT(j.action != disk_io_job::save_resume_data); + TORRENT_ASSERT(j.action != disk_io_job::rename_file); + TORRENT_ASSERT(j.action != disk_io_job::move_storage); + } + + void disk_buffer_holder::reset(char* buf) + { + if (m_ref.storage) m_allocator.reclaim_block(m_ref); + else if (m_buf) m_allocator.free_disk_buffer(m_buf); + m_buf = buf; + m_ref.storage = 0; + } + + char* disk_buffer_holder::release() + { + char* ret = m_buf; + m_buf = 0; + m_ref.storage = 0; + return ret; + } + + disk_buffer_holder::~disk_buffer_holder() { reset(); } +} + diff --git a/src/disk_buffer_pool.cpp b/src/disk_buffer_pool.cpp new file mode 100644 index 0000000..f844241 --- /dev/null +++ b/src/disk_buffer_pool.cpp @@ -0,0 +1,588 @@ +/* + +Copyright (c) 2007-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/disk_buffer_pool.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/allocator.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/io_service.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/disk_observer.hpp" +#include "libtorrent/platform_util.hpp" // for total_physical_ram + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include + +#if TORRENT_HAVE_MMAP +#include +#endif + +#ifdef TORRENT_BSD +#include +#endif + +#ifdef TORRENT_LINUX +#include +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent +{ + namespace { + + // this is posted to the network thread + void watermark_callback(std::vector >* cbs) + { + if (cbs != NULL) + { + for (std::vector >::iterator i = cbs->begin() + , end(cbs->end()); i != end; ++i) + { + boost::shared_ptr o = i->lock(); + if (o) o->on_disk(); + } + delete cbs; + } + } + + } // anonymous namespace + + disk_buffer_pool::disk_buffer_pool(int block_size, io_service& ios + , boost::function const& trigger_trim) + : m_block_size(block_size) + , m_in_use(0) + , m_max_use(64) + , m_low_watermark((std::max)(m_max_use - 32, 0)) + , m_trigger_cache_trim(trigger_trim) + , m_exceeded_max_size(false) + , m_ios(ios) + , m_cache_buffer_chunk_size(0) +#if TORRENT_HAVE_MMAP + , m_cache_fd(-1) + , m_cache_pool(0) +#endif +#ifndef TORRENT_DISABLE_POOL_ALLOCATOR + , m_using_pool_allocator(false) + , m_want_pool_allocator(false) + , m_pool(block_size, 32) +#endif + { +#if TORRENT_USE_ASSERTS + m_magic = 0x1337; + m_settings_set = false; +#endif + } + + disk_buffer_pool::~disk_buffer_pool() + { + TORRENT_ASSERT(m_magic == 0x1337); +#if TORRENT_USE_ASSERTS + m_magic = 0; +#endif + +#if TORRENT_HAVE_MMAP + if (m_cache_pool) + { + munmap(m_cache_pool, boost::uint64_t(m_max_use) * 0x4000); + m_cache_pool = 0; + // attempt to make MacOS not flush this to disk, making close() + // block for a long time + int const best_effort = ftruncate(m_cache_fd, 0); + TORRENT_UNUSED(best_effort); + close(m_cache_fd); + m_cache_fd = -1; + } +#endif + } + + boost::uint32_t disk_buffer_pool::num_to_evict(int num_needed) + { + int ret = 0; + + mutex::scoped_lock l(m_pool_mutex); + + if (m_exceeded_max_size) + ret = m_in_use - (std::min)(m_low_watermark, int(m_max_use - m_observers.size()*2)); + + if (m_in_use + num_needed > m_max_use) + ret = (std::max)(ret, int(m_in_use + num_needed - m_max_use)); + + if (ret < 0) ret = 0; + else if (ret > m_in_use) ret = m_in_use; + + return ret; + } + + // checks to see if we're no longer exceeding the high watermark, + // and if we're in fact below the low watermark. If so, we need to + // post the notification messages to the peers that are waiting for + // more buffers to received data into + void disk_buffer_pool::check_buffer_level(mutex::scoped_lock& l) + { + TORRENT_ASSERT(l.locked()); + if (!m_exceeded_max_size || m_in_use > m_low_watermark) return; + + m_exceeded_max_size = false; + + std::vector >* cbs + = new std::vector >(); + m_observers.swap(*cbs); + l.unlock(); + m_ios.post(boost::bind(&watermark_callback, cbs)); + } + +#if TORRENT_USE_ASSERTS + bool disk_buffer_pool::is_disk_buffer(char* buffer + , mutex::scoped_lock& l) const + { + TORRENT_ASSERT(m_magic == 0x1337); + TORRENT_ASSERT(l.locked()); + TORRENT_UNUSED(l); + +#if TORRENT_HAVE_MMAP + if (m_cache_pool) + { + return buffer >= m_cache_pool && buffer < m_cache_pool + + boost::uint64_t(m_max_use) * 0x4000; + } +#endif + +#if defined TORRENT_DEBUG + return m_buffers_in_use.count(buffer) == 1; +#elif defined TORRENT_DEBUG_BUFFERS + return page_aligned_allocator::in_use(buffer); +#elif defined TORRENT_DISABLE_POOL_ALLOCATOR + return true; +#else + if (m_using_pool_allocator) + return m_pool.is_from(buffer); + else + return true; +#endif + } + + bool disk_buffer_pool::is_disk_buffer(char* buffer) const + { + mutex::scoped_lock l(m_pool_mutex); + return is_disk_buffer(buffer, l); + } +#endif + + char* disk_buffer_pool::allocate_buffer(char const* category) + { + mutex::scoped_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 + , boost::shared_ptr o, char const* category) + { + mutex::scoped_lock l(m_pool_mutex); + char* ret = allocate_buffer_impl(l, category); + if (m_exceeded_max_size) + { + exceeded = true; + if (o) m_observers.push_back(o); + } + return ret; + } + +// this function allocates buffers and +// fills in the iovec array with the buffers + int disk_buffer_pool::allocate_iovec(file::iovec_t* iov, int iov_len) + { + mutex::scoped_lock l(m_pool_mutex); + for (int i = 0; i < iov_len; ++i) + { + iov[i].iov_base = allocate_buffer_impl(l, "pending read"); + iov[i].iov_len = block_size(); + if (iov[i].iov_base == NULL) + { + // uh oh. We failed to allocate the buffer! + // we need to roll back and free all the buffers + // we've already allocated + for (int j = 0; j < i; ++j) + free_buffer_impl(static_cast(iov[j].iov_base), l); + return -1; + } + } + return 0; + } + + void disk_buffer_pool::free_iovec(file::iovec_t* iov, int iov_len) + { + // TODO: perhaps we should sort the buffers here? + mutex::scoped_lock l(m_pool_mutex); + for (int i = 0; i < iov_len; ++i) + free_buffer_impl(static_cast(iov[i].iov_base), l); + check_buffer_level(l); + } + + char* disk_buffer_pool::allocate_buffer_impl(mutex::scoped_lock& l + , char const*) + { + TORRENT_ASSERT(m_settings_set); + TORRENT_ASSERT(m_magic == 0x1337); + TORRENT_ASSERT(l.locked()); + TORRENT_UNUSED(l); + + char* ret; +#if TORRENT_HAVE_MMAP + if (m_cache_pool) + { + if (m_free_list.size() <= (m_max_use - m_low_watermark) + / 2 && !m_exceeded_max_size) + { + m_exceeded_max_size = true; + m_trigger_cache_trim(); + } + if (m_free_list.empty()) return 0; + boost::uint64_t slot_index = m_free_list.back(); + m_free_list.pop_back(); + ret = m_cache_pool + (slot_index * 0x4000); + TORRENT_ASSERT(is_disk_buffer(ret, l)); + } + else +#endif + { +#if defined TORRENT_DISABLE_POOL_ALLOCATOR + + ret = page_aligned_allocator::malloc(m_block_size); + +#else + if (m_using_pool_allocator) + { + int const effective_block_size + = m_in_use >= m_max_use + ? 20 // use small increments once we've exceeded the cache size + : m_cache_buffer_chunk_size + ? m_cache_buffer_chunk_size + : (std::max)(m_max_use / 10, 1); + m_pool.set_next_size(effective_block_size); + ret = static_cast(m_pool.malloc()); + } + else + { + ret = page_aligned_allocator::malloc(m_block_size); + } +#endif + if (ret == NULL) + { + m_exceeded_max_size = true; + m_trigger_cache_trim(); + return 0; + } + } + +#if defined TORRENT_DEBUG + TORRENT_ASSERT(m_buffers_in_use.count(ret) == 0); + m_buffers_in_use.insert(ret); +#endif + + ++m_in_use; + if (m_in_use >= m_low_watermark + (m_max_use - m_low_watermark) + / 2 && !m_exceeded_max_size) + { + m_exceeded_max_size = true; + m_trigger_cache_trim(); + } + + TORRENT_ASSERT(is_disk_buffer(ret, l)); + return ret; + } + + void disk_buffer_pool::free_multiple_buffers(char** bufvec, int numbufs) + { + char** end = bufvec + numbufs; + // sort the pointers in order to maximize cache hits + std::sort(bufvec, end); + + mutex::scoped_lock l(m_pool_mutex); + for (; bufvec != end; ++bufvec) + { + char* buf = *bufvec; + TORRENT_ASSERT(buf); + free_buffer_impl(buf, l); + } + + check_buffer_level(l); + } + + void disk_buffer_pool::free_buffer(char* buf) + { + mutex::scoped_lock l(m_pool_mutex); + free_buffer_impl(buf, l); + check_buffer_level(l); + } + + void disk_buffer_pool::set_settings(aux::session_settings const& sett + , error_code& ec) + { + TORRENT_UNUSED(ec); + + mutex::scoped_lock l(m_pool_mutex); + + // 0 cache_buffer_chunk_size means 'automatic' (i.e. + // proportional to the total disk cache size) + m_cache_buffer_chunk_size = sett.get_int(settings_pack::cache_buffer_chunk_size); +#ifndef TORRENT_DISABLE_POOL_ALLOCATOR + // if the chunk size is set to 1, there's no point in creating a pool + m_want_pool_allocator = sett.get_bool(settings_pack::use_disk_cache_pool) + && (m_cache_buffer_chunk_size != 1); + // if there are no allocated blocks, it's OK to switch allocator + if (m_in_use == 0) + m_using_pool_allocator = m_want_pool_allocator; +#endif + +#if TORRENT_HAVE_MMAP + // if we've already allocated an mmap, we can't change + // anything unless there are no allocations in use + if (m_cache_pool && m_in_use > 0) return; +#endif + + // only allow changing size if we're not using mmapped + // cache, or if we're just about to turn it off + if ( +#if TORRENT_HAVE_MMAP + m_cache_pool == 0 || +#endif + sett.get_str(settings_pack::mmap_cache).empty()) + { + int const cache_size = sett.get_int(settings_pack::cache_size); + if (cache_size < 0) + { + boost::uint64_t phys_ram = total_physical_ram(); + if (phys_ram == 0) m_max_use = 1024; + else + { + // this is the logic to calculate the automatic disk cache size + // based on the amount of physical RAM. + // The more physical RAM, the smaller portion of it is allocated + // for the cache. + + // we take a 30th of everything exceeding 4 GiB + // a 20th of everything exceeding 1 GiB + // and a 10th of everything below a GiB + + boost::int64_t const gb = 1024 * 1024 * 1024; + + boost::int64_t result = 0; + if (phys_ram > 4 * gb) + { + result += (phys_ram - 4 * gb) / 30; + phys_ram = 4 * gb; + } + if (phys_ram > 1 * gb) + { + result += (phys_ram - 1 * gb) / 20; + phys_ram = 1 * gb; + } + result += phys_ram / 10; + m_max_use = result / m_block_size; + } + + if (sizeof(void*) == 4) + { + // 32 bit builds should capped below 2 GB of memory, even + // when more actual ram is available, because we're still + // constrained by the 32 bit virtual address space. + m_max_use = (std::min)(2 * 1024 * 1024 * 3 / 4 * 1024 + / m_block_size, m_max_use); + } + } + else + { + m_max_use = cache_size; + } + m_low_watermark = m_max_use - (std::max)(16, sett.get_int(settings_pack::max_queued_disk_bytes) / 0x4000); + if (m_low_watermark < 0) m_low_watermark = 0; + if (m_in_use >= m_max_use && !m_exceeded_max_size) + { + m_exceeded_max_size = true; + m_trigger_cache_trim(); + } + if (m_cache_buffer_chunk_size > m_max_use) + m_cache_buffer_chunk_size = m_max_use; + } + +#if TORRENT_USE_ASSERTS + m_settings_set = true; +#endif + +#if TORRENT_HAVE_MMAP + // #error support resizing the map + if (m_cache_pool && sett.get_str(settings_pack::mmap_cache).empty()) + { + TORRENT_ASSERT(m_in_use == 0); + munmap(m_cache_pool, boost::uint64_t(m_max_use) * 0x4000); + m_cache_pool = 0; + // attempt to make MacOS not flush this to disk, making close() + // block for a long time + int const best_effort = ftruncate(m_cache_fd, 0); + TORRENT_UNUSED(best_effort); + close(m_cache_fd); + m_cache_fd = -1; + std::vector().swap(m_free_list); + } + else if (m_cache_pool == 0 && !sett.get_str(settings_pack::mmap_cache).empty()) + { + // O_TRUNC here is because we don't actually care about what's + // in the file now, there's no need to ever read that into RAM +#ifndef O_EXLOCK +#define O_EXLOCK 0 +#endif + m_cache_fd = open(sett.get_str(settings_pack::mmap_cache).c_str(), O_RDWR | O_CREAT | O_EXLOCK | O_TRUNC, 0700); + if (m_cache_fd < 0) + { + ec.assign(errno, boost::system::system_category()); + } + else + { +#ifndef MAP_NOCACHE +#define MAP_NOCACHE 0 +#endif + int const best_effort = ftruncate(m_cache_fd, boost::uint64_t(m_max_use) * 0x4000); + TORRENT_UNUSED(best_effort); + m_cache_pool = static_cast(mmap(0, boost::uint64_t(m_max_use) * 0x4000, PROT_READ | PROT_WRITE + , MAP_SHARED | MAP_NOCACHE, m_cache_fd, 0)); + if (intptr_t(m_cache_pool) == -1) + { + ec.assign(errno, boost::system::system_category()); + + m_cache_pool = 0; + // attempt to make MacOS not flush this to disk, making close() + // block for a long time + int const best_effort2 = ftruncate(m_cache_fd, 0); + TORRENT_UNUSED(best_effort2); + close(m_cache_fd); + m_cache_fd = -1; + } + else + { + TORRENT_ASSERT((size_t(m_cache_pool) & 0xfff) == 0); + m_free_list.reserve(m_max_use); + for (int i = 0; i < m_max_use; ++i) + m_free_list.push_back(i); + } + } + } +#endif + } + + void disk_buffer_pool::free_buffer_impl(char* buf, mutex::scoped_lock& l) + { + TORRENT_ASSERT(buf); + TORRENT_ASSERT(m_magic == 0x1337); + TORRENT_ASSERT(m_settings_set); + TORRENT_ASSERT(is_disk_buffer(buf, l)); + TORRENT_ASSERT(l.locked()); + TORRENT_UNUSED(l); + +#if TORRENT_HAVE_MMAP + if (m_cache_pool) + { + TORRENT_ASSERT(buf >= m_cache_pool); + TORRENT_ASSERT(buf < m_cache_pool + boost::uint64_t(m_max_use) * 0x4000); + int slot_index = (buf - m_cache_pool) / 0x4000; + m_free_list.push_back(slot_index); +#if defined MADV_FREE + // tell the virtual memory system that we don't actually care + // about the data in these pages anymore. If this block was + // swapped out to the SSD, it (hopefully) means it won't have + // to be read back in once we start writing our new data to it + madvise(buf, 0x4000, MADV_FREE); +#elif defined MADV_DONTNEED && defined TORRENT_LINUX + // rumor has it that MADV_DONTNEED is in fact destructive + // on linux (i.e. it won't flush it to disk or re-read from disk) + // http://kerneltrap.org/mailarchive/linux-kernel/2007/5/1/84410 + madvise(buf, 0x4000, MADV_DONTNEED); +#endif + } + else +#endif + { +#if defined TORRENT_DISABLE_POOL_ALLOCATOR + + page_aligned_allocator::free(buf); + +#else + if (m_using_pool_allocator) + m_pool.free(buf); + else + page_aligned_allocator::free(buf); +#endif // TORRENT_DISABLE_POOL_ALLOCATOR + } + +#if defined TORRENT_DEBUG + 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 + + --m_in_use; + +#ifndef TORRENT_DISABLE_POOL_ALLOCATOR + // should we switch which allocator to use? + if (m_in_use == 0 && m_want_pool_allocator != m_using_pool_allocator) + { + m_pool.release_memory(); + m_using_pool_allocator = m_want_pool_allocator; + } +#endif + } + + void disk_buffer_pool::release_memory() + { + TORRENT_ASSERT(m_magic == 0x1337); +#ifndef TORRENT_DISABLE_POOL_ALLOCATOR + mutex::scoped_lock l(m_pool_mutex); + if (m_using_pool_allocator) + m_pool.release_memory(); +#endif + } + +} + diff --git a/src/disk_io_job.cpp b/src/disk_io_job.cpp new file mode 100644 index 0000000..0b11605 --- /dev/null +++ b/src/disk_io_job.cpp @@ -0,0 +1,86 @@ +/* + +Copyright (c) 2011-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/disk_io_job.hpp" +#include "libtorrent/storage.hpp" +#include "libtorrent/block_cache.hpp" // for cached_piece_entry +#include "libtorrent/entry.hpp" + +namespace libtorrent +{ + disk_io_job::disk_io_job() + : requester(0) + , piece(0) + , action(read) + , ret(0) + , flags(0) +#if defined TORRENT_DEBUG || defined TORRENT_RELEASE_ASSERTS + , in_use(false) + , job_posted(false) + , callback_called(false) + , blocked(false) +#endif + { + buffer.disk_block = 0; + d.io.offset = 0; + d.io.buffer_size = 0; + d.io.ref.storage = 0; + d.io.ref.piece = 0; + d.io.ref.block = 0; + } + + disk_io_job::~disk_io_job() + { + if (action == rename_file || action == move_storage) + free(buffer.string); + else if (action == save_resume_data) + delete static_cast(buffer.resume_data); + } + + bool disk_io_job::completed(cached_piece_entry const* pe, int block_size) + { + if (action != write) return false; + + int block_offset = d.io.offset & (block_size-1); + int size = d.io.buffer_size; + int start = d.io.offset / block_size; + int end = block_offset > 0 && (size > block_size - block_offset) ? start + 2 : start + 1; + + for (int i = start; i < end; ++i) + if (pe->blocks[i].dirty || pe->blocks[i].pending) return false; + + // if all our blocks are not pending and not dirty, it means they + // were successfully written to disk. This job is complete + return true; + } +} + diff --git a/src/disk_io_thread.cpp b/src/disk_io_thread.cpp new file mode 100644 index 0000000..0499608 --- /dev/null +++ b/src/disk_io_thread.cpp @@ -0,0 +1,3596 @@ +/* + +Copyright (c) 2007-2016, Arvid Norberg, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/storage.hpp" +#include "libtorrent/disk_io_thread.hpp" +#include "libtorrent/disk_buffer_holder.hpp" +#include "libtorrent/alloca.hpp" +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/error.hpp" +#include "libtorrent/file_pool.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/platform_util.hpp" +#include +#include +#include +#include +#include + +#include "libtorrent/time.hpp" +#include "libtorrent/disk_buffer_pool.hpp" +#include "libtorrent/disk_io_job.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/uncork_interface.hpp" +#include "libtorrent/performance_counters.hpp" +#include "libtorrent/alert_manager.hpp" + +#include "libtorrent/debug.hpp" + +#if TORRENT_USE_RLIMIT +#include +#endif + +#define DEBUG_DISK_THREAD 0 + +#if __cplusplus >= 201103L || defined __clang__ + +#if DEBUG_DISK_THREAD +#define DLOG(...) debug_log(__VA_ARGS__) +#else +#define DLOG(...) do {} while(false) +#endif + +#else + +#if DEBUG_DISK_THREAD +#define DLOG debug_log +#else +#define DLOG TORRENT_WHILE_0 debug_log +#endif + +#endif // cplusplus + +namespace libtorrent +{ + +#if TORRENT_USE_ASSERTS + +#define TORRENT_PIECE_ASSERT(cond, piece) \ + do { if (!(cond)) { assert_print_piece(piece); assert_fail(#cond, __LINE__, __FILE__, TORRENT_FUNCTION, 0); } } TORRENT_WHILE_0 + +#else +#define TORRENT_PIECE_ASSERT(cond, piece) do {} TORRENT_WHILE_0 +#endif // TORRENT_USE_ASSERTS + + namespace { + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-function" +#endif + + void debug_log(char const* fmt, ...) + { +#if DEBUG_DISK_THREAD + static mutex log_mutex; + static const time_point start = clock_type::now(); + va_list v; + va_start(v, fmt); + + char usr[2048]; + int len = vsnprintf(usr, sizeof(usr), fmt, v); + + static bool prepend_time = true; + if (!prepend_time) + { + prepend_time = (usr[len-1] == '\n'); + mutex::scoped_lock l(log_mutex); + fputs(usr, stderr); + return; + } + va_end(v); + char buf[2300]; + int t = total_milliseconds(clock_type::now() - start); + snprintf(buf, sizeof(buf), "%05d: [%p] %s", t, pthread_self(), usr); + prepend_time = (usr[len-1] == '\n'); + mutex::scoped_lock l(log_mutex); + fputs(buf, stderr); +#else + TORRENT_UNUSED(fmt); +#endif + } + + int file_flags_for_job(disk_io_job* j + , bool const coalesce_buffers) + { + int ret = 0; + if (!(j->flags & disk_io_job::sequential_access)) ret |= file::random_access; + if (coalesce_buffers) ret |= file::coalesce_buffers; + return ret; + } + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + } // anonymous namespace + +// ------- disk_io_thread ------ + + disk_io_thread::disk_io_thread(io_service& ios + , counters& cnt + , void* userdata + , int block_size) + : m_num_threads(0) + , m_abort(false) + , m_num_running_threads(0) + , m_userdata(userdata) + , m_last_cache_expiry(min_time()) + , m_last_file_check(clock_type::now()) + , m_file_pool(40) + , m_disk_cache(block_size, ios, boost::bind(&disk_io_thread::trigger_cache_trim, this)) + , m_cache_check_state(cache_check_idle) + , m_stats_counters(cnt) + , m_ios(ios) + , m_last_disk_aio_performance_warning(min_time()) + , m_outstanding_reclaim_message(false) +#if TORRENT_USE_ASSERTS + , m_magic(0x1337) +#endif + { +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("disk_io_thread::work"); +#endif + error_code ec; + m_disk_cache.set_settings(m_settings, ec); + TORRENT_ASSERT(!ec); + + // deduct some margin for epoll/kqueue, log files, + // futexes, shared objects etc. + // 80% of the available file descriptors should go to connections + // 20% goes towards regular files + const int max_files = (std::min)((std::max)(5 + , (max_open_files() - 20) * 2 / 10) + , m_file_pool.size_limit()); + m_file_pool.resize(max_files); + } + + disk_io_thread::~disk_io_thread() + { + DLOG("destructing disk_io_thread\n"); + +#if TORRENT_USE_ASSERTS + // by now, all pieces should have been evicted + std::pair pieces + = m_disk_cache.all_pieces(); + TORRENT_ASSERT(pieces.first == pieces.second); +#endif + + TORRENT_ASSERT(m_magic == 0x1337); +#if TORRENT_USE_ASSERTS + m_magic = 0xdead; +#endif + } + + void disk_io_thread::abort(bool wait) + { + m_abort = true; + if (m_num_threads == 0) + { + abort_jobs(); + } + else + { + set_num_threads(0, wait); + } + } + + // TODO: 1 it would be nice to have the number of threads be set dynamically + void disk_io_thread::set_num_threads(int i, bool wait) + { + TORRENT_ASSERT(m_magic == 0x1337); + if (i == m_num_threads) return; + + if (i > m_num_threads) + { + while (m_num_threads < i) + { + int thread_id = (++m_num_threads) - 1; + thread_type_t type = generic_thread; + + // this keeps the io_service::run() call blocked from returning. + // When shutting down, it's possible that the event queue is drained + // before the disk_io_thread has posted its last callback. When this + // happens, the io_service will have a pending callback from the + // disk_io_thread, but the event loop is not running. this means + // that the event is destructed after the disk_io_thread. If the + // event refers to a disk buffer it will try to free it, but the + // buffer pool won't exist anymore, and crash. This prevents that. + boost::shared_ptr work = + boost::make_shared(boost::ref(m_ios)); + + // the magic number 3 is also used in add_job() + // every 4:th thread is a hasher thread + if ((thread_id & 0x3) == 3) type = hasher_thread; + m_threads.push_back(boost::shared_ptr( + new thread(boost::bind(&disk_io_thread::thread_fun, this + , thread_id, type, work)))); + } + } + else + { + while (m_num_threads > i) { --m_num_threads; } + mutex::scoped_lock l(m_job_mutex); + m_job_cond.notify_all(); + m_hash_job_cond.notify_all(); + l.unlock(); + if (wait) for (int j = m_num_threads; j < m_threads.size(); ++j) m_threads[j]->join(); + // this will detach the threads + m_threads.resize(m_num_threads); + } + } + + void disk_io_thread::reclaim_block(block_cache_reference ref) + { + TORRENT_ASSERT(m_magic == 0x1337); + TORRENT_ASSERT(ref.storage); + m_blocks_to_reclaim.push_back(ref); + if (m_outstanding_reclaim_message) return; + + m_ios.post(boost::bind(&disk_io_thread::commit_reclaimed_blocks, this)); + m_outstanding_reclaim_message = true; + } + + void disk_io_thread::commit_reclaimed_blocks() + { + TORRENT_ASSERT(m_magic == 0x1337); + TORRENT_ASSERT(m_outstanding_reclaim_message); + m_outstanding_reclaim_message = false; + mutex::scoped_lock l(m_cache_mutex); + for (int i = 0; i < m_blocks_to_reclaim.size(); ++i) + m_disk_cache.reclaim_block(m_blocks_to_reclaim[i]); + m_blocks_to_reclaim.clear(); + } + + void disk_io_thread::set_settings(settings_pack const* pack, alert_manager& alerts) + { + TORRENT_ASSERT(m_magic == 0x1337); + mutex::scoped_lock l(m_cache_mutex); + apply_pack(pack, m_settings); + error_code ec; + m_disk_cache.set_settings(m_settings, ec); + if (ec && alerts.should_post()) + { + alerts.emplace_alert(ec); + } + } + + // flush all blocks that are below p->hash.offset, since we've + // already hashed those blocks, they won't cause any read-back + int disk_io_thread::try_flush_hashed(cached_piece_entry* p, int cont_block + , jobqueue_t& completed_jobs, mutex::scoped_lock& l) + { + TORRENT_ASSERT(m_magic == 0x1337); + TORRENT_ASSERT(l.locked()); + TORRENT_ASSERT(cont_block > 0); + if (p->hash == 0 && !p->hashing_done) + { + DLOG("try_flush_hashed: (%d) no hash\n", int(p->piece)); + return 0; + } + + if (p->num_dirty == 0) + { + DLOG("try_flush_hashed: no dirty blocks\n"); + return 0; + } + + // end is one past the end + // round offset up to include the last block, which might + // have an odd size + int block_size = m_disk_cache.block_size(); + int end = p->hashing_done ? p->blocks_in_piece : (p->hash->offset + block_size - 1) / block_size; + + // nothing has been hashed yet, don't flush anything + if (end == 0 && !p->need_readback) return 0; + + // the number of contiguous blocks we need to be allowed to flush + int block_limit = (std::min)(cont_block, int(p->blocks_in_piece)); + + // if everything has been hashed, we might as well flush everything + // regardless of the contiguous block restriction + if (end == int(p->blocks_in_piece)) block_limit = 1; + + if (p->need_readback) + { + // if this piece needs a read-back already, don't + // try to keep it from being flushed, since we'll + // need to read it back regardless. Flushing will + // save blocks that can be used to "save" other + // pieces from being fllushed prematurely + end = int(p->blocks_in_piece); + } + + // count number of blocks that would be flushed + int num_blocks = 0; + for (int i = end-1; i >= 0; --i) + num_blocks += (p->blocks[i].dirty && !p->blocks[i].pending); + + // we did not satisfy the block_limit requirement + // i.e. too few blocks would be flushed at this point, put it off + if (block_limit > num_blocks) return 0; + + // if the cache line size is larger than a whole piece, hold + // off flushing this piece until enough adjacent pieces are + // full as well. + int cont_pieces = cont_block / p->blocks_in_piece; + + // at this point, we may enforce flushing full cache stripes even when + // they span multiple pieces. This won't necessarily work in the general + // case, because it assumes that the piece picker will have an affinity + // to download whole stripes at a time. This is why this setting is turned + // off by default, flushing only one piece at a time + + if (cont_pieces <= 1 || m_settings.get_bool(settings_pack::allow_partial_disk_writes)) + { + DLOG("try_flush_hashed: (%d) blocks_in_piece: %d end: %d\n" + , int(p->piece), int(p->blocks_in_piece), end); + + return flush_range(p, 0, end, completed_jobs, l); + } + + // piece range + int range_start = (p->piece / cont_pieces) * cont_pieces; + int range_end = (std::min)(range_start + cont_pieces, p->storage->files()->num_pieces()); + + // look through all the pieces in this range to see if + // they are ready to be flushed. If so, flush them all, + // otherwise, hold off + bool range_full = true; + + cached_piece_entry* first_piece = NULL; + DLOG("try_flush_hashed: multi-piece: "); + for (int i = range_start; i < range_end; ++i) + { + if (i == p->piece) + { + if (i == range_start) first_piece = p; + DLOG("[%d self] ", i); + continue; + } + cached_piece_entry* pe = m_disk_cache.find_piece(p->storage.get(), i); + if (pe == NULL) + { + DLOG("[%d NULL] ", i); + range_full = false; + break; + } + if (i == range_start) first_piece = pe; + + // if this is a read-cache piece, it has already been flushed + if (pe->cache_state != cached_piece_entry::write_lru) + { + DLOG("[%d read-cache] ", i); + continue; + } + int hash_cursor = pe->hash ? pe->hash->offset / block_size : 0; + + // if the piece has all blocks, and they're all dirty, and they've + // all been hashed, then this piece is eligible for flushing + if (pe->num_dirty == pe->blocks_in_piece + && (pe->hashing_done + || hash_cursor == pe->blocks_in_piece + || m_settings.get_bool(settings_pack::disable_hash_checks))) + { + DLOG("[%d hash-done] ", i); + continue; + } + + if (pe->num_dirty < pe->blocks_in_piece) + { + DLOG("[%d dirty:%d] ", i, int(pe->num_dirty)); + } + else if (pe->hashing_done == 0 && hash_cursor < pe->blocks_in_piece) + { + DLOG("[%d cursor:%d] ", i, hash_cursor); + } + else + { + DLOG("[%d xx] ", i); + } + + // TOOD: in this case, the piece should probably not be flushed yet. are there + // any more cases where it should? + + range_full = false; + break; + } + + if (!range_full) + { + DLOG("not flushing\n"); + return 0; + } + DLOG("\n"); + + // now, build a iovec for all pieces that we want to flush, so that they + // can be flushed in a single atomic operation. This is especially important + // when there are more than 1 disk thread, to make sure they don't + // interleave in undesired places. + // in order to remember where each piece boundary ended up in the iovec, + // we keep the indices in the iovec_offset array + + cont_pieces = range_end - range_start; + + file::iovec_t* iov = TORRENT_ALLOCA(file::iovec_t, p->blocks_in_piece * cont_pieces); + int* flushing = TORRENT_ALLOCA(int, p->blocks_in_piece * cont_pieces); + // this is the offset into iov and flushing for each piece + int* iovec_offset = TORRENT_ALLOCA(int, cont_pieces + 1); + int iov_len = 0; + // this is the block index each piece starts at + int block_start = 0; + // keep track of the pieces that have had their refcount incremented + // so we know to decrement them later + int* refcount_pieces = TORRENT_ALLOCA(int, cont_pieces); + for (int i = 0; i < cont_pieces; ++i) + { + cached_piece_entry* pe; + if (i == p->piece) pe = p; + else pe = m_disk_cache.find_piece(p->storage.get(), range_start + i); + if (pe == NULL + || pe->cache_state != cached_piece_entry::write_lru) + { + refcount_pieces[i] = 0; + iovec_offset[i] = iov_len; + block_start += p->blocks_in_piece; + continue; + } + + iovec_offset[i] = iov_len; + refcount_pieces[i] = 1; + TORRENT_ASSERT_VAL(pe->cache_state <= cached_piece_entry::read_lru1 || pe->cache_state == cached_piece_entry::read_lru2, pe); +#if TORRENT_USE_ASSERTS + pe->piece_log.push_back(piece_log_t(piece_log_t::flushing, -1)); +#endif + ++pe->piece_refcount; + + iov_len += build_iovec(pe, 0, p->blocks_in_piece + , iov + iov_len, flushing + iov_len, block_start); + + block_start += p->blocks_in_piece; + } + iovec_offset[cont_pieces] = iov_len; + + // ok, now we have one (or more, but hopefully one) contiguous + // iovec array. Now, flush it to disk + + TORRENT_ASSERT(first_piece != NULL); + + if (iov_len == 0) + { + // we may not exit here if we incremented any piece refcounters + TORRENT_ASSERT(cont_pieces == 0); + DLOG(" iov_len: 0 cont_pieces: %d range_start: %d range_end: %d\n" + , cont_pieces, range_start, range_end); + return 0; + } + + l.unlock(); + + storage_error error; + flush_iovec(first_piece, iov, flushing, iov_len, error); + + l.lock(); + + block_start = 0; + for (int i = 0; i < cont_pieces; ++i) + { + cached_piece_entry* pe; + if (i == p->piece) pe = p; + else pe = m_disk_cache.find_piece(p->storage.get(), range_start + i); + if (pe == NULL) + { + DLOG("iovec_flushed: piece %d gone!\n", range_start + i); + TORRENT_PIECE_ASSERT(refcount_pieces[i] == 0, pe); + block_start += p->blocks_in_piece; + continue; + } + if (refcount_pieces[i]) + { + TORRENT_PIECE_ASSERT(pe->piece_refcount > 0, pe); + --pe->piece_refcount; + m_disk_cache.maybe_free_piece(pe); + } + const int block_diff = iovec_offset[i+1] - iovec_offset[i]; + iovec_flushed(pe, flushing + iovec_offset[i], block_diff + , block_start, error, completed_jobs); + block_start += p->blocks_in_piece; + } + + // if the cache is under high pressure, we need to evict + // the blocks we just flushed to make room for more write pieces + int evict = m_disk_cache.num_to_evict(0); + if (evict > 0) m_disk_cache.try_evict_blocks(evict); + + return iov_len; + } + + // iov and flushing are expected to be arrays to at least pe->blocks_in_piece + // items in them. Returns the numner of iovecs written to the iov array. + // The same number of block indices are written to the flushing array. These + // are block indices that the respecivec iovec structure refers to, since + // we might not be able to flush everything as a single contiguous block, + // the block indices indicates where the block run is broken + // the cache needs to be locked when calling this function + // block_base_index is the offset added to every block index written to + // the flushing array. This can be used when building iovecs spanning + // multiple pieces, the subsequent pieces after the first one, must have + // their block indices start where the previous one left off + int disk_io_thread::build_iovec(cached_piece_entry* pe, int start, int end + , file::iovec_t* iov, int* flushing, int block_base_index) + { + INVARIANT_CHECK; + + DLOG("build_iovec: piece=%d [%d, %d)\n" + , int(pe->piece), start, end); + TORRENT_PIECE_ASSERT(start >= 0, pe); + TORRENT_PIECE_ASSERT(start < end, pe); + end = (std::min)(end, int(pe->blocks_in_piece)); + + int piece_size = pe->storage->files()->piece_size(pe->piece); + TORRENT_PIECE_ASSERT(piece_size > 0, pe); + + int iov_len = 0; + // the blocks we're flushing + int num_flushing = 0; + +#if DEBUG_DISK_THREAD + DLOG("build_iov: piece: %d [", int(pe->piece)); + for (int i = 0; i < start; ++i) DLOG("."); +#endif + + int block_size = m_disk_cache.block_size(); + int size_left = piece_size; + for (int i = start; i < end; ++i, size_left -= block_size) + { + TORRENT_PIECE_ASSERT(size_left > 0, pe); + // don't flush blocks that are empty (buf == 0), not dirty + // (read cache blocks), or pending (already being written) + if (pe->blocks[i].buf == NULL + || pe->blocks[i].pending + || !pe->blocks[i].dirty) + { + DLOG("-"); + continue; + } + + // if we fail to lock the block, it' no longer in the cache + bool locked = m_disk_cache.inc_block_refcount(pe, i, block_cache::ref_flushing); + + // it should always suceed, since it's a dirty block, and + // should never have been marked as volatile + TORRENT_ASSERT(locked); + TORRENT_ASSERT(pe->cache_state != cached_piece_entry::volatile_read_lru); + TORRENT_UNUSED(locked); + + flushing[num_flushing++] = i + block_base_index; + iov[iov_len].iov_base = pe->blocks[i].buf; + iov[iov_len].iov_len = (std::min)(block_size, size_left); + ++iov_len; + pe->blocks[i].pending = true; + + DLOG("x"); + } + DLOG("]\n"); + + TORRENT_PIECE_ASSERT(iov_len == num_flushing, pe); + return iov_len; + } + + // does the actual writing to disk + // the cached_piece_entry is supposed to point to the + // first piece, if the iovec spans multiple pieces + void disk_io_thread::flush_iovec(cached_piece_entry* pe + , file::iovec_t const* iov, int const* flushing + , int num_blocks, storage_error& error) + { + TORRENT_PIECE_ASSERT(!error, pe); + TORRENT_PIECE_ASSERT(num_blocks > 0, pe); + m_stats_counters.inc_stats_counter(counters::num_writing_threads, 1); + + time_point start_time = clock_type::now(); + int block_size = m_disk_cache.block_size(); + +#if DEBUG_DISK_THREAD + DLOG("flush_iovec: piece: %d [ ", int(pe->piece)); + for (int i = 0; i < num_blocks; ++i) + DLOG("%d ", flushing[i]); + DLOG("]\n"); +#endif + + int const file_flags = m_settings.get_bool(settings_pack::coalesce_writes) + ? file::coalesce_buffers : 0; + + // issue the actual write operation + file::iovec_t const* iov_start = iov; + int flushing_start = 0; + int piece = pe->piece; + int blocks_in_piece = pe->blocks_in_piece; + bool failed = false; + for (int i = 1; i <= num_blocks; ++i) + { + if (i < num_blocks && flushing[i] == flushing[i-1]+1) continue; + int ret = pe->storage->get_storage_impl()->writev(iov_start + , i - flushing_start + , piece + flushing[flushing_start] / blocks_in_piece + , (flushing[flushing_start] % blocks_in_piece) * block_size + , file_flags, error); + if (ret < 0 || error) failed = true; + iov_start = &iov[i]; + flushing_start = i; + } + + m_stats_counters.inc_stats_counter(counters::num_writing_threads, -1); + + if (!failed) + { + TORRENT_PIECE_ASSERT(!error, pe); + boost::uint32_t write_time = total_microseconds(clock_type::now() - start_time); + m_write_time.add_sample(write_time / num_blocks); + + m_stats_counters.inc_stats_counter(counters::num_blocks_written, num_blocks); + m_stats_counters.inc_stats_counter(counters::num_write_ops); + m_stats_counters.inc_stats_counter(counters::disk_write_time, write_time); + m_stats_counters.inc_stats_counter(counters::disk_job_time, write_time); +#if DEBUG_DISK_THREAD + DLOG("flush_iovec: %d\n", num_blocks); +#endif + } +#if DEBUG_DISK_THREAD + else + { + DLOG("flush_iovec: error: (%d) %s\n" + , error.ec.value(), error.ec.message().c_str()); + } +#endif + } + + // It is necessary to call this function with the blocks produced by + // build_iovec, to reset their state to not being flushed anymore + // the cache needs to be locked when calling this function + void disk_io_thread::iovec_flushed(cached_piece_entry* pe + , int* flushing, int num_blocks, int block_offset + , storage_error const& error + , jobqueue_t& completed_jobs) + { + for (int i = 0; i < num_blocks; ++i) + flushing[i] -= block_offset; + +#if DEBUG_DISK_THREAD + DLOG("iovec_flushed: piece: %d block_offset: %d [ " + , int(pe->piece), block_offset); + for (int i = 0; i < num_blocks; ++i) + DLOG("%d ", flushing[i]); + DLOG("]\n"); +#endif + m_disk_cache.blocks_flushed(pe, flushing, num_blocks); + + int block_size = m_disk_cache.block_size(); + + if (error) + { + fail_jobs_impl(error, pe->jobs, completed_jobs); + } + else + { + disk_io_job* j = pe->jobs.get_all(); + while (j) + { + disk_io_job* next = j->next; + j->next = NULL; + TORRENT_PIECE_ASSERT((j->flags & disk_io_job::in_progress) || !j->storage, pe); + TORRENT_PIECE_ASSERT(j->piece == pe->piece, pe); + if (j->completed(pe, block_size)) + { + j->ret = j->d.io.buffer_size; + j->error = error; + completed_jobs.push_back(j); + } + else + { + pe->jobs.push_back(j); + } + j = next; + } + } + } + + // issues write operations for blocks in the given + // range on the given piece. + int disk_io_thread::flush_range(cached_piece_entry* pe, int start, int end + , jobqueue_t& completed_jobs, mutex::scoped_lock& l) + { + TORRENT_ASSERT(l.locked()); + INVARIANT_CHECK; + + DLOG("flush_range: piece=%d [%d, %d)\n" + , int(pe->piece), start, end); + TORRENT_PIECE_ASSERT(start >= 0, pe); + TORRENT_PIECE_ASSERT(start < end, pe); + + file::iovec_t* iov = TORRENT_ALLOCA(file::iovec_t, pe->blocks_in_piece); + int* flushing = TORRENT_ALLOCA(int, pe->blocks_in_piece); + int iov_len = build_iovec(pe, start, end, iov, flushing, 0); + if (iov_len == 0) return 0; + + TORRENT_PIECE_ASSERT(pe->cache_state <= cached_piece_entry::read_lru1 || pe->cache_state == cached_piece_entry::read_lru2, pe); +#if TORRENT_USE_ASSERTS + pe->piece_log.push_back(piece_log_t(piece_log_t::flush_range, -1)); +#endif + ++pe->piece_refcount; + + l.unlock(); + + storage_error error; + flush_iovec(pe, iov, flushing, iov_len, error); + + l.lock(); + + TORRENT_PIECE_ASSERT(pe->piece_refcount > 0, pe); + --pe->piece_refcount; + iovec_flushed(pe, flushing, iov_len, 0, error, completed_jobs); + + // if the cache is under high pressure, we need to evict + // the blocks we just flushed to make room for more write pieces + int evict = m_disk_cache.num_to_evict(0); + if (evict > 0) m_disk_cache.try_evict_blocks(evict); + + m_disk_cache.maybe_free_piece(pe); + + return iov_len; + } + + void disk_io_thread::fail_jobs(storage_error const& e, jobqueue_t& jobs_) + { + jobqueue_t jobs; + fail_jobs_impl(e, jobs_, jobs); + if (jobs.size()) add_completed_jobs(jobs); + } + + void disk_io_thread::fail_jobs_impl(storage_error const& e, jobqueue_t& src, jobqueue_t& dst) + { + while (src.size()) + { + disk_io_job* j = src.pop_front(); + TORRENT_ASSERT((j->flags & disk_io_job::in_progress) || !j->storage); + j->ret = -1; + j->error = e; + dst.push_back(j); + } + } + + void disk_io_thread::flush_piece(cached_piece_entry* pe, int flags + , jobqueue_t& completed_jobs, mutex::scoped_lock& l) + { + TORRENT_ASSERT(l.locked()); + if (flags & flush_delete_cache) + { + // delete dirty blocks and post handlers with + // operation_aborted error code + fail_jobs_impl(storage_error(boost::asio::error::operation_aborted) + , pe->jobs, completed_jobs); + fail_jobs_impl(storage_error(boost::asio::error::operation_aborted) + , pe->read_jobs, completed_jobs); + m_disk_cache.abort_dirty(pe); + } + else if ((flags & flush_write_cache) && pe->num_dirty > 0) + { + // issue write commands + flush_range(pe, 0, INT_MAX, completed_jobs, l); + + // if we're also flushing the read cache, this piece + // should be removed as soon as all write jobs finishes + // otherwise it will turn into a read piece + } + + // mark_for_deletion may erase the piece from the cache, that's + // why we don't have the 'i' iterator referencing it at this point + if (flags & (flush_read_cache | flush_delete_cache)) + { + fail_jobs_impl(storage_error(boost::asio::error::operation_aborted), pe->jobs, completed_jobs); + m_disk_cache.mark_for_deletion(pe); + } + } + + void disk_io_thread::flush_cache(piece_manager* storage, boost::uint32_t flags + , jobqueue_t& completed_jobs, mutex::scoped_lock& l) + { + if (storage) + { + boost::unordered_set const& pieces = storage->cached_pieces(); + std::vector piece_index; + piece_index.reserve(pieces.size()); + for (boost::unordered_set::const_iterator i = pieces.begin() + , end(pieces.end()); i != end; ++i) + { + if ((*i)->get_storage() != storage) continue; + piece_index.push_back((*i)->piece); + } + + for (std::vector::iterator i = piece_index.begin() + , end(piece_index.end()); i != end; ++i) + { + cached_piece_entry* pe = m_disk_cache.find_piece(storage, *i); + if (pe == NULL) continue; + TORRENT_PIECE_ASSERT(pe->storage.get() == storage, pe); + flush_piece(pe, flags, completed_jobs, l); + } +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(l.locked()); + // if the user asked to delete the cache for this storage + // we really should not have any pieces left. This is only called + // from disk_io_thread::do_delete, which is a fence job and should + // have any other jobs active, i.e. there should not be any references + // keeping pieces or blocks alive + if ((flags & flush_delete_cache) && (flags & flush_expect_clear)) + { + boost::unordered_set const& storage_pieces = storage->cached_pieces(); + for (boost::unordered_set::const_iterator i = storage_pieces.begin() + , end(storage_pieces.end()); i != end; ++i) + { + cached_piece_entry* pe = m_disk_cache.find_piece(storage, (*i)->piece); + TORRENT_PIECE_ASSERT(pe->num_dirty == 0, pe); + } + } +#endif + } + else + { + std::pair range = m_disk_cache.all_pieces(); + while (range.first != range.second) + { + // TODO: it would be nice to optimize this by having the cache + // pieces also ordered by + if ((flags & (flush_read_cache | flush_delete_cache)) == 0) + { + // if we're not flushing the read cache, and not deleting the + // cache, skip pieces with no dirty blocks, i.e. read cache + // pieces + while (range.first->num_dirty == 0) + { + ++range.first; + if (range.first == range.second) return; + } + } + cached_piece_entry* pe = const_cast(&*range.first); + flush_piece(pe, flags, completed_jobs, l); + range = m_disk_cache.all_pieces(); + } + } + } + + // this is called if we're exceeding (or about to exceed) the cache + // size limit. This means we should not restrict ourselves to contiguous + // blocks of write cache line size, but try to flush all old blocks + // this is why we pass in 1 as cont_block to the flushing functions + void disk_io_thread::try_flush_write_blocks(int num, jobqueue_t& completed_jobs + , mutex::scoped_lock& l) + { + DLOG("try_flush_write_blocks: %d\n", num); + + list_iterator range = m_disk_cache.write_lru_pieces(); + std::vector > pieces; + pieces.reserve(m_disk_cache.num_write_lru_pieces()); + + for (list_iterator p = range; p.get() && num > 0; p.next()) + { + cached_piece_entry* e = p.get(); + if (e->num_dirty == 0) continue; + pieces.push_back(std::make_pair(e->storage.get(), int(e->piece))); + } + + for (std::vector >::iterator i = pieces.begin() + , end(pieces.end()); i != end; ++i) + { + // TODO: instead of doing a lookup each time through the loop, save + // cached_piece_entry pointers with piece_refcount incremented to pin them + cached_piece_entry* pe = m_disk_cache.find_piece(i->first, i->second); + if (pe == NULL) continue; + + // another thread may flush this piece while we're looping and + // evict it into a read piece and then also evict it to ghost + if (pe->cache_state != cached_piece_entry::write_lru) continue; + +#if TORRENT_USE_ASSERTS + pe->piece_log.push_back(piece_log_t(piece_log_t::try_flush_write_blocks, -1)); +#endif + ++pe->piece_refcount; + kick_hasher(pe, l); + num -= try_flush_hashed(pe, 1, completed_jobs, l); + --pe->piece_refcount; + + m_disk_cache.maybe_free_piece(pe); + } + + // when the write cache is under high pressure, it is likely + // counter productive to actually do this, since a piece may + // not have had its flush_hashed job run on it + // so only do it if no other thread is currently flushing + + if (num == 0 || m_stats_counters[counters::num_writing_threads] > 0) return; + + // if we still need to flush blocks, start over and flush + // everything in LRU order (degrade to lru cache eviction) + for (std::vector >::iterator i = pieces.begin() + , end(pieces.end()); i != end; ++i) + { + cached_piece_entry* pe = m_disk_cache.find_piece(i->first, i->second); + if (pe == NULL) continue; + if (pe->num_dirty == 0) continue; + + // another thread may flush this piece while we're looping and + // evict it into a read piece and then also evict it to ghost + if (pe->cache_state != cached_piece_entry::write_lru) continue; + + // don't flush blocks that are being hashed by another thread + if (pe->num_dirty == 0 || pe->hashing) continue; + +#if TORRENT_USE_ASSERTS + pe->piece_log.push_back(piece_log_t(piece_log_t::try_flush_write_blocks2, -1)); +#endif + ++pe->piece_refcount; + + num -= flush_range(pe, 0, INT_MAX, completed_jobs, l); + --pe->piece_refcount; + + m_disk_cache.maybe_free_piece(pe); + } + } + + void disk_io_thread::flush_expired_write_blocks(jobqueue_t& completed_jobs + , mutex::scoped_lock& l) + { + DLOG("flush_expired_write_blocks\n"); + + time_point now = aux::time_now(); + time_duration expiration_limit = seconds(m_settings.get_int(settings_pack::cache_expiry)); + +#if TORRENT_USE_ASSERTS + time_point timeout = min_time(); +#endif + + cached_piece_entry** to_flush = TORRENT_ALLOCA(cached_piece_entry*, 200); + int num_flush = 0; + + for (list_iterator p = m_disk_cache.write_lru_pieces(); p.get(); p.next()) + { + cached_piece_entry* e = p.get(); +#if TORRENT_USE_ASSERTS + TORRENT_PIECE_ASSERT(e->expire >= timeout, e); + timeout = e->expire; +#endif + + // since we're iterating in order of last use, if this piece + // shouldn't be evicted, none of the following ones will either + if (now - e->expire < expiration_limit) break; + if (e->num_dirty == 0) continue; + + TORRENT_PIECE_ASSERT(e->cache_state <= cached_piece_entry::read_lru1 || e->cache_state == cached_piece_entry::read_lru2, e); +#if TORRENT_USE_ASSERTS + e->piece_log.push_back(piece_log_t(piece_log_t::flush_expired, -1)); +#endif + ++e->piece_refcount; + // We can rely on the piece entry not being removed by + // incrementing the piece_refcount + to_flush[num_flush++] = e; + if (num_flush == 200) break; + } + + for (int i = 0; i < num_flush; ++i) + { + flush_range(to_flush[i], 0, INT_MAX, completed_jobs, l); + TORRENT_ASSERT(to_flush[i]->piece_refcount > 0); + --to_flush[i]->piece_refcount; + m_disk_cache.maybe_free_piece(to_flush[i]); + } + } + + namespace { + + typedef int (disk_io_thread::*disk_io_fun_t)(disk_io_job* j, jobqueue_t& completed_jobs); + + // this is a jump-table for disk I/O jobs + const disk_io_fun_t job_functions[] = + { + &disk_io_thread::do_read, + &disk_io_thread::do_write, + &disk_io_thread::do_hash, + &disk_io_thread::do_move_storage, + &disk_io_thread::do_release_files, + &disk_io_thread::do_delete_files, + &disk_io_thread::do_check_fastresume, + &disk_io_thread::do_save_resume_data, + &disk_io_thread::do_rename_file, + &disk_io_thread::do_stop_torrent, +#ifndef TORRENT_NO_DEPRECATE + &disk_io_thread::do_cache_piece, + &disk_io_thread::do_finalize_file, +#endif + &disk_io_thread::do_flush_piece, + &disk_io_thread::do_flush_hashed, + &disk_io_thread::do_flush_storage, + &disk_io_thread::do_trim_cache, + &disk_io_thread::do_file_priority, + &disk_io_thread::do_load_torrent, + &disk_io_thread::do_clear_piece, + &disk_io_thread::do_tick, + }; + + } // anonymous namespace + + // evict and/or flush blocks if we're exceeding the cache size + // or used to exceed it and haven't dropped below the low watermark yet + // the low watermark is dynamic, based on the number of peers waiting + // on buffers to free up. The more waiters, the lower the low watermark + // is. Because of this, the target for flushing jobs may have dropped + // below the number of blocks we flushed by the time we're done flushing + // that's why we need to call this fairly often. Both before and after + // a disk job is executed + void disk_io_thread::check_cache_level(mutex::scoped_lock& l, jobqueue_t& completed_jobs) + { + // when the read cache is disabled, always try to evict all read cache + // blocks + if (!m_settings.get_bool(settings_pack::use_read_cache)) + { + int const evict = m_disk_cache.read_cache_size(); + m_disk_cache.try_evict_blocks(evict); + } + + int evict = m_disk_cache.num_to_evict(0); + if (evict > 0) + { + evict = m_disk_cache.try_evict_blocks(evict); + // don't evict write jobs if at least one other thread + // is flushing right now. Doing so could result in + // unnecessary flushing of the wrong pieces + if (evict > 0 && m_stats_counters[counters::num_writing_threads] == 0) + { + try_flush_write_blocks(evict, completed_jobs, l); + } + } + } + + void disk_io_thread::perform_job(disk_io_job* j, jobqueue_t& completed_jobs) + { + INVARIANT_CHECK; + TORRENT_ASSERT(j->next == 0); + TORRENT_ASSERT((j->flags & disk_io_job::in_progress) || !j->storage); + +#if DEBUG_DISK_THREAD + { + mutex::scoped_lock l(m_cache_mutex); + + DLOG("perform_job job: %s ( %s%s) piece: %d offset: %d outstanding: %d\n" + , job_action_name[j->action] + , (j->flags & disk_io_job::fence) ? "fence ": "" + , (j->flags & disk_io_job::force_copy) ? "force_copy ": "" + , j->piece, j->d.io.offset + , j->storage ? j->storage->num_outstanding_jobs() : -1); + } +#endif + + boost::shared_ptr storage = j->storage; + + // TODO: instead of doing this. pass in the settings to each storage_interface + // call. Each disk thread could hold its most recent understanding of the settings + // in a shared_ptr, and update it every time it wakes up from a job. That way + // each access to the settings won't require a mutex to be held. + if (storage && storage->get_storage_impl()->m_settings == 0) + storage->get_storage_impl()->m_settings = &m_settings; + + TORRENT_ASSERT(j->action < sizeof(job_functions)/sizeof(job_functions[0])); + + time_point start_time = clock_type::now(); + + m_stats_counters.inc_stats_counter(counters::num_running_disk_jobs, 1); + + // call disk function + int ret = (this->*(job_functions[j->action]))(j, completed_jobs); + + // note that -2 erros are OK + TORRENT_ASSERT(ret != -1 || (j->error.ec && j->error.operation != 0)); + + m_stats_counters.inc_stats_counter(counters::num_running_disk_jobs, -1); + + mutex::scoped_lock l(m_cache_mutex); + if (m_cache_check_state == cache_check_idle) + { + m_cache_check_state = cache_check_active; + while (m_cache_check_state != cache_check_idle) + { + check_cache_level(l, completed_jobs); + TORRENT_ASSERT(l.locked()); + --m_cache_check_state; + } + } + else + { + m_cache_check_state = cache_check_reinvoke; + } + l.unlock(); + + if (ret == retry_job) + { + mutex::scoped_lock l2(m_job_mutex); + // to avoid busy looping here, give up + // our quanta in case there aren't any other + // jobs to run in between + + // TODO: a potentially more efficient solution would be to have a special + // queue for retry jobs, that's only ever run when a job completes, in + // any thread. It would only work if counters::num_running_disk_jobs > 0 + + TORRENT_ASSERT((j->flags & disk_io_job::in_progress) || !j->storage); + + bool need_sleep = m_queued_jobs.empty(); + m_queued_jobs.push_back(j); + l2.unlock(); + if (need_sleep) sleep(0); + return; + } + + if (ret == defer_handler) return; + + j->ret = ret; + + time_point now = clock_type::now(); + m_job_time.add_sample(total_microseconds(now - start_time)); + completed_jobs.push_back(j); + } + + int disk_io_thread::do_uncached_read(disk_io_job* j) + { + j->buffer.disk_block = m_disk_cache.allocate_buffer("send buffer"); + if (j->buffer.disk_block == 0) + { + j->error.ec = error::no_memory; + j->error.operation = storage_error::alloc_cache_piece; + return -1; + } + + time_point start_time = clock_type::now(); + + int const file_flags = file_flags_for_job(j + , m_settings.get_bool(settings_pack::coalesce_reads)); + file::iovec_t b = { j->buffer.disk_block, size_t(j->d.io.buffer_size) }; + + int ret = j->storage->get_storage_impl()->readv(&b, 1 + , j->piece, j->d.io.offset, file_flags, j->error); + + TORRENT_ASSERT(ret >= 0 || j->error.ec); + + if (!j->error.ec) + { + boost::uint32_t read_time = total_microseconds(clock_type::now() - start_time); + m_read_time.add_sample(read_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 ret; + } + + int disk_io_thread::do_read(disk_io_job* j, jobqueue_t& completed_jobs) + { + int block_size = m_disk_cache.block_size(); + int piece_size = j->storage->files()->piece_size(j->piece); + int blocks_in_piece = (piece_size + block_size - 1) / block_size; + int iov_len = m_disk_cache.pad_job(j, blocks_in_piece + , m_settings.get_int(settings_pack::read_cache_line_size)); + + file::iovec_t* iov = TORRENT_ALLOCA(file::iovec_t, iov_len); + + mutex::scoped_lock l(m_cache_mutex); + + int evict = m_disk_cache.num_to_evict(iov_len); + if (evict > 0) m_disk_cache.try_evict_blocks(evict); + + cached_piece_entry* pe = m_disk_cache.find_piece(j); + if (pe == NULL) + { + l.unlock(); + return do_uncached_read(j); + } + TORRENT_PIECE_ASSERT(pe->outstanding_read == 1, pe); + + l.unlock(); + + // then we'll actually allocate the buffers + int ret = m_disk_cache.allocate_iovec(iov, iov_len); + + if (ret < 0) + { + ret = do_uncached_read(j); + + mutex::scoped_lock l2(m_cache_mutex); + pe = m_disk_cache.find_piece(j); + if (pe) maybe_issue_queued_read_jobs(pe, completed_jobs); + return ret; + } + + // this is the offset that's aligned to block boundaries + boost::int64_t adjusted_offset = j->d.io.offset & ~(block_size-1); + + // if this is the last piece, adjust the size of the + // last buffer to match up + iov[iov_len-1].iov_len = (std::min)(int(piece_size - adjusted_offset) + - (iov_len-1) * block_size, block_size); + TORRENT_ASSERT(iov[iov_len-1].iov_len > 0); + + // at this point, all the buffers are allocated and iov is initizalied + // and the blocks have their refcounters incremented, so no other thread + // can remove them. We can now release the cache mutex and dive into the + // disk operations. + + int const file_flags = file_flags_for_job(j + , m_settings.get_bool(settings_pack::coalesce_reads)); + time_point start_time = clock_type::now(); + + ret = j->storage->get_storage_impl()->readv(iov, iov_len + , j->piece, adjusted_offset, file_flags, j->error); + + if (!j->error.ec) + { + boost::uint32_t const read_time = total_microseconds(clock_type::now() - start_time); + m_read_time.add_sample(read_time / iov_len); + + m_stats_counters.inc_stats_counter(counters::num_blocks_read, iov_len); + m_stats_counters.inc_stats_counter(counters::num_read_ops); + m_stats_counters.inc_stats_counter(counters::disk_read_time, read_time); + m_stats_counters.inc_stats_counter(counters::disk_job_time, read_time); + } + + l.lock(); + + if (ret < 0) + { + // read failed. free buffers and return error + m_disk_cache.free_iovec(iov, iov_len); + + pe = m_disk_cache.find_piece(j); + if (pe == NULL) + { + // the piece is supposed to be allocated when the + // disk job is allocated + TORRENT_ASSERT(false); + return ret; + } + TORRENT_PIECE_ASSERT(pe->outstanding_read == 1, pe); + + if (pe->read_jobs.size() > 0) + fail_jobs_impl(j->error, pe->read_jobs, completed_jobs); + TORRENT_PIECE_ASSERT(pe->read_jobs.size() == 0, pe); + pe->outstanding_read = 0; +#if TORRENT_USE_ASSERTS + pe->piece_log.push_back(piece_log_t(piece_log_t::clear_outstanding_jobs)); +#endif + m_disk_cache.maybe_free_piece(pe); + return ret; + } + + int block = j->d.io.offset / block_size; +#if TORRENT_USE_ASSERTS + pe->piece_log.push_back(piece_log_t(j->action, block)); +#endif + // as soon we insert the blocks they may be evicted + // (if using purgeable memory). In order to prevent that + // until we can read from them, increment the refcounts + m_disk_cache.insert_blocks(pe, block, iov, iov_len, j, block_cache::blocks_inc_refcount); + + TORRENT_ASSERT(pe->blocks[block].buf); + + int tmp = m_disk_cache.try_read(j, true); + + // This should always succeed because we just checked to see there is a + // buffer for this block + TORRENT_ASSERT(tmp >= 0); + TORRENT_UNUSED(tmp); + + maybe_issue_queued_read_jobs(pe, completed_jobs); + + for (int i = 0; i < iov_len; ++i, ++block) + m_disk_cache.dec_block_refcount(pe, block, block_cache::ref_reading); + + return j->d.io.buffer_size; + } + + void disk_io_thread::maybe_issue_queued_read_jobs(cached_piece_entry* pe + , jobqueue_t& completed_jobs) + { + TORRENT_PIECE_ASSERT(pe->outstanding_read == 1, pe); + + // if we're shutting down, just cancel the jobs + if (m_abort) + { + fail_jobs_impl(storage_error(boost::asio::error::operation_aborted) + , pe->read_jobs, completed_jobs); + TORRENT_PIECE_ASSERT(pe->read_jobs.size() == 0, pe); + pe->outstanding_read = 0; +#if TORRENT_USE_ASSERTS + pe->piece_log.push_back(piece_log_t(piece_log_t::clear_outstanding_jobs)); +#endif + m_disk_cache.maybe_free_piece(pe); + return; + } + + // while we were reading, there may have been a few jobs + // that got queued up also wanting to read from this piece. + // Any job that is a cache hit now, complete it immediately. + // Then, issue the first non-cache-hit job. Once it complete + // it will keep working off this list + jobqueue_t stalled_jobs; + pe->read_jobs.swap(stalled_jobs); + + // the next job to issue (i.e. this is a cache-miss) + disk_io_job* next_job = NULL; + + while (stalled_jobs.size() > 0) + { + disk_io_job* j = stalled_jobs.pop_front(); + TORRENT_ASSERT(j->flags & disk_io_job::in_progress); + + int ret = m_disk_cache.try_read(j); + if (ret >= 0) + { + // cache-hit + m_stats_counters.inc_stats_counter(counters::num_blocks_cache_hits); + DLOG("do_read: cache hit\n"); + j->flags |= disk_io_job::cache_hit; + j->ret = ret; + completed_jobs.push_back(j); + } + else if (ret == -2) + { + // error + j->ret = disk_io_job::operation_failed; + completed_jobs.push_back(j); + } + else + { + // cache-miss, issue the first one + // put back the rest + if (next_job == NULL) + { + next_job = j; + } + else + { + TORRENT_PIECE_ASSERT(j->piece == pe->piece, pe); + pe->read_jobs.push_back(j); + } + } + } + + if (next_job) + { + add_job(next_job, false); + } + else + { + TORRENT_PIECE_ASSERT(pe->read_jobs.size() == 0, pe); + pe->outstanding_read = 0; +#if TORRENT_USE_ASSERTS + pe->piece_log.push_back(piece_log_t(piece_log_t::clear_outstanding_jobs)); +#endif + m_disk_cache.maybe_free_piece(pe); + } + } + + int disk_io_thread::do_uncached_write(disk_io_job* j) + { + time_point start_time = clock_type::now(); + + file::iovec_t const b = { j->buffer.disk_block, size_t(j->d.io.buffer_size) }; + int const file_flags = file_flags_for_job(j + , m_settings.get_bool(settings_pack::coalesce_writes)); + + m_stats_counters.inc_stats_counter(counters::num_writing_threads, 1); + + // the actual write operation + int ret = j->storage->get_storage_impl()->writev(&b, 1 + , j->piece, j->d.io.offset, file_flags, j->error); + + m_stats_counters.inc_stats_counter(counters::num_writing_threads, -1); + + if (!j->error.ec) + { + boost::uint32_t write_time = total_microseconds(clock_type::now() - start_time); + m_write_time.add_sample(write_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); + } + + m_disk_cache.free_buffer(j->buffer.disk_block); + j->buffer.disk_block = NULL; + + return ret; + } + + int disk_io_thread::do_write(disk_io_job* j, jobqueue_t& completed_jobs) + { + INVARIANT_CHECK; + TORRENT_ASSERT(j->d.io.buffer_size <= m_disk_cache.block_size()); + + mutex::scoped_lock l(m_cache_mutex); + + cached_piece_entry* pe = m_disk_cache.find_piece(j); + if (pe && pe->hashing_done) + { +#if TORRENT_USE_ASSERTS + print_piece_log(pe->piece_log); +#endif + TORRENT_ASSERT(pe->blocks[j->d.io.offset / 16 / 1024].buf != j->buffer.disk_block); + TORRENT_ASSERT(pe->blocks[j->d.io.offset / 16 / 1024].buf != NULL); + j->error.ec = error::operation_aborted; + j->error.operation = storage_error::write; + return -1; + } + + pe = m_disk_cache.add_dirty_block(j); + + if (pe) + { +#if TORRENT_USE_ASSERTS + pe->piece_log.push_back(piece_log_t(j->action, j->d.io.offset / 0x4000)); +#endif + + if (!pe->hashing_done + && pe->hash == 0 + && !m_settings.get_bool(settings_pack::disable_hash_checks)) + { + pe->hash = new partial_hash; + m_disk_cache.update_cache_state(pe); + } + + TORRENT_PIECE_ASSERT(pe->cache_state <= cached_piece_entry::read_lru1 || pe->cache_state == cached_piece_entry::read_lru2, pe); + ++pe->piece_refcount; + + // see if we can progress the hash cursor with this new block + kick_hasher(pe, l); + + TORRENT_PIECE_ASSERT(pe->cache_state <= cached_piece_entry::read_lru1 || pe->cache_state == cached_piece_entry::read_lru2, pe); + + // flushes the piece to disk in case + // it satisfies the condition for a write + // piece to be flushed + try_flush_hashed(pe, m_settings.get_int( + settings_pack::write_cache_line_size), completed_jobs, l); + + --pe->piece_refcount; + m_disk_cache.maybe_free_piece(pe); + + return defer_handler; + } + + // ok, we should just perform this job right now. + return do_uncached_write(j); + } + + void disk_io_thread::async_read(piece_manager* storage, peer_request const& r + , boost::function const& handler, void* requester + , int flags) + { + INVARIANT_CHECK; + +#ifdef TORRENT_DEBUG + // the caller must increment the torrent refcount before + // issuing an async disk request + storage->assert_torrent_refcount(); +#endif + + TORRENT_ASSERT(r.length <= m_disk_cache.block_size()); + TORRENT_ASSERT(r.length <= 16 * 1024); + + DLOG("do_read piece: %d block: %d\n", r.piece, r.start / m_disk_cache.block_size()); + + disk_io_job* j = allocate_job(disk_io_job::read); + j->storage = storage->shared_from_this(); + j->piece = r.piece; + j->d.io.offset = r.start; + j->d.io.buffer_size = r.length; + j->buffer.disk_block = 0; + j->flags = flags; + j->requester = requester; + j->callback = handler; + + mutex::scoped_lock l(m_cache_mutex); + int ret = prep_read_job_impl(j); + l.unlock(); + + switch (ret) + { + case 0: + if (handler) handler(j); + free_job(j); + break; + case 1: + add_job(j); + break; + } + } + + // this function checks to see if a read job is a cache hit, + // and if it doesn't have a picece allocated, it allocates + // one and it sets outstanding_read flag and possibly queues + // up the job in the piece read job list + // the cache mutex must be held when calling this + // + // returns 0 if the job succeeded immediately + // 1 if it needs to be added to the job queue + // 2 if it was deferred and will be performed later (no need to + // add it to the queue) + int disk_io_thread::prep_read_job_impl(disk_io_job* j, bool check_fence) + { + TORRENT_ASSERT(j->action == disk_io_job::read); + + int ret = m_disk_cache.try_read(j); + if (ret >= 0) + { + m_stats_counters.inc_stats_counter(counters::num_blocks_cache_hits); + DLOG("do_read: cache hit\n"); + j->flags |= disk_io_job::cache_hit; + j->ret = ret; + return 0; + } + else if (ret == -2) + { + j->error.ec = error::no_memory; + j->error.operation = storage_error::alloc_cache_piece; + j->ret = disk_io_job::operation_failed; + return 0; + } + + if (check_fence && j->storage->is_blocked(j)) + { + // this means the job was queued up inside storage + m_stats_counters.inc_stats_counter(counters::blocked_disk_jobs); + DLOG("blocked job: %s (torrent: %d total: %d)\n" + , job_action_name[j->action], j->storage ? j->storage->num_blocked() : 0 + , int(m_stats_counters[counters::blocked_disk_jobs])); + return 2; + } + + if (!m_settings.get_bool(settings_pack::use_read_cache) + || m_settings.get_int(settings_pack::cache_size) == 0) + { + // if the read cache is disabled then we can skip going through the cache + // but only if there is no existing piece entry. Otherwise there may be a + // partial hit on one-or-more dirty buffers so we must use the cache + // to avoid reading bogus data from storage + if (m_disk_cache.find_piece(j) == NULL) + return 1; + } + + cached_piece_entry* pe = m_disk_cache.allocate_piece(j, cached_piece_entry::read_lru1); + + if (pe == NULL) + { + j->ret = -1; + j->error.ec = error::no_memory; + j->error.operation = storage_error::read; + return 0; + } + if (pe->outstanding_read) + { + TORRENT_PIECE_ASSERT(j->piece == pe->piece, pe); + pe->read_jobs.push_back(j); + return 2; + } + +#if TORRENT_USE_ASSERTS + pe->piece_log.push_back(piece_log_t(piece_log_t::set_outstanding_jobs)); +#endif + pe->outstanding_read = 1; + + return 1; + } + + void disk_io_thread::async_write(piece_manager* storage, peer_request const& r + , disk_buffer_holder& buffer + , boost::function const& handler + , int flags) + { + INVARIANT_CHECK; + +#ifdef TORRENT_DEBUG + // the caller must increment the torrent refcount before + // issuing an async disk request + storage->assert_torrent_refcount(); +#endif + + TORRENT_ASSERT(r.length <= m_disk_cache.block_size()); + TORRENT_ASSERT(r.length <= 16 * 1024); + + disk_io_job* j = allocate_job(disk_io_job::write); + j->storage = storage->shared_from_this(); + j->piece = r.piece; + j->d.io.offset = r.start; + j->d.io.buffer_size = r.length; + j->buffer.disk_block = buffer.get(); + j->callback = handler; + j->flags = flags; + +#if TORRENT_USE_ASSERTS + mutex::scoped_lock l3_(m_cache_mutex); + cached_piece_entry* pe = m_disk_cache.find_piece(j); + if (pe) + { + // we should never add a new dirty block to a piece + // whose hash we have calculated. The piece needs + // to be cleared first, (async_clear_piece). + TORRENT_ASSERT(pe->hashing_done == 0); + + TORRENT_ASSERT(pe->blocks[r.start / 0x4000].refcount == 0 || pe->blocks[r.start / 0x4000].buf == NULL); + } + l3_.unlock(); +#endif + +#if TORRENT_USE_ASSERTS && defined TORRENT_EXPENSIVE_INVARIANT_CHECKS + mutex::scoped_lock l2_(m_cache_mutex); + std::pair range = m_disk_cache.all_pieces(); + for (block_cache::iterator i = range.first; i != range.second; ++i) + { + cached_piece_entry const& p = *i; + int bs = m_disk_cache.block_size(); + int piece_size = p.storage->files()->piece_size(p.piece); + int blocks_in_piece = (piece_size + bs - 1) / bs; + for (int k = 0; k < blocks_in_piece; ++k) + TORRENT_PIECE_ASSERT(p.blocks[k].buf != j->buffer.disk_block, &p); + } + l2_.unlock(); +#endif + +#if !defined TORRENT_DISABLE_POOL_ALLOCATOR && TORRENT_USE_ASSERTS + mutex::scoped_lock l_(m_cache_mutex); + TORRENT_ASSERT(m_disk_cache.is_disk_buffer(j->buffer.disk_block)); + l_.unlock(); +#endif + + TORRENT_ASSERT((r.start % m_disk_cache.block_size()) == 0); + + if (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_action_name[j->action], j->storage ? j->storage->num_blocked() : 0 + , int(m_stats_counters[counters::blocked_disk_jobs])); + // make the holder give up ownership of the buffer + // since the job was successfully queued up + buffer.release(); + return; + } + + mutex::scoped_lock l(m_cache_mutex); + // if we succeed in adding the block to the cache, the job will + // be added along with it. we may not free j if so + cached_piece_entry* dpe = m_disk_cache.add_dirty_block(j); + + // if the buffer was successfully added to the cache + // our holder should no longer own it + if (dpe) buffer.release(); + + if (dpe && dpe->outstanding_flush == 0) + { + dpe->outstanding_flush = 1; + l.unlock(); + + // the block and write job were successfully inserted + // into the cache. Now, see if we should trigger a flush + j = allocate_job(disk_io_job::flush_hashed); + j->storage = storage->shared_from_this(); + j->piece = r.piece; + j->flags = flags; + add_job(j); + } + // if we added the block (regardless of whether we also + // issued a flush job or not), we're done. + if (dpe) return; + l.unlock(); + + add_job(j); + buffer.release(); + } + + void disk_io_thread::async_hash(piece_manager* storage, int piece, int flags + , boost::function const& handler, void* requester) + { +#ifdef TORRENT_DEBUG + // the caller must increment the torrent refcount before + // issuing an async disk request + storage->assert_torrent_refcount(); +#endif + + disk_io_job* j = allocate_job(disk_io_job::hash); + j->storage = storage->shared_from_this(); + j->piece = piece; + j->callback = handler; + j->flags = flags; + j->requester = requester; + + int piece_size = storage->files()->piece_size(piece); + + // first check to see if the hashing is already done + mutex::scoped_lock l(m_cache_mutex); + cached_piece_entry* pe = m_disk_cache.find_piece(j); + if (pe && !pe->hashing && pe->hash && pe->hash->offset == piece_size) + { + sha1_hash result = pe->hash->h.final(); + memcpy(j->d.piece_hash, &result[0], 20); + + delete pe->hash; + pe->hash = NULL; + + if (pe->cache_state != cached_piece_entry::volatile_read_lru) + pe->hashing_done = 1; + +#if TORRENT_USE_ASSERTS + ++pe->hash_passes; +#endif + + l.unlock(); + if (handler) handler(j); + free_job(j); + return; + } + l.unlock(); + add_job(j); + } + + void disk_io_thread::async_move_storage(piece_manager* storage, std::string const& p, int flags + , boost::function const& handler) + { +#ifdef TORRENT_DEBUG + // the caller must increment the torrent refcount before + // issuing an async disk request + storage->assert_torrent_refcount(); +#endif + + disk_io_job* j = allocate_job(disk_io_job::move_storage); + j->storage = storage->shared_from_this(); + j->buffer.string = strdup(p.c_str()); + j->callback = handler; + j->flags = flags; + + add_fence_job(storage, j); + } + + void disk_io_thread::async_release_files(piece_manager* storage + , boost::function const& handler) + { + disk_io_job* j = allocate_job(disk_io_job::release_files); + j->storage = storage->shared_from_this(); + j->callback = handler; + + add_fence_job(storage, j); + } + + void disk_io_thread::async_delete_files(piece_manager* storage + , int const options + , boost::function const& handler) + { +#ifdef TORRENT_DEBUG + // the caller must increment the torrent refcount before + // issuing an async disk request + storage->assert_torrent_refcount(); +#endif + + // remove cache blocks belonging to this torrent + jobqueue_t completed_jobs; + + // remove outstanding jobs belonging to this torrent + mutex::scoped_lock l2(m_job_mutex); + + // TODO: maybe the tailqueue_iterator should contain a pointer-pointer + // instead and have an unlink function + disk_io_job* qj = m_queued_jobs.get_all(); + jobqueue_t to_abort; + + while (qj) + { + disk_io_job* next = qj->next; +#if TORRENT_USE_ASSERTS + qj->next = NULL; +#endif + if (qj->storage.get() == storage) + to_abort.push_back(qj); + else + m_queued_jobs.push_back(qj); + qj = next; + } + l2.unlock(); + + mutex::scoped_lock l(m_cache_mutex); + flush_cache(storage, flush_delete_cache, completed_jobs, l); + l.unlock(); + + disk_io_job* j = allocate_job(disk_io_job::delete_files); + j->storage = storage->shared_from_this(); + j->callback = handler; + j->buffer.delete_options = options; + add_fence_job(storage, j); + + fail_jobs_impl(storage_error(boost::asio::error::operation_aborted) + , to_abort, completed_jobs); + + if (completed_jobs.size()) + add_completed_jobs(completed_jobs); + } + + void disk_io_thread::async_check_fastresume(piece_manager* storage + , bdecode_node const* resume_data + , std::vector& links + , boost::function const& handler) + { +#ifdef TORRENT_DEBUG + // the caller must increment the torrent refcount before + // issuing an async disk request + storage->assert_torrent_refcount(); +#endif + + std::vector* links_vector + = new std::vector(); + links_vector->swap(links); + + disk_io_job* j = allocate_job(disk_io_job::check_fastresume); + j->storage = storage->shared_from_this(); + j->buffer.check_resume_data = resume_data; + j->d.links = links_vector; + j->callback = handler; + + add_fence_job(storage, j); + } + + void disk_io_thread::async_save_resume_data(piece_manager* storage + , boost::function const& handler) + { +#ifdef TORRENT_DEBUG + // the caller must increment the torrent refcount before + // issuing an async disk request + storage->assert_torrent_refcount(); +#endif + + disk_io_job* j = allocate_job(disk_io_job::save_resume_data); + j->storage = storage->shared_from_this(); + j->buffer.resume_data = NULL; + j->callback = handler; + + add_fence_job(storage, j); + } + + void disk_io_thread::async_rename_file(piece_manager* storage, int index, std::string const& name + , boost::function const& handler) + { +#ifdef TORRENT_DEBUG + // the caller must increment the torrent refcount before + // issuing an async disk request + storage->assert_torrent_refcount(); +#endif + + disk_io_job* j = allocate_job(disk_io_job::rename_file); + j->storage = storage->shared_from_this(); + j->piece = index; + j->buffer.string = strdup(name.c_str()); + j->callback = handler; + add_fence_job(storage, j); + } + + void disk_io_thread::async_stop_torrent(piece_manager* storage + , boost::function const& handler) + { + // remove outstanding hash jobs belonging to this torrent + mutex::scoped_lock l2(m_job_mutex); + + disk_io_job* qj = m_queued_hash_jobs.get_all(); + jobqueue_t to_abort; + + while (qj) + { + disk_io_job* next = qj->next; +#if TORRENT_USE_ASSERTS + qj->next = NULL; +#endif + if (qj->storage.get() == storage) + to_abort.push_back(qj); + else + m_queued_hash_jobs.push_back(qj); + qj = next; + } + l2.unlock(); + + disk_io_job* j = allocate_job(disk_io_job::stop_torrent); + j->storage = storage->shared_from_this(); + j->callback = handler; + add_fence_job(storage, j); + + jobqueue_t completed_jobs; + fail_jobs_impl(storage_error(boost::asio::error::operation_aborted) + , to_abort, completed_jobs); + if (completed_jobs.size()) + add_completed_jobs(completed_jobs); + } + +#ifndef TORRENT_NO_DEPRECATE + void disk_io_thread::async_cache_piece(piece_manager* storage, int piece + , boost::function const& handler) + { +#ifdef TORRENT_DEBUG + // the caller must increment the torrent refcount before + // issuing an async disk request + storage->assert_torrent_refcount(); +#endif + + disk_io_job* j = allocate_job(disk_io_job::cache_piece); + j->storage = storage->shared_from_this(); + j->piece = piece; + j->callback = handler; + + add_job(j); + } + + void disk_io_thread::async_finalize_file(piece_manager* storage, int file + , boost::function const& handler) + { +#ifdef TORRENT_DEBUG + // the caller must increment the torrent refcount before + // issuing an async disk request + storage->assert_torrent_refcount(); +#endif + + disk_io_job* j = allocate_job(disk_io_job::finalize_file); + j->storage = storage->shared_from_this(); + j->piece = file; + j->callback = handler; + + add_job(j); + } +#endif // TORRENT_NO_DEPRECATE + + void disk_io_thread::async_flush_piece(piece_manager* storage, int piece + , boost::function const& handler) + { +#ifdef TORRENT_DEBUG + // the caller must increment the torrent refcount before + // issuing an async disk request + storage->assert_torrent_refcount(); +#endif + + disk_io_job* j = allocate_job(disk_io_job::flush_piece); + j->storage = storage->shared_from_this(); + j->piece = piece; + j->callback = handler; + + if (m_abort) + { + j->error.ec = boost::asio::error::operation_aborted; + if (handler) handler(j); + free_job(j); + return; + } + + add_job(j); + } + + void disk_io_thread::async_set_file_priority(piece_manager* storage + , std::vector const& prios + , boost::function const& handler) + { +#ifdef TORRENT_DEBUG + // the caller must increment the torrent refcount before + // issuing an async disk request + storage->assert_torrent_refcount(); +#endif + + std::vector* p = new std::vector(prios); + + disk_io_job* j = allocate_job(disk_io_job::file_priority); + j->storage = storage->shared_from_this(); + j->buffer.priorities = p; + j->callback = handler; + + add_fence_job(storage, j); + } + + void disk_io_thread::async_load_torrent(add_torrent_params* params + , boost::function const& handler) + { + disk_io_job* j = allocate_job(disk_io_job::load_torrent); + j->requester = reinterpret_cast(params); + j->callback = handler; + + add_job(j); + } + + void disk_io_thread::async_tick_torrent(piece_manager* storage + , boost::function const& handler) + { + disk_io_job* j = allocate_job(disk_io_job::tick_storage); + j->storage = storage->shared_from_this(); + j->callback = handler; + + add_job(j); + } + + void disk_io_thread::clear_read_cache(piece_manager* storage) + { + mutex::scoped_lock l(m_cache_mutex); + + jobqueue_t jobs; + boost::unordered_set const& cache = storage->cached_pieces(); + + // note that i is incremented in the body! + for (boost::unordered_set::const_iterator i = cache.begin() + , end(cache.end()); i != end; ) + { + jobqueue_t temp; + if (m_disk_cache.evict_piece(*(i++), temp)) + jobs.append(temp); + } + fail_jobs(storage_error(boost::asio::error::operation_aborted), jobs); + } + + void disk_io_thread::async_clear_piece(piece_manager* storage, int index + , boost::function const& handler) + { +#ifdef TORRENT_DEBUG + // the caller must increment the torrent refcount before + // issuing an async disk request + storage->assert_torrent_refcount(); +#endif + + disk_io_job* j = allocate_job(disk_io_job::clear_piece); + j->storage = storage->shared_from_this(); + j->piece = index; + j->callback = handler; + + // regular jobs are not guaranteed to be executed in-order + // since clear piece must guarantee that all write jobs that + // have been issued finish before the clear piece job completes + + // TODO: this is potentially very expensive. One way to solve + // it would be to have a fence for just this one piece. + add_fence_job(storage, j); + } + + void disk_io_thread::clear_piece(piece_manager* storage, int index) + { + mutex::scoped_lock l(m_cache_mutex); + + cached_piece_entry* pe = m_disk_cache.find_piece(storage, index); + if (pe == 0) return; + TORRENT_PIECE_ASSERT(pe->hashing == false, pe); + pe->hashing_done = 0; + delete pe->hash; + pe->hash = NULL; + + // evict_piece returns true if the piece was in fact + // evicted. A piece may fail to be evicted if there + // are still outstanding operations on it, which should + // never be the case when this function is used + // in fact, no jobs should really be hung on this piece + // at this point + jobqueue_t jobs; + bool ok = m_disk_cache.evict_piece(pe, jobs); + TORRENT_PIECE_ASSERT(ok, pe); + TORRENT_UNUSED(ok); + fail_jobs(storage_error(boost::asio::error::operation_aborted), jobs); + } + + void disk_io_thread::kick_hasher(cached_piece_entry* pe, mutex::scoped_lock& l) + { + if (!pe->hash) return; + if (pe->hashing) return; + + int piece_size = pe->storage.get()->files()->piece_size(pe->piece); + partial_hash* ph = pe->hash; + + // are we already done? + if (ph->offset >= piece_size) return; + + int block_size = m_disk_cache.block_size(); + int cursor = ph->offset / block_size; + int end = cursor; + TORRENT_PIECE_ASSERT(ph->offset % block_size == 0, pe); + + for (int i = cursor; i < pe->blocks_in_piece; ++i) + { + cached_block_entry& bl = pe->blocks[i]; + if (bl.buf == 0) break; + + // if we fail to lock the block, it' no longer in the cache + if (m_disk_cache.inc_block_refcount(pe, i, block_cache::ref_hashing) == false) + break; + + ++end; + } + + // no blocks to hash? + if (end == cursor) return; + + pe->hashing = 1; + + DLOG("kick_hasher: %d - %d (piece: %d offset: %d)\n" + , cursor, end, int(pe->piece), ph->offset); + + l.unlock(); + + time_point start_time = clock_type::now(); + + for (int i = cursor; i < end; ++i) + { + cached_block_entry& bl = pe->blocks[i]; + int size = (std::min)(block_size, piece_size - ph->offset); + ph->h.update(bl.buf, size); + ph->offset += size; + } + + boost::uint64_t hash_time = total_microseconds(clock_type::now() - start_time); + + l.lock(); + + TORRENT_PIECE_ASSERT(pe->hashing, pe); + TORRENT_PIECE_ASSERT(pe->hash, pe); + + m_hash_time.add_sample(hash_time / (end - cursor)); + + m_stats_counters.inc_stats_counter(counters::num_blocks_hashed, end - cursor); + m_stats_counters.inc_stats_counter(counters::disk_hash_time, hash_time); + m_stats_counters.inc_stats_counter(counters::disk_job_time, hash_time); + + pe->hashing = 0; + + // decrement the block refcounters + for (int i = cursor; i < end; ++i) + m_disk_cache.dec_block_refcount(pe, i, block_cache::ref_hashing); + + // did we complete the hash? + if (pe->hash->offset != piece_size) return; + + // if there are any hash-jobs hanging off of this piece + // we should post them now + disk_io_job* j = pe->jobs.get_all(); + jobqueue_t hash_jobs; + while (j) + { + TORRENT_PIECE_ASSERT((j->flags & disk_io_job::in_progress) || !j->storage, pe); + disk_io_job* next = j->next; + j->next = NULL; + TORRENT_PIECE_ASSERT(j->piece == pe->piece, pe); + if (j->action == disk_io_job::hash) hash_jobs.push_back(j); + else pe->jobs.push_back(j); + j = next; + } + if (hash_jobs.size()) + { + sha1_hash result = pe->hash->h.final(); + + for (tailqueue_iterator i = hash_jobs.iterate(); i.get(); i.next()) + { + disk_io_job* hj = const_cast(i.get()); + memcpy(hj->d.piece_hash, result.data(), 20); + hj->ret = 0; + } + + delete pe->hash; + pe->hash = NULL; + if (pe->cache_state != cached_piece_entry::volatile_read_lru) + pe->hashing_done = 1; +#if TORRENT_USE_ASSERTS + ++pe->hash_passes; +#endif + add_completed_jobs(hash_jobs); + } + } + + int disk_io_thread::do_uncached_hash(disk_io_job* j) + { + // we're not using a cache. This is the simple path + // just read straight from the file + TORRENT_ASSERT(m_magic == 0x1337); + + int const piece_size = j->storage->files()->piece_size(j->piece); + int const block_size = m_disk_cache.block_size(); + int const blocks_in_piece = (piece_size + block_size - 1) / block_size; + int const file_flags = file_flags_for_job(j + , m_settings.get_bool(settings_pack::coalesce_reads)); + + file::iovec_t iov; + iov.iov_base = m_disk_cache.allocate_buffer("hashing"); + hasher h; + int ret = 0; + int offset = 0; + for (int i = 0; i < blocks_in_piece; ++i) + { + DLOG("do_hash: (uncached) reading (piece: %d block: %d)\n" + , int(j->piece), i); + + time_point start_time = clock_type::now(); + + iov.iov_len = (std::min)(block_size, piece_size - offset); + ret = j->storage->get_storage_impl()->readv(&iov, 1, j->piece + , offset, file_flags, j->error); + if (ret < 0) break; + + if (!j->error.ec) + { + boost::uint32_t const read_time = total_microseconds(clock_type::now() - start_time); + m_read_time.add_sample(read_time); + + m_stats_counters.inc_stats_counter(counters::num_blocks_read); + m_stats_counters.inc_stats_counter(counters::num_read_ops); + m_stats_counters.inc_stats_counter(counters::disk_read_time, read_time); + m_stats_counters.inc_stats_counter(counters::disk_job_time, read_time); + } + + offset += block_size; + h.update(static_cast(iov.iov_base), iov.iov_len); + } + + m_disk_cache.free_buffer(static_cast(iov.iov_base)); + + sha1_hash piece_hash = h.final(); + memcpy(j->d.piece_hash, &piece_hash[0], 20); + return ret >= 0 ? 0 : -1; + } + + int disk_io_thread::do_hash(disk_io_job* j, jobqueue_t& /* completed_jobs */ ) + { + INVARIANT_CHECK; + + int const piece_size = j->storage->files()->piece_size(j->piece); + int const file_flags = file_flags_for_job(j + , m_settings.get_bool(settings_pack::coalesce_reads)); + + mutex::scoped_lock l(m_cache_mutex); + + cached_piece_entry* pe = m_disk_cache.find_piece(j); + if (pe) + { + TORRENT_ASSERT(pe->in_use); +#if TORRENT_USE_ASSERTS + pe->piece_log.push_back(piece_log_t(j->action)); +#endif + m_disk_cache.cache_hit(pe, j->requester, j->flags & disk_io_job::volatile_read); + + TORRENT_PIECE_ASSERT(pe->cache_state <= cached_piece_entry::read_lru1 || pe->cache_state == cached_piece_entry::read_lru2, pe); + ++pe->piece_refcount; + kick_hasher(pe, l); + --pe->piece_refcount; + + TORRENT_PIECE_ASSERT(pe->cache_state <= cached_piece_entry::read_lru1 || pe->cache_state == cached_piece_entry::read_lru2, pe); + + // are we already done hashing? + if (pe->hash && !pe->hashing && pe->hash->offset == piece_size) + { + DLOG("do_hash: (%d) (already done)\n", int(pe->piece)); + sha1_hash piece_hash = pe->hash->h.final(); + memcpy(j->d.piece_hash, &piece_hash[0], 20); + delete pe->hash; + pe->hash = NULL; + if (pe->cache_state != cached_piece_entry::volatile_read_lru) + pe->hashing_done = 1; +#if TORRENT_USE_ASSERTS + ++pe->hash_passes; +#endif + m_disk_cache.update_cache_state(pe); + m_disk_cache.maybe_free_piece(pe); + return 0; + } + } + else if (m_settings.get_bool(settings_pack::use_read_cache) == false) + { + return do_uncached_hash(j); + } + + if (pe == NULL) + { + int cache_state = (j->flags & disk_io_job::volatile_read) + ? cached_piece_entry::volatile_read_lru + : cached_piece_entry::read_lru1; + pe = m_disk_cache.allocate_piece(j, cache_state); + } + if (pe == NULL) + { + j->error.ec = error::no_memory; + j->error.operation = storage_error::alloc_cache_piece; + return -1; + } + + if (pe->hashing) + { + TORRENT_PIECE_ASSERT(pe->hash, pe); + // another thread is hashing this piece right now + // try again in a little bit + DLOG("do_hash: retry\n"); + // TODO: we should probably just hang the job on the piece and make sure the hasher gets kicked + return retry_job; + } + + pe->hashing = 1; + + TORRENT_PIECE_ASSERT(pe->cache_state <= cached_piece_entry::read_lru1 + || pe->cache_state == cached_piece_entry::read_lru2, pe); + ++pe->piece_refcount; + + if (pe->hash == NULL) + { + pe->hashing_done = 0; + pe->hash = new partial_hash; + } + partial_hash* ph = pe->hash; + + int block_size = m_disk_cache.block_size(); + int blocks_in_piece = (piece_size + block_size - 1) / block_size; + + // keep track of which blocks we have locked by incrementing + // their refcounts. This is used to decrement only these blocks + // later. + int* locked_blocks = TORRENT_ALLOCA(int, blocks_in_piece); + memset(locked_blocks, 0, blocks_in_piece * sizeof(int)); + int num_locked_blocks = 0; + + // increment the refcounts of all + // blocks up front, and then hash them without holding the lock + TORRENT_PIECE_ASSERT(ph->offset % block_size == 0, pe); + for (int i = ph->offset / block_size; i < blocks_in_piece; ++i) + { + // is the block not in the cache? + if (pe->blocks[i].buf == NULL) continue; + + // if we fail to lock the block, it' no longer in the cache + if (m_disk_cache.inc_block_refcount(pe, i, block_cache::ref_hashing) == false) + continue; + + locked_blocks[num_locked_blocks++] = i; + } + + // to keep the cache footprint low, try to evict a volatile piece + m_disk_cache.try_evict_one_volatile(); + + l.unlock(); + + int ret = 0; + int next_locked_block = 0; + for (int i = ph->offset / block_size; i < blocks_in_piece; ++i) + { + file::iovec_t iov; + iov.iov_len = (std::min)(block_size, piece_size - ph->offset); + + if (next_locked_block < num_locked_blocks + && locked_blocks[next_locked_block] == i) + { + ++next_locked_block; + TORRENT_PIECE_ASSERT(pe->blocks[i].buf, pe); + TORRENT_PIECE_ASSERT(ph->offset == i * block_size, pe); + ph->offset += iov.iov_len; + ph->h.update(pe->blocks[i].buf, iov.iov_len); + } + else + { + iov.iov_base = m_disk_cache.allocate_buffer("hashing"); + + if (iov.iov_base == NULL) + { + l.lock(); + // TODO: introduce a holder class that automatically increments + // and decrements the piece_refcount + + // decrement the refcounts of the blocks we just hashed + for (int k = 0; k < num_locked_blocks; ++k) + m_disk_cache.dec_block_refcount(pe, locked_blocks[k], block_cache::ref_hashing); + + --pe->piece_refcount; + pe->hashing = false; + delete pe->hash; + pe->hash = NULL; + + m_disk_cache.maybe_free_piece(pe); + + j->error.ec = errors::no_memory; + j->error.operation = storage_error::alloc_cache_piece; + return -1; + } + + DLOG("do_hash: reading (piece: %d block: %d)\n", int(pe->piece), i); + + time_point start_time = clock_type::now(); + + TORRENT_PIECE_ASSERT(ph->offset == i * block_size, pe); + ret = j->storage->get_storage_impl()->readv(&iov, 1, j->piece + , ph->offset, file_flags, j->error); + + if (ret < 0) + { + TORRENT_ASSERT(j->error.ec && j->error.operation != 0); + m_disk_cache.free_buffer(static_cast(iov.iov_base)); + l.lock(); + break; + } + + // treat a short read as an error. The hash will be invalid, the + // block cannot be cached and the main thread should skip the rest + // of this file + if (ret != iov.iov_len) + { + ret = -1; + j->error.ec.assign(boost::asio::error::eof + , boost::asio::error::get_misc_category()); + j->error.operation = storage_error::read; + m_disk_cache.free_buffer(static_cast(iov.iov_base)); + l.lock(); + break; + } + + if (!j->error.ec) + { + boost::uint32_t read_time = total_microseconds(clock_type::now() - start_time); + m_read_time.add_sample(read_time); + + m_stats_counters.inc_stats_counter(counters::num_read_back); + m_stats_counters.inc_stats_counter(counters::num_blocks_read); + m_stats_counters.inc_stats_counter(counters::num_read_ops); + m_stats_counters.inc_stats_counter(counters::disk_read_time, read_time); + m_stats_counters.inc_stats_counter(counters::disk_job_time, read_time); + } + + TORRENT_PIECE_ASSERT(ph->offset == i * block_size, pe); + ph->offset += iov.iov_len; + ph->h.update(static_cast(iov.iov_base), iov.iov_len); + + l.lock(); + m_disk_cache.insert_blocks(pe, i, &iov, 1, j); + l.unlock(); + } + } + + l.lock(); + + // decrement the refcounts of the blocks we just hashed + for (int i = 0; i < num_locked_blocks; ++i) + m_disk_cache.dec_block_refcount(pe, locked_blocks[i], block_cache::ref_hashing); + + --pe->piece_refcount; + + pe->hashing = 0; + + if (ret >= 0) + { + sha1_hash piece_hash = ph->h.final(); + memcpy(j->d.piece_hash, &piece_hash[0], 20); + + delete pe->hash; + pe->hash = NULL; + if (pe->cache_state != cached_piece_entry::volatile_read_lru) + pe->hashing_done = 1; +#if TORRENT_USE_ASSERTS + ++pe->hash_passes; +#endif + m_disk_cache.update_cache_state(pe); + } + + m_disk_cache.maybe_free_piece(pe); + + TORRENT_ASSERT(ret >= 0 || (j->error.ec && j->error.operation != 0)); + + return ret < 0 ? ret : 0; + } + + int disk_io_thread::do_move_storage(disk_io_job* j, jobqueue_t& /* completed_jobs */ ) + { + // if this assert fails, something's wrong with the fence logic + TORRENT_ASSERT(j->storage->num_outstanding_jobs() == 1); + + // if files have to be closed, that's the storage's responsibility + return j->storage->get_storage_impl()->move_storage(j->buffer.string + , j->flags, j->error); + } + + int disk_io_thread::do_release_files(disk_io_job* j, jobqueue_t& completed_jobs) + { + INVARIANT_CHECK; + + // if this assert fails, something's wrong with the fence logic + TORRENT_ASSERT(j->storage->num_outstanding_jobs() == 1); + + mutex::scoped_lock l(m_cache_mutex); + flush_cache(j->storage.get(), flush_write_cache, completed_jobs, l); + l.unlock(); + + j->storage->get_storage_impl()->release_files(j->error); + return j->error ? -1 : 0; + } + + int disk_io_thread::do_delete_files(disk_io_job* j, jobqueue_t& completed_jobs) + { + TORRENT_ASSERT(j->buffer.delete_options != 0); + INVARIANT_CHECK; + + // if this assert fails, something's wrong with the fence logic + TORRENT_ASSERT(j->storage->num_outstanding_jobs() == 1); + + mutex::scoped_lock l(m_cache_mutex); +#if TORRENT_USE_ASSERTS + m_disk_cache.mark_deleted(*j->storage->files()); +#endif + + flush_cache(j->storage.get(), flush_delete_cache | flush_expect_clear + , completed_jobs, l); + l.unlock(); + + j->storage->get_storage_impl()->delete_files(j->buffer.delete_options, j->error); + return j->error ? -1 : 0; + } + + int disk_io_thread::do_check_fastresume(disk_io_job* j, jobqueue_t& /* completed_jobs */ ) + { + // if this assert fails, something's wrong with the fence logic + TORRENT_ASSERT(j->storage->num_outstanding_jobs() == 1); + + bdecode_node const* rd = j->buffer.check_resume_data; + bdecode_node tmp; + if (rd == NULL) rd = &tmp; + + boost::scoped_ptr > links(j->d.links); + return j->storage->check_fastresume(*rd, links.get(), j->error); + } + + int disk_io_thread::do_save_resume_data(disk_io_job* j, jobqueue_t& completed_jobs) + { + // if this assert fails, something's wrong with the fence logic + TORRENT_ASSERT(j->storage->num_outstanding_jobs() == 1); + + mutex::scoped_lock l(m_cache_mutex); + flush_cache(j->storage.get(), flush_write_cache, completed_jobs, l); + l.unlock(); + + entry* resume_data = new entry(entry::dictionary_t); + j->storage->get_storage_impl()->write_resume_data(*resume_data, j->error); + TORRENT_ASSERT(j->buffer.resume_data == 0); + j->buffer.resume_data = resume_data; + return j->error ? -1 : 0; + } + + int disk_io_thread::do_rename_file(disk_io_job* j, jobqueue_t& /* completed_jobs */ ) + { + // if this assert fails, something's wrong with the fence logic + TORRENT_ASSERT(j->storage->num_outstanding_jobs() == 1); + + // if files need to be closed, that's the storage's responsibility + j->storage->get_storage_impl()->rename_file(j->piece, j->buffer.string + , j->error); + return j->error ? -1 : 0; + } + + int disk_io_thread::do_stop_torrent(disk_io_job* j, jobqueue_t& completed_jobs) + { + // if this assert fails, something's wrong with the fence logic + TORRENT_ASSERT(j->storage->num_outstanding_jobs() == 1); + + // issue write commands for all dirty blocks + // and clear all read jobs + mutex::scoped_lock l(m_cache_mutex); + flush_cache(j->storage.get(), flush_read_cache | flush_write_cache + , completed_jobs, l); + l.unlock(); + + m_disk_cache.release_memory(); + + j->storage->get_storage_impl()->release_files(j->error); + return j->error ? -1 : 0; + } + +#ifndef TORRENT_NO_DEPRECATE + int disk_io_thread::do_cache_piece(disk_io_job* j, jobqueue_t& /* completed_jobs */ ) + { + INVARIANT_CHECK; + TORRENT_ASSERT(j->buffer.disk_block == 0); + + if (m_settings.get_int(settings_pack::cache_size) == 0 + || m_settings.get_bool(settings_pack::use_read_cache) == false) + return 0; + + int const file_flags = file_flags_for_job(j + , m_settings.get_bool(settings_pack::coalesce_reads)); + + mutex::scoped_lock l(m_cache_mutex); + + cached_piece_entry* pe = m_disk_cache.find_piece(j); + if (pe == NULL) + { + int cache_state = (j->flags & disk_io_job::volatile_read) + ? cached_piece_entry::volatile_read_lru + : cached_piece_entry::read_lru1; + pe = m_disk_cache.allocate_piece(j, cache_state); + } + if (pe == NULL) + { + j->error.ec = error::no_memory; + j->error.operation = storage_error::alloc_cache_piece; + return -1; + } + +#if TORRENT_USE_ASSERTS + pe->piece_log.push_back(piece_log_t(j->action)); +#endif + TORRENT_PIECE_ASSERT(pe->cache_state <= cached_piece_entry::read_lru1 + || pe->cache_state == cached_piece_entry::read_lru2, pe); + ++pe->piece_refcount; + + int block_size = m_disk_cache.block_size(); + int piece_size = j->storage->files()->piece_size(j->piece); + int blocks_in_piece = (piece_size + block_size - 1) / block_size; + + file::iovec_t iov; + int ret = 0; + int offset = 0; + + // TODO: it would be nice to not have to lock the mutex every + // turn through this loop + for (int i = 0; i < blocks_in_piece; ++i) + { + iov.iov_len = (std::min)(block_size, piece_size - offset); + + // is the block already in the cache? + if (pe->blocks[i].buf) continue; + l.unlock(); + + iov.iov_base = m_disk_cache.allocate_buffer("read cache"); + + if (iov.iov_base == NULL) + { + //#error introduce a holder class that automatically increments and decrements the piece_refcount + --pe->piece_refcount; + m_disk_cache.maybe_free_piece(pe); + j->error.ec = errors::no_memory; + j->error.operation = storage_error::alloc_cache_piece; + return -1; + } + + DLOG("do_cache_piece: reading (piece: %d block: %d)\n" + , int(pe->piece), i); + + time_point start_time = clock_type::now(); + + ret = j->storage->get_storage_impl()->readv(&iov, 1, j->piece + , offset, file_flags, j->error); + + if (ret < 0) + { + l.lock(); + break; + } + + if (!j->error.ec) + { + boost::uint32_t read_time = total_microseconds(clock_type::now() - start_time); + m_read_time.add_sample(read_time); + + m_stats_counters.inc_stats_counter(counters::num_blocks_read); + m_stats_counters.inc_stats_counter(counters::num_read_ops); + m_stats_counters.inc_stats_counter(counters::disk_read_time, read_time); + m_stats_counters.inc_stats_counter(counters::disk_job_time, read_time); + } + + offset += block_size; + + l.lock(); + m_disk_cache.insert_blocks(pe, i, &iov, 1, j); + } + + --pe->piece_refcount; + m_disk_cache.maybe_free_piece(pe); + return 0; + } + + int disk_io_thread::do_finalize_file(disk_io_job* j, jobqueue_t& /* completed_jobs */) + { + j->storage->get_storage_impl()->finalize_file(j->piece, j->error); + return j->error ? -1 : 0; + } +#endif // TORRENT_NO_DEPRECATE + + namespace { + + void get_cache_info_impl(cached_piece_info& info, cached_piece_entry const* i + , int block_size) + { + info.piece = i->piece; + info.storage = i->storage.get(); + info.last_use = i->expire; + info.need_readback = i->need_readback; + info.next_to_hash = i->hash == 0 ? -1 : (i->hash->offset + block_size - 1) / block_size; + info.kind = i->cache_state == cached_piece_entry::write_lru + ? cached_piece_info::write_cache + : i->cache_state == cached_piece_entry::volatile_read_lru + ? cached_piece_info::volatile_read_cache + : cached_piece_info::read_cache; + int blocks_in_piece = i->blocks_in_piece; + info.blocks.resize(blocks_in_piece); + for (int b = 0; b < blocks_in_piece; ++b) + info.blocks[b] = i->blocks[b].buf != 0; + } + + } // anonymous namespace + + void disk_io_thread::update_stats_counters(counters& c) const + { + // These are atomic_counts, so it's safe to access them from + // a different thread + mutex::scoped_lock jl(m_job_mutex); + + c.set_value(counters::num_read_jobs, read_jobs_in_use()); + c.set_value(counters::num_write_jobs, write_jobs_in_use()); + c.set_value(counters::num_jobs, jobs_in_use()); + c.set_value(counters::queued_disk_jobs, m_queued_jobs.size() + + m_queued_hash_jobs.size()); + + jl.unlock(); + + mutex::scoped_lock l(m_cache_mutex); + + // gauges + c.set_value(counters::disk_blocks_in_use, m_disk_cache.in_use()); + + m_disk_cache.update_stats_counters(c); + } + + void disk_io_thread::get_cache_info(cache_status* ret, bool no_pieces + , piece_manager const* storage) const + { + mutex::scoped_lock l(m_cache_mutex); + +#ifndef TORRENT_NO_DEPRECATE + ret->total_used_buffers = m_disk_cache.in_use(); + + ret->blocks_read_hit = m_stats_counters[counters::num_blocks_cache_hits]; + ret->blocks_read = m_stats_counters[counters::num_blocks_read]; + ret->blocks_written = m_stats_counters[counters::num_blocks_written]; + ret->writes = m_stats_counters[counters::num_write_ops]; + ret->reads = m_stats_counters[counters::num_read_ops]; + + int num_read_jobs = (std::max)(boost::int64_t(1) + , m_stats_counters[counters::num_read_ops]); + int num_write_jobs = (std::max)(boost::int64_t(1) + , m_stats_counters[counters::num_write_ops]); + int num_hash_jobs = (std::max)(boost::int64_t(1) + , m_stats_counters[counters::num_blocks_hashed]); + + ret->average_read_time = m_stats_counters[counters::disk_read_time] / num_read_jobs; + ret->average_write_time = m_stats_counters[counters::disk_write_time] / num_write_jobs; + ret->average_hash_time = m_stats_counters[counters::disk_hash_time] / num_hash_jobs; + ret->average_job_time = m_stats_counters[counters::disk_job_time] + / (num_read_jobs + num_write_jobs + num_hash_jobs); + ret->cumulative_job_time = m_stats_counters[counters::disk_job_time]; + ret->cumulative_read_time = m_stats_counters[counters::disk_read_time]; + ret->cumulative_write_time = m_stats_counters[counters::disk_write_time]; + ret->cumulative_hash_time = m_stats_counters[counters::disk_hash_time]; + ret->total_read_back = m_stats_counters[counters::num_read_back]; + + ret->blocked_jobs = m_stats_counters[counters::blocked_disk_jobs]; + + ret->num_jobs = jobs_in_use(); + ret->num_read_jobs = read_jobs_in_use(); + ret->read_queue_size = read_jobs_in_use(); + ret->num_write_jobs = write_jobs_in_use(); + ret->pending_jobs = m_stats_counters[counters::num_running_disk_jobs]; + ret->num_writing_threads = m_stats_counters[counters::num_writing_threads]; + + for (int i = 0; i < disk_io_job::num_job_ids; ++i) + ret->num_fence_jobs[i] = m_stats_counters[counters::num_fenced_read + i]; + + m_disk_cache.get_stats(ret); + +#endif + + ret->pieces.clear(); + + if (no_pieces == false) + { + int block_size = m_disk_cache.block_size(); + + if (storage) + { + ret->pieces.reserve(storage->num_pieces()); + + for (boost::unordered_set::iterator i + = storage->cached_pieces().begin(), end(storage->cached_pieces().end()); + i != end; ++i) + { + TORRENT_ASSERT((*i)->storage.get() == storage); + + if ((*i)->cache_state == cached_piece_entry::read_lru2_ghost + || (*i)->cache_state == cached_piece_entry::read_lru1_ghost) + continue; + ret->pieces.push_back(cached_piece_info()); + get_cache_info_impl(ret->pieces.back(), *i, block_size); + } + } + else + { + ret->pieces.reserve(m_disk_cache.num_pieces()); + + std::pair range + = m_disk_cache.all_pieces(); + + for (block_cache::iterator i = range.first; i != range.second; ++i) + { + if (i->cache_state == cached_piece_entry::read_lru2_ghost + || i->cache_state == cached_piece_entry::read_lru1_ghost) + continue; + ret->pieces.push_back(cached_piece_info()); + get_cache_info_impl(ret->pieces.back(), &*i, block_size); + } + } + } + + l.unlock(); + +#ifndef TORRENT_NO_DEPRECATE + mutex::scoped_lock jl(m_job_mutex); + ret->queued_jobs = m_queued_jobs.size() + m_queued_hash_jobs.size(); + jl.unlock(); +#endif + } + + int disk_io_thread::do_flush_piece(disk_io_job* j, jobqueue_t& completed_jobs) + { + mutex::scoped_lock l(m_cache_mutex); + + cached_piece_entry* pe = m_disk_cache.find_piece(j); + if (pe == NULL) return 0; + +#if TORRENT_USE_ASSERTS + pe->piece_log.push_back(piece_log_t(j->action)); +#endif + try_flush_hashed(pe, m_settings.get_int( + settings_pack::write_cache_line_size), completed_jobs, l); + + return 0; + } + + // this is triggered every time we insert a new dirty block in a piece + // by the time this gets executed, the block may already have been flushed + // triggered by another mechanism. + int disk_io_thread::do_flush_hashed(disk_io_job* j, jobqueue_t& completed_jobs) + { + mutex::scoped_lock l(m_cache_mutex); + + cached_piece_entry* pe = m_disk_cache.find_piece(j); + + if (pe == NULL) return 0; + + pe->outstanding_flush = 0; + + if (pe->num_dirty == 0) return 0; + + // if multiple threads are flushing this piece, this assert may fire + // this happens if the cache is running full and pieces are started to + // get flushed +// TORRENT_PIECE_ASSERT(pe->outstanding_flush == 1, pe); + +#if TORRENT_USE_ASSERTS + pe->piece_log.push_back(piece_log_t(j->action)); +#endif + TORRENT_PIECE_ASSERT(pe->cache_state <= cached_piece_entry::read_lru1 + || pe->cache_state == cached_piece_entry::read_lru2, pe); + ++pe->piece_refcount; + + if (!pe->hashing_done) + { + if (pe->hash == 0 && !m_settings.get_bool(settings_pack::disable_hash_checks)) + { + pe->hash = new partial_hash; + m_disk_cache.update_cache_state(pe); + } + + // see if we can progress the hash cursor with this new block + kick_hasher(pe, l); + + TORRENT_PIECE_ASSERT(pe->cache_state <= cached_piece_entry::read_lru1 || pe->cache_state == cached_piece_entry::read_lru2, pe); + } + + // flushes the piece to disk in case + // it satisfies the condition for a write + // piece to be flushed + // #error if hash checks are disabled, always just flush + try_flush_hashed(pe, m_settings.get_int( + settings_pack::write_cache_line_size), completed_jobs, l); + + TORRENT_ASSERT(l.locked()); + + --pe->piece_refcount; + + m_disk_cache.maybe_free_piece(pe); + + return 0; + } + + int disk_io_thread::do_flush_storage(disk_io_job* j, jobqueue_t& completed_jobs) + { + mutex::scoped_lock l(m_cache_mutex); + flush_cache(j->storage.get(), flush_write_cache, completed_jobs, l); + return 0; + } + + int disk_io_thread::do_trim_cache(disk_io_job*, jobqueue_t& /* completed_jobs */) + { +//#error implement + return 0; + } + + int disk_io_thread::do_file_priority(disk_io_job* j, jobqueue_t& /* completed_jobs */ ) + { + boost::scoped_ptr > p(j->buffer.priorities); + j->storage->get_storage_impl()->set_file_priority(*p, j->error); + return 0; + } + + int disk_io_thread::do_load_torrent(disk_io_job* j, jobqueue_t& /* completed_jobs */ ) + { + add_torrent_params* params = reinterpret_cast(j->requester); + + std::string filename = resolve_file_url(params->url); + torrent_info* t = new torrent_info(filename, j->error.ec); + if (j->error.ec) + { + j->buffer.torrent_file = NULL; + delete t; + } + else + { + // do this to trigger parsing of the info-dict here. It's better + // than to have it be done in the network thread. It has enough to + // do as it is. + std::string cert = t->ssl_cert(); + j->buffer.torrent_file = t; + } + + return 0; + } + + // this job won't return until all outstanding jobs on this + // piece are completed or cancelled and the buffers for it + // have been evicted + int disk_io_thread::do_clear_piece(disk_io_job* j, jobqueue_t& completed_jobs) + { + mutex::scoped_lock l(m_cache_mutex); + + cached_piece_entry* pe = m_disk_cache.find_piece(j); + if (pe == 0) return 0; + TORRENT_PIECE_ASSERT(pe->hashing == false, pe); + pe->hashing_done = 0; + delete pe->hash; + pe->hash = NULL; + pe->hashing_done = false; + +#if TORRENT_USE_ASSERTS + pe->piece_log.push_back(piece_log_t(j->action)); +#endif + + // evict_piece returns true if the piece was in fact + // evicted. A piece may fail to be evicted if there + // are still outstanding operations on it, in which case + // try again later + jobqueue_t jobs; + if (m_disk_cache.evict_piece(pe, jobs)) + { + fail_jobs_impl(storage_error(boost::asio::error::operation_aborted) + , jobs, completed_jobs); + return 0; + } + + m_disk_cache.mark_for_deletion(pe); + if (pe->num_blocks == 0) return 0; + + // we should always be able to evict the piece, since + // this is a fence job + TORRENT_PIECE_ASSERT(false, pe); + return retry_job; + } + + int disk_io_thread::do_tick(disk_io_job* j, jobqueue_t& /* completed_jobs */ ) + { + // true means this storage wants more ticks, false + // disables ticking (until it's enabled again) + return j->storage->get_storage_impl()->tick(); + } + + void disk_io_thread::add_fence_job(piece_manager* storage, disk_io_job* j + , bool user_add) + { + // if this happens, it means we started to shut down + // the disk threads too early. We have to post all jobs + // before the disk threads are shut down + TORRENT_ASSERT(!m_abort); + + DLOG("add_fence:job: %s (outstanding: %d)\n" + , job_action_name[j->action] + , j->storage->num_outstanding_jobs()); + + m_stats_counters.inc_stats_counter(counters::num_fenced_read + j->action); + + disk_io_job* fj = allocate_job(disk_io_job::flush_storage); + fj->storage = j->storage; + + int ret = storage->raise_fence(j, fj, m_stats_counters); + if (ret == disk_job_fence::fence_post_fence) + { + mutex::scoped_lock l(m_job_mutex); + TORRENT_ASSERT((j->flags & disk_io_job::in_progress) || !j->storage); + // prioritize fence jobs since they're blocking other jobs + m_queued_jobs.push_front(j); + l.unlock(); + + // discard the flush job + free_job(fj); + + if (m_num_threads == 0 && user_add) + immediate_execute(); + + return; + } + + // in this case, we can't run the fence job right now, because there + // are other jobs outstanding on this storage. We need to trigger a + // flush of all those jobs now. Only write jobs linger, those are the + // jobs that needs to be kicked + TORRENT_ASSERT(j->blocked); + + if (ret == disk_job_fence::fence_post_flush) + { + // now, we have to make sure that all outstanding jobs on this + // storage actually get flushed, in order for the fence job to + // be executed + mutex::scoped_lock l(m_job_mutex); + TORRENT_ASSERT((fj->flags & disk_io_job::in_progress) || !fj->storage); + + m_queued_jobs.push_front(fj); + } + else + { + TORRENT_ASSERT((fj->flags & disk_io_job::in_progress) == 0); + TORRENT_ASSERT(fj->blocked); + } + + if (m_num_threads == 0 && user_add) + immediate_execute(); + } + + void disk_io_thread::add_job(disk_io_job* j, bool user_add) + { + TORRENT_ASSERT(m_magic == 0x1337); + + TORRENT_ASSERT(!j->storage || j->storage->files()->is_valid()); + TORRENT_ASSERT(j->next == NULL); + // if this happens, it means we started to shut down + // the disk threads too early. We have to post all jobs + // before the disk threads are shut down + TORRENT_ASSERT(!m_abort + || j->action == disk_io_job::flush_piece + || j->action == disk_io_job::trim_cache); + + // this happens for read jobs that get hung on pieces in the + // block cache, and then get issued + if (j->flags & disk_io_job::in_progress) + { + mutex::scoped_lock l(m_job_mutex); + TORRENT_ASSERT((j->flags & disk_io_job::in_progress) || !j->storage); + m_queued_jobs.push_back(j); + + // if we literally have 0 disk threads, we have to execute the jobs + // immediately. If add job is called internally by the disk_io_thread, + // we need to defer executing it. We only want the top level to loop + // over the job queue (as is done below) + if (m_num_threads == 0 && user_add) + { + l.unlock(); + immediate_execute(); + } + return; + } + + DLOG("add_job: %s (outstanding: %d)\n" + , job_action_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_action_name[j->action], j->storage ? j->storage->num_blocked() : 0 + , int(m_stats_counters[counters::blocked_disk_jobs])); + return; + } + + mutex::scoped_lock l(m_job_mutex); + + TORRENT_ASSERT((j->flags & disk_io_job::in_progress) || !j->storage); + + // if there are at least 3 threads, there's a hasher thread + // and the hash jobs go into a separate queue + // see set_num_threads() + if (m_num_threads > 3 && j->action == disk_io_job::hash) + { + m_queued_hash_jobs.push_back(j); + } + else + { + m_queued_jobs.push_back(j); + // if we literally have 0 disk threads, we have to execute the jobs + // immediately. If add job is called internally by the disk_io_thread, + // we need to defer executing it. We only want the top level to loop + // over the job queue (as is done below) + if (m_num_threads == 0 && user_add) + { + l.unlock(); + immediate_execute(); + } + } + } + + void disk_io_thread::immediate_execute() + { + while (!m_queued_jobs.empty()) + { + disk_io_job* j = m_queued_jobs.pop_front(); + maybe_flush_write_blocks(); + execute_job(j); + } + } + + void disk_io_thread::submit_jobs() + { + mutex::scoped_lock l(m_job_mutex); + if (!m_queued_jobs.empty()) + m_job_cond.notify_all(); + if (!m_queued_hash_jobs.empty()) + m_hash_job_cond.notify_all(); + } + + void disk_io_thread::maybe_flush_write_blocks() + { + time_point now = clock_type::now(); + if (now <= m_last_cache_expiry + seconds(5)) return; + + mutex::scoped_lock l(m_cache_mutex); + DLOG("blocked_jobs: %d queued_jobs: %d num_threads %d\n" + , int(m_stats_counters[counters::blocked_disk_jobs]) + , m_queued_jobs.size(), int(m_num_threads)); + m_last_cache_expiry = now; + jobqueue_t completed_jobs; + flush_expired_write_blocks(completed_jobs, l); + l.unlock(); + if (completed_jobs.size()) + add_completed_jobs(completed_jobs); + } + + void disk_io_thread::execute_job(disk_io_job* j) + { + jobqueue_t completed_jobs; + perform_job(j, completed_jobs); + if (completed_jobs.size()) + add_completed_jobs(completed_jobs); + } + + void disk_io_thread::thread_fun(int thread_id, thread_type_t type + , boost::shared_ptr w) + { + DLOG("started disk thread %d\n", int(thread_id)); + + ++m_num_running_threads; + m_stats_counters.inc_stats_counter(counters::num_running_threads, 1); + + mutex::scoped_lock l(m_job_mutex); + for (;;) + { + disk_io_job* j = 0; + if (type == generic_thread) + { + TORRENT_ASSERT(l.locked()); + while (m_queued_jobs.empty() && thread_id < m_num_threads) m_job_cond.wait(l); + + // if the number of wanted threads is decreased, + // we may stop this thread + // when we're terminating the last thread (id=0), make sure + // we finish up all queued jobs first + if (thread_id >= m_num_threads && !(thread_id == 0 && m_queued_jobs.size() > 0)) + { + // time to exit this thread. + break; + } + + j = m_queued_jobs.pop_front(); + } + else if (type == hasher_thread) + { + TORRENT_ASSERT(l.locked()); + while (m_queued_hash_jobs.empty() && thread_id < m_num_threads) m_hash_job_cond.wait(l); + if (m_queued_hash_jobs.empty() && thread_id >= m_num_threads) break; + j = m_queued_hash_jobs.pop_front(); + } + + l.unlock(); + + TORRENT_ASSERT((j->flags & disk_io_job::in_progress) || !j->storage); + + if (thread_id == 0) + { + // there's no need for all threads to be doing this + maybe_flush_write_blocks(); + } + + execute_job(j); + + l.lock(); + } + l.unlock(); + + // 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(). + m_stats_counters.inc_stats_counter(counters::num_running_threads, -1); + if (--m_num_running_threads > 0 || !m_abort) + { + DLOG("exiting disk thread %d. num_threads: %d aborting: %d\n" + , thread_id, int(m_num_threads), int(m_abort)); + TORRENT_ASSERT(m_magic == 0x1337); + return; + } + + // 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()). + mutex::scoped_lock l2(m_cache_mutex); + TORRENT_ASSERT_VAL(m_disk_cache.pinned_blocks() == 0 + , m_disk_cache.pinned_blocks()); + while (m_disk_cache.pinned_blocks() > 0) + { + l2.unlock(); + sleep(100); + l2.lock(); + } + l2.unlock(); + + DLOG("disk thread %d is the last one alive. cleaning up\n", thread_id); + + abort_jobs(); + + // release the io_service to allow the run() call to return + // we do this once we stop posting new callbacks to it. +#if defined TORRENT_ASIO_DEBUGGING + complete_async("disk_io_thread::work"); +#endif + w.reset(); + + TORRENT_ASSERT(m_magic == 0x1337); + } + + void disk_io_thread::abort_jobs() + { + TORRENT_ASSERT(m_magic == 0x1337); + + jobqueue_t jobs; + m_disk_cache.clear(jobs); + fail_jobs(storage_error(boost::asio::error::operation_aborted), jobs); + + // close all files. This may take a long + // time on certain OSes (i.e. Mac OS) + // that's why it's important to do this in + // the disk thread in parallel with stopping + // trackers. + m_file_pool.release(); + +#if TORRENT_USE_ASSERTS + // by now, all pieces should have been evicted + std::pair pieces + = m_disk_cache.all_pieces(); + TORRENT_ASSERT(pieces.first == pieces.second); +#endif + + TORRENT_ASSERT(m_magic == 0x1337); + } + + // this is a callback called by the block_cache when + // it's exceeding the disk cache size. + void disk_io_thread::trigger_cache_trim() + { + // we just exceeded the cache size limit. Trigger a trim job + disk_io_job* j = allocate_job(disk_io_job::trim_cache); + add_job(j, false); + submit_jobs(); + } + + char* disk_io_thread::allocate_disk_buffer(bool& exceeded + , boost::shared_ptr o + , char const* category) + { + char* ret = m_disk_cache.allocate_buffer(exceeded, o, category); + return ret; + } + + void disk_io_thread::add_completed_job(disk_io_job* j) + { + jobqueue_t tmp; + tmp.push_back(j); + add_completed_jobs(tmp); + } + + void disk_io_thread::add_completed_jobs(jobqueue_t& jobs) + { + jobqueue_t new_completed_jobs; + do + { + // when a job completes, it's possible for it to cause + // a fence to be lowered, issuing the jobs queued up + // behind the fence. It's also possible for some of these + // jobs to be cache-hits, completing immediately. Those + // jobs are added to the new_completed_jobs queue and + // we need to re-issue those + add_completed_jobs_impl(jobs, new_completed_jobs); + TORRENT_ASSERT(jobs.size() == 0); + jobs.swap(new_completed_jobs); + } while (jobs.size() > 0); + } + + void disk_io_thread::add_completed_jobs_impl(jobqueue_t& jobs + , jobqueue_t& completed_jobs) + { + jobqueue_t new_jobs; + int ret = 0; + for (tailqueue_iterator i = jobs.iterate(); i.get(); i.next()) + { + disk_io_job* j = i.get(); + TORRENT_ASSERT((j->flags & disk_io_job::in_progress) || !j->storage); + +// DLOG("job_complete %s outstanding: %d\n" +// , job_action_name[j->action], j->storage ? j->storage->num_outstanding_jobs() : 0); + + if (j->storage) + { + if (j->flags & disk_io_job::fence) + { + m_stats_counters.inc_stats_counter( + counters::num_fenced_read + j->action, -1); + } + + ret += j->storage->job_complete(j, new_jobs); + } + TORRENT_ASSERT(ret == new_jobs.size()); + TORRENT_ASSERT((j->flags & disk_io_job::in_progress) == 0); +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(j->job_posted == false); + j->job_posted = true; +#endif + } + +#if DEBUG_DISK_THREAD + if (ret) DLOG("unblocked %d jobs (%d left)\n", ret + , int(m_stats_counters[counters::blocked_disk_jobs]) - ret); +#endif + + m_stats_counters.inc_stats_counter(counters::blocked_disk_jobs, -ret); + TORRENT_ASSERT(int(m_stats_counters[counters::blocked_disk_jobs]) >= 0); + + if (new_jobs.size() > 0) + { +#if TORRENT_USE_ASSERTS + for (tailqueue_iterator i = new_jobs.iterate(); i.get(); i.next()) + { + disk_io_job const* j = static_cast(i.get()); + TORRENT_ASSERT((j->flags & disk_io_job::in_progress) || !j->storage); + + if (j->action == disk_io_job::write) + { + mutex::scoped_lock l(m_cache_mutex); + cached_piece_entry* pe = m_disk_cache.find_piece(j); + if (pe) + { + TORRENT_ASSERT(pe->blocks[j->d.io.offset / 16 / 1024].buf != j->buffer.disk_block); + TORRENT_ASSERT(pe->blocks[j->d.io.offset / 16 / 1024].buf == NULL); + TORRENT_ASSERT(!pe->hashing_done); + } + } + } +#endif + jobqueue_t other_jobs; + jobqueue_t flush_jobs; + mutex::scoped_lock l_(m_cache_mutex); + while (new_jobs.size() > 0) + { + disk_io_job* j = new_jobs.pop_front(); + + if (j->action == disk_io_job::read) + { + int state = prep_read_job_impl(j, false); + switch (state) + { + case 0: + completed_jobs.push_back(j); + break; + case 1: + other_jobs.push_back(j); + break; + } + continue; + } + + // write jobs should be put straight into the cache + if (j->action != disk_io_job::write) + { + other_jobs.push_back(j); + continue; + } + + cached_piece_entry* pe = m_disk_cache.add_dirty_block(j); + + if (pe == NULL) + { + // this isn't correct, since jobs in the jobs + // queue aren't ordered + other_jobs.push_back(j); + continue; + } + +#if TORRENT_USE_ASSERTS + pe->piece_log.push_back(piece_log_t(j->action, j->d.io.offset / 0x4000)); +#endif + + if (!pe->hashing_done + && pe->hash == 0 + && !m_settings.get_bool(settings_pack::disable_hash_checks)) + { + pe->hash = new partial_hash; + m_disk_cache.update_cache_state(pe); + } + + TORRENT_PIECE_ASSERT(pe->cache_state <= cached_piece_entry::read_lru1 || pe->cache_state == cached_piece_entry::read_lru2, pe); + + if (pe->outstanding_flush == 0) + { + pe->outstanding_flush = 1; + + // the block and write job were successfully inserted + // into the cache. Now, see if we should trigger a flush + disk_io_job* fj = allocate_job(disk_io_job::flush_hashed); + fj->storage = j->storage; + fj->piece = j->piece; + flush_jobs.push_back(fj); + } + } + l_.unlock(); + + mutex::scoped_lock l(m_job_mutex); + m_queued_jobs.append(other_jobs); + l.unlock(); + + while (flush_jobs.size() > 0) + { + disk_io_job* j = flush_jobs.pop_front(); + add_job(j, false); + } + + m_job_cond.notify_all(); + } + + mutex::scoped_lock l(m_completed_jobs_mutex); + + bool need_post = m_completed_jobs.size() == 0; + m_completed_jobs.append(jobs); + l.unlock(); + + if (need_post) + { +#if DEBUG_DISK_THREAD + // we take this lock just to make the logging prettier (non-interleaved) + DLOG("posting job handlers (%d)\n", m_completed_jobs.size()); +#endif + m_ios.post(boost::bind(&disk_io_thread::call_job_handlers, this, m_userdata)); + } + } + + // This is run in the network thread + void disk_io_thread::call_job_handlers(void* userdata) + { + mutex::scoped_lock l(m_completed_jobs_mutex); + +#if DEBUG_DISK_THREAD + DLOG("call_job_handlers (%d)\n", m_completed_jobs.size()); +#endif + + int num_jobs = m_completed_jobs.size(); + disk_io_job* j = m_completed_jobs.get_all(); + l.unlock(); + + uncork_interface* uncork = static_cast(userdata); + std::vector to_delete; + to_delete.reserve(num_jobs); + + while (j) + { + TORRENT_ASSERT(j->job_posted == true); + TORRENT_ASSERT(j->callback_called == false); +// DLOG(" callback: %s\n", job_action_name[j->action]); + disk_io_job* next = j->next; + +#if TORRENT_USE_ASSERTS + j->callback_called = true; +#endif + if (j->callback) j->callback(j); + to_delete.push_back(j); + j = next; + } + + if (!to_delete.empty()) + free_jobs(&to_delete[0], to_delete.size()); + + // uncork all peers who received a disk event. This is + // to coalesce all the socket writes caused by the events. + if (uncork) uncork->do_delayed_uncork(); + } + +#if TORRENT_USE_INVARIANT_CHECKS + void disk_io_thread::check_invariant() const + { + } +#endif +} + diff --git a/src/disk_job_pool.cpp b/src/disk_job_pool.cpp new file mode 100644 index 0000000..2b0d4b1 --- /dev/null +++ b/src/disk_job_pool.cpp @@ -0,0 +1,110 @@ +/* + +Copyright (c) 2012-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/disk_job_pool.hpp" +#include "libtorrent/disk_io_job.hpp" + +namespace libtorrent +{ + disk_job_pool::disk_job_pool() + : m_jobs_in_use(0) + , m_read_jobs(0) + , m_write_jobs(0) + , m_job_pool(sizeof(disk_io_job)) + {} + + disk_job_pool::~disk_job_pool() + { +// #error this should be fixed! +// TORRENT_ASSERT(m_jobs_in_use == 0); + } + + disk_io_job* disk_job_pool::allocate_job(int type) + { + mutex::scoped_lock l(m_job_mutex); + disk_io_job* ptr = static_cast(m_job_pool.malloc()); + m_job_pool.set_next_size(100); + if (ptr == 0) return 0; + ++m_jobs_in_use; + if (type == disk_io_job::read) ++m_read_jobs; + else if (type == disk_io_job::write) ++m_write_jobs; + l.unlock(); + TORRENT_ASSERT(ptr); + + new (ptr) disk_io_job; + ptr->action = static_cast(type); +#if defined TORRENT_DEBUG || defined TORRENT_RELEASE_ASSERTS + ptr->in_use = true; +#endif + return ptr; + } + + void disk_job_pool::free_job(disk_io_job* j) + { + TORRENT_ASSERT(j); + if (j == 0) return; +#if defined TORRENT_DEBUG || defined TORRENT_RELEASE_ASSERTS + TORRENT_ASSERT(j->in_use); + j->in_use = false; +#endif + int type = j->action; + j->~disk_io_job(); + mutex::scoped_lock l(m_job_mutex); + if (type == disk_io_job::read) --m_read_jobs; + else if (type == disk_io_job::write) --m_write_jobs; + --m_jobs_in_use; + m_job_pool.free(j); + } + + void disk_job_pool::free_jobs(disk_io_job** j, int num) + { + if (num == 0) return; + + int read_jobs = 0; + int write_jobs = 0; + for (int i = 0; i < num; ++i) + { + int type = j[i]->action; + j[i]->~disk_io_job(); + if (type == disk_io_job::read) ++read_jobs; + else if (type == disk_io_job::write) ++write_jobs; + } + + mutex::scoped_lock l(m_job_mutex); + m_read_jobs -= read_jobs; + m_write_jobs -= write_jobs; + m_jobs_in_use -= num; + for (int i = 0; i < num; ++i) + m_job_pool.free(j[i]); + } +} + diff --git a/src/entry.cpp b/src/entry.cpp new file mode 100644 index 0000000..d47eeb3 --- /dev/null +++ b/src/entry.cpp @@ -0,0 +1,736 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#if TORRENT_USE_IOSTREAM +#include +#endif +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/entry.hpp" +#ifndef TORRENT_NO_DEPRECATE +#include "libtorrent/lazy_entry.hpp" +#endif +#include "libtorrent/bdecode.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/hex.hpp" + +namespace +{ + template + void call_destructor(T* o) + { + TORRENT_ASSERT(o); + o->~T(); + } +} + +namespace libtorrent +{ + namespace detail + { + TORRENT_EXPORT char const* integer_to_str(char* buf, int size + , entry::integer_type val) + { + int sign = 0; + if (val < 0) + { + sign = 1; + val = -val; + } + buf[--size] = '\0'; + if (val == 0) buf[--size] = '0'; + for (; size > sign && val != 0;) + { + buf[--size] = '0' + char(val % 10); + val /= 10; + } + if (sign) buf[--size] = '-'; + return buf + size; + } + } + + entry& entry::operator[](char const* key) + { + dictionary_type::iterator i = dict().find(key); + if (i != dict().end()) return i->second; + dictionary_type::iterator ret = dict().insert( + std::pair(key, entry())).first; + return ret->second; + } + + entry& entry::operator[](std::string const& key) + { + dictionary_type::iterator i = dict().find(key); + if (i != dict().end()) return i->second; + dictionary_type::iterator ret = dict().insert( + std::make_pair(key, entry())).first; + return ret->second; + } + + entry* entry::find_key(char const* key) + { + dictionary_type::iterator i = dict().find(key); + if (i == dict().end()) return 0; + return &i->second; + } + + entry const* entry::find_key(char const* key) const + { + dictionary_type::const_iterator i = dict().find(key); + if (i == dict().end()) return 0; + return &i->second; + } + + entry* entry::find_key(std::string const& key) + { + dictionary_type::iterator i = dict().find(key); + if (i == dict().end()) return 0; + return &i->second; + } + + entry const* entry::find_key(std::string const& key) const + { + dictionary_type::const_iterator i = dict().find(key); + if (i == dict().end()) return 0; + return &i->second; + } + +#ifndef BOOST_NO_EXCEPTIONS + const entry& entry::operator[](char const* key) const + { + return (*this)[std::string(key)]; + } + + const entry& entry::operator[](std::string const& key) const + { + dictionary_type::const_iterator i = dict().find(key); + if (i == dict().end()) throw type_error( + (std::string("key not found: ") + key).c_str()); + return i->second; + } +#endif + + entry::data_type entry::type() const + { +#ifdef TORRENT_DEBUG + m_type_queried = true; +#endif + return entry::data_type(m_type); + } + + entry::~entry() { destruct(); } + + void entry::operator=(const entry& e) + { + if (&e == this) return; + destruct(); + copy(e); + } + + entry::integer_type& entry::integer() + { + if (m_type == undefined_t) construct(int_t); +#ifndef BOOST_NO_EXCEPTIONS + if (m_type != int_t) throw_type_error(); +#elif defined TORRENT_DEBUG + TORRENT_ASSERT(m_type_queried); +#endif + TORRENT_ASSERT(m_type == int_t); + return *reinterpret_cast(data); + } + + entry::integer_type const& entry::integer() const + { +#ifndef BOOST_NO_EXCEPTIONS + if (m_type != int_t) throw_type_error(); +#elif defined TORRENT_DEBUG + 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); +#ifndef BOOST_NO_EXCEPTIONS + if (m_type != string_t) throw_type_error(); +#elif defined TORRENT_DEBUG + TORRENT_ASSERT(m_type_queried); +#endif + TORRENT_ASSERT(m_type == string_t); + return *reinterpret_cast(data); + } + + entry::string_type const& entry::string() const + { +#ifndef BOOST_NO_EXCEPTIONS + if (m_type != string_t) throw_type_error(); +#elif defined TORRENT_DEBUG + 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); +#ifndef BOOST_NO_EXCEPTIONS + if (m_type != list_t) throw_type_error(); +#elif defined TORRENT_DEBUG + TORRENT_ASSERT(m_type_queried); +#endif + TORRENT_ASSERT(m_type == list_t); + return *reinterpret_cast(data); + } + + entry::list_type const& entry::list() const + { +#ifndef BOOST_NO_EXCEPTIONS + if (m_type != list_t) throw_type_error(); +#elif defined TORRENT_DEBUG + 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); +#ifndef BOOST_NO_EXCEPTIONS + if (m_type != dictionary_t) throw_type_error(); +#elif defined TORRENT_DEBUG + TORRENT_ASSERT(m_type_queried); +#endif + TORRENT_ASSERT(m_type == dictionary_t); + return *reinterpret_cast(data); + } + + entry::dictionary_type const& entry::dict() const + { +#ifndef BOOST_NO_EXCEPTIONS + if (m_type != dictionary_t) throw_type_error(); +#elif defined TORRENT_DEBUG + 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); +#ifndef BOOST_NO_EXCEPTIONS + if (m_type != preformatted_t) throw_type_error(); +#elif defined TORRENT_DEBUG + TORRENT_ASSERT(m_type_queried); +#endif + TORRENT_ASSERT(m_type == preformatted_t); + return *reinterpret_cast(data); + } + + entry::preformatted_type const& entry::preformatted() const + { +#ifndef BOOST_NO_EXCEPTIONS + if (m_type != preformatted_t) throw_type_error(); +#elif defined TORRENT_DEBUG + TORRENT_ASSERT(m_type_queried); +#endif + TORRENT_ASSERT(m_type == preformatted_t); + return *reinterpret_cast(data); + } + + entry::entry() + : m_type(undefined_t) + { +#ifdef TORRENT_DEBUG + m_type_queried = true; +#endif + } + + entry::entry(data_type t) + : m_type(undefined_t) + { + construct(t); +#ifdef TORRENT_DEBUG + m_type_queried = true; +#endif + } + + entry::entry(const entry& e) + : m_type(undefined_t) + { + copy(e); +#ifdef TORRENT_DEBUG + m_type_queried = e.m_type_queried; +#endif + } + + entry::entry(dictionary_type const& v) + : m_type(undefined_t) + { +#ifdef TORRENT_DEBUG + m_type_queried = true; +#endif + new(data) dictionary_type(v); + m_type = dictionary_t; + } + + entry::entry(string_type const& v) + : m_type(undefined_t) + { +#ifdef TORRENT_DEBUG + m_type_queried = true; +#endif + new(data) string_type(v); + m_type = string_t; + } + + entry::entry(list_type const& v) + : m_type(undefined_t) + { +#ifdef TORRENT_DEBUG + m_type_queried = true; +#endif + new(data) list_type(v); + m_type = list_t; + } + + entry::entry(integer_type const& v) + : m_type(undefined_t) + { +#ifdef TORRENT_DEBUG + m_type_queried = true; +#endif + new(data) integer_type(v); + m_type = int_t; + } + + entry::entry(preformatted_type const& v) + : m_type(undefined_t) + { +#ifdef TORRENT_DEBUG + m_type_queried = true; +#endif + new(data) preformatted_type(v); + m_type = preformatted_t; + } + + // convert a bdecode_node into an old skool entry + void entry::operator=(bdecode_node const& e) + { + switch (e.type()) + { + case bdecode_node::string_t: + this->string() = e.string_value(); + 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] = elem.second; + } + break; + } + case bdecode_node::list_t: + { + list_type& l = this->list(); + for (int i = 0; i < e.list_size(); ++i) + { + l.push_back(entry()); + l.back() = e.list_at(i); + } + break; + } + case bdecode_node::none_t: + destruct(); + break; + } + } + +#ifndef TORRENT_NO_DEPRECATE + // convert a lazy_entry into an old skool entry + void entry::operator=(lazy_entry const& e) + { + switch (e.type()) + { + case lazy_entry::string_t: + this->string() = e.string_value(); + break; + case lazy_entry::int_t: + this->integer() = e.int_value(); + break; + case lazy_entry::dict_t: + { + dictionary_type& d = this->dict(); + for (int i = 0; i < e.dict_size(); ++i) + { + std::pair elem = e.dict_at(i); + d[elem.first] = *elem.second; + } + break; + } + case lazy_entry::list_t: + { + list_type& l = this->list(); + for (int i = 0; i < e.list_size(); ++i) + { + l.push_back(entry()); + l.back() = *e.list_at(i); + } + break; + } + case lazy_entry::none_t: + destruct(); + break; + } + } +#endif + + void entry::operator=(preformatted_type const& v) + { + destruct(); + new(data) preformatted_type(v); + m_type = preformatted_t; +#ifdef TORRENT_DEBUG + m_type_queried = true; +#endif + } + + void entry::operator=(dictionary_type const& v) + { + destruct(); + new(data) dictionary_type(v); + m_type = dictionary_t; +#ifdef TORRENT_DEBUG + m_type_queried = true; +#endif + } + + void entry::operator=(string_type const& v) + { + destruct(); + new(data) string_type(v); + m_type = string_t; +#ifdef TORRENT_DEBUG + m_type_queried = true; +#endif + } + + void entry::operator=(list_type const& v) + { + destruct(); + new(data) list_type(v); + m_type = list_t; +#ifdef TORRENT_DEBUG + m_type_queried = true; +#endif + } + + void entry::operator=(integer_type const& v) + { + destruct(); + new(data) integer_type(v); + m_type = int_t; +#ifdef TORRENT_DEBUG + m_type_queried = true; +#endif + } + + bool entry::operator==(entry const& e) const + { + if (m_type != e.m_type) return false; + + switch (m_type) + { + case int_t: + return integer() == e.integer(); + case string_t: + return string() == e.string(); + case list_t: + return list() == e.list(); + case dictionary_t: + return dict() == e.dict(); + case preformatted_t: + return preformatted() == e.preformatted(); + default: + TORRENT_ASSERT(m_type == undefined_t); + return true; + } + } + + void entry::construct(data_type t) + { + switch (t) + { + case int_t: + new(data) integer_type; + 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; +#ifdef TORRENT_DEBUG + 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(); +#ifdef TORRENT_DEBUG + 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; +#ifdef TORRENT_DEBUG + 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(false); + } + } + + std::string entry::to_string() const + { + std::string ret; + to_string_impl(ret, 0); + return ret; + } + + void entry::to_string_impl(std::string& out, int indent) const + { + TORRENT_ASSERT(indent >= 0); + for (int i = 0; i < indent; ++i) out += " "; + switch (m_type) + { + case int_t: + out += libtorrent::to_string(integer()).elems; + out += "\n"; + break; + case string_t: + { + bool binary_string = false; + for (std::string::const_iterator i = string().begin(); i != string().end(); ++i) + { + if (!is_print(static_cast(*i))) + { + binary_string = true; + break; + } + } + if (binary_string) + { + out += to_hex(string()); + out += "\n"; + } + else + { + out += string(); + out += "\n"; + } + } break; + case list_t: + { + out += "list\n"; + for (list_type::const_iterator i = list().begin(); i != list().end(); ++i) + { + i->to_string_impl(out, indent+1); + } + } break; + case dictionary_t: + { + out += "dictionary\n"; + for (dictionary_type::const_iterator i = dict().begin(); i != dict().end(); ++i) + { + bool binary_string = false; + for (std::string::const_iterator k = i->first.begin(); k != i->first.end(); ++k) + { + if (!is_print(static_cast(*k))) + { + binary_string = true; + break; + } + } + for (int j = 0; j < indent+1; ++j) out += " "; + out += "["; + if (binary_string) out += to_hex(i->first); + else out += i->first; + out += "]"; + + if (i->second.type() != entry::string_t + && i->second.type() != entry::int_t) + out += "\n"; + else out += " "; + i->second.to_string_impl(out, indent+2); + } + } break; + case preformatted_t: + out += "\n"; + break; + case undefined_t: + default: + out += "\n"; + } + } +} + diff --git a/src/enum_net.cpp b/src/enum_net.cpp new file mode 100644 index 0000000..7d612bb --- /dev/null +++ b/src/enum_net.cpp @@ -0,0 +1,1149 @@ +/* + +Copyright (c) 2007-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/enum_net.hpp" +#include "libtorrent/broadcast_socket.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/socket_type.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include // for wcstombscstombs + +#if TORRENT_USE_IFCONF +#include +#include +#include +#include +#include +#endif + +#if TORRENT_USE_SYSCTL +#include +#include +#include +#endif + +#if TORRENT_USE_GETIPFORWARDTABLE || TORRENT_USE_GETADAPTERSADDRESSES +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#include +#endif + +#if TORRENT_USE_NETLINK +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#if TORRENT_USE_IFADDRS +#include +#endif + +#if TORRENT_USE_IFADDRS || TORRENT_USE_IFCONF || TORRENT_USE_NETLINK || TORRENT_USE_SYSCTL +// capture this here where warnings are disabled (the macro generates warnings) +const unsigned long siocgifmtu = SIOCGIFMTU; +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#if defined(TORRENT_OS2) && !defined(IF_NAMESIZE) +#define IF_NAMESIZE IFNAMSIZ +#endif + +namespace libtorrent { namespace +{ + + address inaddr_to_address(in_addr const* ina, int len = 4) + { + typedef boost::asio::ip::address_v4::bytes_type bytes_t; + bytes_t b; + std::memset(&b[0], 0, b.size()); + if (len > 0) std::memcpy(&b[0], ina, (std::min)(len, int(b.size()))); + return address_v4(b); + } + +#if TORRENT_USE_IPV6 + address inaddr6_to_address(in6_addr const* ina6, int len = 16) + { + typedef boost::asio::ip::address_v6::bytes_type bytes_t; + bytes_t b; + std::memset(&b[0], 0, b.size()); + if (len > 0) std::memcpy(&b[0], ina6, (std::min)(len, int(b.size()))); + return address_v6(b); + } +#endif + + 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) - offsetof(sockaddr, sa_data)); +#if TORRENT_USE_IPV6 + else if (sin->sa_family == AF_INET6 || assume_family == AF_INET6) + return inaddr6_to_address(&reinterpret_cast(sin)->sin6_addr + , sockaddr_len(sin) - offsetof(sockaddr, sa_data)); +#endif + return address(); + } + +#if TORRENT_USE_NETLINK + + int read_nl_sock(int sock, char *buf, int bufsize, int seq, int pid) + { + nlmsghdr* nl_hdr; + + int msg_len = 0; + + do + { + int read_len = recv(sock, buf, bufsize - msg_len, 0); + if (read_len < 0) return -1; + + nl_hdr = (nlmsghdr*)buf; + + if ((NLMSG_OK(nl_hdr, read_len) == 0) || (nl_hdr->nlmsg_type == NLMSG_ERROR)) + return -1; + + if (nl_hdr->nlmsg_type == NLMSG_DONE) break; + + buf += read_len; + msg_len += read_len; + + if ((nl_hdr->nlmsg_flags & NLM_F_MULTI) == 0) break; + + } while((nl_hdr->nlmsg_seq != seq) || (nl_hdr->nlmsg_pid != pid)); + return msg_len; + } + + bool parse_route(int s, nlmsghdr* nl_hdr, ip_route* rt_info) + { + rtmsg* rt_msg = (rtmsg*)NLMSG_DATA(nl_hdr); + + if((rt_msg->rtm_family != AF_INET && rt_msg->rtm_family != AF_INET6) || (rt_msg->rtm_table != RT_TABLE_MAIN + && rt_msg->rtm_table != RT_TABLE_LOCAL)) + return false; + + int if_index = 0; + int rt_len = RTM_PAYLOAD(nl_hdr); + for (rtattr* rt_attr = (rtattr*)RTM_RTA(rt_msg); + RTA_OK(rt_attr,rt_len); rt_attr = RTA_NEXT(rt_attr,rt_len)) + { + switch(rt_attr->rta_type) + { + case RTA_OIF: + if_index = *(int*)RTA_DATA(rt_attr); + break; + case RTA_GATEWAY: +#if TORRENT_USE_IPV6 + if (rt_msg->rtm_family == AF_INET6) + { + rt_info->gateway = inaddr6_to_address((in6_addr*)RTA_DATA(rt_attr)); + } + else +#endif + { + rt_info->gateway = inaddr_to_address((in_addr*)RTA_DATA(rt_attr)); + } + break; + case RTA_DST: +#if TORRENT_USE_IPV6 + if (rt_msg->rtm_family == AF_INET6) + { + rt_info->destination = inaddr6_to_address((in6_addr*)RTA_DATA(rt_attr)); + } + else +#endif + { + rt_info->destination = inaddr_to_address((in_addr*)RTA_DATA(rt_attr)); + } + break; + } + } + + if_indextoname(if_index, rt_info->name); + ifreq req; + memset(&req, 0, sizeof(req)); + if_indextoname(if_index, req.ifr_name); + ioctl(s, siocgifmtu, &req); + rt_info->mtu = req.ifr_mtu; +// obviously this doesn't work correctly. How do you get the netmask for a route? +// if (ioctl(s, SIOCGIFNETMASK, &req) == 0) { +// rt_info->netmask = sockaddr_to_address(&req.ifr_addr, req.ifr_addr.sa_family); +// } + return true; + } +#endif + +#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 s, rt_msghdr* rtm, ip_route* rt_info) + { + sockaddr* rti_info[RTAX_MAX]; + sockaddr* sa = reinterpret_cast(rtm + 1); + for (int i = 0; i < RTAX_MAX; ++i) + { + if ((rtm->rtm_addrs & (1 << i)) == 0) + { + rti_info[i] = 0; + 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 == 0 + || rti_info[RTAX_DST] == 0 + || rti_info[RTAX_NETMASK] == 0 + || (sa->sa_family != AF_INET +#if TORRENT_USE_IPV6 + && sa->sa_family != AF_INET6 +#endif + )) + 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); + + // TODO: get the MTU (and other interesting metrics) from the rt_msghdr instead + ifreq req; + memset(&req, 0, sizeof(req)); + if_indextoname(rtm->rtm_index, req.ifr_name); + + // ignore errors here. This is best-effort + ioctl(s, siocgifmtu, &req); + rt_info->mtu = req.ifr_mtu; + + return true; + } +#endif + +#if TORRENT_USE_IFADDRS && !defined TORRENT_BUILD_SIMULATOR + bool iface_from_ifaddrs(ifaddrs *ifa, ip_interface &rv) + { + int family = ifa->ifa_addr->sa_family; + + if (family != AF_INET +#if TORRENT_USE_IPV6 + && family != AF_INET6 +#endif + ) + { + return false; + } + + strncpy(rv.name, ifa->ifa_name, sizeof(rv.name)); + rv.name[sizeof(rv.name)-1] = 0; + + // determine address + rv.interface_address = sockaddr_to_address(ifa->ifa_addr); + // determine netmask + if (ifa->ifa_netmask != NULL) + { + rv.netmask = sockaddr_to_address(ifa->ifa_netmask); + } + return true; + } +#endif + +}} // + +namespace libtorrent +{ + + // 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 TORRENT_USE_IPV6 + if (a1.is_v6()) + { + address_v6::bytes_type b1; + address_v6::bytes_type b2; + address_v6::bytes_type m; + b1 = a1.to_v6().to_bytes(); + b2 = a2.to_v6().to_bytes(); + m = mask.to_v6().to_bytes(); + for (int i = 0; i < int(b1.size()); ++i) + { + b1[i] &= m[i]; + b2[i] &= m[i]; + } + return memcmp(&b1[0], &b2[0], b1.size()) == 0; + } +#endif + return (a1.to_v4().to_ulong() & mask.to_v4().to_ulong()) + == (a2.to_v4().to_ulong() & mask.to_v4().to_ulong()); + } + + bool in_local_network(io_service& ios, address const& addr, error_code& ec) + { + std::vector net = enum_net_interfaces(ios, ec); + if (ec) return false; + return in_local_network(net, addr); + } + + bool in_local_network(std::vector const& net, address const& addr) + { + for (std::vector::const_iterator i = net.begin() + , end(net.end()); i != end; ++i) + { + if (match_addr_mask(addr, i->interface_address, i->netmask)) + return true; + } + return false; + } + +#if TORRENT_USE_GETIPFORWARDTABLE + address build_netmask(int bits, int family) + { + if (family == AF_INET) + { + typedef boost::asio::ip::address_v4::bytes_type bytes_t; + bytes_t b; + std::memset(&b[0], 0xff, b.size()); + for (int i = sizeof(bytes_t)/8-1; i > 0; --i) + { + if (bits < 8) + { + b[i] <<= bits; + break; + } + b[i] = 0; + bits -= 8; + } + return address_v4(b); + } +#if TORRENT_USE_IPV6 + else if (family == AF_INET6) + { + typedef boost::asio::ip::address_v6::bytes_type bytes_t; + bytes_t b; + std::memset(&b[0], 0xff, b.size()); + for (int i = sizeof(bytes_t)/8-1; i > 0; --i) + { + if (bits < 8) + { + b[i] <<= bits; + break; + } + b[i] = 0; + bits -= 8; + } + return address_v6(b); + } +#endif + else + { + return address(); + } + } +#endif + + std::vector enum_net_interfaces(io_service& ios, error_code& ec) + { + TORRENT_UNUSED(ios); // this may be unused depending on configuration + std::vector ret; +#if defined TORRENT_BUILD_SIMULATOR + + TORRENT_UNUSED(ec); + + std::vector
    ips = ios.get_ips(); + + for (int i = 0; i < int(ips.size()); ++i) + { + ip_interface wan; + wan.interface_address = ips[i]; + wan.netmask = address_v4::from_string("255.255.255.255"); + strcpy(wan.name, "eth0"); + wan.mtu = ios.sim().config().path_mtu(ips[i], ips[i]); + ret.push_back(wan); + } + +#elif TORRENT_USE_IFADDRS + int s = socket(AF_INET, SOCK_DGRAM, 0); + if (s < 0) + { + ec = error_code(errno, system_category()); + return ret; + } + + ifaddrs *ifaddr; + if (getifaddrs(&ifaddr) == -1) + { + ec = error_code(errno, system_category()); + close(s); + return ret; + } + + for (ifaddrs* ifa = ifaddr; ifa; ifa = ifa->ifa_next) + { + if (ifa->ifa_addr == 0) continue; + if ((ifa->ifa_flags & IFF_UP) == 0) continue; + + int family = ifa->ifa_addr->sa_family; + if (family == AF_INET +#if TORRENT_USE_IPV6 + || family == AF_INET6 +#endif + ) + { + ip_interface iface; + if (iface_from_ifaddrs(ifa, iface)) + { + ifreq req; + memset(&req, 0, sizeof(req)); + // -1 to leave a null terminator + strncpy(req.ifr_name, iface.name, IF_NAMESIZE - 1); + + // ignore errors here. This is best-effort + ioctl(s, siocgifmtu, &req); + iface.mtu = req.ifr_mtu; + ret.push_back(iface); + } + } + } + close(s); + freeifaddrs(ifaddr); +// MacOS X, BSD and solaris +#elif TORRENT_USE_IFCONF + int s = socket(AF_INET, SOCK_DGRAM, 0); + if (s < 0) + { + ec = error_code(errno, system_category()); + return ret; + } + ifconf ifc; + // make sure the buffer is aligned to hold ifreq structs + ifreq buf[40]; + ifc.ifc_len = sizeof(buf); + ifc.ifc_buf = (char*)buf; + if (ioctl(s, SIOCGIFCONF, &ifc) < 0) + { + ec = error_code(errno, system_category()); + close(s); + return ret; + } + + char *ifr = (char*)ifc.ifc_req; + int remaining = ifc.ifc_len; + + while (remaining > 0) + { + ifreq const& item = *reinterpret_cast(ifr); + +#ifdef _SIZEOF_ADDR_IFREQ + int current_size = _SIZEOF_ADDR_IFREQ(item); +#elif defined TORRENT_BSD + int current_size = item.ifr_addr.sa_len + IFNAMSIZ; +#else + int current_size = sizeof(ifreq); +#endif + + if (remaining < current_size) break; + + if (item.ifr_addr.sa_family == AF_INET +#if TORRENT_USE_IPV6 + || item.ifr_addr.sa_family == AF_INET6 +#endif + ) + { + ip_interface iface; + iface.interface_address = sockaddr_to_address(&item.ifr_addr); + strcpy(iface.name, item.ifr_name); + + ifreq req; + memset(&req, 0, sizeof(req)); + // -1 to leave a null terminator + strncpy(req.ifr_name, item.ifr_name, IF_NAMESIZE - 1); + if (ioctl(s, siocgifmtu, &req) < 0) + { + ec = error_code(errno, system_category()); + close(s); + return ret; + } +#ifndef TORRENT_OS2 + iface.mtu = req.ifr_mtu; +#else + iface.mtu = req.ifr_metric; // according to tcp/ip reference +#endif + + memset(&req, 0, sizeof(req)); + strncpy(req.ifr_name, item.ifr_name, IF_NAMESIZE - 1); + if (ioctl(s, SIOCGIFNETMASK, &req) < 0) + { +#if TORRENT_USE_IPV6 + if (iface.interface_address.is_v6()) + { + // this is expected to fail (at least on MacOS X) + iface.netmask = address_v6::any(); + } + else +#endif + { + ec = error_code(errno, system_category()); + close(s); + return ret; + } + } + else + { + iface.netmask = sockaddr_to_address(&req.ifr_addr, item.ifr_addr.sa_family); + } + ret.push_back(iface); + } + + ifr += current_size; + remaining -= current_size; + } + close(s); + +#elif TORRENT_USE_GETADAPTERSADDRESSES + +#if _WIN32_WINNT >= 0x0501 + // Load Iphlpapi library + HMODULE iphlp = LoadLibraryA("Iphlpapi.dll"); + if (iphlp) + { + // Get GetAdaptersAddresses() pointer + typedef ULONG (WINAPI *GetAdaptersAddresses_t)(ULONG,ULONG,PVOID,PIP_ADAPTER_ADDRESSES,PULONG); + GetAdaptersAddresses_t GetAdaptersAddresses = (GetAdaptersAddresses_t)GetProcAddress( + iphlp, "GetAdaptersAddresses"); + + if (GetAdaptersAddresses == NULL) + { + FreeLibrary(iphlp); + ec = error_code(boost::system::errc::not_supported, generic_category()); + return std::vector(); + } + + ULONG buf_size = 10000; + std::vector buffer(buf_size); + PIP_ADAPTER_ADDRESSES adapter_addresses + = reinterpret_cast(&buffer[0]); + + DWORD r = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER + | GAA_FLAG_SKIP_ANYCAST, NULL, adapter_addresses, &buf_size); + if (r == ERROR_BUFFER_OVERFLOW) + { + buffer.resize(buf_size); + adapter_addresses = reinterpret_cast(&buffer[0]); + r = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER + | GAA_FLAG_SKIP_ANYCAST, NULL, adapter_addresses, &buf_size); + } + if (r != NO_ERROR) + { + FreeLibrary(iphlp); + ec = error_code(WSAGetLastError(), system_category()); + return std::vector(); + } + + for (PIP_ADAPTER_ADDRESSES adapter = adapter_addresses; + adapter != 0; adapter = adapter->Next) + { + ip_interface r; + strncpy(r.name, adapter->AdapterName, sizeof(r.name)); + r.name[sizeof(r.name)-1] = 0; + r.mtu = adapter->Mtu; + IP_ADAPTER_UNICAST_ADDRESS* unicast = adapter->FirstUnicastAddress; + while (unicast) + { + r.interface_address = sockaddr_to_address(unicast->Address.lpSockaddr); + + ret.push_back(r); + + unicast = unicast->Next; + } + } + + // Free memory + FreeLibrary(iphlp); + return ret; + } +#endif + + SOCKET s = socket(AF_INET, SOCK_DGRAM, 0); + if (s == SOCKET_ERROR) + { + ec = error_code(WSAGetLastError(), system_category()); + return ret; + } + + INTERFACE_INFO buffer[30]; + DWORD size; + + if (WSAIoctl(s, SIO_GET_INTERFACE_LIST, 0, 0, buffer, + sizeof(buffer), &size, 0, 0) != 0) + { + ec = error_code(WSAGetLastError(), system_category()); + closesocket(s); + return ret; + } + closesocket(s); + + int n = size / sizeof(INTERFACE_INFO); + + ip_interface iface; + for (int i = 0; i < n; ++i) + { + 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); + iface.name[0] = 0; + iface.mtu = 1500; // how to get the MTU? + ret.push_back(iface); + } + +#else + +#ifdef _MSC_VER +#pragma message ( "THIS OS IS NOT RECOGNIZED, enum_net_interfaces WILL PROBABLY NOT WORK" ) +#else +#warning "THIS OS IS NOT RECOGNIZED, enum_net_interfaces WILL PROBABLY NOT WORK" +#endif + + // make a best guess of the interface we're using and its IP + udp::resolver r(ios); + udp::resolver::iterator i = r.resolve(udp::resolver::query(boost::asio::ip::host_name(ec), "0"), ec); + if (ec) return ret; + ip_interface iface; + for (;i != udp::resolver_iterator(); ++i) + { + iface.interface_address = i->endpoint().address(); + iface.mtu = 1500; + if (iface.interface_address.is_v4()) + iface.netmask = address_v4::netmask(iface.interface_address.to_v4()); + ret.push_back(iface); + } +#endif + return ret; + } + + address get_default_gateway(io_service& ios, error_code& ec) + { + std::vector ret = enum_routes(ios, ec); + std::vector::iterator i = std::find_if(ret.begin(), ret.end() + , boost::bind(&ip_route::destination, _1) == address()); + if (i == ret.end()) return address(); + return i->gateway; + } + + std::vector enum_routes(io_service& ios, error_code& ec) + { + std::vector ret; + TORRENT_UNUSED(ios); + +#ifdef TORRENT_BUILD_SIMULATOR + + TORRENT_UNUSED(ec); + + std::vector
    ips = ios.get_ips(); + + for (int i = 0; i < int(ips.size()); ++i) + { + ip_route r; + if (ips[i].is_v4()) + { + r.destination = address_v4(); + r.netmask = address_v4::from_string("255.255.255.0"); + address_v4::bytes_type b = ips[i].to_v4().to_bytes(); + b[3] = 1; + r.gateway = address_v4(b); + } + else + { + r.destination = address_v6(); + r.netmask = address_v6::from_string("FFFF:FFFF:FFFF:FFFF::0"); + address_v6::bytes_type b = ips[i].to_v6().to_bytes(); + b[14] = 1; + r.gateway = address_v6(b); + } + strcpy(r.name, "eth0"); + r.mtu = ios.sim().config().path_mtu(ips[i], ips[i]); + 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}; + + size_t needed = 0; +#ifdef TORRENT_OS2 + if (__libsocket_sysctl(mib, 6, 0, &needed, 0, 0) < 0) +#else + if (sysctl(mib, 6, 0, &needed, 0, 0) < 0) +#endif + { + ec = error_code(errno, system_category()); + return std::vector(); + } + + if (needed <= 0) + { + return std::vector(); + } + + boost::scoped_array buf(new (std::nothrow) char[needed]); + if (buf.get() == 0) + { + 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, 0, 0) < 0) +#endif + { + ec = error_code(errno, system_category()); + return std::vector(); + } + + char* end = buf.get() + needed; + + int s = socket(AF_INET, SOCK_DGRAM, 0); + if (s < 0) + { + ec = error_code(errno, system_category()); + return std::vector(); + } + 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) + { + continue; + } + + ip_route r; + if (parse_route(s, rtm, &r)) ret.push_back(r); + } + close(s); + +#elif TORRENT_USE_GETIPFORWARDTABLE +/* + move this to enum_net_interfaces + // Load Iphlpapi library + HMODULE iphlp = LoadLibraryA("Iphlpapi.dll"); + if (!iphlp) + { + ec = boost::asio::error::operation_not_supported; + return std::vector(); + } + + // Get GetAdaptersInfo() pointer + typedef DWORD (WINAPI *GetAdaptersInfo_t)(PIP_ADAPTER_INFO, PULONG); + GetAdaptersInfo_t GetAdaptersInfo = (GetAdaptersInfo_t)GetProcAddress(iphlp, "GetAdaptersInfo"); + if (!GetAdaptersInfo) + { + FreeLibrary(iphlp); + 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) + { + FreeLibrary(iphlp); + ec = boost::asio::error::operation_not_supported; + return std::vector(); + } + + adapter_info = (IP_ADAPTER_INFO*)malloc(out_buf_size); + if (!adapter_info) + { + FreeLibrary(iphlp); + ec = boost::asio::error::no_memory; + return std::vector(); + } + + if (GetAdaptersInfo(adapter_info, &out_buf_size) == NO_ERROR) + { + for (PIP_ADAPTER_INFO adapter = adapter_info; + adapter != 0; adapter = adapter->Next) + { + + ip_route r; + r.destination = address::from_string(adapter->IpAddressList.IpAddress.String, ec); + r.gateway = address::from_string(adapter->GatewayList.IpAddress.String, ec); + r.netmask = address::from_string(adapter->IpAddressList.IpMask.String, ec); + strncpy(r.name, adapter->AdapterName, sizeof(r.name)); + + if (ec) + { + ec = error_code(); + continue; + } + ret.push_back(r); + } + } + + // Free memory + free(adapter_info); + FreeLibrary(iphlp); +*/ + + // Load Iphlpapi library + HMODULE iphlp = LoadLibraryA("Iphlpapi.dll"); + if (!iphlp) + { + ec = boost::asio::error::operation_not_supported; + return std::vector(); + } + + typedef DWORD (WINAPI *GetIfEntry_t)(PMIB_IFROW pIfRow); + GetIfEntry_t GetIfEntry = (GetIfEntry_t)GetProcAddress(iphlp, "GetIfEntry"); + if (!GetIfEntry) + { + ec = boost::asio::error::operation_not_supported; + return std::vector(); + } + +#if _WIN32_WINNT >= 0x0600 + typedef DWORD (WINAPI *GetIpForwardTable2_t)( + ADDRESS_FAMILY, PMIB_IPFORWARD_TABLE2*); + typedef void (WINAPI *FreeMibTable_t)(PVOID Memory); + + GetIpForwardTable2_t GetIpForwardTable2 = (GetIpForwardTable2_t)GetProcAddress( + iphlp, "GetIpForwardTable2"); + FreeMibTable_t FreeMibTable = (FreeMibTable_t)GetProcAddress( + iphlp, "FreeMibTable"); + if (GetIpForwardTable2 && FreeMibTable) + { + MIB_IPFORWARD_TABLE2* routes = NULL; + int res = GetIpForwardTable2(AF_UNSPEC, &routes); + if (res == NO_ERROR) + { + for (int i = 0; i < routes->NumEntries; ++i) + { + ip_route r; + r.gateway = sockaddr_to_address((const sockaddr*)&routes->Table[i].NextHop); + r.destination = sockaddr_to_address( + (const sockaddr*)&routes->Table[i].DestinationPrefix.Prefix); + r.netmask = build_netmask(routes->Table[i].SitePrefixLength + , routes->Table[i].DestinationPrefix.Prefix.si_family); + MIB_IFROW ifentry; + ifentry.dwIndex = routes->Table[i].InterfaceIndex; + if (GetIfEntry(&ifentry) == NO_ERROR) + { + wcstombs(r.name, ifentry.wszName, sizeof(r.name)); + r.mtu = ifentry.dwMtu; + ret.push_back(r); + } + } + } + if (routes) FreeMibTable(routes); + FreeLibrary(iphlp); + return ret; + } +#endif + + // Get GetIpForwardTable() pointer + typedef DWORD (WINAPI *GetIpForwardTable_t)(PMIB_IPFORWARDTABLE pIpForwardTable,PULONG pdwSize,BOOL bOrder); + + GetIpForwardTable_t GetIpForwardTable = (GetIpForwardTable_t)GetProcAddress( + iphlp, "GetIpForwardTable"); + if (!GetIpForwardTable) + { + FreeLibrary(iphlp); + ec = boost::asio::error::operation_not_supported; + return std::vector(); + } + + MIB_IPFORWARDTABLE* routes = NULL; + ULONG out_buf_size = 0; + if (GetIpForwardTable(routes, &out_buf_size, FALSE) != ERROR_INSUFFICIENT_BUFFER) + { + FreeLibrary(iphlp); + ec = boost::asio::error::operation_not_supported; + return std::vector(); + } + + routes = (MIB_IPFORWARDTABLE*)malloc(out_buf_size); + if (!routes) + { + FreeLibrary(iphlp); + ec = boost::asio::error::no_memory; + return std::vector(); + } + + if (GetIpForwardTable(routes, &out_buf_size, FALSE) == NO_ERROR) + { + for (int i = 0; i < routes->dwNumEntries; ++i) + { + ip_route r; + r.destination = inaddr_to_address((in_addr const*)&routes->table[i].dwForwardDest); + r.netmask = inaddr_to_address((in_addr const*)&routes->table[i].dwForwardMask); + r.gateway = inaddr_to_address((in_addr const*)&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)); + r.name[sizeof(r.name)-1] = 0; + r.mtu = ifentry.dwMtu; + ret.push_back(r); + } + } + } + + // Free memory + free(routes); + FreeLibrary(iphlp); +#elif TORRENT_USE_NETLINK + enum { BUFSIZE = 8192 }; + + int sock = socket(PF_ROUTE, SOCK_DGRAM, NETLINK_ROUTE); + if (sock < 0) + { + ec = error_code(errno, system_category()); + return std::vector(); + } + + int seq = 0; + + char msg[BUFSIZE]; + memset(msg, 0, BUFSIZE); + nlmsghdr* nl_msg = (nlmsghdr*)msg; + + nl_msg->nlmsg_len = NLMSG_LENGTH(sizeof(rtmsg)); + nl_msg->nlmsg_type = RTM_GETROUTE; + nl_msg->nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST; + nl_msg->nlmsg_seq = seq++; + nl_msg->nlmsg_pid = getpid(); + + if (send(sock, nl_msg, nl_msg->nlmsg_len, 0) < 0) + { + ec = error_code(errno, system_category()); + close(sock); + return std::vector(); + } + + int len = read_nl_sock(sock, msg, BUFSIZE, seq, getpid()); + if (len < 0) + { + ec = error_code(errno, system_category()); + close(sock); + return std::vector(); + } + + int s = socket(AF_INET, SOCK_DGRAM, 0); + if (s < 0) + { + ec = error_code(errno, system_category()); + return std::vector(); + } + for (; NLMSG_OK(nl_msg, len); nl_msg = NLMSG_NEXT(nl_msg, len)) + { + ip_route r; + if (parse_route(s, nl_msg, &r)) ret.push_back(r); + } + close(s); + close(sock); + +#endif + return ret; + } + + // returns true if the given device exists + bool has_interface(char const* name, io_service& ios, error_code& ec) + { + std::vector ifs = enum_net_interfaces(ios, ec); + if (ec) return false; + + for (int i = 0; i < int(ifs.size()); ++i) + if (ifs[i].name == name) return true; + return false; + } + + // returns the device name whose local address is ``addr``. If + // no such device is found, an empty string is returned. + std::string device_for_address(address addr, io_service& ios, error_code& ec) + { + std::vector ifs = enum_net_interfaces(ios, ec); + if (ec) return std::string(); + + for (int i = 0; i < int(ifs.size()); ++i) + if (ifs[i].interface_address == addr) return ifs[i].name; + return std::string(); + } +} + + diff --git a/src/error_code.cpp b/src/error_code.cpp new file mode 100644 index 0000000..c6b0175 --- /dev/null +++ b/src/error_code.cpp @@ -0,0 +1,353 @@ +/* + +Copyright (c) 2008-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/string_util.hpp" // for to_string() +#include "libtorrent/aux_/escape_string.hpp" // for convert_to_native + +namespace libtorrent +{ + struct libtorrent_error_category : boost::system::error_category + { + virtual const char* name() const BOOST_SYSTEM_NOEXCEPT; + virtual std::string message(int ev) const BOOST_SYSTEM_NOEXCEPT; + virtual boost::system::error_condition default_error_condition(int ev) const BOOST_SYSTEM_NOEXCEPT + { return boost::system::error_condition(ev, *this); } + }; + + const char* libtorrent_error_category::name() const BOOST_SYSTEM_NOEXCEPT + { + return "libtorrent"; + } + + std::string libtorrent_error_category::message(int ev) const BOOST_SYSTEM_NOEXCEPT + { + 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", + "", + "", + "", + "", + "", + +// 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", +#ifndef TORRENT_NO_DEPRECATE + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + +// 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", +#endif + }; + if (ev < 0 || ev >= int(sizeof(msgs)/sizeof(msgs[0]))) + return "Unknown error"; + return msgs[ev]; + } + + boost::system::error_category& get_libtorrent_category() + { + static libtorrent_error_category libtorrent_category; + return libtorrent_category; + } + + struct TORRENT_EXPORT http_error_category : boost::system::error_category + { + virtual const char* name() const BOOST_SYSTEM_NOEXCEPT + { return "http error"; } + virtual std::string message(int ev) const BOOST_SYSTEM_NOEXCEPT + { + std::string ret; + ret += to_string(ev).elems; + 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; + } + virtual boost::system::error_condition default_error_condition( + int ev) const BOOST_SYSTEM_NOEXCEPT + { return boost::system::error_condition(ev, *this); } + }; + + boost::system::error_category& get_http_category() + { + static http_error_category http_category; + return http_category; + } + +#ifndef BOOST_NO_EXCEPTIONS + const char* libtorrent_exception::what() const TORRENT_EXCEPTION_THROW_SPECIFIER + { + if (!m_msg) + { + std::string msg = convert_from_native(m_error.message()); + m_msg = allocate_string_copy(msg.c_str()); + } + + return m_msg; + } + + libtorrent_exception::~libtorrent_exception() TORRENT_EXCEPTION_THROW_SPECIFIER + { + free(m_msg); + } +#endif + + namespace errors + { + // hidden + boost::system::error_code make_error_code(error_code_enum e) + { + return boost::system::error_code(e, get_libtorrent_category()); + } + } + +} + diff --git a/src/escape_string.cpp b/src/escape_string.cpp new file mode 100644 index 0000000..c910d44 --- /dev/null +++ b/src/escape_string.cpp @@ -0,0 +1,623 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include +#include + +#include +#include +#include + +#ifdef TORRENT_WINDOWS +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#endif + +#if TORRENT_USE_ICONV +#include +#include +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/assert.hpp" +#include "libtorrent/parse_url.hpp" +#include "libtorrent/random.hpp" + +#include "libtorrent/utf8.hpp" +#include "libtorrent/thread.hpp" + +#include "libtorrent/aux_/escape_string.hpp" +#include "libtorrent/string_util.hpp" // for to_string + +namespace libtorrent +{ + // defined in hex.cpp + extern const char hex_chars[]; + + std::string unescape_string(std::string const& s, error_code& ec) + { + std::string ret; + for (std::string::const_iterator 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 const char 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"; + + // the offset is used to ignore the first characters in the unreserved_chars table. + static std::string escape_string_impl(const char* str, int len, int offset) + { + TORRENT_ASSERT(str != 0); + 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 += hex_chars[boost::uint8_t(*str) >> 4]; + ret += hex_chars[boost::uint8_t(*str) & 15]; + } + ++str; + } + return ret; + } + + std::string escape_string(const char* str, int len) + { + return escape_string_impl(str, len, 11); + } + + std::string escape_path(const char* str, int len) + { + return escape_string_impl(str, len, 10); + } + + bool need_encoding(char const* str, int len) + { + for (int i = 0; i < len; ++i) + { + if (std::strchr(unreserved_chars, *str) == 0 || *str == 0) + return true; + ++str; + } + return false; + } + + void convert_path_to_posix(std::string& path) + { + for (std::string::iterator i = path.begin() + , end(path.end()); i != end; ++i) + if (*i == '\\') *i = '/'; + } + +#ifdef TORRENT_WINDOWS + void convert_path_to_windows(std::string& path) + { + for (std::string::iterator i = path.begin() + , end(path.end()); i != end; ++i) + if (*i == '/') *i = '\\'; + } +#endif + + // TODO: 2 this should probably be moved into string_util.cpp + std::string read_until(char const*& str, char 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; + boost::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(), path.size())) + return url; + + char msg[TORRENT_MAX_PATH*4]; + snprintf(msg, sizeof(msg), "%s://%s%s%s%s%s%s", protocol.c_str(), auth.c_str() + , auth.empty()?"":"@", host.c_str() + , port == -1 ? "" : ":" + , port == -1 ? "" : to_string(port).elems + , escape_path(path.c_str(), path.size()).c_str()); + return msg; + } + + std::string resolve_file_url(std::string const& url) + { + TORRENT_ASSERT(url.substr(0, 7) == "file://"); + // first, strip the file:// part. + // On windows, we have + // to strip the first / as well + int num_to_strip = 7; +#ifdef TORRENT_WINDOWS + if (url[7] == '/' || url[7] == '\\') ++num_to_strip; +#endif + std::string ret = url.substr(num_to_strip); + + // we also need to URL-decode it + error_code ec; + std::string unescaped = unescape_string(ret, ec); + if (ec) unescaped = ret; + + // on windows, we need to convert forward slashes + // to backslashes +#ifdef TORRENT_WINDOWS + convert_path_to_windows(unescaped); +#endif + + return unescaped; + } + + std::string base64encode(const std::string& s) + { + static const char 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', '+', '/' + }; + + unsigned char inbuf[3]; + unsigned char outbuf[4]; + + std::string ret; + for (std::string::const_iterator i = s.begin(); i != s.end();) + { + // available input is 1,2 or 3 bytes + // since we read 3 bytes at a time at most + int available_input = (std::min)(3, int(s.end()-i)); + + // clear input buffer + std::fill(inbuf, inbuf+3, 0); + + // read a chunk of input into inbuf + std::copy(i, i + available_input, inbuf); + i += available_input; + + // encode inbuf to outbuf + outbuf[0] = (inbuf[0] & 0xfc) >> 2; + outbuf[1] = ((inbuf[0] & 0x03) << 4) | ((inbuf [1] & 0xf0) >> 4); + outbuf[2] = ((inbuf[1] & 0x0f) << 2) | ((inbuf [2] & 0xc0) >> 6); + 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 < 3 - available_input; ++j) + { + ret += '='; + } + } + return ret; + } + + std::string base32encode(std::string const& s, int flags) + { + static const char 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 const char 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' + }; + const char *base32_table = 0 != (flags & string::lowercase) ? base32_table_lowercase : base32_table_canonical; + + int input_output_mapping[] = {0, 2, 4, 5, 7, 8}; + + unsigned char inbuf[5]; + unsigned char outbuf[8]; + + std::string ret; + for (std::string::const_iterator i = s.begin(); i != s.end();) + { + int available_input = (std::min)(5, int(s.end()-i)); + + // clear input buffer + std::fill(inbuf, inbuf+5, 0); + + // read a chunk of input into inbuf + std::copy(i, i + available_input, inbuf); + i += available_input; + + // encode inbuf to outbuf + outbuf[0] = (inbuf[0] & 0xf8) >> 3; + outbuf[1] = ((inbuf[0] & 0x07) << 2) | ((inbuf[1] & 0xc0) >> 6); + outbuf[2] = ((inbuf[1] & 0x3e) >> 1); + outbuf[3] = ((inbuf[1] & 0x01) << 4) | ((inbuf[2] & 0xf0) >> 4); + outbuf[4] = ((inbuf[2] & 0x0f) << 1) | ((inbuf[3] & 0x80) >> 7); + outbuf[5] = ((inbuf[3] & 0x7c) >> 2); + outbuf[6] = ((inbuf[3] & 0x03) << 3) | ((inbuf[4] & 0xe0) >> 5); + outbuf[7] = inbuf[4] & 0x1f; + + // write output + int num_out = input_output_mapping[available_input]; + for (int j = 0; j < num_out; ++j) + { + ret += base32_table[outbuf[j]]; + } + + if (0 == (flags & string::no_padding)) + { + // write pad + for (int j = 0; j < 8 - num_out; ++j) + { + ret += '='; + } + } + } + return ret; + } + + std::string base32decode(std::string const& s) + { + unsigned char inbuf[8]; + unsigned char outbuf[5]; + + std::string ret; + for (std::string::const_iterator i = s.begin(); i != s.end();) + { + int available_input = (std::min)(8, int(s.end()-i)); + + int pad_start = 0; + if (available_input < 8) pad_start = available_input; + + // clear input buffer + std::fill(inbuf, inbuf+8, 0); + for (int j = 0; j < available_input; ++j) + { + char in = std::toupper(*i++); + if (in >= 'A' && in <= 'Z') + inbuf[j] = in - 'A'; + else if (in >= '2' && in <= '7') + inbuf[j] = in - '2' + ('Z' - 'A') + 1; + 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; + outbuf[0] |= inbuf[1] >> 2; + outbuf[1] = (inbuf[1] & 0x3) << 6; + outbuf[1] |= inbuf[2] << 1; + outbuf[1] |= (inbuf[3] & 0x10) >> 4; + outbuf[2] = (inbuf[3] & 0x0f) << 4; + outbuf[2] |= (inbuf[4] & 0x1e) >> 1; + outbuf[3] = (inbuf[4] & 0x01) << 7; + outbuf[3] |= (inbuf[5] & 0x1f) << 2; + outbuf[3] |= (inbuf[6] & 0x18) >> 3; + outbuf[4] = (inbuf[6] & 0x07) << 5; + outbuf[4] |= inbuf[7]; + + int 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, outbuf + num_out, std::back_inserter(ret)); + } + return ret; + } + + std::string url_has_argument( + std::string const& url, std::string argument, std::string::size_type* out_pos) + { + size_t i = url.find('?'); + if (i == std::string::npos) return std::string(); + ++i; + + argument += '='; + + if (url.compare(i, argument.size(), argument) == 0) + { + size_t pos = i + argument.size(); + if (out_pos) *out_pos = pos; + return url.substr(pos, url.find('&', pos) - pos); + } + argument.insert(0, "&"); + i = url.find(argument, i); + if (i == std::string::npos) return std::string(); + size_t pos = i + argument.size(); + if (out_pos) *out_pos = pos; + return url.substr(pos, url.find('&', pos) - pos); + } + +#if defined TORRENT_WINDOWS && TORRENT_USE_WSTRING + std::wstring convert_to_wstring(std::string const& s) + { + std::wstring ret; + int result = libtorrent::utf8_wchar(s, ret); + if (result == 0) return ret; + + ret.clear(); + const char* end = &s[0] + s.size(); + for (const char* i = &s[0]; i < end;) + { + wchar_t c = '.'; + result = std::mbtowc(&c, i, end - i); + if (result > 0) i += result; + else ++i; + ret += c; + } + return ret; + } + + std::string convert_from_wstring(std::wstring const& s) + { + std::string ret; + int result = libtorrent::wchar_utf8(s, ret); + if (result == 0) return ret; + + ret.clear(); + const wchar_t* end = &s[0] + s.size(); + for (const wchar_t* i = &s[0]; i < end;) + { + char c[10]; + TORRENT_ASSERT(sizeof(c) >= MB_CUR_MAX); + result = std::wctomb(c, *i); + if (result > 0) + { + i += result; + ret.append(c, result); + } + else + { + ++i; + ret += "."; + } + } + return ret; + } +#endif + +#if TORRENT_USE_ICONV + std::string iconv_convert_impl(std::string const& s, iconv_t h) + { + std::string ret; + size_t insize = s.size(); + size_t outsize = insize * 4; + ret.resize(outsize); + char const* in = s.c_str(); + char* out = &ret[0]; + // posix has a weird iconv signature. implementations + // differ on what this signature should be, so we use + // a macro to let config.hpp determine it + size_t retval = iconv(h, TORRENT_ICONV_ARG &in, &insize, + &out, &outsize); + if (retval == (size_t)-1) return s; + // if this string has an invalid utf-8 sequence in it, don't touch it + if (insize != 0) return s; + // not sure why this would happen, but it seems to be possible + if (outsize > s.size() * 4) return s; + // outsize is the number of bytes unused of the out-buffer + TORRENT_ASSERT(ret.size() >= outsize); + ret.resize(ret.size() - outsize); + return ret; + } + + std::string convert_to_native(std::string const& s) + { + static mutex iconv_mutex; + // only one thread can use this handle at a time + mutex::scoped_lock l(iconv_mutex); + + // the empty string represents the local dependent encoding + static iconv_t iconv_handle = iconv_open("", "UTF-8"); + if (iconv_handle == iconv_t(-1)) return s; + return iconv_convert_impl(s, iconv_handle); + } + + std::string convert_from_native(std::string const& s) + { + static mutex iconv_mutex; + // only one thread can use this handle at a time + mutex::scoped_lock l(iconv_mutex); + + // the empty string represents the local dependent encoding + static iconv_t iconv_handle = iconv_open("UTF-8", ""); + if (iconv_handle == iconv_t(-1)) return s; + return iconv_convert_impl(s, iconv_handle); + } + +#elif defined TORRENT_WINDOWS + + std::string convert_to_native(std::string const& s) + { + std::wstring ws; + libtorrent::utf8_wchar(s, ws); + std::string ret; + ret.resize(ws.size() * 4 + 1); + std::size_t size = WideCharToMultiByte(CP_ACP, 0, ws.c_str(), -1, &ret[0], ret.size(), NULL, NULL); + if (size == std::size_t(-1)) return s; + if (size != 0 && ret[size - 1] == '\0') --size; + ret.resize(size); + return ret; + } + + std::string convert_from_native(std::string const& s) + { + std::wstring ws; + ws.resize(s.size() + 1); + std::size_t size = MultiByteToWideChar(CP_ACP, 0, s.c_str(), -1, &ws[0], ws.size()); + if (size == std::size_t(-1)) return s; + if (size != 0 && ws[size - 1] == '\0') --size; + ws.resize(size); + std::string ret; + libtorrent::wchar_utf8(ws, ret); + return ret; + } + +#elif TORRENT_USE_LOCALE + + std::string convert_to_native(std::string const& s) + { + std::wstring ws; + libtorrent::utf8_wchar(s, ws); + std::size_t size = wcstombs(0, ws.c_str(), 0); + if (size == std::size_t(-1)) return s; + std::string ret; + ret.resize(size); + size = wcstombs(&ret[0], ws.c_str(), size + 1); + if (size == std::size_t(-1)) return s; + ret.resize(size); + return ret; + } + + std::string convert_from_native(std::string const& s) + { + std::wstring ws; + ws.resize(s.size()); + std::size_t size = mbstowcs(&ws[0], s.c_str(), s.size()); + if (size == std::size_t(-1)) return s; + std::string ret; + libtorrent::wchar_utf8(ws, ret); + return ret; + } + +#endif + +} + diff --git a/src/file.cpp b/src/file.cpp new file mode 100644 index 0000000..6557de4 --- /dev/null +++ b/src/file.cpp @@ -0,0 +1,2371 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-macros" +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-macros" +#endif + +// these defines are just in case the system we're on needs them for 64 bit file +// support +#define _FILE_OFFSET_BITS 64 +#define _LARGE_FILES 1 + +// on mingw this is necessary to enable 64-bit time_t, specifically used for +// the stat struct. Without this, modification times returned by stat may be +// incorrect and consistently fail resume data +#ifndef __MINGW_USE_VC2005_COMPAT +# define __MINGW_USE_VC2005_COMPAT +#endif + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +#include "libtorrent/config.hpp" +#include "libtorrent/alloca.hpp" +#include "libtorrent/allocator.hpp" // page_size +#include "libtorrent/file.hpp" +#include +#include + +#ifdef TORRENT_DEBUG_FILE_LEAKS +#include +#include "libtorrent/thread.hpp" +#endif + +// for convert_to_wstring and convert_to_native +#include "libtorrent/aux_/escape_string.hpp" +#include +#include "libtorrent/assert.hpp" + +#include +#include + +#ifdef TORRENT_DISK_STATS +#include "libtorrent/io.hpp" +#endif + +#include + +#ifdef TORRENT_WINDOWS +// windows part + +#ifndef PtrToPtr64 +#define PtrToPtr64(x) (x) +#endif + +#include "libtorrent/utf8.hpp" + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#include +#ifndef TORRENT_MINGW +#include // for _getcwd, _mkdir +#else +#include +#endif +#include +#else +// posix part + +#include +#include +#include +#include + +#ifdef TORRENT_LINUX +// linux specifics + +#ifdef TORRENT_ANDROID +#include +#define statvfs statfs +#define fstatvfs fstatfs +#else +#include +#endif + +#include +#ifdef TORRENT_ANDROID +#include +#define lseek lseek64 +#endif + +#elif defined __APPLE__ && defined __MACH__ && MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 +// mac specifics + +#include + +#endif + +#undef _FILE_OFFSET_BITS + +// make sure the _FILE_OFFSET_BITS define worked +// on this platform. It's supposed to make file +// related functions support 64-bit offsets. +// this test makes sure lseek() returns a type +// at least 64 bits wide +BOOST_STATIC_ASSERT(sizeof(lseek(0, 0, 0)) >= 8); + +#endif // posix part + +#if TORRENT_USE_PREADV +# if defined TORRENT_WINDOWS +namespace +{ + // wrap the windows function in something that looks + // like preadv() and pwritev() + + // windows only lets us wait for 64 handles at a time, so this function makes + // sure we wait for all of them, partially in sequence + int wait_for_multiple_objects(int num_handles, HANDLE* h) + { + int batch_size = (std::min)(num_handles, MAXIMUM_WAIT_OBJECTS); + while (WaitForMultipleObjects(batch_size, h, TRUE, INFINITE) != WAIT_FAILED) + { + h += batch_size; + num_handles -= batch_size; + batch_size = (std::min)(num_handles, MAXIMUM_WAIT_OBJECTS); + if (batch_size <= 0) return WAIT_OBJECT_0; + } + return WAIT_FAILED; + } + + int preadv(HANDLE fd, libtorrent::file::iovec_t const* bufs, int num_bufs, boost::int64_t file_offset) + { + OVERLAPPED* ol = TORRENT_ALLOCA(OVERLAPPED, num_bufs); + memset(ol, 0, sizeof(OVERLAPPED) * num_bufs); + + HANDLE* h = TORRENT_ALLOCA(HANDLE, num_bufs); + + for (int i = 0; i < num_bufs; ++i) + { + ol[i].OffsetHigh = file_offset >> 32; + ol[i].Offset = file_offset & 0xffffffff; + ol[i].hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + h[i] = ol[i].hEvent; + if (h[i] == NULL) + { + // we failed to create the event, roll-back and return an error + for (int j = 0; j < i; ++j) CloseHandle(h[i]); + return -1; + } + file_offset += bufs[i].iov_len; + } + + int ret = 0; + for (int i = 0; i < num_bufs; ++i) + { + DWORD num_read; + if (ReadFile(fd, bufs[i].iov_base, bufs[i].iov_len, &num_read, &ol[i]) == FALSE + && GetLastError() != ERROR_IO_PENDING +#ifdef ERROR_CANT_WAIT + && GetLastError() != ERROR_CANT_WAIT +#endif + ) + { + ret = -1; + goto done; + } + } + + if (wait_for_multiple_objects(num_bufs, h) == WAIT_FAILED) + { + ret = -1; + goto done; + } + + for (int i = 0; i < num_bufs; ++i) + { + if (WaitForSingleObject(ol[i].hEvent, INFINITE) == WAIT_FAILED) + { + ret = -1; + break; + } + DWORD num_read; + if (GetOverlappedResult(fd, &ol[i], &num_read, FALSE) == FALSE) + { +#ifdef ERROR_CANT_WAIT + TORRENT_ASSERT(GetLastError() != ERROR_CANT_WAIT); +#endif + ret = -1; + break; + } + ret += num_read; + } +done: + + for (int i = 0; i < num_bufs; ++i) + CloseHandle(h[i]); + + return ret; + } + + int pwritev(HANDLE fd, libtorrent::file::iovec_t const* bufs, int num_bufs, boost::int64_t file_offset) + { + OVERLAPPED* ol = TORRENT_ALLOCA(OVERLAPPED, num_bufs); + memset(ol, 0, sizeof(OVERLAPPED) * num_bufs); + + HANDLE* h = TORRENT_ALLOCA(HANDLE, num_bufs); + + for (int i = 0; i < num_bufs; ++i) + { + ol[i].OffsetHigh = file_offset >> 32; + ol[i].Offset = file_offset & 0xffffffff; + ol[i].hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + h[i] = ol[i].hEvent; + if (h[i] == NULL) + { + // we failed to create the event, roll-back and return an error + for (int j = 0; j < i; ++j) CloseHandle(h[i]); + return -1; + } + file_offset += bufs[i].iov_len; + } + + int ret = 0; + for (int i = 0; i < num_bufs; ++i) + { + DWORD num_written; + if (WriteFile(fd, bufs[i].iov_base, bufs[i].iov_len, &num_written, &ol[i]) == FALSE + && GetLastError() != ERROR_IO_PENDING +#ifdef ERROR_CANT_WAIT + && GetLastError() != ERROR_CANT_WAIT +#endif + ) + { + ret = -1; + goto done; + } + } + + if (wait_for_multiple_objects(num_bufs, h) == WAIT_FAILED) + { + ret = -1; + goto done; + } + + for (int i = 0; i < num_bufs; ++i) + { + if (WaitForSingleObject(ol[i].hEvent, INFINITE) == WAIT_FAILED) + { + ret = -1; + break; + } + DWORD num_written; + if (GetOverlappedResult(fd, &ol[i], &num_written, FALSE) == FALSE) + { +#ifdef ERROR_CANT_WAIT + TORRENT_ASSERT(GetLastError() != ERROR_CANT_WAIT); +#endif + ret = -1; + break; + } + ret += num_written; + } +done: + + for (int i = 0; i < num_bufs; ++i) + CloseHandle(h[i]); + + return ret; + } +} +# else +# undef _BSD_SOURCE +# define _BSD_SOURCE // deprecated since glibc 2.20 +# undef _DEFAULT_SOURCE +# define _DEFAULT_SOURCE +# include +# endif +#endif + +#ifdef TORRENT_DEBUG +BOOST_STATIC_ASSERT((libtorrent::file::rw_mask & libtorrent::file::sparse) == 0); +BOOST_STATIC_ASSERT((libtorrent::file::rw_mask & libtorrent::file::attribute_mask) == 0); +BOOST_STATIC_ASSERT((libtorrent::file::sparse & libtorrent::file::attribute_mask) == 0); +#endif + +#if defined TORRENT_WINDOWS && defined UNICODE && !TORRENT_USE_WSTRING + +#ifdef _MSC_VER +#pragma message ( "wide character support not available. Files will be saved using narrow string names" ) +#else +#warning "wide character support not available. Files will be saved using narrow string names" +#endif + +#endif // TORRENT_WINDOWS + +namespace libtorrent +{ + int bufs_size(file::iovec_t const* bufs, int num_bufs) + { + std::size_t size = 0; + for (file::iovec_t const* i = bufs, *end(bufs + num_bufs); i < end; ++i) + size += i->iov_len; + return int(size); + } + +#ifdef TORRENT_WINDOWS + std::string convert_separators(std::string p) + { + for (int i = 0; i < int(p.size()); ++i) + if (p[i] == '/') p[i] = '\\'; + return p; + } + + time_t file_time_to_posix(FILETIME f) + { + const boost::uint64_t posix_time_offset = 11644473600LL; + boost::uint64_t ft = (boost::uint64_t(f.dwHighDateTime) << 32) + | f.dwLowDateTime; + + // windows filetime is specified in 100 nanoseconds resolution. + // convert to seconds + return time_t(ft / 10000000 - posix_time_offset); + } +#endif + + void stat_file(std::string const& inf, file_status* s + , error_code& ec, int flags) + { + ec.clear(); +#ifdef TORRENT_WINDOWS + + TORRENT_UNUSED(flags); + + std::string p = convert_separators(inf); +#if TORRENT_USE_UNC_PATHS + // UNC paths must be absolute + // network paths are already UNC paths + if (inf.substr(0,2) == "\\\\") p = inf; + else p = "\\\\?\\" + (is_complete(p) ? p : combine_path(current_working_directory(), p)); +#endif + +#if TORRENT_USE_WSTRING +#define CreateFile_ CreateFileW + std::wstring f = convert_to_wstring(p); +#else +#define CreateFile_ CreateFileA + std::string f = convert_to_native(p); +#endif + + // in order to open a directory, we need the FILE_FLAG_BACKUP_SEMANTICS + HANDLE h = CreateFile_(f.c_str(), 0, FILE_SHARE_DELETE | FILE_SHARE_READ + | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + + if (h == INVALID_HANDLE_VALUE) + { + ec.assign(GetLastError(), system_category()); + TORRENT_ASSERT(ec); + return; + } + + BY_HANDLE_FILE_INFORMATION data; + if (!GetFileInformationByHandle(h, &data)) + { + ec.assign(GetLastError(), system_category()); + TORRENT_ASSERT(ec); + CloseHandle(h); + return; + } + + s->file_size = (boost::uint64_t(data.nFileSizeHigh) << 32) | data.nFileSizeLow; + s->ctime = file_time_to_posix(data.ftCreationTime); + s->atime = file_time_to_posix(data.ftLastAccessTime); + s->mtime = file_time_to_posix(data.ftLastWriteTime); + + s->mode = (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + ? file_status::directory + : (data.dwFileAttributes & FILE_ATTRIBUTE_DEVICE) + ? file_status::character_special : file_status::regular_file; + CloseHandle(h); +#else + + // posix version + + std::string const& f = convert_to_native(inf); + + struct stat ret; + int retval; + if (flags & dont_follow_links) + retval = ::lstat(f.c_str(), &ret); + else + retval = ::stat(f.c_str(), &ret); + if (retval < 0) + { + ec.assign(errno, system_category()); + return; + } + + s->file_size = ret.st_size; + s->atime = ret.st_atime; + s->mtime = ret.st_mtime; + s->ctime = 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(); + +#if TORRENT_USE_WSTRING && defined TORRENT_WINDOWS + std::wstring f1 = convert_to_wstring(inf); + std::wstring f2 = convert_to_wstring(newf); + if (_wrename(f1.c_str(), f2.c_str()) < 0) +#else + std::string const& f1 = convert_to_native(inf); + std::string const& f2 = convert_to_native(newf); + if (::rename(f1.c_str(), f2.c_str()) < 0) +#endif + { + ec.assign(errno, generic_category()); + return; + } + } + + 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)) 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(); + +#ifdef TORRENT_WINDOWS +#if TORRENT_USE_WSTRING +#define CreateDirectory_ CreateDirectoryW + std::wstring n = convert_to_wstring(f); +#else +#define CreateDirectory_ CreateDirectoryA + std::string const& n = convert_to_native(f); +#endif // TORRENT_USE_WSTRING + + if (CreateDirectory_(n.c_str(), 0) == 0 + && GetLastError() != ERROR_ALREADY_EXISTS) + ec.assign(GetLastError(), system_category()); +#else + std::string n = convert_to_native(f); + int ret = mkdir(n.c_str(), 0777); + 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) + { +#ifdef TORRENT_WINDOWS + +#if TORRENT_USE_WSTRING +#define CreateHardLink_ CreateHardLinkW + std::wstring n_exist = convert_to_wstring(file); + std::wstring n_link = convert_to_wstring(link); +#else +#define CreateHardLink_ CreateHardLinkA + std::string n_exist = convert_to_native(file); + std::string n_link = convert_to_native(link); +#endif + BOOL ret = CreateHardLink_(n_link.c_str(), n_exist.c_str(), NULL); + if (ret) + { + ec.clear(); + return; + } + + // something failed. Does the filesystem not support hard links? + // TODO: 3 find out what error code is reported when the filesystem + // does not support hard links. + DWORD error = GetLastError(); + if (error != ERROR_NOT_SUPPORTED && error != ERROR_ACCESS_DENIED) + { + // it's possible CreateHardLink will copy the file internally too, + // if the filesystem does not support it. + ec.assign(GetLastError(), system_category()); + return; + } + + // fall back to making a copy + +#else + + std::string n_exist = convert_to_native(file); + std::string n_link = convert_to_native(link); + + // assume posix's link() function exists + int ret = ::link(n_exist.c_str(), n_link.c_str()); + + if (ret == 0) + { + ec.clear(); + return; + } + + // most errors are passed through, except for the ones that indicate that + // hard links are not supported and require a copy. + // TODO: 2 test this on a FAT volume to see what error we get! + if (errno != EMLINK || errno != EXDEV) + { + // some error happened, report up to the caller + ec.assign(errno, system_category()); + return; + } + + // fall back to making a copy + +#endif + + // if we get here, we should copy the file + copy_file(file, link, ec); + } + + bool is_directory(std::string const& f, error_code& ec) + { + ec.clear(); + error_code e; + file_status s; + stat_file(f, &s, e); + if (!e && s.mode & file_status::directory) return true; + ec = e; + return false; + } + + void recursive_copy(std::string const& old_path, std::string const& new_path, error_code& ec) + { + TORRENT_ASSERT(!ec); + if (is_directory(old_path, ec)) + { + create_directory(new_path, ec); + if (ec) return; + for (directory i(old_path, ec); !i.done(); i.next(ec)) + { + std::string f = i.file(); + if (f == ".." || f == ".") continue; + recursive_copy(combine_path(old_path, f), combine_path(new_path, f), ec); + if (ec) return; + } + } + else if (!ec) + { + copy_file(old_path, new_path, ec); + } + } + + void copy_file(std::string const& inf, std::string const& newf, error_code& ec) + { + ec.clear(); +#ifdef TORRENT_WINDOWS +#if TORRENT_USE_WSTRING +#define CopyFile_ CopyFileW + std::wstring f1 = convert_to_wstring(inf); + std::wstring f2 = convert_to_wstring(newf); +#else +#define CopyFile_ CopyFileA + std::string const& f1 = convert_to_native(inf); + std::string const& f2 = convert_to_native(newf); +#endif + + if (CopyFile_(f1.c_str(), f2.c_str(), false) == 0) + ec.assign(GetLastError(), system_category()); +#elif defined __APPLE__ && defined __MACH__ && MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 + std::string f1 = convert_to_native(inf); + std::string f2 = convert_to_native(newf); + + // this only works on 10.5 + copyfile_state_t state = copyfile_state_alloc(); + if (copyfile(f1.c_str(), f2.c_str(), state, COPYFILE_ALL) < 0) + ec.assign(errno, system_category()); + copyfile_state_free(state); +#else + std::string f1 = convert_to_native(inf); + std::string f2 = convert_to_native(newf); + + int infd = ::open(f1.c_str(), O_RDONLY); + if (infd < 0) + { + ec.assign(errno, system_category()); + return; + } + + // rely on default umask to filter x and w permissions + // for group and others + int permissions = S_IRUSR | S_IWUSR + | S_IRGRP | S_IWGRP + | S_IROTH | S_IWOTH; + + int outfd = ::open(f2.c_str(), O_WRONLY | O_CREAT, permissions); + if (outfd < 0) + { + close(infd); + ec.assign(errno, system_category()); + return; + } + char buffer[4096]; + for (;;) + { + int num_read = read(infd, buffer, sizeof(buffer)); + if (num_read == 0) break; + if (num_read < 0) + { + ec.assign(errno, system_category()); + break; + } + int num_written = write(outfd, buffer, num_read); + if (num_written < num_read) + { + ec.assign(errno, system_category()); + break; + } + if (num_read < int(sizeof(buffer))) break; + } + close(infd); + close(outfd); +#endif // TORRENT_WINDOWS + } + + void move_file(std::string const& inf, std::string const& newf, error_code& ec) + { + ec.clear(); + + file_status s; + stat_file(inf, &s, ec); + if (ec) return; + + if (has_parent_path(newf)) + { + create_directories(parent_path(newf), ec); + if (ec) return; + } + + rename(inf, newf, ec); + + // on OSX, the error when trying to rename a file across different + // volumes is EXDEV, which will make it fall back to copying. + + if (ec) + { + if (ec != boost::system::errc::no_such_file_or_directory + && ec != boost::system::errc::invalid_argument + && ec != boost::system::errc::permission_denied) + { + ec.clear(); + copy_file(inf, newf, ec); + + if (!ec) + { + // ignore errors when removing + error_code ignore; + remove(inf, ignore); + } + } + } + } + + std::string split_path(std::string const& f) + { + if (f.empty()) return f; + + std::string ret; + char const* start = f.c_str(); + char const* p = start; + while (*start != 0) + { + while (*p != '/' + && *p != '\0' +#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) + && *p != '\\' +#endif + ) ++p; + if (p - start > 0) + { + ret.append(start, p - start); + ret.append(1, '\0'); + } + if (*p != 0) ++p; + start = p; + } + ret.append(1, '\0'); + return ret; + } + + char const* next_path_element(char const* p) + { + p += strlen(p) + 1; + if (*p == 0) return 0; + return p; + } + + std::string extension(std::string const& f) + { + for (int i = f.size() - 1; i >= 0; --i) + { + if (f[i] == '/') break; +#ifdef TORRENT_WINDOWS + if (f[i] == '\\') break; +#endif + if (f[i] != '.') continue; + return f.substr(i); + } + return ""; + } + + std::string remove_extension(std::string const& f) + { + char const* slash = strrchr(f.c_str(), '/'); +#ifdef TORRENT_WINDOWS + slash = (std::max)((char const*)strrchr(f.c_str(), '\\'), slash); +#endif + char const* ext = strrchr(f.c_str(), '.'); + // if we don't have an extension, just return f + if (ext == 0 || ext == &f[0] || (slash != NULL && ext < slash)) return f; + return f.substr(0, ext - &f[0]); + } + + void replace_extension(std::string& f, std::string const& ext) + { + for (int i = f.size() - 1; i >= 0; --i) + { + if (f[i] == '/') break; +#ifdef TORRENT_WINDOWS + if (f[i] == '\\') break; +#endif + + if (f[i] != '.') continue; + + f.resize(i); + break; + } + f += '.'; + f += ext; + } + + 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 i = 2; i < int(f.size()) - 1; ++i) + { + if (f[i] != '\\' && f[i] != '/') 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 has_parent_path(std::string const& f) + { + if (f.empty()) return false; + if (is_root_path(f)) return false; + + int len = f.size() - 1; + // if the last character is / or \ ignore it + if (f[len] == '/' || f[len] == '\\') --len; + while (len >= 0) + { + if (f[len] == '/' || f[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 = 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); + } + + char const* filename_cstr(char const* f) + { + if (f == 0) return f; + + char const* sep = strrchr(f, '/'); +#ifdef TORRENT_WINDOWS + char const* altsep = strrchr(f, '\\'); + if (sep == 0 || altsep > sep) sep = altsep; +#endif + if (sep == 0) return f; + return sep+1; + } + + std::string filename(std::string const& 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 == 0) return f; + + if (sep - first == int(f.size()) - 1) + { + // if the last character is a / (or \) + // ignore it + int len = 0; + while (sep > first) + { + --sep; + if (*sep == '/' +#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) + || *sep == '\\' +#endif + ) + return std::string(sep + 1, len); + ++len; + } + return std::string(first, len); + + } + return std::string(sep + 1); + } + + void append_path(std::string& branch, std::string const& leaf) + { + append_path(branch, leaf.c_str(), leaf.size()); + } + + void append_path(std::string& branch + , char const* str, int len) + { + TORRENT_ASSERT(!is_complete(std::string(str, len))); + if (branch.empty() || branch == ".") + { + branch.assign(str, len); + return; + } + if (len == 0) return; + +#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) +#define TORRENT_SEPARATOR_CHAR '\\' + bool need_sep = branch[branch.size()-1] != '\\' + && branch[branch.size()-1] != '/'; +#else +#define TORRENT_SEPARATOR_CHAR '/' + bool need_sep = branch[branch.size()-1] != '/'; +#endif + + if (need_sep) branch += TORRENT_SEPARATOR_CHAR; + branch.append(str, len); + } + + std::string combine_path(std::string const& lhs, std::string const& rhs) + { + TORRENT_ASSERT(!is_complete(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 + std::string ret; + int target_size = lhs.size() + rhs.size() + 2; + ret.resize(target_size); + target_size = snprintf(&ret[0], target_size, "%s%s%s", lhs.c_str() + , (need_sep?TORRENT_SEPARATOR:""), rhs.c_str()); + ret.resize(target_size); + return ret; + } + + std::string current_working_directory() + { +#if defined TORRENT_WINDOWS && !defined TORRENT_MINGW +#if TORRENT_USE_WSTRING + wchar_t cwd[TORRENT_MAX_PATH]; + _wgetcwd(cwd, sizeof(cwd) / sizeof(wchar_t)); +#else + char cwd[TORRENT_MAX_PATH]; + _getcwd(cwd, sizeof(cwd)); +#endif // TORRENT_USE_WSTRING +#else + char cwd[TORRENT_MAX_PATH]; + if (getcwd(cwd, sizeof(cwd)) == 0) return "/"; +#endif +#if defined TORRENT_WINDOWS && !defined TORRENT_MINGW && TORRENT_USE_WSTRING + return convert_from_wstring(cwd); +#else + return convert_from_native(cwd); +#endif + } + +#if TORRENT_USE_UNC_PATHS + std::string canonicalize_path(std::string const& f) + { + std::string ret; + ret.resize(f.size()); + char* write_cur = &ret[0]; + char* last_write_sep = write_cur; + + char const* read_cur = f.c_str(); + char const* last_read_sep = read_cur; + + // the last_*_sep pointers point to one past + // the last path separator encountered and is + // initializes to the first character in the path + while (*read_cur) + { + if (*read_cur != '\\') + { + *write_cur++ = *read_cur++; + continue; + } + int element_len = read_cur - last_read_sep; + if (element_len == 1 && memcmp(last_read_sep, ".", 1) == 0) + { + --write_cur; + ++read_cur; + last_read_sep = read_cur; + continue; + } + if (element_len == 2 && 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 + + boost::int64_t file_size(std::string const& f) + { + error_code ec; + file_status s; + stat_file(f, &s, ec); + if (ec) return 0; + return s.file_size; + } + + bool exists(std::string const& f, error_code& ec) + { + file_status s; + stat_file(f, &s, ec); + if (ec) + { + if (ec == boost::system::errc::no_such_file_or_directory) + ec.clear(); + return false; + } + return true; + } + + bool exists(std::string const& f) + { + error_code ec; + return exists(f, ec); + } + + void remove(std::string const& inf, error_code& ec) + { + ec.clear(); + +#ifdef TORRENT_WINDOWS + // windows does not allow trailing / or \ in + // the path when removing files + std::string pruned; + if (inf[inf.size() - 1] == '/' + || inf[inf.size() - 1] == '\\') + pruned = inf.substr(0, inf.size() - 1); + else + pruned = inf; +#if TORRENT_USE_WSTRING +#define DeleteFile_ DeleteFileW +#define RemoveDirectory_ RemoveDirectoryW + std::wstring f = convert_to_wstring(pruned); +#else +#define DeleteFile_ DeleteFileA +#define RemoveDirectory_ RemoveDirectoryA + std::string f = convert_to_native(pruned); +#endif + if (DeleteFile_(f.c_str()) == 0) + { + if (GetLastError() == ERROR_ACCESS_DENIED) + { + if (RemoveDirectory_(f.c_str()) != 0) + return; + } + ec.assign(GetLastError(), system_category()); + return; + } +#else // TORRENT_WINDOWS + std::string const& f = convert_to_native(inf); + if (::remove(f.c_str()) < 0) + { + ec.assign(errno, system_category()); + return; + } +#endif // TORRENT_WINDOWS + } + + void remove_all(std::string const& f, error_code& ec) + { + ec.clear(); + + file_status s; + stat_file(f, &s, ec); + if (ec) return; + + if (s.mode & file_status::directory) + { + for (directory i(f, ec); !i.done(); i.next(ec)) + { + if (ec) return; + std::string p = i.file(); + if (p == "." || p == "..") continue; + remove_all(combine_path(f, p), ec); + if (ec) return; + } + } + remove(f, ec); + } + + std::string complete(std::string const& f) + { + if (is_complete(f)) return f; + if (f == ".") return current_working_directory(); + return combine_path(current_working_directory(), f); + } + + bool is_complete(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] && 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 + } + + directory::directory(std::string const& path, error_code& ec) + : m_done(false) + { + ec.clear(); +#ifdef TORRENT_WINDOWS + m_inode = 0; + // the path passed to FindFirstFile() must be + // a pattern + std::string f = convert_separators(path); + if (!f.empty() && f[f.size()-1] != '\\') f += "\\*"; + else f += "*"; +#if TORRENT_USE_WSTRING +#define FindFirstFile_ FindFirstFileW + std::wstring p = convert_to_wstring(f); +#else +#define FindFirstFile_ FindFirstFileA + std::string p = convert_to_native(f); +#endif + m_handle = FindFirstFile_(p.c_str(), &m_fd); + if (m_handle == INVALID_HANDLE_VALUE) + { + ec.assign(GetLastError(), system_category()); + m_done = true; + return; + } +#else + + memset(&m_dirent, 0, sizeof(dirent)); + m_name[0] = 0; + + // the path passed to opendir() may not + // end with a / + std::string p = path; + if (!path.empty() && path[path.size()-1] == '/') + p.resize(path.size()-1); + + p = convert_to_native(p); + m_handle = opendir(p.c_str()); + if (m_handle == 0) + { + ec.assign(errno, system_category()); + m_done = true; + return; + } + // read the first entry + next(ec); +#endif + } + + directory::~directory() + { +#ifdef TORRENT_WINDOWS + if (m_handle != INVALID_HANDLE_VALUE) + FindClose(m_handle); +#else + if (m_handle) closedir(m_handle); +#endif + } + + boost::uint64_t directory::inode() const + { +#ifdef TORRENT_WINDOWS + return m_inode; +#else + return m_dirent.d_ino; +#endif + } + + std::string directory::file() const + { +#ifdef TORRENT_WINDOWS +#if TORRENT_USE_WSTRING + return convert_from_wstring(m_fd.cFileName); +#else + return convert_from_native(m_fd.cFileName); +#endif +#else + return convert_from_native(m_dirent.d_name); +#endif + } + + void directory::next(error_code& ec) + { + ec.clear(); +#ifdef TORRENT_WINDOWS +#if TORRENT_USE_WSTRING +#define FindNextFile_ FindNextFileW +#else +#define FindNextFile_ FindNextFileA +#endif + if (FindNextFile_(m_handle, &m_fd) == 0) + { + m_done = true; + int err = GetLastError(); + if (err != ERROR_NO_MORE_FILES) + ec.assign(err, system_category()); + } + ++m_inode; +#else + dirent* dummy; + if (readdir_r(m_handle, &m_dirent, &dummy) != 0) + { + ec.assign(errno, system_category()); + m_done = true; + } + if (dummy == 0) m_done = true; +#endif + } + +#ifndef INVALID_HANDLE_VALUE +#define INVALID_HANDLE_VALUE -1 +#endif + +#ifdef TORRENT_WINDOWS + struct overlapped_t + { + overlapped_t() + { + memset(&ol, 0, sizeof(ol)); + ol.hEvent = CreateEvent(0, true, false, 0); + } + ~overlapped_t() + { + if (ol.hEvent != INVALID_HANDLE_VALUE) + CloseHandle(ol.hEvent); + } + int wait(HANDLE file, error_code& ec) + { + if (ol.hEvent != INVALID_HANDLE_VALUE + && WaitForSingleObject(ol.hEvent, INFINITE) == WAIT_FAILED) + { + ec.assign(GetLastError(), system_category()); + return -1; + } + + DWORD ret = -1; + if (GetOverlappedResult(file, &ol, &ret, false) == 0) + { + DWORD last_error = GetLastError(); + if (last_error != ERROR_HANDLE_EOF) + { +#ifdef ERROR_CANT_WAIT + TORRENT_ASSERT(last_error != ERROR_CANT_WAIT); +#endif + ec.assign(last_error, system_category()); + return -1; + } + } + return ret; + } + + OVERLAPPED ol; + }; +#endif // TORRENT_WINDOWS + + +#ifdef TORRENT_WINDOWS + bool get_manage_volume_privs(); + + // this needs to be run before CreateFile + bool file::has_manage_volume_privs = get_manage_volume_privs(); +#endif + + file::file() + : m_file_handle(INVALID_HANDLE_VALUE) + , m_open_mode(0) + { +#ifdef TORRENT_DISK_STATS + m_file_id = 0; +#endif + } + + file::file(std::string const& path, int mode, error_code& ec) + : m_file_handle(INVALID_HANDLE_VALUE) + , m_open_mode(0) + { +#ifdef TORRENT_DISK_STATS + m_file_id = 0; +#endif + // the return value is not important, since the + // error code contains the same information + open(path, mode, ec); + } + + file::~file() + { + close(); + } + +#ifdef TORRENT_DISK_STATS + namespace + { + boost::uint32_t silly_hash(std::string const& str) + { + boost::uint32_t ret = 1; + for (int i = 0; i < str.size(); ++i) + { + if (str[i] == 0) continue; + ret *= int(str[i]); + } + return ret; + } + } +#endif + + bool file::open(std::string const& path, int mode, error_code& ec) + { + close(); + +#ifdef TORRENT_DEBUG_FILE_LEAKS + m_file_path = path; +#endif + +#ifdef TORRENT_DISK_STATS + m_file_id = silly_hash(path); +#endif + +#ifdef TORRENT_WINDOWS + + struct open_mode_t + { + DWORD rw_mode; + DWORD create_mode; + }; + + static const open_mode_t mode_array[] = + { + // read_only + {GENERIC_READ, OPEN_EXISTING}, + // write_only + {GENERIC_WRITE, OPEN_ALWAYS}, + // read_write + {GENERIC_WRITE | GENERIC_READ, OPEN_ALWAYS}, + }; + + static const DWORD attrib_array[] = + { + FILE_ATTRIBUTE_NORMAL, // no attrib + FILE_ATTRIBUTE_HIDDEN, // hidden + FILE_ATTRIBUTE_NORMAL, // executable + FILE_ATTRIBUTE_HIDDEN, // hidden + executable + }; + + std::string p = convert_separators(path); +#if TORRENT_USE_UNC_PATHS + // UNC paths must be absolute + // network paths are already UNC paths + if (path.substr(0,2) == "\\\\") p = path; + else p = "\\\\?\\" + (is_complete(p) ? p : combine_path(current_working_directory(), p)); +#endif + +#if TORRENT_USE_WSTRING +#define CreateFile_ CreateFileW + std::wstring file_path = convert_to_wstring(p); +#else +#define CreateFile_ CreateFileA + std::string file_path = convert_to_native(p); +#endif + + TORRENT_ASSERT((mode & rw_mask) < sizeof(mode_array)/sizeof(mode_array[0])); + open_mode_t const& m = mode_array[mode & rw_mask]; + DWORD a = attrib_array[(mode & attribute_mask) >> 12]; + + // one might think it's a good idea to pass in FILE_FLAG_RANDOM_ACCESS. It + // turns out that it isn't. That flag will break your operating system: + // http://support.microsoft.com/kb/2549369 + + DWORD flags = ((mode & random_access) ? 0 : FILE_FLAG_SEQUENTIAL_SCAN) + | (a ? a : FILE_ATTRIBUTE_NORMAL) + | FILE_FLAG_OVERLAPPED + | ((mode & no_cache) ? FILE_FLAG_WRITE_THROUGH : 0); + + handle_type handle = CreateFile_(file_path.c_str(), m.rw_mode + , (mode & lock_file) ? FILE_SHARE_READ : FILE_SHARE_READ | FILE_SHARE_WRITE + , 0, m.create_mode, flags, 0); + + if (handle == INVALID_HANDLE_VALUE) + { + ec.assign(GetLastError(), system_category()); + TORRENT_ASSERT(ec); + return false; + } + + m_file_handle = handle; + + // try to make the file sparse if supported + // only set this flag if the file is opened for writing + if ((mode & file::sparse) && (mode & rw_mask) != read_only) + { + DWORD temp; + overlapped_t ol; + BOOL ret = ::DeviceIoControl(native_handle(), FSCTL_SET_SPARSE, 0, 0 + , 0, 0, &temp, &ol.ol); + error_code error; + if (ret == FALSE && GetLastError() == ERROR_IO_PENDING) + ol.wait(native_handle(), error); + } +#else // TORRENT_WINDOWS + + // rely on default umask to filter x and w permissions + // for group and others + int permissions = S_IRUSR | S_IWUSR + | S_IRGRP | S_IWGRP + | S_IROTH | S_IWOTH; + + if (mode & attribute_executable) + permissions |= S_IXGRP | S_IXOTH | S_IXUSR; +#ifdef O_BINARY + static const int mode_array[] = {O_RDONLY | O_BINARY, O_WRONLY | O_CREAT | O_BINARY, O_RDWR | O_CREAT | O_BINARY}; +#else + static const int mode_array[] = {O_RDONLY, O_WRONLY | O_CREAT, O_RDWR | O_CREAT}; +#endif + + int open_mode = 0 +#ifdef O_NOATIME + | ((mode & no_atime) ? O_NOATIME : 0) +#endif +#ifdef O_SYNC + | ((mode & no_cache) ? O_SYNC: 0) +#endif + ; + + handle_type handle = ::open(convert_to_native(path).c_str() + , mode_array[mode & rw_mask] | open_mode + , permissions); + +#ifdef O_NOATIME + // O_NOATIME is not allowed for files we don't own + // so, if we get EPERM when we try to open with it + // try again without O_NOATIME + if (handle == -1 && (mode & no_atime) && errno == EPERM) + { + mode &= ~no_atime; + open_mode &= ~O_NOATIME; + handle = ::open(path.c_str(), mode_array[mode & rw_mask] | open_mode + , permissions); + } +#endif + if (handle == -1) + { + ec.assign(errno, system_category()); + TORRENT_ASSERT(ec); + return false; + } + + m_file_handle = handle; + + // The purpose of the lock_file flag is primarily to prevent other + // processes from corrupting files that are being used by libtorrent. + // the posix file locking mechanism does not prevent others from + // accessing files, unless they also attempt to lock the file. That's + // why the SETLK mechanism is not used here. + +#ifdef DIRECTIO_ON + // for solaris + if (mode & no_cache) + { + int yes = 1; + directio(native_handle(), DIRECTIO_ON); + } +#endif + +#ifdef F_NOCACHE + // for BSD/Mac + if (mode & no_cache) + { + int yes = 1; + fcntl(native_handle(), F_NOCACHE, &yes); + +#ifdef F_NODIRECT + // it's OK to temporarily cache written pages + fcntl(native_handle(), F_NODIRECT, &yes); +#endif + } +#endif + +#ifdef POSIX_FADV_RANDOM + if (mode & random_access) + { + // disable read-ahead + posix_fadvise(native_handle(), 0, 0, POSIX_FADV_RANDOM); + } +#endif + +#endif + m_open_mode = mode; + + TORRENT_ASSERT(is_open()); + return true; + } + +#ifdef TORRENT_DEBUG_FILE_LEAKS + void file::print_info(FILE* out) const + { + if (!is_open()) return; + fprintf(out, "\n===> FILE: %s\n", m_file_path.c_str()); + } +#endif + + bool file::is_open() const + { + return m_file_handle != INVALID_HANDLE_VALUE; + } + +#ifdef TORRENT_WINDOWS + // returns true if the given file has any regions that are + // sparse, i.e. not allocated. + bool is_sparse(HANDLE file) + { + LARGE_INTEGER file_size; + if (!GetFileSizeEx(file, &file_size)) + return false; + + overlapped_t ol; + if (ol.ol.hEvent == NULL) return false; + +#ifdef TORRENT_MINGW +typedef struct _FILE_ALLOCATED_RANGE_BUFFER { + LARGE_INTEGER FileOffset; + LARGE_INTEGER Length; +} FILE_ALLOCATED_RANGE_BUFFER, *PFILE_ALLOCATED_RANGE_BUFFER; +#define FSCTL_QUERY_ALLOCATED_RANGES ((0x9 << 16) | (1 << 14) | (51 << 2) | 3) +#endif + FILE_ALLOCATED_RANGE_BUFFER in; + in.FileOffset.QuadPart = 0; + in.Length.QuadPart = file_size.QuadPart; + + FILE_ALLOCATED_RANGE_BUFFER out[2]; + + DWORD returned_bytes = 0; + BOOL ret = DeviceIoControl(file, FSCTL_QUERY_ALLOCATED_RANGES, (void*)&in, sizeof(in) + , out, sizeof(out), &returned_bytes, &ol.ol); + + if (ret == FALSE && GetLastError() == ERROR_IO_PENDING) + { + error_code ec; + returned_bytes = ol.wait(file, ec); + if (ec) return true; + } + else if (ret == FALSE) + { +// int error = GetLastError(); + return true; + } + + // if we have more than one range in the file, we're sparse + if (returned_bytes != sizeof(FILE_ALLOCATED_RANGE_BUFFER)) { + return true; + } + + return (in.Length.QuadPart != out[0].Length.QuadPart); + } +#endif + + void file::close() + { +#ifdef TORRENT_DISK_STATS + m_file_id = 0; +#endif + + if (!is_open()) return; + +#ifdef TORRENT_WINDOWS + + // if this file is open for writing, has the sparse + // flag set, but there are no sparse regions, unset + // the flag + int rw_mode = m_open_mode & rw_mask; + if ((rw_mode != read_only) + && (m_open_mode & sparse) + && !is_sparse(native_handle())) + { + overlapped_t ol; + // according to MSDN, clearing the sparse flag of a file only + // works on windows vista and later +#ifdef TORRENT_MINGW + typedef struct _FILE_SET_SPARSE_BUFFER { + BOOLEAN SetSparse; + } FILE_SET_SPARSE_BUFFER, *PFILE_SET_SPARSE_BUFFER; +#endif + DWORD temp; + FILE_SET_SPARSE_BUFFER b; + b.SetSparse = FALSE; + BOOL ret = ::DeviceIoControl(native_handle(), FSCTL_SET_SPARSE, &b, sizeof(b) + , 0, 0, &temp, &ol.ol); + error_code ec; + if (ret == FALSE && GetLastError() == ERROR_IO_PENDING) + { + ol.wait(native_handle(), ec); + } + } + + CloseHandle(native_handle()); +#else + if (m_file_handle != INVALID_HANDLE_VALUE) + ::close(m_file_handle); +#endif + + m_file_handle = INVALID_HANDLE_VALUE; + + m_open_mode = 0; + } + + namespace { + + void gather_copy(file::iovec_t const* bufs, int num_bufs, char* dst) + { + std::size_t offset = 0; + for (int i = 0; i < num_bufs; ++i) + { + memcpy(dst + offset, bufs[i].iov_base, bufs[i].iov_len); + offset += bufs[i].iov_len; + } + } + + void scatter_copy(file::iovec_t const* bufs, int num_bufs, char const* src) + { + std::size_t offset = 0; + for (int i = 0; i < num_bufs; ++i) + { + memcpy(bufs[i].iov_base, src + offset, bufs[i].iov_len); + offset += bufs[i].iov_len; + } + } + +#if !TORRENT_USE_PREADV + bool coalesce_read_buffers(file::iovec_t const*& bufs, int& num_bufs + , file::iovec_t* tmp) + { + int const buf_size = bufs_size(bufs, num_bufs); + char* buf = static_cast(malloc(buf_size)); + if (!buf) return false; + tmp->iov_base = buf; + tmp->iov_len = buf_size; + bufs = tmp; + num_bufs = 1; + return true; + } + + void coalesce_read_buffers_end(file::iovec_t const* bufs, int const num_bufs + , char* const buf, bool const copy) + { + if (copy) scatter_copy(bufs, num_bufs, buf); + free(buf); + } + + bool coalesce_write_buffers(file::iovec_t const*& bufs, int& num_bufs + , file::iovec_t* tmp) + { + int const buf_size = bufs_size(bufs, num_bufs); + char* buf = static_cast(malloc(buf_size)); + if (!buf) return false; + gather_copy(bufs, num_bufs, buf); + tmp->iov_base = buf; + tmp->iov_len = buf_size; + bufs = tmp; + num_bufs = 1; + return true; + } +#endif // TORRENT_USE_PREADV + + template + boost::int64_t iov(Fun f, handle_type fd, boost::int64_t file_offset, file::iovec_t const* bufs_in + , int num_bufs_in, error_code& ec) + { + file::iovec_t const* bufs = bufs_in; + int num_bufs = num_bufs_in; + +#if TORRENT_USE_PREADV + + int ret = 0; + while (num_bufs > 0) + { + int nbufs = (std::min)(num_bufs, TORRENT_IOV_MAX); + int tmp_ret = 0; + tmp_ret = f(fd, bufs, nbufs, file_offset); + if (tmp_ret < 0) + { +#ifdef TORRENT_WINDOWS + ec.assign(GetLastError(), system_category()); +#else + ec.assign(errno, system_category()); +#endif + return -1; + } + file_offset += tmp_ret; + ret += tmp_ret; + + // we got a short read/write. It's either 0, and we're at EOF, or we + // just need to issue the read/write operation again. In either case, + // punt that to the upper layer, as reissuing the operations is + // complicated here + const int expected_len = bufs_size(bufs, nbufs); + if (tmp_ret < expected_len) break; + + num_bufs -= nbufs; + bufs += nbufs; + } + return ret; + +#elif TORRENT_USE_PREAD + + int ret = 0; + for (file::iovec_t const* i = bufs, *end(bufs + num_bufs); i < end; ++i) + { + int tmp_ret = f(fd, i->iov_base, i->iov_len, 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->iov_len)) break; + } + + return ret; + +#else // not PREADV nor PREAD + + int ret = 0; + +#ifdef TORRENT_WINDOWS + if (SetFilePointerEx(fd, offs, &offs, FILE_BEGIN) == FALSE) + { + ec.assign(GetLastError(), system_category()); + return -1; + } +#else + if (lseek(fd, file_offset, SEEK_SET) < 0) + { + ec.assign(errno, system_category()); + return -1; + } +#endif + + for (file::iovec_t const* i = bufs, *end(bufs + num_bufs); i < end; ++i) + { + int tmp_ret = f(fd, i->iov_base, i->iov_len); + 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->iov_len)) break; + } + + return ret; + +#endif + } + + } // 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 + boost::int64_t file::readv(boost::int64_t file_offset, iovec_t const* bufs, int num_bufs + , error_code& ec, int flags) + { + if (m_file_handle == INVALID_HANDLE_VALUE) + { +#ifdef TORRENT_WINDOWS + ec = error_code(ERROR_INVALID_HANDLE, system_category()); +#else + ec = error_code(boost::system::errc::bad_file_descriptor, generic_category()); +#endif + return -1; + } + TORRENT_ASSERT((m_open_mode & rw_mask) == read_only || (m_open_mode & rw_mask) == read_write); + TORRENT_ASSERT(bufs); + TORRENT_ASSERT(num_bufs > 0); + TORRENT_ASSERT(is_open()); + +#if TORRENT_USE_PREADV + TORRENT_UNUSED(flags); + + int ret = iov(&::preadv, native_handle(), file_offset, bufs, num_bufs, ec); +#else + + // there's no point in coalescing single buffer writes + if (num_bufs == 1) + { + flags &= ~file::coalesce_buffers; + } + + file::iovec_t tmp; + file::iovec_t const* const orig_bufs = bufs; + int const orig_num_bufs = num_bufs; + if ((flags & file::coalesce_buffers)) + { + if (!coalesce_read_buffers(bufs, num_bufs, &tmp)) + // ok, that failed, don't coalesce this read + flags &= ~file::coalesce_buffers; + } + +#if TORRENT_USE_PREAD + int ret = iov(&::pread, native_handle(), file_offset, bufs, num_bufs, ec); +#else + int ret = iov(&::read, native_handle(), file_offset, bufs, num_bufs, ec); +#endif + + if ((flags & file::coalesce_buffers)) + coalesce_read_buffers_end(orig_bufs, orig_num_bufs + , static_cast(tmp.iov_base), !ec); + +#endif + return ret; + } + + // This has to be thread safe, i.e. atomic. + // that means, on posix this has to be turned into a series of + // pwrite() calls + boost::int64_t file::writev(boost::int64_t file_offset, iovec_t const* bufs, int num_bufs + , error_code& ec, int flags) + { + if (m_file_handle == INVALID_HANDLE_VALUE) + { +#ifdef TORRENT_WINDOWS + ec = error_code(ERROR_INVALID_HANDLE, system_category()); +#else + ec = error_code(boost::system::errc::bad_file_descriptor, generic_category()); +#endif + return -1; + } + TORRENT_ASSERT((m_open_mode & rw_mask) == write_only || (m_open_mode & rw_mask) == read_write); + TORRENT_ASSERT(bufs); + TORRENT_ASSERT(num_bufs > 0); + TORRENT_ASSERT(is_open()); + + ec.clear(); + +#if TORRENT_USE_PREADV + TORRENT_UNUSED(flags); + + int ret = iov(&::pwritev, native_handle(), file_offset, bufs, num_bufs, ec); +#else + + // there's no point in coalescing single buffer writes + if (num_bufs == 1) + { + flags &= ~file::coalesce_buffers; + } + + file::iovec_t tmp; + if (flags & file::coalesce_buffers) + { + if (!coalesce_write_buffers(bufs, num_bufs, &tmp)) + // ok, that failed, don't coalesce writes + flags &= ~file::coalesce_buffers; + } + +#if TORRENT_USE_PREAD + int ret = iov(&::pwrite, native_handle(), file_offset, bufs, num_bufs, ec); +#else + int ret = iov(&::write, native_handle(), file_offset, bufs, num_bufs, ec); +#endif + + if (flags & file::coalesce_buffers) + free(tmp.iov_base); + +#endif +#if TORRENT_USE_FDATASYNC \ + && !defined F_NOCACHE && \ + !defined DIRECTIO_ON + if (m_open_mode & no_cache) + { + if (fdatasync(native_handle()) != 0 + && errno != EINVAL + && errno != ENOSYS) + { + ec.assign(errno, system_category()); + } + } +#endif + return ret; + } + +#ifdef TORRENT_WINDOWS + bool get_manage_volume_privs() + { + typedef BOOL (WINAPI *OpenProcessToken_t)( + HANDLE ProcessHandle, + DWORD DesiredAccess, + PHANDLE TokenHandle); + + typedef BOOL (WINAPI *LookupPrivilegeValue_t)( + LPCSTR lpSystemName, + LPCSTR lpName, + PLUID lpLuid); + + typedef BOOL (WINAPI *AdjustTokenPrivileges_t)( + HANDLE TokenHandle, + BOOL DisableAllPrivileges, + PTOKEN_PRIVILEGES NewState, + DWORD BufferLength, + PTOKEN_PRIVILEGES PreviousState, + PDWORD ReturnLength); + + static OpenProcessToken_t pOpenProcessToken = NULL; + static LookupPrivilegeValue_t pLookupPrivilegeValue = NULL; + static AdjustTokenPrivileges_t pAdjustTokenPrivileges = NULL; + static bool failed_advapi = false; + + if (pOpenProcessToken == NULL && !failed_advapi) + { + HMODULE advapi = LoadLibraryA("advapi32"); + if (advapi == NULL) + { + failed_advapi = true; + return false; + } + pOpenProcessToken = (OpenProcessToken_t)GetProcAddress(advapi, "OpenProcessToken"); + pLookupPrivilegeValue = (LookupPrivilegeValue_t)GetProcAddress(advapi, "LookupPrivilegeValueA"); + pAdjustTokenPrivileges = (AdjustTokenPrivileges_t)GetProcAddress(advapi, "AdjustTokenPrivileges"); + if (pOpenProcessToken == NULL + || pLookupPrivilegeValue == NULL + || pAdjustTokenPrivileges == NULL) + { + failed_advapi = true; + return false; + } + } + + HANDLE token; + if (!pOpenProcessToken(GetCurrentProcess() + , TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token)) + return false; + + TOKEN_PRIVILEGES privs; + if (!pLookupPrivilegeValue(NULL, "SeManageVolumePrivilege" + , &privs.Privileges[0].Luid)) + { + CloseHandle(token); + return false; + } + + privs.PrivilegeCount = 1; + privs.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + + bool ret = pAdjustTokenPrivileges(token, FALSE, &privs, 0, NULL, NULL) + && GetLastError() == ERROR_SUCCESS; + + CloseHandle(token); + + return ret; + } + + void set_file_valid_data(HANDLE f, boost::int64_t size) + { + typedef BOOL (WINAPI *SetFileValidData_t)(HANDLE, LONGLONG); + static SetFileValidData_t pSetFileValidData = NULL; + static bool failed_kernel32 = false; + + if (pSetFileValidData == NULL && !failed_kernel32) + { + HMODULE k32 = LoadLibraryA("kernel32"); + if (k32 == NULL) + { + failed_kernel32 = true; + return; + } + pSetFileValidData = (SetFileValidData_t)GetProcAddress(k32, "SetFileValidData"); + if (pSetFileValidData == NULL) + { + failed_kernel32 = true; + return; + } + } + + TORRENT_ASSERT(pSetFileValidData); + + // we don't necessarily expect to have enough + // privilege to do this, so ignore errors. + pSetFileValidData(f, size); + } +#endif + + bool file::set_size(boost::int64_t s, error_code& ec) + { + TORRENT_ASSERT(is_open()); + TORRENT_ASSERT(s >= 0); + +#ifdef TORRENT_WINDOWS + + LARGE_INTEGER offs; + LARGE_INTEGER cur_size; + if (GetFileSizeEx(native_handle(), &cur_size) == FALSE) + { + ec.assign(GetLastError(), system_category()); + return false; + } + offs.QuadPart = s; + // only set the file size if it's not already at + // the right size. We don't want to update the + // modification time if we don't have to + if (cur_size.QuadPart != s) + { + if (SetFilePointerEx(native_handle(), offs, &offs, FILE_BEGIN) == FALSE) + { + ec.assign(GetLastError(), system_category()); + return false; + } + if (::SetEndOfFile(native_handle()) == FALSE) + { + ec.assign(GetLastError(), system_category()); + return false; + } + } + +#if _WIN32_WINNT >= 0x0600 // only if Windows Vista or newer + if ((m_open_mode & sparse) == 0) + { + typedef DWORD (WINAPI *GetFileInformationByHandleEx_t)(HANDLE hFile + , FILE_INFO_BY_HANDLE_CLASS FileInformationClass + , LPVOID lpFileInformation + , DWORD dwBufferSize); + + static GetFileInformationByHandleEx_t GetFileInformationByHandleEx_ = NULL; + + static bool failed_kernel32 = false; + + if ((GetFileInformationByHandleEx_ == NULL) && !failed_kernel32) + { + HMODULE kernel32 = LoadLibraryA("kernel32.dll"); + if (kernel32) + { + GetFileInformationByHandleEx_ = (GetFileInformationByHandleEx_t)GetProcAddress(kernel32, "GetFileInformationByHandleEx"); + } + else + { + failed_kernel32 = true; + } + } + + offs.QuadPart = 0; + if (GetFileInformationByHandleEx_) + { + // only allocate the space if the file + // is not fully allocated + FILE_STANDARD_INFO inf; + if (GetFileInformationByHandleEx_(native_handle() + , FileStandardInfo, &inf, sizeof(inf)) == FALSE) + { + ec.assign(GetLastError(), system_category()); + if (ec) return false; + } + offs = inf.AllocationSize; + } + + if (offs.QuadPart != s) + { + // if the user has permissions, avoid filling + // the file with zeroes, but just fill it with + // garbage instead + set_file_valid_data(m_file_handle, s); + } + } +#endif // if Windows Vista +#else // NON-WINDOWS + struct stat st; + if (fstat(native_handle(), &st) != 0) + { + ec.assign(errno, system_category()); + return false; + } + + // only truncate the file if it doesn't already + // have the right size. We don't want to update + if (st.st_size != s && ftruncate(native_handle(), s) < 0) + { + ec.assign(errno, system_category()); + return false; + } + + // if we're not in sparse mode, allocate the storage + // but only if the number of allocated blocks for the file + // is less than the file size. Otherwise we would just + // update the modification time of the file for no good + // reason. + if ((m_open_mode & sparse) == 0 + && st.st_blocks < (s + st.st_blksize - 1) / st.st_blksize) + { + // How do we know that the file is already allocated? + // if we always try to allocate the space, we'll update + // the modification time without actually changing the file + // but if we don't do anything if the file size is +#ifdef F_PREALLOCATE + fstore_t f = {F_ALLOCATECONTIG, F_PEOFPOSMODE, 0, s, 0}; + if (fcntl(native_handle(), F_PREALLOCATE, &f) < 0) + { + if (errno != ENOSPC) + { + ec.assign(errno, system_category()); + return false; + } + // ok, let's try to allocate non contiguous space then + f.fst_flags = F_ALLOCATEALL; + if (fcntl(native_handle(), F_PREALLOCATE, &f) < 0) + { + ec.assign(errno, system_category()); + return false; + } + } +#endif // F_PREALLOCATE + +#ifdef F_ALLOCSP64 + flock64 fl64; + fl64.l_whence = SEEK_SET; + fl64.l_start = 0; + fl64.l_len = s; + if (fcntl(native_handle(), F_ALLOCSP64, &fl64) < 0) + { + ec.assign(errno, system_category()); + return false; + } + +#endif // F_ALLOCSP64 + +#if defined TORRENT_LINUX || TORRENT_HAS_FALLOCATE + int ret; +#endif + +#if TORRENT_HAS_FALLOCATE + // if fallocate failed, we have to use posix_fallocate + // which can be painfully slow + // if you get a compile error here, you might want to + // define TORRENT_HAS_FALLOCATE to 0. + ret = posix_fallocate(native_handle(), 0, s); + // posix_allocate fails with EINVAL in case the underlying + // filesystem does not support this operation + if (ret != 0 && ret != EINVAL) + { + ec.assign(ret, system_category()); + return false; + } +#endif // TORRENT_HAS_FALLOCATE + } +#endif // TORRENT_WINDOWS + return true; + } + + boost::int64_t file::get_size(error_code& ec) const + { +#ifdef TORRENT_WINDOWS + LARGE_INTEGER file_size; + if (!GetFileSizeEx(native_handle(), &file_size)) + { + ec.assign(GetLastError(), system_category()); + return -1; + } + return file_size.QuadPart; +#else + struct stat fs; + if (fstat(native_handle(), &fs) != 0) + { + ec.assign(errno, system_category()); + return -1; + } + return fs.st_size; +#endif + } + + boost::int64_t file::sparse_end(boost::int64_t start) const + { +#ifdef TORRENT_WINDOWS + +#ifdef TORRENT_MINGW +typedef struct _FILE_ALLOCATED_RANGE_BUFFER { + LARGE_INTEGER FileOffset; + LARGE_INTEGER Length; +} FILE_ALLOCATED_RANGE_BUFFER, *PFILE_ALLOCATED_RANGE_BUFFER; +#define FSCTL_QUERY_ALLOCATED_RANGES ((0x9 << 16) | (1 << 14) | (51 << 2) | 3) +#endif // TORRENT_MINGW + + FILE_ALLOCATED_RANGE_BUFFER buffer; + DWORD bytes_returned = 0; + FILE_ALLOCATED_RANGE_BUFFER in; + error_code ec; + boost::int64_t file_size = get_size(ec); + if (ec) return start; + + in.FileOffset.QuadPart = start; + in.Length.QuadPart = file_size - start; + + if (!DeviceIoControl(native_handle(), FSCTL_QUERY_ALLOCATED_RANGES + , &in, sizeof(FILE_ALLOCATED_RANGE_BUFFER) + , &buffer, sizeof(FILE_ALLOCATED_RANGE_BUFFER), &bytes_returned, 0)) + { + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) return start; + } + + // if there are no allocated regions within the rest + // of the file, return the end of the file + if (bytes_returned == 0) return file_size; + + // assume that this range overlaps the start of the + // region we were interested in, and that start actually + // resides in an allocated region. + if (buffer.FileOffset.QuadPart < start) return start; + + // return the offset to the next allocated region + return buffer.FileOffset.QuadPart; + +#elif defined SEEK_DATA + // this is supported on solaris + boost::int64_t ret = lseek(native_handle(), start, SEEK_DATA); + if (ret < 0) return start; + return start; +#else + return start; +#endif + } + +#ifdef TORRENT_DEBUG_FILE_LEAKS + std::set global_file_handles; + mutex file_handle_mutex; + + file_handle::file_handle() + { + mutex::scoped_lock l(file_handle_mutex); + global_file_handles.insert(this); + stack[0] = 0; + } + file_handle::file_handle(file* f): m_file(f) + { + mutex::scoped_lock l(file_handle_mutex); + global_file_handles.insert(this); + if (f) print_backtrace(stack, sizeof(stack), 10); + else stack[0] = 0; + } + file_handle::file_handle(file_handle const& fh) + { + mutex::scoped_lock l(file_handle_mutex); + global_file_handles.insert(this); + m_file = fh.m_file; + if (m_file) print_backtrace(stack, sizeof(stack), 10); + else stack[0] = 0; + } + file_handle::~file_handle() + { + mutex::scoped_lock l(file_handle_mutex); + global_file_handles.erase(this); + stack[0] = 0; + } + file* file_handle::operator->() { return m_file.get(); } + file const* file_handle::operator->() const { return m_file.get(); } + file& file_handle::operator*() { return *m_file.get(); } + file const& file_handle::operator*() const { return *m_file.get(); } + file* file_handle::get() { return m_file.get(); } + file const* file_handle::get() const { return m_file.get(); } + file_handle::operator bool() const { return m_file.get(); } + file_handle& file_handle::reset(file* f) + { + mutex::scoped_lock l(file_handle_mutex); + if (f) print_backtrace(stack, sizeof(stack), 10); + else stack[0] = 0; + l.unlock(); + m_file.reset(f); + return *this; + } + + void print_open_files(char const* event, char const* name) + { + FILE* out = fopen("open_files.log", "a+"); + mutex::scoped_lock l(file_handle_mutex); + fprintf(out, "\n\nEVENT: %s TORRENT: %s\n\n", event, name); + for (std::set::iterator i = global_file_handles.begin() + , end(global_file_handles.end()); i != end; ++i) + { + TORRENT_ASSERT(*i != NULL); + if (!*i) continue; + file_handle const& h = **i; + if (!h) continue; + + if (!h->is_open()) continue; + h->print_info(out); + fprintf(out, "\n%s\n\n", h.stack); + } + fclose(out); + } +#endif +} + diff --git a/src/file_pool.cpp b/src/file_pool.cpp new file mode 100644 index 0000000..6035b6d --- /dev/null +++ b/src/file_pool.cpp @@ -0,0 +1,349 @@ +/* + +Copyright (c) 2006-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/assert.hpp" +#include "libtorrent/file_pool.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/file_storage.hpp" // for file_entry +#include "libtorrent/aux_/time.hpp" + +namespace libtorrent +{ + file_pool::file_pool(int size) + : m_size(size) + , m_low_prio_io(true) + { + } + + file_pool::~file_pool() + { + } + +#ifdef TORRENT_WINDOWS + void set_low_priority(file_handle const& f) + { + // file prio is only supported on vista and up + // so load the functions dynamically + typedef enum _FILE_INFO_BY_HANDLE_CLASS { + FileBasicInfo, + FileStandardInfo, + FileNameInfo, + FileRenameInfo, + FileDispositionInfo, + FileAllocationInfo, + FileEndOfFileInfo, + FileStreamInfo, + FileCompressionInfo, + FileAttributeTagInfo, + FileIdBothDirectoryInfo, + FileIdBothDirectoryRestartInfo, + FileIoPriorityHintInfo, + FileRemoteProtocolInfo, + MaximumFileInfoByHandleClass + } FILE_INFO_BY_HANDLE_CLASS, *PFILE_INFO_BY_HANDLE_CLASS; + + typedef enum _PRIORITY_HINT { + IoPriorityHintVeryLow = 0, + IoPriorityHintLow, + IoPriorityHintNormal, + MaximumIoPriorityHintType + } PRIORITY_HINT; + + typedef struct _FILE_IO_PRIORITY_HINT_INFO { + PRIORITY_HINT PriorityHint; + } FILE_IO_PRIORITY_HINT_INFO, *PFILE_IO_PRIORITY_HINT_INFO; + + typedef BOOL (WINAPI *SetFileInformationByHandle_t)(HANDLE hFile, FILE_INFO_BY_HANDLE_CLASS FileInformationClass, LPVOID lpFileInformation, DWORD dwBufferSize); + static SetFileInformationByHandle_t SetFileInformationByHandle = NULL; + + static bool failed_kernel_load = false; + + if (failed_kernel_load) return; + + if (SetFileInformationByHandle == NULL) + { + HMODULE kernel32 = LoadLibraryA("kernel32.dll"); + if (kernel32 == NULL) + { + failed_kernel_load = true; + return; + } + + SetFileInformationByHandle = (SetFileInformationByHandle_t)GetProcAddress(kernel32, "SetFileInformationByHandle"); + if (SetFileInformationByHandle == NULL) + { + failed_kernel_load = true; + return; + } + } + + TORRENT_ASSERT(SetFileInformationByHandle); + + FILE_IO_PRIORITY_HINT_INFO io_hint; + io_hint.PriorityHint = IoPriorityHintLow; + SetFileInformationByHandle(f->native_handle(), + FileIoPriorityHintInfo, &io_hint, sizeof(io_hint)); + } +#endif // TORRENT_WINDOWS + + file_handle file_pool::open_file(void* st, std::string const& p + , int file_index, file_storage const& fs, int m, error_code& ec) + { + // potentially used to hold a reference to a file object that's + // about to be destructed. If we have such object we assign it to + // this member to be destructed after we release the mutex. On some + // operating systems (such as OSX) closing a file may take a long + // time. We don't want to hold the mutex for that. + file_handle defer_destruction; + + mutex::scoped_lock l(m_mutex); + +#if TORRENT_USE_ASSERTS + // we're not allowed to open a file + // from a deleted storage! + TORRENT_ASSERT(std::find(m_deleted_storages.begin(), m_deleted_storages.end() + , std::make_pair(fs.name(), static_cast(&fs))) + == m_deleted_storages.end()); +#endif + + TORRENT_ASSERT(st != 0); + TORRENT_ASSERT(is_complete(p)); + TORRENT_ASSERT((m & file::rw_mask) == file::read_only + || (m & file::rw_mask) == file::read_write); + file_set::iterator i = m_files.find(std::make_pair(st, file_index)); + if (i != m_files.end()) + { + lru_file_entry& e = i->second; + e.last_use = aux::time_now(); + + if (e.key != st && ((e.mode & file::rw_mask) != file::read_only + || (m & file::rw_mask) != file::read_only)) + { + // this means that another instance of the storage + // is using the exact same file. + ec = errors::file_collision; + return file_handle(); + } + + e.key = st; + // if we asked for a file in write mode, + // and the cached file is is not opened in + // write mode, re-open it + if ((((e.mode & file::rw_mask) != file::read_write) + && ((m & file::rw_mask) == file::read_write)) + || (e.mode & file::random_access) != (m & file::random_access)) + { + // close the file before we open it with + // the new read/write privileges, since windows may + // file opening a file twice. However, since there may + // be outstanding operations on it, we can't close the + // file, we can only delete our reference to it. + // if this is the only reference to the file, it will be closed + defer_destruction = e.file_ptr; + e.file_ptr = boost::make_shared(); + + std::string full_path = fs.file_path(file_index, p); + if (!e.file_ptr->open(full_path, m, ec)) + { + m_files.erase(i); + return file_handle(); + } +#ifdef TORRENT_WINDOWS + if (m_low_prio_io) + set_low_priority(e.file_ptr); +#endif + + TORRENT_ASSERT(e.file_ptr->is_open()); + e.mode = m; + } + return e.file_ptr; + } + + lru_file_entry e; + e.file_ptr = boost::make_shared(); + if (!e.file_ptr) + { + ec = error_code(boost::system::errc::not_enough_memory, generic_category()); + return e.file_ptr; + } + std::string full_path = fs.file_path(file_index, p); + if (!e.file_ptr->open(full_path, m, ec)) + return file_handle(); +#ifdef TORRENT_WINDOWS + if (m_low_prio_io) + set_low_priority(e.file_ptr); +#endif + e.mode = m; + e.key = st; + m_files.insert(std::make_pair(std::make_pair(st, file_index), e)); + TORRENT_ASSERT(e.file_ptr->is_open()); + + file_handle file_ptr = e.file_ptr; + + // the file is not in our cache + if (int(m_files.size()) >= m_size) + { + // the file cache is at its maximum size, close + // the least recently used (lru) file from it + remove_oldest(l); + } + return file_ptr; + } + + void file_pool::get_status(std::vector* files, void* st) const + { + mutex::scoped_lock l(m_mutex); + + file_set::const_iterator start = m_files.lower_bound(std::make_pair(st, 0)); + file_set::const_iterator end = m_files.upper_bound(std::make_pair(st, INT_MAX)); + + for (file_set::const_iterator i = start; i != end; ++i) + { + pool_file_status s; + s.file_index = i->first.second; + s.open_mode = i->second.mode; + s.last_use = i->second.last_use; + files->push_back(s); + } + } + + void file_pool::remove_oldest(mutex::scoped_lock& l) + { + file_set::iterator i = std::min_element(m_files.begin(), m_files.end() + , boost::bind(&lru_file_entry::last_use, boost::bind(&file_set::value_type::second, _1)) + < boost::bind(&lru_file_entry::last_use, boost::bind(&file_set::value_type::second, _2))); + if (i == m_files.end()) return; + + file_handle file_ptr = i->second.file_ptr; + m_files.erase(i); + + // closing a file may be long running operation (mac os x) + l.unlock(); + file_ptr.reset(); + l.lock(); + } + + void file_pool::release(void* st, int file_index) + { + mutex::scoped_lock l(m_mutex); + + file_set::iterator i = m_files.find(std::make_pair(st, file_index)); + if (i == m_files.end()) return; + + file_handle file_ptr = i->second.file_ptr; + m_files.erase(i); + + // closing a file may be long running operation (mac os x) + l.unlock(); + file_ptr.reset(); + } + + // closes files belonging to the specified + // storage. If 0 is passed, all files are closed + void file_pool::release(void* st) + { + mutex::scoped_lock l(m_mutex); + + if (st == 0) + { + file_set tmp; + tmp.swap(m_files); + l.unlock(); + return; + } + + std::vector to_close; + for (file_set::iterator i = m_files.begin(); + i != m_files.end();) + { + if (i->second.key == st) + { + to_close.push_back(i->second.file_ptr); + m_files.erase(i++); + } + else + ++i; + } + l.unlock(); + // the files are closed here + } + +#if TORRENT_USE_ASSERTS + void file_pool::mark_deleted(file_storage const& fs) + { + mutex::scoped_lock l(m_mutex); + m_deleted_storages.push_back(std::make_pair(fs.name() + , static_cast(&fs))); + if(m_deleted_storages.size() > 100) + m_deleted_storages.erase(m_deleted_storages.begin()); + } + + bool file_pool::assert_idle_files(void* st) const + { + mutex::scoped_lock l(m_mutex); + + for (file_set::const_iterator i = m_files.begin(); + i != m_files.end(); ++i) + { + if (i->second.key == st && !i->second.file_ptr.unique()) + return false; + } + return true; + } +#endif + + void file_pool::resize(int size) + { + mutex::scoped_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) + remove_oldest(l); + } + +} + diff --git a/src/file_progress.cpp b/src/file_progress.cpp new file mode 100644 index 0000000..e416d70 --- /dev/null +++ b/src/file_progress.cpp @@ -0,0 +1,171 @@ +/* + +Copyright (c) 2015-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/piece_picker.hpp" +#include "libtorrent/file_storage.hpp" +#include "libtorrent/alert_manager.hpp" +#include "libtorrent/aux_/file_progress.hpp" +#include "libtorrent/alert_types.hpp" + +namespace libtorrent { namespace aux +{ + + file_progress::file_progress() + { + } + + void file_progress::init(piece_picker const& picker, file_storage const& fs) + { + if (!m_file_progress.empty()) return; + + int num_pieces = fs.num_pieces(); + int num_files = fs.num_files(); + + m_file_progress.resize(num_files, 0); + std::fill(m_file_progress.begin(), m_file_progress.end(), 0); + + // initialize the progress of each file + + const int piece_size = fs.piece_length(); + boost::uint64_t off = 0; + boost::uint64_t total_size = fs.total_size(); + int file_index = 0; + for (int piece = 0; piece < num_pieces; ++piece, off += piece_size) + { + TORRENT_ASSERT(file_index < fs.num_files()); + boost::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.num_files()); + 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; + + int size = (std::min)(boost::uint64_t(piece_size), total_size - off); + TORRENT_ASSERT(size >= 0); + + while (size) + { + int const add = (std::min)(boost::int64_t(size), fs.file_size(file_index) - file_offset); + TORRENT_ASSERT(add >= 0); + m_file_progress[file_index] += add; + + TORRENT_ASSERT(m_file_progress[file_index] + <= fs.file_size(file_index)); + + size -= add; + TORRENT_ASSERT(size >= 0); + if (size > 0) + { + ++file_index; + TORRENT_ASSERT(file_index < fs.num_files()); + file_offset = 0; + } + } + } + } + + void file_progress::export_progress(std::vector &fp) + { + fp.resize(m_file_progress.size(), 0); + std::copy(m_file_progress.begin(), m_file_progress.end(), fp.begin()); + } + + void file_progress::clear() + { + std::vector().swap(m_file_progress); + } + + // update the file progress now that we just completed downloading piece + // 'index' + void file_progress::update(file_storage const& fs, int index + , alert_manager* alerts, torrent_handle const& h) + { + if (m_file_progress.empty()) + return; + + const int piece_size = fs.piece_length(); + boost::int64_t off = boost::int64_t(index) * piece_size; + int file_index = fs.file_index_at_offset(off); + int size = fs.piece_size(index); + for (; size > 0; ++file_index) + { + boost::int64_t file_offset = off - fs.file_offset(file_index); + TORRENT_ASSERT(file_index != fs.num_files()); + TORRENT_ASSERT(file_offset <= fs.file_size(file_index)); + int add = (std::min)(fs.file_size(file_index) + - file_offset, boost::int64_t(size)); + m_file_progress[file_index] += add; + + TORRENT_ASSERT(m_file_progress[file_index] + <= fs.file_size(file_index)); + + // TODO: it would be nice to not depend on alert_manager here + if (m_file_progress[file_index] >= fs.file_size(file_index) && alerts) + { + if (!fs.pad_file_at(file_index)) + { + if (alerts->should_post()) + { + // this file just completed, post alert + alerts->emplace_alert(h, file_index); + } + } + } + size -= add; + off += add; + TORRENT_ASSERT(size >= 0); + } + } + +#if TORRENT_USE_INVARIANT_CHECKS + void file_progress::check_invariant(file_storage const& fs) const + { + if (!m_file_progress.empty()) + { + for (std::vector::const_iterator i = m_file_progress.begin() + , end(m_file_progress.end()); i != end; ++i) + { + int index = i - m_file_progress.begin(); + TORRENT_ASSERT(*i <= fs.file_size(index)); + } + } + } +#endif +} } + + diff --git a/src/file_storage.cpp b/src/file_storage.cpp new file mode 100644 index 0000000..834a075 --- /dev/null +++ b/src/file_storage.cpp @@ -0,0 +1,1098 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/file_storage.hpp" +#include "libtorrent/string_util.hpp" // for allocate_string_copy +#include "libtorrent/file.hpp" +#include "libtorrent/utf8.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include +#include + +#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) +#define TORRENT_SEPARATOR '\\' +#define TORRENT_SEPARATOR_STR "\\" +#else +#define TORRENT_SEPARATOR '/' +#define TORRENT_SEPARATOR_STR "/" +#endif + +namespace libtorrent +{ + file_storage::file_storage() + : m_piece_length(0) + , m_num_pieces(0) + , m_total_size(0) + , m_num_files(0) + {} + + file_storage::~file_storage() {} + + // 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& f) + : m_piece_length(f.m_piece_length) + , m_num_pieces(f.m_num_pieces) + , m_files(f.m_files) + , m_file_hashes(f.m_file_hashes) + , m_symlinks(f.m_symlinks) + , m_mtime(f.m_mtime) +#ifndef TORRENT_NO_DEPRECATE + , m_file_base(f.m_file_base) +#endif + , m_paths(f.m_paths) + , m_name(f.m_name) + , m_total_size(f.m_total_size) + , m_num_files(f.m_num_files) + { + } + + file_storage& file_storage::operator=(file_storage const& f) + { + m_piece_length = f.m_piece_length; + m_num_pieces = f.m_num_pieces; + m_files = f.m_files; + m_file_hashes = f.m_file_hashes; + m_symlinks = f.m_symlinks; + m_mtime = f.m_mtime; +#ifndef TORRENT_NO_DEPRECATE + m_file_base = f.m_file_base; +#endif + m_paths = f.m_paths; + m_name = f.m_name; + m_total_size = f.m_total_size; + m_num_files = f.m_num_files; + return *this; + } + + void file_storage::reserve(int num_files) + { + m_files.reserve(num_files); + } + + int file_storage::piece_size(int index) const + { + TORRENT_ASSERT_PRECOND(index >= 0 && index < num_pieces()); + if (index == num_pieces()-1) + { + boost::int64_t size_except_last = num_pieces() - 1; + size_except_last *= boost::int64_t(piece_length()); + boost::int64_t size = total_size() - size_except_last; + TORRENT_ASSERT(size > 0); + TORRENT_ASSERT(size <= piece_length()); + return int(size); + } + else + return piece_length(); + } + + namespace + { + bool compare_string(char const* str, int len, std::string const& str2) + { + if (str2.size() != len) return false; + return memcmp(str2.c_str(), str, len) == 0; + } + + bool compare_file_entry_size(internal_file_entry const& fe1 + , internal_file_entry const& fe2) + { + return fe1.size < fe2.size; + } + + bool compare_file_offset(internal_file_entry const& lhs + , internal_file_entry const& rhs) + { + return lhs.offset < rhs.offset; + } + } + + // path is not supposed to include the name of the torrent itself. + void file_storage::update_path_index(internal_file_entry& e + , std::string const& path, bool set_name) + { + if (is_complete(path)) + { + TORRENT_ASSERT(set_name); + e.set_name(path.c_str()); + e.path_index = -2; + return; + } + + TORRENT_ASSERT(path[0] != '/'); + + // sorry about this messy string handling, but I did + // profile it, and it was expensive + char const* leaf = filename_cstr(path.c_str()); + char const* branch_path = ""; + int branch_len = 0; + if (leaf > path.c_str()) + { + // split the string into the leaf filename + // and the branch path + branch_path = path.c_str(); + branch_len = leaf - path.c_str(); + } + if (branch_len <= 0) + { + if (set_name) e.set_name(leaf); + e.path_index = -1; + return; + } + + if (branch_len >= m_name.size() + && std::memcmp(branch_path, m_name.c_str(), m_name.size()) == 0) + { + // the +1 is to skip the trailing '/' (or '\') + int offset = m_name.size() + + (m_name.size() == branch_len?0:1); + branch_path += offset; + branch_len -= offset; + e.no_root_dir = false; + } + else + { + e.no_root_dir = true; + } + + // do we already have this path in the path list? + std::vector::reverse_iterator p + = std::find_if(m_paths.rbegin(), m_paths.rend() + , boost::bind(&compare_string, branch_path, branch_len, _1)); + + if (p == m_paths.rend()) + { + // no, we don't. add it + e.path_index = m_paths.size(); + TORRENT_ASSERT(branch_path[0] != '/'); + + // trim trailing slashes + if (branch_len > 0 && branch_path[branch_len-1] == TORRENT_SEPARATOR) + --branch_len; + + // poor man's emplace back + m_paths.resize(m_paths.size() + 1); + m_paths.back().assign(branch_path, branch_len); + } + else + { + // yes we do. use it + e.path_index = p.base() - m_paths.begin() - 1; + } + if (set_name) e.set_name(leaf); + } + +#ifndef TORRENT_NO_DEPRECATE + file_entry::file_entry(): offset(0), size(0), file_base(0) + , mtime(0), pad_file(false), hidden_attribute(false) + , executable_attribute(false) + , symlink_attribute(false) + {} + + file_entry::~file_entry() {} +#endif // TORRENT_NO_DEPRECATE + + internal_file_entry::~internal_file_entry() + { + if (name_len == name_is_owned) free(const_cast(name)); + } + + internal_file_entry::internal_file_entry(internal_file_entry const& fe) + : offset(fe.offset) + , symlink_index(fe.symlink_index) + , no_root_dir(fe.no_root_dir) + , size(fe.size) + , name_len(fe.name_len) + , pad_file(fe.pad_file) + , hidden_attribute(fe.hidden_attribute) + , executable_attribute(fe.executable_attribute) + , symlink_attribute(fe.symlink_attribute) + , name(0) + , path_index(fe.path_index) + { + if (fe.name_len == name_is_owned) + name = allocate_string_copy(fe.name); + else + name = fe.name; + } + + internal_file_entry& internal_file_entry::operator=(internal_file_entry const& fe) + { + 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; + set_name(fe.filename().c_str()); + return *this; + } + + // if borrow_chars >= 0, don't take ownership over n, just + // point to it. It points to borrow_chars number of characters. + // if borrow_chars == -1, n is a null terminated string that + // should be copied + void internal_file_entry::set_name(char const* n, bool borrow_string, int string_len) + { + TORRENT_ASSERT(string_len >= 0); + + // we have limited space in the length field. truncate string + // if it's too long + if (string_len >= name_is_owned) string_len = name_is_owned - 1; + + // free the current string, before assigning the new one + if (name_len == name_is_owned) free(const_cast(name)); + if (n == NULL) + { + TORRENT_ASSERT(borrow_string == false); + name = NULL; + } + else if (borrow_string) + { + name = n; + name_len = string_len; + } + else + { + name = allocate_string_copy(n); + name_len = name_is_owned; + } + } + + std::string internal_file_entry::filename() const + { + if (name_len != name_is_owned) return std::string(name, name_len); + return name ? name : ""; + } + + void file_storage::apply_pointer_offset(ptrdiff_t off) + { + for (int i = 0; i < m_files.size(); ++i) + { + if (m_files[i].name_len == internal_file_entry::name_is_owned) continue; + m_files[i].name += off; + } + + for (int i = 0; i < m_file_hashes.size(); ++i) + { + if (m_file_hashes[i] == NULL) continue; + m_file_hashes[i] += off; + } + } + +#ifndef TORRENT_NO_DEPRECATE + + void file_storage::add_file(file_entry const& fe, char const* filehash) + { + int flags = 0; + 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(NULL, 0, fe.path, fe.size, flags, filehash, fe.mtime + , fe.symlink_path); + } + +#if TORRENT_USE_WSTRING + void file_storage::set_name(std::wstring const& n) + { + std::string utf8; + wchar_utf8(n, utf8); + m_name = utf8; + } + + void file_storage::rename_file_deprecated(int index, std::wstring const& new_filename) + { + TORRENT_ASSERT_PRECOND(index >= 0 && index < int(m_files.size())); + std::string utf8; + wchar_utf8(new_filename, utf8); + update_path_index(m_files[index], utf8); + } + + void file_storage::add_file(std::wstring const& file, boost::int64_t file_size + , int file_flags, std::time_t mtime, std::string const& symlink_path) + { + std::string utf8; + wchar_utf8(file, utf8); + add_file(utf8, file_size, file_flags, mtime, symlink_path); + } + + void file_storage::rename_file(int index, std::wstring const& new_filename) + { + rename_file_deprecated(index, new_filename); + } +#endif // TORRENT_USE_WSTRING +#endif // TORRENT_NO_DEPRECATE + + void file_storage::rename_file(int index, std::string const& new_filename) + { + TORRENT_ASSERT_PRECOND(index >= 0 && index < int(m_files.size())); + update_path_index(m_files[index], new_filename); + } + +#ifndef TORRENT_NO_DEPRECATE + file_storage::iterator file_storage::file_at_offset_deprecated(boost::int64_t offset) const + { + // find the file iterator and file offset + internal_file_entry target; + target.offset = offset; + TORRENT_ASSERT(!compare_file_offset(target, m_files.front())); + + std::vector::const_iterator 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(boost::int64_t offset) const + { + return file_at_offset_deprecated(offset); + } +#endif + + int file_storage::file_index_at_offset(boost::int64_t offset) const + { + // find the file iterator and file offset + internal_file_entry target; + target.offset = offset; + TORRENT_ASSERT(!compare_file_offset(target, m_files.front())); + + std::vector::const_iterator 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_iter - m_files.begin(); + } + + char const* file_storage::file_name_ptr(int index) const + { + return m_files[index].name; + } + + int file_storage::file_name_len(int index) const + { + if (m_files[index].name_len == internal_file_entry::name_is_owned) + return -1; + return m_files[index].name_len; + } + + std::vector file_storage::map_block(int const piece + , boost::int64_t const offset + , int size) const + { + TORRENT_ASSERT_PRECOND(num_files() > 0); + std::vector ret; + + if (m_files.empty()) return ret; + + // find the file iterator and file offset + internal_file_entry target; + target.offset = piece * boost::int64_t(m_piece_length) + offset; + TORRENT_ASSERT_PRECOND(boost::int64_t(target.offset + size) <= m_total_size); + TORRENT_ASSERT(!compare_file_offset(target, m_files.front())); + + // in case the size is past the end, fix it up + if (boost::int64_t(target.offset + size) > m_total_size) + size = m_total_size - target.offset; + + std::vector::const_iterator file_iter = std::upper_bound( + m_files.begin(), m_files.end(), target, compare_file_offset); + + TORRENT_ASSERT(file_iter != m_files.begin()); + --file_iter; + + boost::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 < boost::int64_t(file_iter->size)) + { + file_slice f; + f.file_index = file_iter - m_files.begin(); + f.offset = file_offset +#ifndef TORRENT_NO_DEPRECATE + + file_base_deprecated(f.file_index) +#endif + ; + f.size = (std::min)(boost::uint64_t(file_iter->size) - file_offset, boost::uint64_t(size)); + TORRENT_ASSERT(f.size <= size); + size -= int(f.size); + file_offset += f.size; + ret.push_back(f); + } + + TORRENT_ASSERT(size >= 0); + } + return ret; + } + +#ifndef TORRENT_NO_DEPRECATE + file_entry file_storage::at(int index) const + { + return at_deprecated(index); + } + + file_entry file_storage::at_deprecated(int index) const + { + TORRENT_ASSERT_PRECOND(index >= 0 && index < int(m_files.size())); + file_entry ret; + internal_file_entry const& ife = m_files[index]; + ret.path = file_path(index); + ret.offset = ife.offset; + ret.size = ife.size; + ret.file_base = file_base(index); + ret.mtime = mtime(index); + ret.pad_file = ife.pad_file; + ret.hidden_attribute = ife.hidden_attribute; + ret.executable_attribute = ife.executable_attribute; + ret.symlink_attribute = ife.symlink_attribute; + if (ife.symlink_index != internal_file_entry::not_a_symlink) + ret.symlink_path = symlink(index); + ret.filehash = hash(index); + return ret; + } +#endif // TORRENT_NO_DEPRECATE + + peer_request file_storage::map_file(int file_index, boost::int64_t file_offset + , int size) const + { + TORRENT_ASSERT_PRECOND(file_index < num_files()); + TORRENT_ASSERT_PRECOND(file_index >= 0); + TORRENT_ASSERT(m_num_pieces >= 0); + + peer_request ret; + if (file_index < 0 || file_index >= num_files()) + { + ret.piece = m_num_pieces; + ret.start = 0; + ret.length = 0; + return ret; + } + + boost::int64_t offset = file_offset + this->file_offset(file_index); + + if (offset >= total_size()) + { + ret.piece = m_num_pieces; + ret.start = 0; + ret.length = 0; + } + else + { + ret.piece = int(offset / piece_length()); + ret.start = int(offset % piece_length()); + ret.length = size; + if (offset + size > total_size()) + ret.length = int(total_size() - offset); + } + return ret; + } + + void file_storage::add_file(std::string const& path, boost::int64_t file_size + , int file_flags, std::time_t mtime, std::string const& symlink_path) + { + add_file_borrow(NULL, 0, path, file_size, file_flags, NULL, mtime + , symlink_path); + } + + void file_storage::add_file_borrow(char const* filename, int const filename_len + , std::string const& path, boost::int64_t const file_size + , boost::uint32_t const file_flags, char const* filehash + , boost::int64_t const mtime, std::string const& symlink_path) + { + TORRENT_ASSERT_PRECOND(file_size >= 0); + 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 = split_path(path).c_str(); + } + + // this is poor-man's emplace_back() + m_files.resize(m_files.size() + 1); + internal_file_entry& e = m_files.back(); + + // the last argument specified whether the function should also set + // the filename. If it does, it will copy the leaf filename from path. + // if filename is NULL, 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 == NULL); + + // filename is allowed to be NULL, in which case we just use path + if (filename) + e.set_name(filename, true, filename_len); + + e.size = file_size; + e.offset = m_total_size; + e.pad_file = (file_flags & file_storage::flag_pad_file) != 0; + e.hidden_attribute = (file_flags & file_storage::flag_hidden) != 0; + e.executable_attribute = (file_flags & file_storage::flag_executable) != 0; + e.symlink_attribute = (file_flags & file_storage::flag_symlink) != 0; + + if (filehash) + { + if (m_file_hashes.size() < m_files.size()) m_file_hashes.resize(m_files.size()); + m_file_hashes[m_files.size() - 1] = filehash; + } + if (!symlink_path.empty() + && m_symlinks.size() < internal_file_entry::not_a_symlink - 1) + { + e.symlink_index = m_symlinks.size(); + m_symlinks.push_back(symlink_path); + } + else + { + e.symlink_attribute = false; + } + if (mtime) + { + if (m_mtime.size() < m_files.size()) m_mtime.resize(m_files.size()); + m_mtime[m_files.size() - 1] = mtime; + } + + ++m_num_files; + m_total_size += e.size; + } + + sha1_hash file_storage::hash(int index) const + { + if (index >= int(m_file_hashes.size())) return sha1_hash(0); + return sha1_hash(m_file_hashes[index]); + } + + std::string const& file_storage::symlink(int index) const + { + TORRENT_ASSERT_PRECOND(index >= 0 && index < int(m_files.size())); + internal_file_entry const& fe = m_files[index]; + TORRENT_ASSERT(fe.symlink_index < int(m_symlinks.size())); + return m_symlinks[fe.symlink_index]; + } + + time_t file_storage::mtime(int index) const + { + if (index >= int(m_mtime.size())) return 0; + return m_mtime[index]; + } + + namespace + { + template + void process_string_lowercase(CRC& crc, char const* str, int len) + { + for (int i = 0; i < len; ++i, ++str) + crc.process_byte(to_lower(*str)); + } + + template + void process_path_lowercase( + boost::unordered_set& table + , CRC crc + , char const* str, int len) + { + if (len == 0) return; + for (int i = 0; i < len; ++i, ++str) + { + if (*str == TORRENT_SEPARATOR) + table.insert(crc.checksum()); + crc.process_byte(to_lower(*str)); + } + table.insert(crc.checksum()); + } + } + + void file_storage::all_path_hashes( + boost::unordered_set& table) const + { + boost::crc_optimal<32, 0x1EDC6F41, 0xFFFFFFFF, 0xFFFFFFFF, true, true> crc; + + if (!m_name.empty()) + { + process_string_lowercase(crc, m_name.c_str(), m_name.size()); + TORRENT_ASSERT(m_name[m_name.size()-1] != TORRENT_SEPARATOR); + crc.process_byte(TORRENT_SEPARATOR); + } + + for (int i = 0; i != int(m_paths.size()); ++i) + { + std::string const& p = m_paths[i]; + process_path_lowercase(table, crc, p.c_str(), p.size()); + } + } + + boost::uint32_t file_storage::file_path_hash(int index + , std::string const& save_path) const + { + TORRENT_ASSERT_PRECOND(index >= 0 && index < int(m_files.size())); + internal_file_entry const& fe = m_files[index]; + + boost::crc_optimal<32, 0x1EDC6F41, 0xFFFFFFFF, 0xFFFFFFFF, true, true> crc; + + if (fe.path_index == -2) + { + // -2 means this is an absolute path filename + process_string_lowercase(crc, fe.filename_ptr(), fe.filename_len()); + } + else if (fe.path_index == -1) + { + // -1 means no path + if (!save_path.empty()) + { + process_string_lowercase(crc, save_path.c_str(), save_path.size()); + TORRENT_ASSERT(save_path[save_path.size()-1] != TORRENT_SEPARATOR); + crc.process_byte(TORRENT_SEPARATOR); + } + process_string_lowercase(crc, fe.filename_ptr(), fe.filename_len()); + } + else if (fe.no_root_dir) + { + if (!save_path.empty()) + { + process_string_lowercase(crc, save_path.c_str(), save_path.size()); + 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.c_str(), p.size()); + TORRENT_ASSERT(p[p.size()-1] != TORRENT_SEPARATOR); + crc.process_byte(TORRENT_SEPARATOR); + } + process_string_lowercase(crc, fe.filename_ptr(), fe.filename_len()); + } + else + { + if (!save_path.empty()) + { + process_string_lowercase(crc, save_path.c_str(), save_path.size()); + TORRENT_ASSERT(save_path[save_path.size()-1] != TORRENT_SEPARATOR); + crc.process_byte(TORRENT_SEPARATOR); + } + process_string_lowercase(crc, m_name.c_str(), m_name.size()); + 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.c_str(), p.size()); + TORRENT_ASSERT(p.size() > 0); + TORRENT_ASSERT(p[p.size()-1] != TORRENT_SEPARATOR); + crc.process_byte(TORRENT_SEPARATOR); + } + process_string_lowercase(crc, fe.filename_ptr(), fe.filename_len()); + } + + return crc.checksum(); + } + + std::string file_storage::file_path(int index, std::string const& save_path) const + { + TORRENT_ASSERT_PRECOND(index >= 0 && index < int(m_files.size())); + internal_file_entry const& fe = m_files[index]; + + std::string ret; + + // -2 means this is an absolute path filename + if (fe.path_index == -2) + { + ret.assign(fe.filename_ptr(), fe.filename_len()); + } + else if (fe.path_index == -1) + { + // -1 means no path + ret.reserve(save_path.size() + fe.filename_len() + 1); + ret.assign(save_path); + append_path(ret, fe.filename_ptr(), fe.filename_len()); + } + else if (fe.no_root_dir) + { + std::string const& p = m_paths[fe.path_index]; + + ret.reserve(save_path.size() + p.size() + fe.filename_len() + 2); + ret.assign(save_path); + append_path(ret, p); + append_path(ret, fe.filename_ptr(), fe.filename_len()); + } + else + { + std::string const& p = m_paths[fe.path_index]; + + ret.reserve(save_path.size() + m_name.size() + p.size() + fe.filename_len() + 3); + ret.assign(save_path); + append_path(ret, m_name); + append_path(ret, p); + append_path(ret, fe.filename_ptr(), fe.filename_len()); + } + + // a single return statement, just to make NRVO more likely to kick in + return ret; + } + + std::string file_storage::file_name(int index) const + { + TORRENT_ASSERT_PRECOND(index >= 0 && index < int(m_files.size())); + internal_file_entry const& fe = m_files[index]; + return fe.filename(); + } + + boost::int64_t file_storage::file_size(int index) const + { + TORRENT_ASSERT_PRECOND(index >= 0 && index < int(m_files.size())); + return m_files[index].size; + } + + bool file_storage::pad_file_at(int index) const + { + TORRENT_ASSERT_PRECOND(index >= 0 && index < int(m_files.size())); + return m_files[index].pad_file; + } + + boost::int64_t file_storage::file_offset(int index) const + { + TORRENT_ASSERT_PRECOND(index >= 0 && index < int(m_files.size())); + return m_files[index].offset; + } + + int file_storage::file_flags(int index) const + { + internal_file_entry const& fe = m_files[index]; + return (fe.pad_file ? flag_pad_file : 0) + | (fe.hidden_attribute ? flag_hidden : 0) + | (fe.executable_attribute ? flag_executable : 0) + | (fe.symlink_attribute ? flag_symlink : 0); + } + + bool file_storage::file_absolute_path(int index) const + { + internal_file_entry const& fe = m_files[index]; + return fe.path_index == -2; + } + +#ifndef TORRENT_NO_DEPRECATE + void file_storage::set_file_base(int index, boost::int64_t off) + { + TORRENT_ASSERT_PRECOND(index >= 0 && index < int(m_files.size())); + if (int(m_file_base.size()) <= index) m_file_base.resize(index + 1, 0); + m_file_base[index] = off; + } + + boost::int64_t file_storage::file_base_deprecated(int index) const + { + if (index >= int(m_file_base.size())) return 0; + return m_file_base[index]; + } + + boost::int64_t file_storage::file_base(int index) const + { + if (index >= int(m_file_base.size())) return 0; + return m_file_base[index]; + } + + sha1_hash file_storage::hash(internal_file_entry const& fe) const + { + int index = &fe - &m_files[0]; + if (index >= int(m_file_hashes.size())) return sha1_hash(0); + return sha1_hash(m_file_hashes[index]); + } + + std::string const& file_storage::symlink(internal_file_entry const& fe) const + { + TORRENT_ASSERT_PRECOND(fe.symlink_index < int(m_symlinks.size())); + return m_symlinks[fe.symlink_index]; + } + + time_t file_storage::mtime(internal_file_entry const& fe) const + { + int index = &fe - &m_files[0]; + if (index >= int(m_mtime.size())) return 0; + return m_mtime[index]; + } + + int file_storage::file_index(internal_file_entry const& fe) const + { + int index = &fe - &m_files[0]; + TORRENT_ASSERT_PRECOND(index >= 0 && index < int(m_files.size())); + return index; + } + + void file_storage::set_file_base(internal_file_entry const& fe, boost::int64_t off) + { + int index = &fe - &m_files[0]; + TORRENT_ASSERT_PRECOND(index >= 0 && index < int(m_files.size())); + if (int(m_file_base.size()) <= index) m_file_base.resize(index + 1, 0); + m_file_base[index] = off; + } + + boost::int64_t file_storage::file_base(internal_file_entry const& fe) const + { + int index = &fe - &m_files[0]; + if (index >= int(m_file_base.size())) return 0; + return m_file_base[index]; + } + + std::string file_storage::file_path(internal_file_entry const& fe + , std::string const& save_path) const + { + int index = &fe - &m_files[0]; + return file_path(index, save_path); + } + + std::string file_storage::file_name(internal_file_entry const& fe) const + { + return fe.filename(); + } + + boost::int64_t file_storage::file_size(internal_file_entry const& fe) const + { + return fe.size; + } + + bool file_storage::pad_file_at(internal_file_entry const& fe) const + { + return fe.pad_file; + } + + boost::int64_t file_storage::file_offset(internal_file_entry const& fe) const + { + return fe.offset; + } + + file_entry file_storage::at(file_storage::iterator i) const + { return at_deprecated(i - m_files.begin()); } +#endif // TORRENT_NO_DEPRECATE + + void file_storage::reorder_file(int index, int dst) + { + TORRENT_ASSERT(index < int(m_files.size())); + TORRENT_ASSERT(dst < int(m_files.size())); + TORRENT_ASSERT(dst < index); + + std::iter_swap(m_files.begin() + index, m_files.begin() + dst); + if (!m_mtime.empty()) + { + TORRENT_ASSERT(m_mtime.size() == m_files.size()); + if (int(m_mtime.size()) < index) m_mtime.resize(index+1, 0); + std::iter_swap(m_mtime.begin() + dst, m_mtime.begin() + index); + } + if (!m_file_hashes.empty()) + { + TORRENT_ASSERT(m_file_hashes.size() == m_files.size()); + if (int(m_file_hashes.size()) < index) m_file_hashes.resize(index + 1, NULL); + std::iter_swap(m_file_hashes.begin() + dst, m_file_hashes.begin() + index); + } +#ifndef TORRENT_NO_DEPRECATE + if (!m_file_base.empty()) + { + TORRENT_ASSERT(m_file_base.size() == m_files.size()); + if (int(m_file_base.size()) < index) m_file_base.resize(index + 1, 0); + std::iter_swap(m_file_base.begin() + dst, m_file_base.begin() + index); + } +#endif // TORRENT_DEPRECATED + } + + void file_storage::optimize(int pad_file_limit, int alignment + , bool tail_padding) + { + if (alignment == -1) + alignment = m_piece_length; + + boost::int64_t off = 0; + int padding_file = 0; + for (std::vector::iterator i = m_files.begin(); + i != m_files.end(); ++i) + { + if ((off % alignment) == 0) + { + // this file position is aligned, pick the largest + // available file to put here + std::vector::iterator best_match + = std::max_element(i, m_files.end() + , &compare_file_entry_size); + + if (best_match != i) + { + int const index = best_match - m_files.begin(); + int const cur_index = i - m_files.begin(); + reorder_file(index, cur_index); + i = m_files.begin() + cur_index; + } + } + else if (pad_file_limit >= 0 + && i->size > boost::uint32_t(pad_file_limit) + && i->pad_file == false) + { + // if we have pad files enabled, and this file is + // not piece-aligned and the file size exceeds the + // limit, and it's not a padding file itself. + // so add a padding file in front of it + int const pad_size = alignment - (off % alignment); + + // find the largest file that fits in pad_size + std::vector::iterator best_match = m_files.end(); + + // if pad_file_limit is 0, it means all files are padded, there's + // no point in trying to find smaller files to use as filling + if (pad_file_limit > 0) + { + for (std::vector::iterator j = i+1; j < m_files.end(); ++j) + { + if (j->size > boost::uint32_t(pad_size)) continue; + if (best_match == m_files.end() || j->size > best_match->size) + best_match = j; + } + + if (best_match != m_files.end()) + { + // we found one + // We cannot have found i, because i->size > pad_file_limit + // which is forced to be no less than alignment. We only + // look for files <= pad_size, which never is greater than + // alignment + TORRENT_ASSERT(best_match != i); + int index = best_match - m_files.begin(); + int cur_index = i - m_files.begin(); + reorder_file(index, cur_index); + i = m_files.begin() + cur_index; + i->offset = off; + off += i->size; + continue; + } + } + + // we could not find a file that fits in pad_size + // add a padding file + // note that i will be set to point to the + // new pad file. Once we're done adding it, we need + // to increment i to point to the current file again + // first add the pad file to the end of the file list + // then swap it in place. This minimizes the amount + // of copying of internal_file_entry, which is somewhat + // expensive (until we have move semantics) + add_pad_file(pad_size, i, off, padding_file); + + TORRENT_ASSERT((off % alignment) == 0); + continue; + } + i->offset = off; + off += i->size; + + if (tail_padding + && i->size > boost::uint32_t(pad_file_limit) + && (off % alignment) != 0) + { + // skip the file we just put in place, so we put the pad + // file after it + ++i; + + // tail-padding is enabled, and the offset after this file is not + // aligned and it's not the last file. The last file must be padded + // too, in order to match an equivalent tail-padded file. + add_pad_file(alignment - (off % alignment), i, off, padding_file); + + TORRENT_ASSERT((off % alignment) == 0); + } + } + m_total_size = off; + } + + void file_storage::add_pad_file(int size + , std::vector::iterator& i + , boost::int64_t& offset + , int& pad_file_counter) + { + int cur_index = i - m_files.begin(); + int index = m_files.size(); + m_files.push_back(internal_file_entry()); + ++m_num_files; + internal_file_entry& e = m_files.back(); + // i may have been invalidated, refresh it + i = m_files.begin() + cur_index; + e.size = size; + e.offset = offset; + char name[30]; + snprintf(name, sizeof(name), ".pad" TORRENT_SEPARATOR_STR "%d" + , pad_file_counter); + std::string path = combine_path(m_name, name); + e.set_name(path.c_str()); + e.pad_file = true; + offset += size; + ++pad_file_counter; + + if (!m_mtime.empty()) m_mtime.resize(index + 1, 0); + if (!m_file_hashes.empty()) m_file_hashes.resize(index + 1, NULL); +#ifndef TORRENT_NO_DEPRECATE + if (!m_file_base.empty()) m_file_base.resize(index + 1, 0); +#endif + + reorder_file(index, cur_index); + } + + void file_storage::unload() + { + std::vector().swap(m_files); + std::vector().swap(m_file_hashes); + std::vector().swap(m_symlinks); + std::vector().swap(m_mtime); +#ifndef TORRENT_NO_DEPRECATE + std::vector().swap(m_file_base); +#endif + std::vector().swap(m_paths); + } +} + diff --git a/src/gzip.cpp b/src/gzip.cpp new file mode 100644 index 0000000..637d5cf --- /dev/null +++ b/src/gzip.cpp @@ -0,0 +1,279 @@ +/* + +Copyright (c) 2007-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/assert.hpp" +#include "libtorrent/puff.hpp" +#include "libtorrent/gzip.hpp" + +#include +#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 : boost::system::error_category + { + virtual const char* name() const BOOST_SYSTEM_NOEXCEPT; + virtual std::string message(int ev) const BOOST_SYSTEM_NOEXCEPT; + virtual boost::system::error_condition default_error_condition(int ev) const BOOST_SYSTEM_NOEXCEPT + { return boost::system::error_condition(ev, *this); } + }; + + const char* gzip_error_category::name() const BOOST_SYSTEM_NOEXCEPT + { + return "gzip error"; + } + + std::string gzip_error_category::message(int ev) const BOOST_SYSTEM_NOEXCEPT + { + 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& get_gzip_category() + { + static gzip_error_category gzip_category; + return gzip_category; + } + + namespace gzip_errors + { + boost::system::error_code make_error_code(error_code_enum e) + { + return boost::system::error_code(e, get_gzip_category()); + } + } + + namespace + { + // returns -1 if gzip header is invalid or the header size in bytes + int gzip_header(const char* buf, int size) + { + TORRENT_ASSERT(buf != 0); + + const unsigned char* buffer = reinterpret_cast(buf); + const int total_size = size; + + // gzip is defined in https://tools.ietf.org/html/rfc1952 + + // The zip header cannot be shorter than 10 bytes + if (size < 10 || buf == 0) return -1; + + // check the magic header of gzip + if ((buffer[0] != GZIP_MAGIC0) || (buffer[1] != GZIP_MAGIC1)) return -1; + + int method = buffer[2]; + int 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-->) + // +---+---+---+---+---+---+---+---+---+---+ + + size -= 10; + buffer += 10; + + if (flags & FEXTRA) + { + int extra_len; + + if (size < 2) return -1; + + extra_len = (buffer[1] << 8) | buffer[0]; + + if (size < (extra_len+2)) return -1; + size -= (extra_len + 2); + buffer += (extra_len + 2); + } + + if (flags & FNAME) + { + while (size && *buffer) + { + --size; + ++buffer; + } + if (!size || *buffer) return -1; + + --size; + ++buffer; + } + + if (flags & FCOMMENT) + { + while (size && *buffer) + { + --size; + ++buffer; + } + if (!size || *buffer) return -1; + + --size; + ++buffer; + } + + if (flags & FHCRC) + { + if (size < 2) return -1; + + size -= 2; +// buffer += 2; + } + + return total_size - size; + } + } // anonymous namespace + + TORRENT_EXTRA_EXPORT void inflate_gzip( + char const* in + , int size + , std::vector& buffer + , int maximum_size + , error_code& ec) + { + ec.clear(); + TORRENT_ASSERT(maximum_size > 0); + + int header_len = gzip_header(in, size); + 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; + unsigned long srclen = size - header_len; + in += header_len; + + do + { + TORRENT_TRY { + buffer.resize(destlen); + } TORRENT_CATCH(std::exception&) { + ec = errors::no_memory; + return; + } + + ret = puff(reinterpret_cast(&buffer[0]), &destlen + , reinterpret_cast(in), &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 == boost::uint32_t(maximum_size)) + { + ec = gzip_errors::inflated_data_too_large; + return; + } + + destlen *= 2; + if (destlen > boost::uint32_t(maximum_size)) + destlen = maximum_size; + } + } while (ret == 1); + + if (ret != 0) + { + switch (ret) + { + case 2: ec = gzip_errors::data_did_not_terminate; return; + case 1: ec = gzip_errors::space_exhausted; return; + case -1: ec = gzip_errors::invalid_block_type; return; + case -2: ec = gzip_errors::invalid_stored_block_length; return; + case -3: ec = gzip_errors::too_many_length_or_distance_codes; return; + case -4: ec = gzip_errors::code_lengths_codes_incomplete; return; + case -5: ec = gzip_errors::repeat_lengths_with_no_first_length; return; + case -6: ec = gzip_errors::repeat_more_than_specified_lengths; return; + case -7: ec = gzip_errors::invalid_literal_length_code_lengths; return; + case -8: ec = gzip_errors::invalid_distance_code_lengths; return; + case -9: ec = gzip_errors::invalid_literal_code_in_block; return; + case -10: ec = gzip_errors::distance_too_far_back_in_block; return; + default: ec = gzip_errors::unknown_gzip_error; return; + } + } + + if (destlen > buffer.size()) + { + ec = gzip_errors::unknown_gzip_error; + return; + } + + buffer.resize(destlen); + } + +} + diff --git a/src/hasher.cpp b/src/hasher.cpp new file mode 100644 index 0000000..17b810d --- /dev/null +++ b/src/hasher.cpp @@ -0,0 +1,137 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/hasher.hpp" +#include "libtorrent/sha1.hpp" + +namespace libtorrent +{ + hasher::hasher() + { +#ifdef TORRENT_USE_GCRYPT + gcry_md_open(&m_context, GCRY_MD_SHA1, 0); +#elif TORRENT_USE_COMMONCRYPTO + CC_SHA1_Init(&m_context); +#elif defined TORRENT_USE_OPENSSL + SHA1_Init(&m_context); +#else + SHA1_init(&m_context); +#endif + } + + hasher::hasher(const char* data, int len) + { + TORRENT_ASSERT(data != 0); + TORRENT_ASSERT(len > 0); +#ifdef TORRENT_USE_GCRYPT + gcry_md_open(&m_context, GCRY_MD_SHA1, 0); + gcry_md_write(m_context, data, len); +#elif TORRENT_USE_COMMONCRYPTO + CC_SHA1_Init(&m_context); + CC_SHA1_Update(&m_context, reinterpret_cast(data), len); +#elif defined TORRENT_USE_OPENSSL + SHA1_Init(&m_context); + SHA1_Update(&m_context, reinterpret_cast(data), len); +#else + SHA1_init(&m_context); + SHA1_update(&m_context, reinterpret_cast(data), len); +#endif + } + +#ifdef TORRENT_USE_GCRYPT + hasher::hasher(hasher const& h) + { + gcry_md_copy(&m_context, h.m_context); + } + + hasher& hasher::operator=(hasher const& h) + { + gcry_md_close(m_context); + gcry_md_copy(&m_context, h.m_context); + return *this; + } +#endif + + hasher& hasher::update(const char* data, int len) + { + TORRENT_ASSERT(data != 0); + TORRENT_ASSERT(len > 0); +#ifdef TORRENT_USE_GCRYPT + gcry_md_write(m_context, data, len); +#elif TORRENT_USE_COMMONCRYPTO + CC_SHA1_Update(&m_context, reinterpret_cast(data), len); +#elif defined TORRENT_USE_OPENSSL + SHA1_Update(&m_context, reinterpret_cast(data), len); +#else + SHA1_update(&m_context, reinterpret_cast(data), len); +#endif + return *this; + } + + sha1_hash hasher::final() + { + sha1_hash digest; +#ifdef TORRENT_USE_GCRYPT + gcry_md_final(m_context); + digest.assign((const char*)gcry_md_read(m_context, 0)); +#elif TORRENT_USE_COMMONCRYPTO + CC_SHA1_Final(digest.begin(), &m_context); +#elif defined TORRENT_USE_OPENSSL + SHA1_Final(digest.begin(), &m_context); +#else + SHA1_final(digest.begin(), &m_context); +#endif + return digest; + } + + void hasher::reset() + { +#ifdef TORRENT_USE_GCRYPT + gcry_md_reset(m_context); +#elif TORRENT_USE_COMMONCRYPTO + CC_SHA1_Init(&m_context); +#elif defined TORRENT_USE_OPENSSL + SHA1_Init(&m_context); +#else + SHA1_init(&m_context); +#endif + } + + hasher::~hasher() + { +#ifdef TORRENT_USE_GCRYPT + gcry_md_close(m_context); +#endif + } + +} + diff --git a/src/hex.cpp b/src/hex.cpp new file mode 100644 index 0000000..eecf3db --- /dev/null +++ b/src/hex.cpp @@ -0,0 +1,101 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/hex.hpp" + +namespace libtorrent +{ + + namespace detail { + + TORRENT_EXTRA_EXPORT 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; + } + + TORRENT_EXTRA_EXPORT bool is_hex(char const *in, int len) + { + for (char const* end = in + len; in < end; ++in) + { + int t = hex_to_int(*in); + if (t == -1) return false; + } + return true; + } + + } // detail namespace + + TORRENT_EXPORT bool from_hex(char const *in, int len, char* out) + { + for (char const* end = in + len; in < end; ++in, ++out) + { + int t = detail::hex_to_int(*in); + if (t == -1) return false; + *out = t << 4; + ++in; + t = detail::hex_to_int(*in); + if (t == -1) return false; + *out |= t & 15; + } + return true; + } + + extern const char hex_chars[]; + + const char hex_chars[] = "0123456789abcdef"; + + TORRENT_EXPORT std::string to_hex(std::string const& s) + { + std::string ret; + for (std::string::const_iterator i = s.begin(); i != s.end(); ++i) + { + ret += hex_chars[boost::uint8_t(*i) >> 4]; + ret += hex_chars[boost::uint8_t(*i) & 0xf]; + } + return ret; + } + + TORRENT_EXPORT void to_hex(char const *in, int len, char* out) + { + for (char const* end = in + len; in < end; ++in) + { + *out++ = hex_chars[boost::uint8_t(*in) >> 4]; + *out++ = hex_chars[boost::uint8_t(*in) & 0xf]; + } + *out = '\0'; + } + +} + diff --git a/src/http_connection.cpp b/src/http_connection.cpp new file mode 100644 index 0000000..8f2f9eb --- /dev/null +++ b/src/http_connection.cpp @@ -0,0 +1,956 @@ +/* + +Copyright (c) 2007-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/http_connection.hpp" +#include "libtorrent/aux_/escape_string.hpp" +#include "libtorrent/instantiate_connection.hpp" +#include "libtorrent/gzip.hpp" +#include "libtorrent/parse_url.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/socket_type.hpp" // for async_shutdown +#include "libtorrent/resolver_interface.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/aux_/time.hpp" +#include "libtorrent/random.hpp" + +#if defined TORRENT_ASIO_DEBUGGING +#include "libtorrent/debug.hpp" +#endif + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { + +http_connection::http_connection(io_service& ios + , resolver_interface& resolver + , http_handler const& handler + , bool bottled + , int max_bottled_buffer_size + , http_connect_handler const& ch + , http_filter_handler const& fh +#ifdef TORRENT_USE_OPENSSL + , ssl::context* ssl_ctx +#endif + ) + : m_next_ep(0) + , m_sock(ios) +#ifdef TORRENT_USE_OPENSSL + , m_ssl_ctx(ssl_ctx) + , m_own_ssl_context(false) +#endif +#if TORRENT_USE_I2P + , m_i2p_conn(0) +#endif + , m_resolver(resolver) + , m_handler(handler) + , m_connect_handler(ch) + , m_filter_handler(fh) + , m_timer(ios) + , m_read_timeout(seconds(5)) + , 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(0) + , m_port(0) + , m_bottled(bottled) + , m_called(false) + , m_limiter_timer_active(false) + , m_ssl(false) + , m_abort(false) + , m_connecting(false) +{ + TORRENT_ASSERT(!m_handler.empty()); +} + +http_connection::~http_connection() +{ +#ifdef TORRENT_USE_OPENSSL + if (m_own_ssl_context) delete m_ssl_ctx; +#endif +} + +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 + , address const& bind_addr, int 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; + + boost::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 + boost::shared_ptr me(shared_from_this()); + + if (ec) + { + m_timer.get_io_service().post(boost::bind(&http_connection::callback + , me, ec, static_cast(NULL), 0)); + return; + } + + if (protocol != "http" +#ifdef TORRENT_USE_OPENSSL + && protocol != "https" +#endif + ) + { + error_code err(errors::unsupported_url_protocol); + m_timer.get_io_service().post(boost::bind(&http_connection::callback + , me, err, static_cast(NULL), 0)); + return; + } + + TORRENT_ASSERT(prio >= 0 && prio < 3); + + bool ssl = false; + if (protocol == "https") ssl = true; + + char request[4096]; + char* end = request + sizeof(request); + char* ptr = request; + +#define APPEND_FMT(fmt) ptr += snprintf(ptr, end - ptr, fmt) +#define APPEND_FMT1(fmt, arg) ptr += snprintf(ptr, end - ptr, fmt, arg) +#define APPEND_FMT2(fmt, arg1, arg2) ptr += snprintf(ptr, end - ptr, fmt, arg1, arg2) + + // 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 + APPEND_FMT1("GET %s HTTP/1.1\r\n", url.c_str()); + if (ps->type == settings_pack::http_pw) + APPEND_FMT1("Proxy-Authorization: Basic %s\r\n", base64encode( + ps->username + ":" + ps->password).c_str()); + + hostname = ps->hostname; + port = ps->port; + + APPEND_FMT1("Host: %s", hostname.c_str()); + if (port != default_port) APPEND_FMT1(":%d\r\n", port); + else APPEND_FMT("\r\n"); + } + else + { + APPEND_FMT2("GET %s HTTP/1.1\r\n" + "Host: %s", path.c_str(), hostname.c_str()); + if (port != default_port) APPEND_FMT1(":%d\r\n", port); + else APPEND_FMT("\r\n"); + } + +// APPEND_FMT("Accept: */*\r\n"); + + if (!m_user_agent.empty()) + APPEND_FMT1("User-Agent: %s\r\n", m_user_agent.c_str()); + + if (m_bottled) + APPEND_FMT("Accept-Encoding: gzip\r\n"); + + if (!auth.empty()) + APPEND_FMT1("Authorization: Basic %s\r\n", base64encode(auth).c_str()); + + APPEND_FMT("Connection: close\r\n\r\n"); + + m_sendbuffer.assign(request); + 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 + , address const& bind_addr + , int 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 + boost::shared_ptr me(shared_from_this()); + + m_completion_timeout = timeout; + m_read_timeout = seconds(5); + if (m_read_timeout < timeout / 5) m_read_timeout = timeout / 5; + error_code ec; + m_timer.expires_from_now((std::min)( + m_read_timeout, m_completion_timeout), ec); +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("http_connection::on_timeout"); +#endif + m_timer.async_wait(boost::bind(&http_connection::on_timeout + , boost::weak_ptr(me), _1)); + m_called = false; + m_parser.reset(); + m_recvbuffer.clear(); + m_read_pos = 0; + m_priority = prio; + + if (ec) + { + m_timer.get_io_service().post(boost::bind(&http_connection::callback + , me, ec, static_cast(NULL), 0)); + return; + } + + if (m_sock.is_open() && m_hostname == hostname && m_port == port + && m_ssl == ssl && m_bind_addr == bind_addr) + { +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("http_connection::on_write"); +#endif + async_write(m_sock, boost::asio::buffer(m_sendbuffer) + , boost::bind(&http_connection::on_write, me, _1)); + } + else + { + m_ssl = ssl; + m_bind_addr = bind_addr; + error_code err; + if (m_sock.is_open()) m_sock.close(err); + +#if TORRENT_USE_I2P + bool is_i2p = false; + char const* top_domain = strrchr(hostname.c_str(), '.'); + if (top_domain && strcmp(top_domain, ".i2p") == 0 && 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; + m_read_timeout *= 4; + } +#endif + +#if TORRENT_USE_I2P + if (is_i2p && i2p_conn->proxy().type != settings_pack::i2p_proxy) + { + m_timer.get_io_service().post(boost::bind(&http_connection::callback + , me, error_code(errors::no_i2p_router, get_libtorrent_category()), static_cast(NULL), 0)); + return; + } +#endif + + aux::proxy_settings const* proxy = ps; +#if TORRENT_USE_I2P + aux::proxy_settings i2p_proxy; + if (is_i2p) + { + 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 = 0; + } + aux::proxy_settings null_proxy; + + void* userdata = 0; +#ifdef TORRENT_USE_OPENSSL + if (m_ssl) + { + if (m_ssl_ctx == 0) + { + m_ssl_ctx = new (std::nothrow) ssl::context( + m_timer.get_io_service(), ssl::context::sslv23_client); + if (m_ssl_ctx) + { + m_own_ssl_context = true; + m_ssl_ctx->set_verify_mode(ssl::context::verify_none, ec); + if (ec) + { + m_timer.get_io_service().post(boost::bind(&http_connection::callback + , me, ec, static_cast(NULL), 0)); + return; + } + } + } + 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 NULL as the proxy + // pointer. + instantiate_connection(m_timer.get_io_service() + , proxy ? *proxy : null_proxy, m_sock, userdata, NULL, false, false); + + if (m_bind_addr != address_v4::any()) + { + 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) + { + m_timer.get_io_service().post(boost::bind(&http_connection::callback + , me, ec, static_cast(NULL), 0)); + return; + } + } + + setup_ssl_hostname(m_sock, hostname, ec); + if (ec) + { + m_timer.get_io_service().post(boost::bind(&http_connection::callback + , me, ec, static_cast(NULL), 0)); + 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 + { +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("http_connection::on_i2p_resolve"); +#endif + i2p_conn->async_name_lookup(hostname.c_str(), boost::bind(&http_connection::on_i2p_resolve + , me, _1, _2)); + } + else + connect_i2p_tracker(hostname.c_str()); + } + else +#endif + if (ps && ps->proxy_hostnames + && (ps->type == settings_pack::socks5 + || ps->type == settings_pack::socks5_pw)) + { + m_hostname = hostname; + m_port = port; + m_endpoints.push_back(tcp::endpoint(address(), port)); + connect(); + } + else + { +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("http_connection::on_resolve"); +#endif + m_resolver.async_resolve(hostname, m_resolve_flags + , boost::bind(&http_connection::on_resolve + , me, _1, _2)); + } + m_hostname = hostname; + m_port = port; + } +} + +void http_connection::on_timeout(boost::weak_ptr p + , error_code const& e) +{ +#if defined TORRENT_ASIO_DEBUGGING + complete_async("http_connection::on_timeout"); +#endif + boost::shared_ptr c = p.lock(); + if (!c) return; + + if (e == boost::asio::error::operation_aborted) return; + + if (c->m_abort) return; + + time_point now = clock_type::now(); + + if (c->m_start_time + c->m_completion_timeout <= now + || c->m_last_receive + c->m_read_timeout <= 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 < c->m_endpoints.size()) + { + error_code ec; + c->m_sock.close(ec); + if (!c->m_connecting) c->connect(); + } + else + { + c->callback(boost::asio::error::timed_out); + } + return; + } + else + { + if (!c->m_sock.is_open()) return; + } + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("http_connection::on_timeout"); +#endif + error_code ec; + c->m_timer.expires_at((std::min)( + c->m_last_receive + c->m_read_timeout + , c->m_start_time + c->m_completion_timeout), ec); + c->m_timer.async_wait(boost::bind(&http_connection::on_timeout, p, _1)); +} + +void http_connection::close(bool force) +{ + if (m_abort) return; + + error_code ec; + if (force) + m_sock.close(ec); + else + async_shutdown(m_sock, shared_from_this()); + + m_timer.cancel(ec); + m_limiter_timer.cancel(ec); + + m_hostname.clear(); + m_port = 0; + m_handler.clear(); + m_abort = true; +} + +#if TORRENT_USE_I2P +void http_connection::connect_i2p_tracker(char const* destination) +{ +#ifdef TORRENT_USE_OPENSSL + TORRENT_ASSERT(m_ssl == false); + TORRENT_ASSERT(m_sock.get()); + TORRENT_ASSERT(m_sock.get()->get()); + m_sock.get()->get()->set_destination(destination); + m_sock.get()->get()->set_command(i2p_stream::cmd_connect); + m_sock.get()->get()->set_session_id(m_i2p_conn->session_id()); +#else + m_sock.get()->set_destination(destination); + m_sock.get()->set_command(i2p_stream::cmd_connect); + m_sock.get()->set_session_id(m_i2p_conn->session_id()); +#endif +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("http_connection::on_connect"); +#endif + m_sock.async_connect(tcp::endpoint(), boost::bind(&http_connection::on_connect + , shared_from_this(), _1)); +} + +void http_connection::on_i2p_resolve(error_code const& e + , char const* destination) +{ +#if defined TORRENT_ASIO_DEBUGGING + complete_async("http_connection::on_i2p_resolve"); +#endif + if (e) + { + callback(e); + return; + } + connect_i2p_tracker(destination); +} +#endif + +void http_connection::on_resolve(error_code const& e + , std::vector
    const& addresses) +{ +#if defined TORRENT_ASIO_DEBUGGING + complete_async("http_connection::on_resolve"); +#endif + if (e) + { + callback(e); + return; + } + TORRENT_ASSERT(!addresses.empty()); + + for (std::vector
    ::const_iterator i = addresses.begin() + , end(addresses.end()); i != end; ++i) + m_endpoints.push_back(tcp::endpoint(*i, m_port)); + + if (m_filter_handler) m_filter_handler(*this, m_endpoints); + if (m_endpoints.empty()) + { + close(); + return; + } + + std::random_shuffle(m_endpoints.begin(), m_endpoints.end(), randint); + + // The following statement causes msvc to crash (ICE). Since it's not + // necessary in the vast majority of cases, just ignore the endpoint + // order for windows +#if !defined _MSC_VER || _MSC_VER > 1310 + // sort the endpoints so that the ones with the same IP version as our + // bound listen socket are first. So that when contacting a tracker, + // we'll talk to it from the same IP that we're listening on + if (m_bind_addr != address_v4::any()) + std::partition(m_endpoints.begin(), m_endpoints.end() + , boost::bind(&address::is_v4, boost::bind(&tcp::endpoint::address, _1)) + == m_bind_addr.is_v4()); +#endif + + connect(); +} + +void http_connection::connect() +{ + TORRENT_ASSERT(m_next_ep < m_endpoints.size()); + + boost::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 = address::from_string(m_hostname, ec); + + if (ec) + { + // we're using a socks proxy and we're resolving + // hostnames through it +#ifdef TORRENT_USE_OPENSSL + if (m_ssl) + { + TORRENT_ASSERT(m_sock.get >()); + m_sock.get >()->next_layer().set_dst_name(m_hostname); + } + else +#endif + { + TORRENT_ASSERT(m_sock.get()); + m_sock.get()->set_dst_name(m_hostname); + } + } + else + { + m_endpoints[0].address(adr); + } + } + + TORRENT_ASSERT(m_next_ep < m_endpoints.size()); + if (m_next_ep >= m_endpoints.size()) return; + + tcp::endpoint target_address = m_endpoints[m_next_ep]; + ++m_next_ep; + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("http_connection::on_connect"); +#endif + TORRENT_ASSERT(!m_connecting); + m_connecting = true; + m_sock.async_connect(target_address, boost::bind(&http_connection::on_connect + , shared_from_this(), _1)); +} + +void http_connection::on_connect(error_code const& e) +{ +#if defined TORRENT_ASIO_DEBUGGING + complete_async("http_connection::on_connect"); +#endif + 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); +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("http_connection::on_write"); +#endif + async_write(m_sock, boost::asio::buffer(m_sendbuffer) + , boost::bind(&http_connection::on_write, shared_from_this(), _1)); + } + else if (m_next_ep < m_endpoints.size() && !m_abort) + { + // The connection failed. Try the next endpoint in the list. + error_code ec; + m_sock.close(ec); + connect(); + } + else + { + callback(e); + } +} + +void http_connection::callback(error_code e, char* data, int size) +{ + if (m_bottled && m_called) return; + + std::vector buf; + if (data && m_bottled && m_parser.header_finished()) + { + size = m_parser.collapse_chunk_headers(data, size); + + std::string const& encoding = m_parser.header("content-encoding"); + if ((encoding == "gzip" || encoding == "x-gzip") && size > 0 && data) + { + error_code ec; + inflate_gzip(data, size, buf, m_max_bottled_buffer_size, ec); + + if (ec) + { + if (m_handler) m_handler(ec, m_parser, data, size, *this); + return; + } + size = int(buf.size()); + data = size == 0 ? 0 : &buf[0]; + } + + // if we completed the whole response, no need + // to tell the user that the connection was closed by + // the server or by us. Just clear any error + if (m_parser.finished()) e.clear(); + } + m_called = true; + error_code ec; + m_timer.cancel(ec); + if (m_handler) m_handler(e, m_parser, data, size, *this); +} + +void http_connection::on_write(error_code const& e) +{ +#if defined TORRENT_ASIO_DEBUGGING + complete_async("http_connection::on_write"); +#endif + + 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 = 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) + { +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("http_connection::on_assign_bandwidth"); +#endif + on_assign_bandwidth(error_code()); + } + return; + } + } +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("http_connection::on_read"); +#endif + m_sock.async_read_some(boost::asio::buffer(&m_recvbuffer[0] + m_read_pos + , amount_to_read) + , boost::bind(&http_connection::on_read + , shared_from_this(), _1, _2)); +} + +void http_connection::on_read(error_code const& e + , std::size_t bytes_transferred) +{ +#if defined TORRENT_ASIO_DEBUGGING + complete_async("http_connection::on_read"); +#endif + + if (m_rate_limit) + { + m_download_quota -= 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 + boost::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); + char* data = 0; + std::size_t size = 0; + if (m_bottled && m_parser.header_finished()) + { + data = &m_recvbuffer[0] + m_parser.body_start(); + size = m_parser.get_body().left(); + } + callback(ec, data, size); + return; + } + + if (e) + { + TORRENT_ASSERT(bytes_transferred == 0); + callback(e); + return; + } + + m_read_pos += bytes_transferred; + TORRENT_ASSERT(m_read_pos <= int(m_recvbuffer.size())); + + if (m_bottled || !m_parser.header_finished()) + { + libtorrent::buffer::const_interval rcv_buf(&m_recvbuffer[0] + , &m_recvbuffer[0] + m_read_pos); + bool error = false; + m_parser.incoming(rcv_buf, error); + if (error) + { + // HTTP parse error + error_code ec = errors::http_parse_error; + callback(ec, 0, 0); + 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, shared_from_this()); + 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, &m_recvbuffer[0] + m_parser.body_start() + , m_read_pos - m_parser.body_start()); + m_read_pos = 0; + m_last_receive = clock_type::now(); + } + else if (m_bottled && m_parser.finished()) + { + error_code ec; + m_timer.cancel(ec); + callback(e, &m_recvbuffer[0] + m_parser.body_start(), m_parser.get_body().left()); + } + } + else + { + TORRENT_ASSERT(!m_bottled); + callback(e, &m_recvbuffer[0], 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 = 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) + { +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("http_connection::on_assign_bandwidth"); +#endif + on_assign_bandwidth(error_code()); + } + return; + } + } +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("http_connection::on_read"); +#endif + m_sock.async_read_some(boost::asio::buffer(&m_recvbuffer[0] + m_read_pos + , amount_to_read) + , boost::bind(&http_connection::on_read + , me, _1, _2)); +} + +void http_connection::on_assign_bandwidth(error_code const& e) +{ +#if defined TORRENT_ASIO_DEBUGGING + complete_async("http_connection::on_assign_bandwidth"); +#endif + 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_download_quota > 0) return; + + m_download_quota = m_rate_limit / 4; + + int amount_to_read = 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; + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("http_connection::on_read"); +#endif + m_sock.async_read_some(boost::asio::buffer(&m_recvbuffer[0] + m_read_pos + , amount_to_read) + , boost::bind(&http_connection::on_read + , shared_from_this(), _1, _2)); + + error_code ec; + m_limiter_timer_active = true; + m_limiter_timer.expires_from_now(milliseconds(250), ec); +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("http_connection::on_assign_bandwidth"); +#endif + m_limiter_timer.async_wait(boost::bind(&http_connection::on_assign_bandwidth + , shared_from_this(), _1)); +} + +void http_connection::rate_limit(int limit) +{ + if (!m_sock.is_open()) return; + + if (!m_limiter_timer_active) + { + error_code ec; + m_limiter_timer_active = true; + m_limiter_timer.expires_from_now(milliseconds(250), ec); +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("http_connection::on_assign_bandwidth"); +#endif + m_limiter_timer.async_wait(boost::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..52f6152 --- /dev/null +++ b/src/http_parser.cpp @@ -0,0 +1,597 @@ +/* + +Copyright (c) 2008-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/http_parser.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/parse_url.hpp" // for parse_url_components +#include "libtorrent/aux_/escape_string.hpp" // for read_until + +using namespace libtorrent; + +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 boost::tuples::ignore; + boost::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. + if ((url.empty() || url[url.size()-1] != '/')) + url += '/'; + url += location; + } + return url; + } + + http_parser::~http_parser() {} + + http_parser::http_parser(int flags) + : m_recv_pos(0) + , m_content_length(-1) + , m_range_start(-1) + , m_range_end(-1) + , m_recv_buffer(0, 0) + , m_cur_chunk_end(-1) + , m_status_code(-1) + , m_chunk_header_size(0) + , m_partial_chunk_header(0) + , m_flags(flags) + , m_body_start_pos(0) + , m_state(read_status) + , m_connection_close(false) + , m_chunked_encoding(false) + , m_finished(false) + {} + + boost::tuple http_parser::incoming( + buffer::const_interval recv_buffer, bool& error) + { + TORRENT_ASSERT(recv_buffer.left() >= m_recv_buffer.left()); + boost::tuple ret(0, 0); + int start_pos = m_recv_buffer.left(); + + // early exit if there's nothing new in the receive buffer + if (start_pos == recv_buffer.left()) return ret; + m_recv_buffer = recv_buffer; + + if (m_state == error_state) + { + error = true; + return ret; + } + + char const* pos = recv_buffer.begin + 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) + { + boost::get<1>(ret) += m_recv_buffer.left() - 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; + boost::get<1>(ret) += newline - (m_recv_buffer.begin + 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.begin; + } + + 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 preceeded by a CR + // charachter, 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 < INT_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 = strtoll(value.c_str(), 0, 10); + if (m_content_length < 0) + { + 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 = strtoll(ptr, &end, 10); + if (m_range_start < 0) + { + 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 = strtoll(ptr, &end, 10); + if (m_range_end < 0) + { + 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 <= recv_buffer.left()); + TORRENT_ASSERT(pos <= recv_buffer.end); + newline = std::find(pos, recv_buffer.end, '\n'); + } + boost::get<1>(ret) += newline - (m_recv_buffer.begin + start_pos); + } + + if (m_state == read_body) + { + int incoming = 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) + { + boost::int64_t payload = m_cur_chunk_end - m_recv_pos; + if (payload > 0) + { + TORRENT_ASSERT(payload < INT_MAX); + m_recv_pos += payload; + boost::get<0>(ret) += int(payload); + incoming -= int(payload); + } + buffer::const_interval buf(recv_buffer.begin + m_cur_chunk_end, recv_buffer.end); + boost::int64_t chunk_size; + int header_size; + if (parse_chunk_header(buf, &chunk_size, &header_size)) + { + if (chunk_size < 0) + { + 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; + TORRENT_ASSERT(m_content_length < 0 || m_recv_pos - m_body_start_pos + - m_chunk_header_size == m_content_length); + } + header_size -= m_partial_chunk_header; + m_partial_chunk_header = 0; +// fprintf(stderr, "parse_chunk_header(%d, -> %d, -> %d) -> %d\n" +// " incoming = %d\n m_recv_pos = %d\n m_cur_chunk_end = %d\n" +// " content-length = %d\n" +// , buf.left(), int(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; + +// fprintf(stderr, "parse_chunk_header(%d, -> %d, -> %d) -> %d\n" +// " incoming = %d\n m_recv_pos = %d\n m_cur_chunk_end = %d\n" +// " content-length = %d\n" +// , buf.left(), int(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; + boost::get<1>(ret) += header_size; + incoming -= header_size; + } + if (incoming > 0) + { + m_recv_pos += incoming; + boost::get<0>(ret) += incoming; +// incoming = 0; + } + } + else + { + boost::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 < INT_MAX); + incoming = int(m_content_length - m_recv_pos + m_body_start_pos); + } + + TORRENT_ASSERT(incoming >= 0); + m_recv_pos += incoming; + boost::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; + } + + bool http_parser::parse_chunk_header(buffer::const_interval buf + , boost::int64_t* chunk_size, int* header_size) + { + TORRENT_ASSERT(buf.begin <= buf.end); + char const* pos = buf.begin; + + // 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 + + // first, read the chunk length + *chunk_size = strtoll(pos, 0, 16); + if (*chunk_size < 0) return true; + + if (*chunk_size != 0) + { + *header_size = newline - buf.begin; + // the newline alone is two bytes + TORRENT_ASSERT(newline - buf.begin > 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 preceeded by a CR + // charachter, 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 = newline - buf.begin; + + // the newline alone is two bytes + TORRENT_ASSERT(newline - buf.begin > 2); + + // we were successfull in parsing the headers. + // add them to the headers in the parser + for (std::map::const_iterator i = tail_headers.begin(); + i != tail_headers.end(); ++i) + m_header.insert(std::make_pair(i->first, i->second)); + + 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)); +// fprintf(stderr, "tail_header: %s: %s\n", name.c_str(), value.c_str()); + + newline = std::find(pos, buf.end, '\n'); + } + return false; + } + + buffer::const_interval http_parser::get_body() const + { + TORRENT_ASSERT(m_state == read_body); + boost::int64_t last_byte = m_chunked_encoding && !m_chunked_ranges.empty() + ? (std::min)(m_chunked_ranges.back().second, m_recv_pos) + : m_content_length < 0 + ? m_recv_pos : (std::min)(m_body_start_pos + m_content_length, m_recv_pos); + + TORRENT_ASSERT(last_byte >= m_body_start_pos); + return buffer::const_interval(m_recv_buffer.begin + m_body_start_pos + , m_recv_buffer.begin + last_byte); + } + + 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.begin = 0; + m_recv_buffer.end = 0; + 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; + } + + int http_parser::collapse_chunk_headers(char* buffer, int size) const + { + if (!chunked_encoding()) return size; + + // 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; + // 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 offset = body_start(); + std::vector > const& c = chunks(); + for (std::vector >::const_iterator i = c.begin() + , end(c.end()); i != end; ++i) + { + TORRENT_ASSERT(i->second - i->first < INT_MAX); + TORRENT_ASSERT(i->second - offset <= size); + int len = int(i->second - i->first); + if (i->first - offset + len > size) len = size - int(i->first) + offset; + memmove(write_ptr, buffer + i->first - offset, len); + write_ptr += len; + } + size = write_ptr - buffer; + return size; + } +} + diff --git a/src/http_seed_connection.cpp b/src/http_seed_connection.cpp new file mode 100644 index 0000000..ac84d71 --- /dev/null +++ b/src/http_seed_connection.cpp @@ -0,0 +1,475 @@ +/* + +Copyright (c) 2008-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/http_seed_connection.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/identify_client.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/version.hpp" +#include "libtorrent/aux_/session_impl.hpp" +#include "libtorrent/parse_url.hpp" +#include "libtorrent/peer_info.hpp" +#include "libtorrent/hex.hpp" // for is_hex + +using boost::shared_ptr; +using libtorrent::aux::session_impl; + +namespace libtorrent +{ + http_seed_connection::http_seed_connection(peer_connection_args const& pack + , web_seed_t& web) + : web_connection_base(pack, web) + , m_url(web.url) + , m_web(&web) + , m_response_left(0) + , m_chunk_pos(0) + , m_partial_chunk_header(0) + { + INVARIANT_CHECK; + + if (!m_settings.get_bool(settings_pack::report_web_seed_downloads)) + ignore_stats(true); + + 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::disconnect(error_code const& ec + , operation_t op, int error) + { + if (is_disconnecting()) return; + + if (op == op_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()); + } + + boost::shared_ptr t = associated_torrent().lock(); + peer_connection::disconnect(ec, op, error); + if (t) t->disconnect_web_seed(this); + } + + boost::optional + http_seed_connection::downloading_piece_progress() const + { + if (m_requests.empty()) + return boost::optional(); + + boost::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 receive_buffer_size = m_recv_buffer.get().left() - m_parser.body_start(); + // TODO: 1 in chunked encoding mode, this assert won't hold. + // the chunk headers should be subtracted from the receive_buffer_size + TORRENT_ASSERT_VAL(receive_buffer_size <= t->block_size(), receive_buffer_size); + ret.bytes_downloaded = 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 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(); + const int last_piece = t->torrent_file().num_pieces() - 1; + 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; + + boost::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 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 = 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(reinterpret_cast(&t->torrent_file().info_hash()[0]), 20); + request += "&piece="; + request += to_string(r.piece).elems; + + // 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).elems; + request += "-"; + // ranges are inclusive, just like HTTP + request += to_string(r.start + r.length - 1).elems; + } + + 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.c_str(), request.size(), message_type_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, bytes_transferred); +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ERROR" + , "http_seed_connection error: %s", error.message().c_str()); +#endif + return; + } + + boost::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + + for (;;) + { + buffer::const_interval recv_buffer = m_recv_buffer.get(); + + if (bytes_transferred == 0) break; + TORRENT_ASSERT(recv_buffer.left() > 0); + + TORRENT_ASSERT(!m_requests.empty()); + if (m_requests.empty()) + { + received_bytes(0, bytes_transferred); + disconnect(errors::http_error, op_bittorrent, 2); + 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; + boost::tie(payload, protocol) = m_parser.incoming(recv_buffer, parse_error); + received_bytes(0, protocol); + bytes_transferred -= protocol; +#if defined TORRENT_DEBUG || defined TORRENT_RELEASE_ASSERTS + if (payload > front_request.length) payload = front_request.length; +#endif + + if (parse_error) + { + received_bytes(0, bytes_transferred); + disconnect(errors::http_parse_error, op_bittorrent, 2); + return; + } + + TORRENT_ASSERT(recv_buffer.left() == 0 || *recv_buffer.begin == 'H'); + + TORRENT_ASSERT(recv_buffer.left() <= 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())) + { + int retry_time = atoi(m_parser.header("retry-after").c_str()); + if (retry_time <= 0) retry_time = 5 * 60; + // temporarily unavailable, retry later + t->retry_web_seed(this, retry_time); + + std::string error_msg = to_string(m_parser.status_code()).elems + + (" " + m_parser.message()); + if (t->alerts().should_post()) + { + t->alerts().emplace_alert(t->get_handle(), url() + , error_msg); + } + received_bytes(0, bytes_transferred); + disconnect(error_code(m_parser.status_code(), get_http_category()), op_bittorrent, 1); + 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, bytes_transferred); + + if (location.empty()) + { + // we should not try this server again. + t->remove_web_seed(this, errors::missing_location, op_bittorrent, 2); + return; + } + + // add the redirected url and remove the current one + t->add_web_seed(location, web_seed_entry::http_seed); + t->remove_web_seed(this, errors::redirecting, op_bittorrent, 2); + return; + } + + std::string const& server_version = m_parser.header("server"); + if (!server_version.empty()) + { + m_server_string = "URL seed @ "; + m_server_string += m_host; + m_server_string += " ("; + m_server_string += server_version; + m_server_string += ")"; + } + + m_response_left = atol(m_parser.header("content-length").c_str()); + if (m_response_left == -1) + { + received_bytes(0, bytes_transferred); + // we should not try this server again. + t->remove_web_seed(this, errors::no_content_length, op_bittorrent, 2); + return; + } + if (m_response_left != front_request.length) + { + received_bytes(0, bytes_transferred); + // we should not try this server again. + t->remove_web_seed(this, errors::invalid_range, op_bittorrent, 2); + return; + } + m_body_start = m_parser.body_start(); + } + + recv_buffer.begin += m_body_start; + + // ========================= + // === CHUNKED ENCODING === + // ========================= + while (m_parser.chunked_encoding() + && m_chunk_pos >= 0 + && m_chunk_pos < recv_buffer.left()) + { + int header_size = 0; + boost::int64_t chunk_size = 0; + buffer::const_interval chunk_start = recv_buffer; + chunk_start.begin += m_chunk_pos; + TORRENT_ASSERT(chunk_start.begin[0] == '\r' + || detail::is_hex(chunk_start.begin, 1)); + bool ret = m_parser.parse_chunk_header(chunk_start, &chunk_size, &header_size); + if (!ret) + { + TORRENT_ASSERT(bytes_transferred >= size_t(chunk_start.left() - m_partial_chunk_header)); + bytes_transferred -= chunk_start.left() - m_partial_chunk_header; + received_bytes(0, chunk_start.left() - m_partial_chunk_header); + m_partial_chunk_header = chunk_start.left(); + 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 >= size_t(header_size - m_partial_chunk_header)); + bytes_transferred -= 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.left() <= header_size || chunk_start.begin[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, int(m_chunk_pos + m_body_start)); + recv_buffer = m_recv_buffer.get(); + recv_buffer.begin += m_body_start; + m_chunk_pos += chunk_size; + if (chunk_size == 0) + { + TORRENT_ASSERT(m_recv_buffer.get().left() < m_chunk_pos + m_body_start + 1 + || m_recv_buffer.get()[int(m_chunk_pos + m_body_start)] == 'H' + || (m_parser.chunked_encoding() && m_recv_buffer.get()[int(m_chunk_pos + m_body_start)] == '\r')); + m_chunk_pos = -1; + } + } + } + + int payload = bytes_transferred; + if (payload > m_response_left) payload = int(m_response_left); + if (payload > front_request.length) payload = front_request.length; + received_bytes(payload, 0); + incoming_piece_fragment(payload); + m_response_left -= payload; + + if (m_parser.status_code() == 503) + { + if (!m_parser.finished()) return; + + int retry_time = atol(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, bytes_transferred); + // temporarily unavailable, retry later + t->retry_web_seed(this, retry_time); + disconnect(error_code(m_parser.status_code(), get_http_category()), op_bittorrent, 1); + return; + } + + + // we only received the header, no data + if (recv_buffer.left() == 0) break; + + if (recv_buffer.left() < 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 size_to_cut = m_body_start + front_request.length; + TORRENT_ASSERT(m_recv_buffer.get().left() < 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; + bytes_transferred -= payload; + m_body_start = 0; + if (m_response_left > 0) continue; + TORRENT_ASSERT(m_response_left == 0); + m_parser.reset(); + } + } + + void http_seed_connection::get_specific_peer_info(peer_info& p) const + { + web_connection_base::get_specific_peer_info(p); + p.flags |= peer_info::local_connection; + p.connection_type = peer_info::http_seed; + } + +} + diff --git a/src/http_stream.cpp b/src/http_stream.cpp new file mode 100644 index 0000000..5cfc983 --- /dev/null +++ b/src/http_stream.cpp @@ -0,0 +1,151 @@ +/* + +Copyright (c) 2007-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/http_stream.hpp" +#include "libtorrent/aux_/escape_string.hpp" // for base64encode +#include "libtorrent/socket_io.hpp" + +namespace libtorrent +{ + + void http_stream::name_lookup(error_code const& e, tcp::resolver::iterator i + , boost::shared_ptr h) + { + if (handle_error(e, h)) return; + + m_sock.async_connect(i->endpoint(), boost::bind( + &http_stream::connected, this, _1, h)); + } + + void http_stream::connected(error_code const& e, boost::shared_ptr h) + { + if (handle_error(e, h)) return; + + using namespace libtorrent::detail; + + if (m_no_connect) + { + std::vector().swap(m_buffer); + (*h)(e); + return; + } + + // send CONNECT + std::back_insert_iterator > p(m_buffer); + std::string endpoint; + if (!m_hostname.empty()) + { + endpoint = m_hostname + ':' + to_string(m_remote_endpoint.port()).elems; + } + else + { + 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) + , boost::bind(&http_stream::handshake1, this, _1, h)); + } + + void http_stream::handshake1(error_code const& e, boost::shared_ptr 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) + , boost::bind(&http_stream::handshake2, this, _1, h)); + } + + void http_stream::handshake2(error_code const& e, boost::shared_ptr h) + { + if (handle_error(e, h)) return; + + int 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 (m_buffer[read_pos - 1] == '\n' && read_pos > 2) + { + 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* status = std::strchr(&m_buffer[0], ' '); + if (status == 0) + { + (*h)(boost::asio::error::operation_not_supported); + error_code ec; + close(ec); + return; + } + + status++; + int 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[0] + read_pos, 1) + , boost::bind(&http_stream::handshake2, this, _1, h)); + } + +} + diff --git a/src/http_tracker_connection.cpp b/src/http_tracker_connection.cpp new file mode 100644 index 0000000..4887462 --- /dev/null +++ b/src/http_tracker_connection.cpp @@ -0,0 +1,602 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/gzip.hpp" +#include "libtorrent/socket_io.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include + +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/tracker_manager.hpp" +#include "libtorrent/http_tracker_connection.hpp" +#include "libtorrent/http_connection.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/broadcast_socket.hpp" // for is_local +#include "libtorrent/string_util.hpp" // for is_i2p_url +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/resolver_interface.hpp" +#include "libtorrent/ip_filter.hpp" + +using namespace libtorrent; + +namespace libtorrent +{ + http_tracker_connection::http_tracker_connection( + io_service& ios + , tracker_manager& man + , tracker_request const& req + , boost::weak_ptr c) + : tracker_connection(man, req, ios, c) + , m_man(man) +#if TORRENT_USE_I2P + , m_i2p_conn(NULL) +#endif + {} + + void http_tracker_connection::start() + { + std::string url = tracker_req().url; + + if (0 != (tracker_req().kind & tracker_request::scrape_request)) + { + // find and replace "announce" with "scrape" + // in request + + std::size_t pos = url.find("announce"); + if (pos == std::string::npos) + { + tracker_connection::fail(error_code(errors::scrape_not_available)); + return; + } + url.replace(pos, 8, "scrape"); + } + +#if TORRENT_USE_I2P + bool 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 + size_t arguments_start = url.find('?'); + if (arguments_start != std::string::npos) + url += "&"; + else + url += "?"; + + url += "info_hash="; + url += escape_string(tracker_req().info_hash.data(), 20); + + if (0 == (tracker_req().kind & tracker_request::scrape_request)) + { + const char* event_string[] = {"completed", "started", "stopped", "paused"}; + + char str[1024]; + const bool stats = tracker_req().send_stats; + 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 + , i2p ? 1 : tracker_req().listen_port + , stats ? tracker_req().uploaded : 0 + , stats ? tracker_req().downloaded : 0 + , stats ? tracker_req().left : 0 + , stats ? tracker_req().corrupt : 0 + , tracker_req().key + , (tracker_req().event != tracker_request::none) ? "&event=" : "" + , (tracker_req().event != tracker_request::none) ? event_string[tracker_req().event - 1] : "" + , tracker_req().num_want); + url += str; +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + 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 (stats && settings.get_bool(settings_pack::report_redundant_bytes)) + { + url += "&redundant="; + url += to_string(tracker_req().redundant).elems; + } + if (!tracker_req().trackerid.empty()) + { + std::string id = tracker_req().trackerid; + url += "&trackerid="; + url += escape_string(id.c_str(), id.length()); + } + +#if TORRENT_USE_I2P + if (i2p && tracker_req().i2pconn) + { + if (tracker_req().i2pconn->local_endpoint().empty()) + { + fail(error_code(errors::no_i2p_endpoint), -1, "Waiting for i2p acceptor from SAM bridge", 5); + return; + } + else + { + url += "&ip=" + tracker_req ().i2pconn->local_endpoint () + ".i2p"; + } + } + else +#endif + if (!settings.get_bool(settings_pack::anonymous_mode)) + { + std::string announce_ip = settings.get_str(settings_pack::announce_ip); + if (!announce_ip.empty()) + { + url += "&ip=" + escape_string(announce_ip.c_str(), announce_ip.size()); + } + } + } + +#if TORRENT_USE_IPV6 + if (tracker_req().ipv6 != address_v6() && !i2p) + { + error_code err; + std::string const ip = tracker_req().ipv6.to_string(err); + if (!err) + { + url += "&ipv6="; + url += ip; + } + } +#endif + + m_tracker_connection.reset(new http_connection(get_io_service(), m_man.host_resolver() + , boost::bind(&http_tracker_connection::on_response, shared_from_this(), _1, _2, _3, _4) + , true, settings.get_int(settings_pack::max_http_recv_buffer_size) + , boost::bind(&http_tracker_connection::on_connect, shared_from_this(), _1) + , boost::bind(&http_tracker_connection::on_filter, shared_from_this(), _1, _2) +#ifdef TORRENT_USE_OPENSSL + , tracker_req().ssl_ctx +#endif + )); + + int timeout = tracker_req().event==tracker_request::stopped + ?settings.get_int(settings_pack::stop_tracker_timeout) + :settings.get_int(settings_pack::tracker_completion_timeout); + + // when sending stopped requests, prefer the cached DNS entry + // to avoid being blocked for slow or failing responses. Chances + // are that we're shutting down, and this should be a best-effort + // attempt. It's not worth stalling shutdown. + aux::proxy_settings ps(settings); + m_tracker_connection->get(url, seconds(timeout) + , tracker_req().event == tracker_request::stopped ? 2 : 1 + , ps.proxy_tracker_connections ? &ps : NULL + , 5, settings.get_bool(settings_pack::anonymous_mode) + ? "" : settings.get_str(settings_pack::user_agent) + , bind_interface() + , tracker_req().event == tracker_request::stopped + ? resolver_interface::prefer_cache + : resolver_interface::abort_on_shutdown +#ifndef TORRENT_NO_DEPRECATE + , tracker_req().auth +#else + , "" +#endif +#if TORRENT_USE_I2P + , tracker_req().i2pconn +#endif + ); + + // the url + 100 estimated header size + sent_bytes(url.size() + 100); + +#ifndef TORRENT_DISABLE_LOGGING + + boost::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(); + } + tracker_connection::close(); + } + + // endpoints is an in-out parameter + void http_tracker_connection::on_filter(http_connection& c + , std::vector& endpoints) + { + TORRENT_UNUSED(c); + if (!tracker_req().filter) return; + + // remove endpoints that are filtered by the IP filter + for (std::vector::iterator 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 + boost::shared_ptr cb = requester(); + if (cb) + { + cb->debug_log("*** TRACKER_FILTER"); + } +#endif + if (endpoints.empty()) + fail(error_code(errors::banned_by_ip_filter)); + } + + void http_tracker_connection::on_connect(http_connection& c) + { + error_code ec; + tcp::endpoint ep = c.socket().remote_endpoint(ec); + m_tracker_ip = ep.address(); + boost::shared_ptr cb = requester(); + } + + void http_tracker_connection::on_response(error_code const& ec + , http_parser const& parser, char const* data, int size) + { + // keep this alive + boost::shared_ptr me(shared_from_this()); + + if (ec && ec != boost::asio::error::eof) + { + fail(ec); + return; + } + + if (!parser.header_finished()) + { + fail(boost::asio::error::eof); + return; + } + + if (parser.status_code() != 200) + { + fail(error_code(parser.status_code(), get_http_category()) + , parser.status_code(), parser.message().c_str()); + return; + } + + if (ec && ec != boost::asio::error::eof) + { + fail(ec, parser.status_code()); + return; + } + + received_bytes(size + parser.body_start()); + + // handle tracker response + error_code ecode; + + boost::shared_ptr cb = requester(); + if (!cb) + { + close(); + return; + } + + tracker_response resp = parse_tracker_response(data, size, 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, parser.status_code(), resp.failure_reason.c_str() + , resp.interval, resp.min_interval); + close(); + return; + } + + // do slightly different things for scrape requests + if (0 != (tracker_req().kind & tracker_request::scrape_request)) + { + cb->tracker_scrape_response(tracker_req(), resp.complete + , resp.incomplete, resp.downloaded, resp.downloaders); + } + else + { + std::list
    ip_list; + if (m_tracker_connection) + { + error_code ignore; + std::vector const& epts = m_tracker_connection->endpoints(); + for (std::vector::const_iterator i = epts.begin() + , end(epts.end()); i != end; ++i) + { + ip_list.push_back(i->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.assign(errors::invalid_peer_dict, get_libtorrent_category()); + 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 + std::fill_n(ret.pid.begin(), 20, 0); + } + + // extract ip + i = info.dict_find_string("ip"); + if (i == 0) + { + ec.assign(errors::invalid_tracker_response, get_libtorrent_category()); + return false; + } + ret.hostname = i.string_value(); + + // extract port + i = info.dict_find_int("port"); + if (i == 0) + { + ec.assign(errors::invalid_tracker_response, get_libtorrent_category()); + return false; + } + ret.port = boost::uint16_t(i.int_value()); + + return true; + } + + tracker_response parse_tracker_response(char const* data, int size, error_code& ec + , int flags, sha1_hash scrape_ih) + { + tracker_response resp; + + bdecode_node e; + int res = bdecode(data, data + size, e, ec); + + if (ec) return resp; + + if (res != 0 || e.type() != bdecode_node::dict_t) + { + ec.assign(errors::invalid_tracker_response, get_libtorrent_category()); + return resp; + } + + int interval = int(e.dict_find_int_value("interval", 0)); + // if no interval is specified, default to 30 minutes + if (interval == 0) interval = 1800; + int min_interval = int(e.dict_find_int_value("min interval", 30)); + + resp.interval = interval; + resp.min_interval = min_interval; + + bdecode_node tracker_id = e.dict_find_string("tracker id"); + if (tracker_id) + resp.trackerid = tracker_id.string_value(); + + // parse the response + bdecode_node failure = e.dict_find_string("failure reason"); + if (failure) + { + resp.failure_reason = failure.string_value(); + ec.assign(errors::tracker_failure, get_libtorrent_category()); + return resp; + } + + bdecode_node warning = e.dict_find_string("warning message"); + if (warning) + resp.warning_message = warning.string_value(); + + if (0 != (flags & tracker_request::scrape_request)) + { + bdecode_node files = e.dict_find_dict("files"); + if (!files) + { + ec.assign(errors::invalid_files_entry, get_libtorrent_category()); + return resp; + } + + bdecode_node scrape_data = files.dict_find_dict( + scrape_ih.to_string()); + + if (!scrape_data) + { + ec.assign(errors::invalid_hash_entry, get_libtorrent_category()); + 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 len = peers_ent.string_length(); +#if TORRENT_USE_I2P + if (0 != (flags & tracker_request::i2p)) + { + error_code parse_error; + 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(len / 6); + for (int i = 0; i < len; i += 6) + { + if (len - i < 6) break; + + ipv4_peer_entry p; + p.ip = detail::read_v4_address(peers).to_v4().to_bytes(); + p.port = detail::read_uint16(peers); + resp.peers4.push_back(p); + } + } + } + else if (peers_ent && peers_ent.type() == bdecode_node::list_t) + { + int len = peers_ent.list_size(); + resp.peers.reserve(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(); + } + +#if TORRENT_USE_IPV6 + bdecode_node ipv6_peers = e.dict_find_string("peers6"); + if (ipv6_peers) + { + char const* peers = ipv6_peers.string_ptr(); + int len = ipv6_peers.string_length(); + resp.peers6.reserve(len / 18); + for (int i = 0; i < len; i += 18) + { + if (len - i < 18) break; + + ipv6_peer_entry p; + p.ip = detail::read_v6_address(peers).to_v6().to_bytes(); + p.port = detail::read_uint16(peers); + resp.peers6.push_back(p); + } + } + else + { + ipv6_peers.clear(); + } +#else + bdecode_node ipv6_peers; +#endif +/* + // if we didn't receive any peers. We don't care if we're stopping anyway + if (peers_ent == 0 && ipv6_peers == 0 + && tracker_req().event != tracker_request::stopped) + { + ec.assign(errors::invalid_peers_entry, get_libtorrent_category()); + return resp; + } +*/ + bdecode_node ip_ent = e.dict_find_string("external ip"); + if (ip_ent) + { + char const* p = ip_ent.string_ptr(); + if (ip_ent.string_length() == int(address_v4::bytes_type().size())) + resp.external_ip = detail::read_v4_address(p); +#if TORRENT_USE_IPV6 + else if (ip_ent.string_length() == int(address_v6::bytes_type().size())) + resp.external_ip = detail::read_v6_address(p); +#endif + } + + return resp; + } +} + diff --git a/src/i2p_stream.cpp b/src/i2p_stream.cpp new file mode 100644 index 0000000..09377c2 --- /dev/null +++ b/src/i2p_stream.cpp @@ -0,0 +1,533 @@ +/* + +Copyright (c) 2009-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/i2p_stream.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/string_util.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/hex.hpp" + +#if TORRENT_USE_I2P + +#include + +#if defined TORRENT_ASIO_DEBUGGING +#include "libtorrent/debug.hpp" +#endif + +namespace libtorrent +{ + + struct i2p_error_category : boost::system::error_category + { + virtual const char* name() const BOOST_SYSTEM_NOEXCEPT + { return "i2p error"; } + virtual std::string message(int ev) const BOOST_SYSTEM_NOEXCEPT + { + 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]; + } + virtual boost::system::error_condition default_error_condition( + int ev) const BOOST_SYSTEM_NOEXCEPT + { return boost::system::error_condition(ev, *this); } + }; + + + boost::system::error_category& get_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 error_code(e, get_i2p_category()); + } + } + + i2p_connection::i2p_connection(io_service& ios) + : m_port(0) + , m_state(sam_idle) + , m_io_service(ios) + {} + + i2p_connection::~i2p_connection() + {} + + 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 = m_port; + ret.type = settings_pack::i2p_proxy; + return ret; + } + + void i2p_connection::open(std::string const& s, int port + , i2p_stream::handler_type const& handler) + { + // we already seem to have a session to this SAM router + if (m_hostname == s + && m_port == port + && m_sam_socket + && (is_open() || m_state == sam_connecting)) return; + + m_hostname = s; + m_port = port; + + if (m_hostname.empty()) return; + + m_state = sam_connecting; + + char tmp[20]; + std::generate(tmp, tmp + sizeof(tmp), &std::rand); + m_session_id.resize(sizeof(tmp)*2); + to_hex(tmp, 20, &m_session_id[0]); + + m_sam_socket.reset(new i2p_stream(m_io_service)); + m_sam_socket->set_proxy(m_hostname, m_port); + m_sam_socket->set_command(i2p_stream::cmd_create_session); + m_sam_socket->set_session_id(m_session_id.c_str()); + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("i2p_stream::on_sam_connect"); +#endif + m_sam_socket->async_connect(tcp::endpoint() + , boost::bind(&i2p_connection::on_sam_connect, this, _1, handler, m_sam_socket)); + } + + void i2p_connection::on_sam_connect(error_code const& ec, i2p_stream::handler_type const& h, boost::shared_ptr) + { +#if defined TORRENT_ASIO_DEBUGGING + complete_async("i2p_stream::on_sam_connect"); +#endif + m_state = sam_idle; + + if (ec) + { + h(ec); + return; + } + + do_name_lookup("ME", boost::bind(&i2p_connection::set_local_endpoint, this, _1, _2, h)); + } + + void i2p_connection::set_local_endpoint(error_code const& ec, char const* dest + , i2p_stream::handler_type const& h) + { + if (!ec && dest != 0) + m_i2p_local_endpoint = dest; + else + m_i2p_local_endpoint.clear(); + + h(ec); + } + + void i2p_connection::async_name_lookup(char const* name + , i2p_connection::name_lookup_handler handler) + { + if (m_state == sam_idle && m_name_lookup.empty() && is_open()) + do_name_lookup(name, handler); + else + m_name_lookup.push_back(std::make_pair(std::string(name), handler)); + } + + void i2p_connection::do_name_lookup(std::string const& name + , name_lookup_handler const& handler) + { + TORRENT_ASSERT(m_state == sam_idle); + m_state = sam_name_lookup; + m_sam_socket->set_name_lookup(name.c_str()); + boost::shared_ptr h(new i2p_stream::handler_type( + boost::bind(&i2p_connection::on_name_lookup, this, _1, handler, m_sam_socket))); + m_sam_socket->send_name_lookup(h); + } + + void i2p_connection::on_name_lookup(error_code const& ec + , name_lookup_handler handler, boost::shared_ptr) + { + m_state = sam_idle; + + std::string name = m_sam_socket->name_lookup(); + if (!m_name_lookup.empty()) + { + std::pair& nl = m_name_lookup.front(); + do_name_lookup(nl.first, nl.second); + m_name_lookup.pop_front(); + } + + if (ec) + { + handler(ec, 0); + return; + } + + handler(ec, name.c_str()); + } + + i2p_stream::i2p_stream(io_service& io_service) + : proxy_base(io_service) + , m_id(0) + , m_command(cmd_create_session) + , m_state(0) + { +#if TORRENT_USE_ASSERTS + m_magic = 0x1337; +#endif + } + + i2p_stream::~i2p_stream() + { +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(m_magic == 0x1337); + m_magic = 0; +#endif + } + + void i2p_stream::do_connect(error_code const& e, tcp::resolver::iterator i + , boost::shared_ptr h) + { + TORRENT_ASSERT(m_magic == 0x1337); + if (e || i == tcp::resolver::iterator()) + { + (*h)(e); + error_code ec; + close(ec); + return; + } + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("i2p_stream::connected"); +#endif + m_sock.async_connect(i->endpoint(), boost::bind( + &i2p_stream::connected, this, _1, h)); + } + + void i2p_stream::connected(error_code const& e, boost::shared_ptr h) + { + TORRENT_ASSERT(m_magic == 0x1337); +#if defined TORRENT_ASIO_DEBUGGING + complete_async("i2p_stream::connected"); +#endif + 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"; + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("i2p_stream::start_read_line"); +#endif + async_write(m_sock, boost::asio::buffer(cmd, sizeof(cmd) - 1) + , boost::bind(&i2p_stream::start_read_line, this, _1, h)); +// fprintf(stderr, ">>> %s", cmd); + } + + void i2p_stream::start_read_line(error_code const& e, boost::shared_ptr h) + { + TORRENT_ASSERT(m_magic == 0x1337); +#if defined TORRENT_ASIO_DEBUGGING + complete_async("i2p_stream::start_read_line"); +#endif + if (handle_error(e, h)) return; + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("i2p_stream::read_line"); +#endif + m_buffer.resize(1); + async_read(m_sock, boost::asio::buffer(m_buffer) + , boost::bind(&i2p_stream::read_line, this, _1, h)); + } + + void i2p_stream::read_line(error_code const& e, boost::shared_ptr h) + { + TORRENT_ASSERT(m_magic == 0x1337); +#if defined TORRENT_ASIO_DEBUGGING + complete_async("i2p_stream::read_line"); +#endif + if (handle_error(e, h)) return; + + int read_pos = m_buffer.size(); + + // look for \n which means end of the response + if (m_buffer[read_pos - 1] != '\n') + { +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("i2p_stream::read_line"); +#endif + // read another byte from the socket + m_buffer.resize(read_pos + 1); + async_read(m_sock, boost::asio::buffer(&m_buffer[read_pos], 1) + , boost::bind(&i2p_stream::read_line, this, _1, 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 + , get_i2p_category()); + + // null-terminate the string and parse it + m_buffer.push_back(0); + char* ptr = &m_buffer[0]; + char* next = ptr; + + char const* expect1 = 0; + char const* expect2 = 0; + + switch (m_state) + { + case read_hello_response: + expect1 = "HELLO"; + expect2 = "REPLY"; + break; + case read_connect_response: + case read_accept_response: + expect1 = "STREAM"; + expect2 = "STATUS"; + break; + case read_session_create_response: + expect1 = "SESSION"; + expect2 = "STATUS"; + break; + case read_name_lookup_response: + expect1 = "NAMING"; + expect2 = "REPLY"; + break; + } + +// fprintf(stderr, "<<< %s\n", &m_buffer[0]); + ptr = string_tokenize(next, ' ', &next); + if (ptr == 0 || expect1 == 0 || strcmp(expect1, ptr)) { handle_error(invalid_response, h); return; } + ptr = string_tokenize(next, ' ', &next); + if (ptr == 0 || expect2 == 0 || strcmp(expect2, ptr)) { handle_error(invalid_response, h); return; } + + int result = 0; +// char const* message = 0; +// float version = 3.0f; + + for(;;) + { + char* name = string_tokenize(next, '=', &next); + if (name == 0) break; +// fprintf(stderr, "name=\"%s\"\n", name); + char* ptr2 = string_tokenize(next, ' ', &next); + if (ptr2 == 0) { handle_error(invalid_response, h); return; } +// fprintf(stderr, "value=\"%s\"\n", ptr2); + + if (strcmp("RESULT", name) == 0) + { + if (strcmp("OK", ptr2) == 0) + result = i2p_error::no_error; + else if (strcmp("CANT_REACH_PEER", ptr2) == 0) + result = i2p_error::cant_reach_peer; + else if (strcmp("I2P_ERROR", ptr2) == 0) + result = i2p_error::i2p_error; + else if (strcmp("INVALID_KEY", ptr2) == 0) + result = i2p_error::invalid_key; + else if (strcmp("INVALID_ID", ptr2) == 0) + result = i2p_error::invalid_id; + else if (strcmp("TIMEOUT", ptr2) == 0) + result = i2p_error::timeout; + else if (strcmp("KEY_NOT_FOUND", ptr2) == 0) + result = i2p_error::key_not_found; + else if (strcmp("DUPLICATED_ID", ptr2) == 0) + result = i2p_error::duplicated_id; + else + result = i2p_error::num_errors; // unknown error + } + else if (strcmp("MESSAGE", name) == 0) + { +// message = ptr2; + } + else if (strcmp("VERSION", name) == 0) + { +// version = float(atof(ptr2)); + } + else if (strcmp("VALUE", name) == 0) + { + m_name_lookup = ptr2; + } + else if (strcmp("DESTINATION", name) == 0) + { + m_dest = ptr2; + } + } + + error_code ec(result, get_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(h); + break; + case cmd_accept: + send_accept(h); + break; + case cmd_connect: + send_connect(h); + break; + default: + (*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); +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("i2p_stream::read_line"); +#endif + async_read(m_sock, boost::asio::buffer(m_buffer) + , boost::bind(&i2p_stream::read_line, this, _1, h)); + break; + } + + return; + } + + void i2p_stream::send_connect(boost::shared_ptr h) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_state = read_connect_response; + char cmd[1024]; + int size = snprintf(cmd, sizeof(cmd), "STREAM CONNECT ID=%s DESTINATION=%s\n" + , m_id, m_dest.c_str()); +// fprintf(stderr, ">>> %s", cmd); +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("i2p_stream::start_read_line"); +#endif + async_write(m_sock, boost::asio::buffer(cmd, size) + , boost::bind(&i2p_stream::start_read_line, this, _1, h)); + } + + void i2p_stream::send_accept(boost::shared_ptr h) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_state = read_accept_response; + char cmd[400]; + int size = snprintf(cmd, sizeof(cmd), "STREAM ACCEPT ID=%s\n", m_id); +// fprintf(stderr, ">>> %s", cmd); +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("i2p_stream::start_read_line"); +#endif + async_write(m_sock, boost::asio::buffer(cmd, size) + , boost::bind(&i2p_stream::start_read_line, this, _1, h)); + } + + void i2p_stream::send_session_create(boost::shared_ptr h) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_state = read_session_create_response; + char cmd[400]; + int size = snprintf(cmd, sizeof(cmd), "SESSION CREATE STYLE=STREAM ID=%s DESTINATION=TRANSIENT\n" + , m_id); +// fprintf(stderr, ">>> %s", cmd); +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("i2p_stream::start_read_line"); +#endif + async_write(m_sock, boost::asio::buffer(cmd, size) + , boost::bind(&i2p_stream::start_read_line, this, _1, h)); + } + + void i2p_stream::send_name_lookup(boost::shared_ptr h) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_state = read_name_lookup_response; + char cmd[1024]; + int size = snprintf(cmd, sizeof(cmd), "NAMING LOOKUP NAME=%s\n", m_name_lookup.c_str()); +// fprintf(stderr, ">>> %s", cmd); +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("i2p_stream::start_read_line"); +#endif + async_write(m_sock, boost::asio::buffer(cmd, size) + , boost::bind(&i2p_stream::start_read_line, this, _1, h)); + } +} + +#endif + diff --git a/src/identify_client.cpp b/src/identify_client.cpp new file mode 100644 index 0000000..a04444b --- /dev/null +++ b/src/identify_client.cpp @@ -0,0 +1,422 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#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" + +namespace +{ + + using namespace libtorrent; + + int decode_digit(char c) + { + if (is_digit(c)) return c - '0'; + return unsigned(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(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] = id[1]; + ret.name[1] = 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(id[0]) && !is_digit(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] = 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, "%c%d-%d-%d--", &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 + map_entry name_map[] = + { + {"A", "ABC"} + , {"AG", "Ares"} + , {"AR", "Arctic Torrent"} + , {"AT", "Artemis"} + , {"AV", "Avicora"} + , {"AX", "BitPump"} + , {"AZ", "Azureus"} + , {"A~", "Ares"} + , {"BB", "BitBuddy"} + , {"BC", "BitComet"} + , {"BE", "baretorrent"} + , {"BF", "Bitflu"} + , {"BG", "BTG"} + , {"BL", "BitBlinder"} + , {"BP", "BitTorrent Pro"} + , {"BR", "BitRocket"} + , {"BS", "BTSlave"} + , {"BT", "BitTorrent"} + , {"BU", "BigUp"} + , {"BW", "BitWombat"} + , {"BX", "BittorrentX"} + , {"CD", "Enhanced CTorrent"} + , {"CT", "CTorrent"} + , {"DE", "Deluge"} + , {"DP", "Propagate Data Client"} + , {"EB", "EBit"} + , {"ES", "electric sheep"} + , {"FC", "FileCroc"} + , {"FT", "FoxTorrent"} + , {"FX", "Freebox BitTorrent"} + , {"GS", "GSTorrent"} + , {"HK", "Hekate"} + , {"HL", "Halite"} + , {"HN", "Hydranode"} + , {"IL", "iLivid"} + , {"KG", "KGet"} + , {"KT", "KTorrent"} + , {"LC", "LeechCraft"} + , {"LH", "LH-ABC"} + , {"LK", "Linkage"} + , {"LP", "lphant"} + , {"LT", "libtorrent"} + , {"LW", "Limewire"} + , {"M", "Mainline"} + , {"ML", "MLDonkey"} + , {"MO", "Mono Torrent"} + , {"MP", "MooPolice"} + , {"MR", "Miro"} + , {"MT", "Moonlight Torrent"} + , {"NX", "Net Transport"} + , {"O", "Osprey Permaseed"} + , {"OS", "OneSwarm"} + , {"OT", "OmegaTorrent"} + , {"PD", "Pando"} + , {"Q", "BTQueue"} + , {"QD", "QQDownload"} + , {"QT", "Qt 4"} + , {"R", "Tribler"} + , {"RT", "Retriever"} + , {"RZ", "RezTorrent"} + , {"S", "Shadow"} + , {"SB", "Swiftbit"} + , {"SD", "Xunlei"} + , {"SK", "spark"} + , {"SN", "ShareNet"} + , {"SS", "SwarmScope"} + , {"ST", "SymTorrent"} + , {"SZ", "Shareaza"} + , {"S~", "Shareaza (beta)"} + , {"T", "BitTornado"} + , {"TB", "Torch"} + , {"TL", "Tribler"} + , {"TN", "Torrent.NET"} + , {"TR", "Transmission"} + , {"TS", "TorrentStorm"} + , {"TT", "TuoTu"} + , {"U", "UPnP"} + , {"UL", "uLeecher"} + , {"UM", "uTorrent Mac"} + , {"UT", "uTorrent"} + , {"VG", "Vagaa"} + , {"WT", "BitLet"} + , {"WY", "FireTorrent"} + , {"XF", "Xfplay"} + , {"XL", "Xunlei"} + , {"XS", "XSwifter"} + , {"XT", "XanTorrent"} + , {"XX", "Xtorrent"} + , {"ZT", "ZipTorrent"} + , {"lt", "rTorrent"} + , {"pX", "pHoeniX"} + , {"qB", "qBittorrent"} + , {"st", "SharkTorrent"} + }; + + struct generic_map_entry + { + int offset; + char const* id; + char const* name; + }; + // non-standard names + generic_map_entry generic_mappings[] = + { + {0, "Deadman Walking-", "Deadman"} + , {5, "Azureus", "Azureus 2.0.3.2"} + , {0, "DansClient", "XanTorrent"} + , {4, "btfans", "SimpleBT"} + , {0, "PRC.P---", "Bittorrent Plus! II"} + , {0, "P87.P---", "Bittorrent Plus!"} + , {0, "S587Plus", "Bittorrent Plus!"} + , {0, "martini", "Martini Man"} + , {0, "Plus---", "Bittorrent Plus"} + , {0, "turbobt", "TurboBT"} + , {0, "a00---0", "Swarmy"} + , {0, "a02---0", "Swarmy"} + , {0, "T00---0", "Teeweety"} + , {0, "BTDWV-", "Deadman Walking"} + , {2, "BS", "BitSpirit"} + , {0, "Pando-", "Pando"} + , {0, "LIME", "LimeWire"} + , {0, "btuga", "BTugaXP"} + , {0, "oernu", "BTugaXP"} + , {0, "Mbrst", "Burst!"} + , {0, "PEERAPP", "PeerApp"} + , {0, "Plus", "Plus!"} + , {0, "-Qt-", "Qt"} + , {0, "exbc", "BitComet"} + , {0, "DNA", "BitTorrent DNA"} + , {0, "-G3", "G3 Torrent"} + , {0, "-FG", "FlashGet"} + , {0, "-ML", "MLdonkey"} + , {0, "-MG", "Media Get"} + , {0, "XBT", "XBT"} + , {0, "OP", "Opera"} + , {2, "RS", "Rufus"} + , {0, "AZ2500BT", "BitTyrant"} + , {0, "btpd/", "BitTorrent Protocol Daemon"} + , {0, "TIX", "Tixati"} + , {0, "QVOD", "Qvod"} + }; + + bool compare_id(map_entry const& lhs, map_entry const& rhs) + { + return lhs.id[0] < rhs.id[0] + || ((lhs.id[0] == rhs.id[0]) && (lhs.id[1] < rhs.id[1])); + } + + std::string lookup(fingerprint const& f) + { + char identity[200]; + + const int size = sizeof(name_map)/sizeof(name_map[0]); + map_entry tmp = {f.name, ""}; + 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 = 0; + 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 + memcpy(temp, f.name, 2); + temp[2] = 0; + name = temp; + } + + int num_chars = snprintf(identity, sizeof(identity), "%s %u.%u.%u", name + , f.major_version, f.minor_version, f.revision_version); + + if (f.tag_version != 0) + { + snprintf(identity + num_chars, sizeof(identity) - num_chars + , ".%u", f.tag_version); + } + + return identity; + } + + bool find_string(char const* id, char const* search) + { + return std::equal(search, search + std::strlen(search), id); + } +} + +namespace libtorrent +{ + + 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 + f = parse_mainline_style(p); + if (f) return f; + return f; + } + + std::string identify_client(peer_id const& p) + { + char const* PID = p.data(); + boost::optional f; + + if (p.is_all_zeros()) return "Unknown"; + + // ---------------------- + // non standard encodings + // ---------------------- + + int num_generic_mappings = sizeof(generic_mappings) / sizeof(generic_mappings[0]); + + for (int i = 0; i < num_generic_mappings; ++i) + { + generic_map_entry const& e = generic_mappings[i]; + 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, PID + 14); + return std::string("eXeem ('") + user.c_str() + "')"; + } + + if (std::equal(PID, PID + 13, "\0\0\0\0\0\0\0\0\0\0\0\0\x97")) + return "Experimental 3.2.1b2"; + + if (std::equal(PID, PID + 13, "\0\0\0\0\0\0\0\0\0\0\0\0\0")) + return "Experimental 3.1"; + + // look for azureus style id + 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 (std::equal(PID, PID + 12, "\0\0\0\0\0\0\0\0\0\0\0\0")) + return "Generic"; + + std::string unknown("Unknown ["); + for (peer_id::const_iterator i = p.begin(); i != p.end(); ++i) + { + unknown += is_print(char(*i))?*i:'.'; + } + unknown += "]"; + return unknown; + } +} + diff --git a/src/instantiate_connection.cpp b/src/instantiate_connection.cpp new file mode 100644 index 0000000..0512b6c --- /dev/null +++ b/src/instantiate_connection.cpp @@ -0,0 +1,150 @@ +/* + +Copyright (c) 2007-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/socket.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/socket_type.hpp" +#include "libtorrent/utp_socket_manager.hpp" +#include "libtorrent/instantiate_connection.hpp" +#include +#include + +namespace libtorrent +{ + // TODO: 2 peer_connection and tracker_connection should probably be flags + // TODO: 2 move this function into libtorrent::aux namespace + bool instantiate_connection(io_service& ios + , aux::proxy_settings const& ps, socket_type& s + , void* ssl_context + , utp_socket_manager* sm + , bool peer_connection + , bool tracker_connection) + { +#ifndef TORRENT_USE_OPENSSL + TORRENT_UNUSED(ssl_context); +#endif + + if (sm) + { + utp_stream* str; +#ifdef TORRENT_USE_OPENSSL + if (ssl_context) + { + s.instantiate >(ios, ssl_context); + str = &s.get >()->next_layer(); + } + else +#endif + { + s.instantiate(ios); + str = s.get(); + } + str->set_impl(sm->new_utp_socket(str)); + } +#if TORRENT_USE_I2P + else if (ps.type == settings_pack::i2p_proxy) + { + // it doesn't make any sense to try ssl over i2p + TORRENT_ASSERT(ssl_context == 0); + s.instantiate(ios); + s.get()->set_proxy(ps.hostname, ps.port); + } +#endif + else if (ps.type == settings_pack::none + || (peer_connection && !ps.proxy_peer_connections) + || (tracker_connection && !ps.proxy_tracker_connections)) + { +#ifdef TORRENT_USE_OPENSSL + if (ssl_context) + { + s.instantiate >(ios, ssl_context); + } + else +#endif + { + s.instantiate(ios); + } + } + else if (ps.type == settings_pack::http + || ps.type == settings_pack::http_pw) + { + http_stream* str; +#ifdef TORRENT_USE_OPENSSL + if (ssl_context) + { + s.instantiate >(ios, ssl_context); + str = &s.get >()->next_layer(); + } + else +#endif + { + s.instantiate(ios); + str = s.get(); + } + + str->set_proxy(ps.hostname, ps.port); + if (ps.type == settings_pack::http_pw) + str->set_username(ps.username, ps.password); + } + else if (ps.type == settings_pack::socks5 + || ps.type == settings_pack::socks5_pw + || ps.type == settings_pack::socks4) + { + socks5_stream* str; +#ifdef TORRENT_USE_OPENSSL + if (ssl_context) + { + s.instantiate >(ios, ssl_context); + str = &s.get >()->next_layer(); + } + else +#endif + { + s.instantiate(ios); + str = s.get(); + } + str->set_proxy(ps.hostname, ps.port); + if (ps.type == settings_pack::socks5_pw) + str->set_username(ps.username, ps.password); + if (ps.type == settings_pack::socks4) + str->set_version(4); + } + else + { + TORRENT_ASSERT_VAL(false, ps.type); + return false; + } + return true; + } + +} + diff --git a/src/ip_filter.cpp b/src/ip_filter.cpp new file mode 100644 index 0000000..fa3f78b --- /dev/null +++ b/src/ip_filter.cpp @@ -0,0 +1,98 @@ +/* + +Copyright (c) 2005-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/ip_filter.hpp" +#include + + +namespace libtorrent +{ + void ip_filter::add_rule(address first, address last, boost::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); + } +#if TORRENT_USE_IPV6 + 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); + } +#endif + else + TORRENT_ASSERT(false); + } + + int ip_filter::access(address const& addr) const + { + if (addr.is_v4()) + return m_filter4.access(addr.to_v4().to_bytes()); +#if TORRENT_USE_IPV6 + TORRENT_ASSERT(addr.is_v6()); + return m_filter6.access(addr.to_v6().to_bytes()); +#else + return 0; +#endif + } + + ip_filter::filter_tuple_t ip_filter::export_filter() const + { +#if TORRENT_USE_IPV6 + return boost::make_tuple(m_filter4.export_filter() + , m_filter6.export_filter()); +#else + return m_filter4.export_filter(); +#endif + } + + void port_filter::add_rule(boost::uint16_t first, boost::uint16_t last, boost::uint32_t flags) + { + m_filter.add_rule(first, last, flags); + } + + int port_filter::access(boost::uint16_t port) const + { + return m_filter.access(port); + } +/* + void ip_filter::print() const + { + for (range_t::iterator i = m_access_list.begin(); i != m_access_list.end(); ++i) + { + std::cout << i->start.as_string() << " " << i->access << "\n"; + } + } +*/ +} + diff --git a/src/ip_voter.cpp b/src/ip_voter.cpp new file mode 100644 index 0000000..8d0697f --- /dev/null +++ b/src/ip_voter.cpp @@ -0,0 +1,194 @@ +/* + +Copyright (c) 2013-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/ip_voter.hpp" +#include "libtorrent/broadcast_socket.hpp" // for is_any() etc. +#include "libtorrent/socket_io.hpp" // for hash_address +#include "libtorrent/random.hpp" // for random() +#include "libtorrent/aux_/time.hpp" // for aux::time_now() + +#include + +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 + std::vector::iterator i; + 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; + } + + 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 + , int source_type, address const& source) + { + if (is_any(ip)) return false; + if (is_local(ip)) return false; + if (is_loopback(ip)) return false; + + // don't trust source that aren't connected to us + // on a different address family than the external + // IP they claim we have + if (ip.is_v4() != source.is_v4()) return false; + + // this is the key to use for the bloom filters + // it represents the identity of the voter + sha1_hash k; + hash_address(source, k); + + // do we already have an entry for this external IP? + std::vector::iterator i = std::find_if(m_external_addresses.begin() + , m_external_addresses.end(), boost::bind(&external_ip_t::addr, _1) == 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() % 100 < 50) + 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.push_back(external_ip_t()); + 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_v4()) + { + // 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, int type) + { + sources |= type; + if (voters.find(k)) return false; + voters.set(k); + ++num_votes; + return true; + } + + bool external_ip::cast_vote(address const& ip, int source_type, address const& source) + { + return m_vote_group[ip.is_v6()].cast_vote(ip, source_type, source); + } + + address external_ip::external_address(address const& ip) const + { + address ext = m_vote_group[ip.is_v6()].external_address(); +#if TORRENT_USE_IPV6 + if (ip.is_v6() && ext == address_v4()) return address_v6(); +#endif + return ext; + } +} + diff --git a/src/kademlia/dht_storage.cpp b/src/kademlia/dht_storage.cpp new file mode 100644 index 0000000..57d2fd1 --- /dev/null +++ b/src/kademlia/dht_storage.cpp @@ -0,0 +1,576 @@ +/* + +Copyright (c) 2012-2016, Arvid Norberg, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/kademlia/dht_storage.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include // for memset + +namespace libtorrent { +namespace dht { +namespace +{ + using detail::write_endpoint; + + // 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; + }; + + // 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::set peers; + }; + +#ifndef TORRENT_NO_DEPRECATE + struct count_peers + { + int* count; + count_peers(int* c): count(c) {} + void operator()(std::pair const& t) + { + *count += t.second.peers.size(); + } + }; +#endif + + // TODO: 2 make this configurable in dht_settings + enum { announce_interval = 30 }; + + struct dht_immutable_item + { + dht_immutable_item() : value(0), num_announcers(0), size(0) {} + // malloced space for the actual value + char* 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 + time_point last_seen; + // number of IPs in the bloom filter + int num_announcers; + // size of malloced space pointed to by value + int size; + }; + + struct ed25519_public_key { char bytes[item_pk_len]; }; + + struct dht_mutable_item : dht_immutable_item + { + char sig[item_sig_len]; + boost::int64_t seq; + ed25519_public_key key; + char* salt; + int salt_size; + }; + + void touch_item(dht_immutable_item* f, address const& address) + { + f->last_seen = aux::time_now(); + + // maybe increase num_announcers if we haven't seen this IP before + sha1_hash iphash; + hash_address(address, iphash); + 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 + { + immutable_item_comparator(node_id const& our_id) : m_our_id(our_id) {} + immutable_item_comparator(immutable_item_comparator const& c) + : m_our_id(c.m_our_id) {} + + bool operator() (std::pair const& lhs + , std::pair const& rhs) const + { + int l_distance = distance_exp(lhs.first, m_our_id); + int r_distance = distance_exp(rhs.first, m_our_id); + + // 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&); + + node_id const& m_our_id; + }; + + class dht_default_storage TORRENT_FINAL : public dht_storage_interface, boost::noncopyable + { + typedef std::map table_t; + typedef std::map dht_immutable_table_t; + typedef std::map dht_mutable_table_t; + + public: + + dht_default_storage(sha1_hash const& id, dht_settings const& settings) + : m_id(id) + , m_settings(settings) + { + memset(&m_counters, 0, sizeof(m_counters)); + } + + ~dht_default_storage() {} + +#ifndef TORRENT_NO_DEPRECATE + size_t num_torrents() const TORRENT_OVERRIDE { return m_map.size(); } + size_t num_peers() const TORRENT_OVERRIDE + { + int ret = 0; + std::for_each(m_map.begin(), m_map.end(), count_peers(&ret)); + return ret; + } +#endif + + bool get_peers(sha1_hash const& info_hash + , bool noseed, bool scrape + , entry& peers) const TORRENT_OVERRIDE + { + table_t::const_iterator i = m_map.lower_bound(info_hash); + if (i == m_map.end()) return false; + if (i->first != info_hash) return false; + + torrent_entry const& v = i->second; + + if (!v.name.empty()) peers["n"] = v.name; + + if (scrape) + { + bloom_filter<256> downloaders; + bloom_filter<256> seeds; + + for (std::set::const_iterator peer_it = v.peers.begin() + , end(v.peers.end()); peer_it != end; ++peer_it) + { + sha1_hash iphash; + hash_address(peer_it->addr.address(), iphash); + if (peer_it->seed) seeds.set(iphash); + else downloaders.set(iphash); + } + + peers["BFpe"] = downloaders.to_string(); + peers["BFsd"] = seeds.to_string(); + } + else + { + int num = (std::min)(int(v.peers.size()), m_settings.max_peers_reply); + std::set::const_iterator iter = v.peers.begin(); + entry::list_type& pe = peers["values"].list(); + std::string endpoint; + + for (int t = 0, m = 0; m < num && iter != v.peers.end(); ++iter, ++t) + { + if ((random() / float(UINT_MAX + 1.f)) * (num - t) >= num - m) continue; + if (noseed && iter->seed) continue; + endpoint.resize(18); + std::string::iterator out = endpoint.begin(); + write_endpoint(iter->addr, out); + endpoint.resize(out - endpoint.begin()); + pe.push_back(entry(endpoint)); + + ++m; + } + } + return true; + } + + void announce_peer(sha1_hash const& info_hash + , tcp::endpoint const& endp + , std::string const& name, bool seed) TORRENT_OVERRIDE + { + table_t::iterator ti = m_map.find(info_hash); + torrent_entry* v; + if (ti == m_map.end()) + { + // we don't have this torrent, add it + // do we need to remove another one first? + if (!m_map.empty() && int(m_map.size()) >= m_settings.max_torrents) + { + // we need to remove some. Remove the ones with the + // fewest peers + int num_peers = m_map.begin()->second.peers.size(); + table_t::iterator candidate = m_map.begin(); + for (table_t::iterator i = m_map.begin() + , end(m_map.end()); i != end; ++i) + { + if (int(i->second.peers.size()) > num_peers) continue; + if (i->first == info_hash) continue; + num_peers = i->second.peers.size(); + candidate = i; + } + m_map.erase(candidate); + m_counters.peers -= num_peers; + m_counters.torrents -= 1; + } + 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()) + { + std::string tname = name; + if (tname.size() > 100) tname.resize(100); + v->name = tname; + } + + peer_entry peer; + peer.addr = endp; + peer.added = aux::time_now(); + peer.seed = seed; + std::set::iterator i = v->peers.find(peer); + if (i != v->peers.end()) + { + v->peers.erase(i++); + m_counters.peers -= 1; + } + else if (v->peers.size() >= m_settings.max_peers) + { + // when we're at capacity, there's a 50/50 chance of dropping the + // announcing peer or an existing peer + if (random() & 1) return; + i = v->peers.lower_bound(peer); + if (i == v->peers.end()) --i; + v->peers.erase(i++); + m_counters.peers -= 1; + } + v->peers.insert(i, peer); + m_counters.peers += 1; + } + + bool get_immutable_item(sha1_hash const& target + , entry& item) const TORRENT_OVERRIDE + { + dht_immutable_table_t::const_iterator i = m_immutable_table.find(target); + if (i == m_immutable_table.end()) return false; + + item["v"] = bdecode(i->second.value, i->second.value + i->second.size); + return true; + } + + void put_immutable_item(sha1_hash const& target + , char const* buf, int size + , address const& addr) TORRENT_OVERRIDE + { + dht_immutable_table_t::iterator i = m_immutable_table.find(target); + if (i == m_immutable_table.end()) + { + // make sure we don't add too many items + if (int(m_immutable_table.size()) >= m_settings.max_dht_items) + { + // delete the least important one (i.e. the one + // the fewest peers are announcing, and farthest + // from our node ID) + dht_immutable_table_t::iterator j = std::min_element(m_immutable_table.begin() + , m_immutable_table.end() + , immutable_item_comparator(m_id)); + + TORRENT_ASSERT(j != m_immutable_table.end()); + free(j->second.value); + m_immutable_table.erase(j); + m_counters.immutable_data -= 1; + } + dht_immutable_item to_add; + to_add.value = static_cast(malloc(size)); + to_add.size = size; + memcpy(to_add.value, buf, size); + + boost::tie(i, boost::tuples::ignore) = m_immutable_table.insert( + std::make_pair(target, to_add)); + m_counters.immutable_data += 1; + } + +// 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 + , boost::int64_t& seq) const TORRENT_OVERRIDE + { + dht_mutable_table_t::const_iterator 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 + , boost::int64_t seq, bool force_fill + , entry& item) const TORRENT_OVERRIDE + { + dht_mutable_table_t::const_iterator 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; + if (force_fill || (0 <= seq && seq < f.seq)) + { + item["v"] = bdecode(f.value, f.value + f.size); + item["sig"] = std::string(f.sig, f.sig + sizeof(f.sig)); + item["k"] = std::string(f.key.bytes, f.key.bytes + sizeof(f.key.bytes)); + } + return true; + } + + void put_mutable_item(sha1_hash const& target + , char const* buf, int size + , char const* sig + , boost::int64_t seq + , char const* pk + , char const* salt, int salt_size + , address const& addr) TORRENT_OVERRIDE + { + dht_mutable_table_t::iterator i = m_mutable_table.find(target); + if (i == m_mutable_table.end()) + { + // this is the case where we don't have an item in this slot + // make sure we don't add too many items + if (int(m_mutable_table.size()) >= m_settings.max_dht_items) + { + // delete the least important one (i.e. the one + // the fewest peers are announcing) + // TODO: c++11 use a lambda here instead + dht_mutable_table_t::iterator j = std::min_element(m_mutable_table.begin() + , m_mutable_table.end() + , boost::bind(&dht_immutable_item::num_announcers + , boost::bind(&dht_mutable_table_t::value_type::second, _1)) + < boost::bind(&dht_immutable_item::num_announcers + , boost::bind(&dht_mutable_table_t::value_type::second, _2))); + TORRENT_ASSERT(j != m_mutable_table.end()); + free(j->second.value); + free(j->second.salt); + m_mutable_table.erase(j); + m_counters.mutable_data -= 1; + } + dht_mutable_item to_add; + to_add.value = static_cast(malloc(size)); + to_add.size = size; + to_add.seq = seq; + to_add.salt = NULL; + to_add.salt_size = 0; + if (salt_size > 0) + { + to_add.salt = static_cast(malloc(salt_size)); + to_add.salt_size = salt_size; + memcpy(to_add.salt, salt, salt_size); + } + memcpy(to_add.sig, sig, sizeof(to_add.sig)); + memcpy(to_add.value, buf, size); + memcpy(&to_add.key, pk, sizeof(to_add.key)); + + boost::tie(i, boost::tuples::ignore) = m_mutable_table.insert( + std::make_pair(target, to_add)); + m_counters.mutable_data += 1; + +// fprintf(stderr, "added mutable item (%d)\n", int(m_mutable_table.size())); + } + else + { + // this is the case where we already + dht_mutable_item* item = &i->second; + + if (item->seq < seq) + { + if (item->size != size) + { + free(item->value); + item->value = static_cast(malloc(size)); + item->size = size; + } + item->seq = seq; + memcpy(item->sig, sig, sizeof(item->sig)); + memcpy(item->value, buf, size); + } + } + + touch_item(&i->second, addr); + } + + void tick() TORRENT_OVERRIDE + { + time_point now(aux::time_now()); + + // look through all peers and see if any have timed out + for (table_t::iterator i = m_map.begin(), end(m_map.end()); i != end;) + { + torrent_entry& t = i->second; + purge_peers(t.peers); + + if (!t.peers.empty()) + { + ++i; + continue; + } + + // if there are no more peers, remove the entry altogether + m_map.erase(i++); + m_counters.torrents -= 1;// peers is decreased by purge_peers + } + + if (0 == m_settings.item_lifetime) return; + + time_duration lifetime = seconds(m_settings.item_lifetime); + // item lifetime must >= 120 minutes. + if (lifetime < minutes(120)) lifetime = minutes(120); + + for (dht_immutable_table_t::iterator i = m_immutable_table.begin(); + i != m_immutable_table.end();) + { + if (i->second.last_seen + lifetime > now) + { + ++i; + continue; + } + free(i->second.value); + m_immutable_table.erase(i++); + m_counters.immutable_data -= 1; + } + + for (dht_mutable_table_t::iterator i = m_mutable_table.begin(); + i != m_mutable_table.end();) + { + if (i->second.last_seen + lifetime > now) + { + ++i; + continue; + } + free(i->second.value); + free(i->second.salt); + m_mutable_table.erase(i++); + m_counters.mutable_data -= 1; + } + } + + virtual dht_storage_counters counters() const TORRENT_OVERRIDE + { + return m_counters; + } + + private: + sha1_hash m_id; + dht_settings const& m_settings; + dht_storage_counters m_counters; + + table_t m_map; + dht_immutable_table_t m_immutable_table; + dht_mutable_table_t m_mutable_table; + + void purge_peers(std::set& peers) + { + for (std::set::iterator i = peers.begin() + , end(peers.end()); i != end;) + { + // the peer has timed out + if (i->added + minutes(int(announce_interval * 1.5f)) < aux::time_now()) + { + peers.erase(i++); + m_counters.peers -= 1; + } + else + ++i; + } + } + }; +} + +dht_storage_interface* dht_default_storage_constructor(sha1_hash const& id + , dht_settings const& settings) +{ + return new dht_default_storage(id, settings); +} + +} } // namespace libtorrent::dht diff --git a/src/kademlia/dht_tracker.cpp b/src/kademlia/dht_tracker.cpp new file mode 100644 index 0000000..67beeb9 --- /dev/null +++ b/src/kademlia/dht_tracker.cpp @@ -0,0 +1,449 @@ +/* + +Copyright (c) 2006-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 "libtorrent/config.hpp" + +#include "libtorrent/kademlia/node.hpp" +#include "libtorrent/kademlia/node_id.hpp" +#include "libtorrent/kademlia/traversal_algorithm.hpp" +#include "libtorrent/kademlia/dht_tracker.hpp" +#include "libtorrent/kademlia/msg.hpp" +#include "libtorrent/kademlia/dht_observer.hpp" + +#include "libtorrent/socket.hpp" +#include "libtorrent/socket_io.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/version.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/performance_counters.hpp" // for counters + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +using boost::ref; +using libtorrent::dht::node; +using libtorrent::dht::node_id; +using libtorrent::dht::packet_t; +using libtorrent::dht::msg; +using libtorrent::detail::write_endpoint; + +namespace libtorrent { namespace dht +{ + void incoming_error(entry& e, char const* msg); + + namespace { + + // generate a new write token key every 5 minutes + time_duration const key_refresh + = duration_cast(minutes(5)); + + node_id extract_node_id(entry const& e) + { + if (e.type() != entry::dictionary_t) return (node_id::min)(); + entry const* nid = e.find_key("node-id"); + if (nid == NULL || nid->type() != entry::string_t || nid->string().length() != 20) + return (node_id::min)(); + return node_id(nid->string().c_str()); + } + + } // 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 + , rate_limited_udp_socket& sock + , dht_settings const& settings + , counters& cnt + , dht_storage_constructor_type storage_constructor + , entry const& state) + : m_counters(cnt) + , m_dht(this, settings, extract_node_id(state), observer, cnt, storage_constructor) + , m_sock(sock) + , m_log(observer) + , m_key_refresh_timer(sock.get_io_service()) + , m_connection_timer(sock.get_io_service()) + , m_refresh_timer(sock.get_io_service()) + , m_settings(settings) + , m_abort(false) + , m_host_resolver(sock.get_io_service()) + { +#ifndef TORRENT_DISABLE_LOGGING + m_log->log(dht_logger::tracker, "starting DHT tracker with node id: %s" + , to_hex(m_dht.nid().to_string()).c_str()); +#endif + } + + dht_tracker::~dht_tracker() {} + + void dht_tracker::update_node_id() + { + m_dht.update_node_id(); + } + + // defined in node.cpp + void nop(); + + void dht_tracker::start(entry const& bootstrap + , find_data::nodes_callback const& f) + { + std::vector initial_nodes; + + if (bootstrap.type() == entry::dictionary_t) + { + TORRENT_TRY { + if (entry const* nodes = bootstrap.find_key("nodes")) + read_endpoint_list(nodes, initial_nodes); + } TORRENT_CATCH(std::exception&) {} + } + + error_code ec; + refresh_key(ec); + + m_connection_timer.expires_from_now(seconds(1), ec); + m_connection_timer.async_wait( + boost::bind(&dht_tracker::connection_timeout, self(), _1)); + + m_refresh_timer.expires_from_now(seconds(5), ec); + m_refresh_timer.async_wait(boost::bind(&dht_tracker::refresh_timeout, self(), _1)); + m_dht.bootstrap(initial_nodes, f); + } + + void dht_tracker::stop() + { + m_abort = true; + error_code ec; + m_key_refresh_timer.cancel(ec); + m_connection_timer.cancel(ec); + m_refresh_timer.cancel(ec); + m_host_resolver.cancel(); + } + +#ifndef TORRENT_NO_DEPRECATE + void dht_tracker::dht_status(session_status& s) + { + m_dht.status(s); + } +#endif + + void dht_tracker::dht_status(std::vector& table + , std::vector& requests) + { + m_dht.status(table, requests); + } + + void dht_tracker::update_stats_counters(counters& c) const + { + m_dht.update_stats_counters(c); + } + + void dht_tracker::connection_timeout(error_code const& e) + { + if (e || m_abort) return; + + time_duration d = m_dht.connection_timeout(); + error_code ec; + m_connection_timer.expires_from_now(d, ec); + m_connection_timer.async_wait(boost::bind(&dht_tracker::connection_timeout, self(), _1)); + } + + void dht_tracker::refresh_timeout(error_code const& e) + { + if (e || m_abort) return; + + m_dht.tick(); + + // periodically update the DOS blocker's settings from the dht_settings + m_blocker.set_block_timer(m_settings.block_timeout); + m_blocker.set_rate_limit(m_settings.block_ratelimit); + + error_code ec; + m_refresh_timer.expires_from_now(seconds(5), ec); + m_refresh_timer.async_wait( + boost::bind(&dht_tracker::refresh_timeout, self(), _1)); + } + + void dht_tracker::refresh_key(error_code const& e) + { + if (e || m_abort) return; + + error_code ec; + m_key_refresh_timer.expires_from_now(key_refresh, ec); + m_key_refresh_timer.async_wait(boost::bind(&dht_tracker::refresh_key, self(), _1)); + + m_dht.new_write_key(); +#ifndef TORRENT_DISABLE_LOGGING + m_log->log(dht_logger::tracker, "*** new write key***"); +#endif + } + +/* +#if defined TORRENT_DEBUG && TORRENT_USE_IOSTREAM + std::ofstream st("dht_routing_table_state.txt", std::ios_base::trunc); + m_dht.print_state(st); +#endif +*/ + + void dht_tracker::get_peers(sha1_hash const& ih + , boost::function const&)> f) + { + m_dht.get_peers(ih, f, NULL, false); + } + + void dht_tracker::announce(sha1_hash const& ih, int listen_port, int flags + , boost::function const&)> f) + { + m_dht.announce(ih, listen_port, flags, f); + } + + void dht_tracker::get_item(sha1_hash const& target + , boost::function cb) + { + m_dht.get_item(target, cb); + } + + // key is a 32-byte binary string, the public key to look up. + // the salt is optional + void dht_tracker::get_item(char const* key + , boost::function cb + , std::string salt) + { + m_dht.get_item(key, salt, cb); + } + + void dht_tracker::put_item(entry const& data + , boost::function cb) + { + std::string flat_data; + bencode(std::back_inserter(flat_data), data); + sha1_hash target = item_target_id( + std::pair(flat_data.c_str(), flat_data.size())); + + m_dht.put_item(target, data, cb); + } + + void dht_tracker::put_item(char const* key + , boost::function cb + , boost::function data_cb, std::string salt) + { + m_dht.put_item(key, salt, cb, data_cb); + } + + void dht_tracker::direct_request(udp::endpoint ep, entry& e + , boost::function f) + { + m_dht.direct_request(ep, e, f); + } + + // translate bittorrent kademlia message into the generice kademlia message + // used by the library + bool dht_tracker::incoming_packet(error_code const& ec + , udp::endpoint const& ep, char const* buf, int size) + { + if (ec) + { + 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 + ) + { + m_dht.unreachable(ep); + } + return false; + } + + if (size <= 20 || *buf != 'd' || buf[size-1] != 'e') return false; + // remove this line/check once the DHT supports IPv6 + if (!ep.address().is_v4()) return false; + + m_counters.inc_stats_counter(counters::dht_bytes_in, size); + // account for IP and UDP overhead + m_counters.inc_stats_counter(counters::recv_ip_overhead_bytes + , ep.address().is_v6() ? 48 : 28); + m_counters.inc_stats_counter(counters::dht_messages_in); + + if (m_settings.ignore_dark_internet && ep.address().is_v4()) + { + 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 + boost::uint8_t class_a[] = { 3, 6, 7, 9, 11, 19, 21, 22, 25 + , 26, 28, 29, 30, 33, 34, 48, 51, 56 }; + + int num = sizeof(class_a)/sizeof(class_a[0]); + if (std::find(class_a, class_a + num, b[0]) != class_a + num) + return true; + } + + if (!m_blocker.incoming(ep.address(), clock_type::now(), m_log)) + return true; + + using libtorrent::entry; + using libtorrent::bdecode; + + TORRENT_ASSERT(size > 0); + + int pos; + error_code err; + int ret = bdecode(buf, buf + size, m_msg, err, &pos, 10, 500); + if (ret != 0) + { +#ifndef TORRENT_DISABLE_LOGGING + m_log->log_packet(dht_logger::incoming_message, buf, size, ep); +#endif + return false; + } + + if (m_msg.type() != bdecode_node::dict_t) + { +#ifndef TORRENT_DISABLE_LOGGING + m_log->log_packet(dht_logger::incoming_message, buf, size, 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 + , size, ep); +#endif + + libtorrent::dht::msg m(m_msg, ep); + m_dht.incoming(m); + return true; + } + + namespace { + + void add_node_fun(void* userdata, node_entry const& e) + { + entry* n = static_cast(userdata); + std::string node; + std::back_insert_iterator out(node); + write_endpoint(e.ep(), out); + n->list().push_back(entry(node)); + } + + } // anonymous namespace + + entry dht_tracker::state() const + { + entry ret(entry::dictionary_t); + { + entry nodes(entry::list_t); + m_dht.m_table.for_each_node(&add_node_fun, &add_node_fun, &nodes); + bucket_t cache; + m_dht.replacement_cache(cache); + for (bucket_t::iterator i(cache.begin()) + , end(cache.end()); i != end; ++i) + { + std::string node; + std::back_insert_iterator out(node); + write_endpoint(i->ep(), out); + nodes.list().push_back(entry(node)); + } + if (!nodes.list().empty()) + ret["nodes"] = nodes; + } + + ret["node-id"] = m_dht.nid().to_string(); + return ret; + } + + void dht_tracker::add_node(udp::endpoint node) + { + m_dht.add_node(node); + } + + void dht_tracker::add_router_node(udp::endpoint const& node) + { + m_dht.add_router_node(node); + } + + bool dht_tracker::has_quota() + { + return m_sock.has_quota(); + } + + bool dht_tracker::send_packet(libtorrent::entry& e, udp::endpoint const& addr, int send_flags) + { + using libtorrent::bencode; + using libtorrent::entry; + + static char const version_str[] = {'L', 'T' + , LIBTORRENT_VERSION_MAJOR, LIBTORRENT_VERSION_MINOR}; + e["v"] = std::string(version_str, version_str + 4); + + m_send_buf.clear(); + bencode(std::back_inserter(m_send_buf), e); + error_code ec; + + bool ret = m_sock.send(addr, &m_send_buf[0], int(m_send_buf.size()), ec, send_flags); + if (!ret || 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[0] + , m_send_buf.size(), addr); +#endif + return false; + } + + m_counters.inc_stats_counter(counters::dht_bytes_out, m_send_buf.size()); + // account for IP and UDP overhead + m_counters.inc_stats_counter(counters::sent_ip_overhead_bytes + , addr.address().is_v6() ? 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[0] + , m_send_buf.size(), addr); +#endif + return true; + } + +}} + diff --git a/src/kademlia/dos_blocker.cpp b/src/kademlia/dos_blocker.cpp new file mode 100644 index 0000000..7221e66 --- /dev/null +++ b/src/kademlia/dos_blocker.cpp @@ -0,0 +1,108 @@ +/* + +Copyright (c) 2006-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/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 (int i = 0; i < num_ban_nodes; ++i) + { + m_ban_nodes[i].count = 0; + m_ban_nodes[i].limit = min_time(); + } + } + + bool dos_blocker::incoming(address addr, time_point now, dht_logger* logger) + { + node_ban_entry* match = 0; + 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 + logger->log(dht_logger::tracker, "BANNING PEER [ ip: %s time: %f count: %d ]" + , print_address(addr).c_str() + , total_milliseconds((now - match->limit) + seconds(10)) / 1000.f + , int(match->count)); +#endif + // 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/find_data.cpp b/src/kademlia/find_data.cpp new file mode 100644 index 0000000..17092ae --- /dev/null +++ b/src/kademlia/find_data.cpp @@ -0,0 +1,182 @@ +/* + +Copyright (c) 2006-2016, Arvid Norberg & Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include +#include +#include +#include + +namespace libtorrent { namespace dht +{ + +using detail::read_endpoint_list; +using detail::read_v4_endpoint; +#if TORRENT_USE_IPV6 +using detail::read_v6_endpoint; +#endif + +void find_data_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, "[%p] missing response dict" + , static_cast(algorithm())); +#endif + timeout(); + return; + } + + bdecode_node id = r.dict_find_string("id"); + if (!id || id.string_length() != 20) + { +#ifndef TORRENT_DISABLE_LOGGING + get_observer()->log(dht_logger::traversal, "[%p] invalid id in response" + , static_cast(algorithm())); +#endif + timeout(); + return; + } + bdecode_node token = r.dict_find_string("token"); + if (token) + { + static_cast(algorithm())->got_write_token( + node_id(id.string_ptr()), token.string_value()); + } + + traversal_observer::reply(m); + done(); +} + +find_data::find_data( + node& dht_node + , node_id target + , nodes_callback const& ncallback) + : traversal_algorithm(dht_node, target) + , m_nodes_callback(ncallback) + , m_done(false) +{ +} + +void find_data::start() +{ + // if the user didn't add seed-nodes manually, grab k (bucket size) + // nodes from routing table. + if (m_results.empty()) + { + std::vector nodes; + m_node.m_table.find_node(m_target, nodes, routing_table::include_failed); + + for (std::vector::iterator i = nodes.begin() + , end(nodes.end()); i != end; ++i) + { + add_entry(i->id, i->ep(), observer::flag_initial); + } + } + + traversal_algorithm::start(); +} + +void find_data::got_write_token(node_id const& n, std::string const& write_token) +{ +#ifndef TORRENT_DISABLE_LOGGING + get_node().observer()->log(dht_logger::traversal + , "[%p] adding write token '%s' under id '%s'" + , static_cast(this), to_hex(write_token).c_str() + , to_hex(n.to_string()).c_str()); +#endif + m_write_tokens[n] = write_token; +} + +observer_ptr find_data::new_observer(void* ptr + , udp::endpoint const& ep, node_id const& id) +{ + observer_ptr o(new (ptr) find_data_observer(this, ep, id)); +#if defined TORRENT_DEBUG || defined TORRENT_RELEASE_ASSERTS + 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 + get_node().observer()->log(dht_logger::traversal, "[%p] %s DONE" + , static_cast(this), name()); +#endif + + std::vector > results; + int num_results = m_node.m_table.bucket_size(); + for (std::vector::iterator 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) == 0) + { +#ifndef TORRENT_DISABLE_LOGGING + get_node().observer()->log(dht_logger::traversal, "[%p] not alive: %s" + , static_cast(this), print_endpoint(o->target_ep()).c_str()); +#endif + continue; + } + std::map::iterator j = m_write_tokens.find(o->id()); + if (j == m_write_tokens.end()) + { +#ifndef TORRENT_DISABLE_LOGGING + get_node().observer()->log(dht_logger::traversal, "[%p] no write token: %s" + , static_cast(this), print_endpoint(o->target_ep()).c_str()); +#endif + continue; + } + results.push_back(std::make_pair(node_entry(o->id(), o->target_ep()), j->second)); +#ifndef TORRENT_DISABLE_LOGGING + get_node().observer()->log(dht_logger::traversal, "[%p] %s" + , static_cast(this), 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..61c9bd3 --- /dev/null +++ b/src/kademlia/get_item.cpp @@ -0,0 +1,229 @@ +/* + +Copyright (c) 2013, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include +#include +#include + +#if TORRENT_USE_ASSERTS +#include +#endif + +namespace libtorrent { namespace dht +{ + +void get_item::got_data(bdecode_node const& v, + char const* pk, + boost::uint64_t seq, + char 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 != m_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; + } + + // immutalbe data should has been handled before this line, only mutable + // data can reach here, which means pk and sig must be valid. + if (!pk || !sig) return; + + std::string temp_copy(m_data.salt()); + std::pair salt(temp_copy.c_str(), int(temp_copy.size())); + sha1_hash incoming_target = item_target_id(salt, pk); + if (incoming_target != m_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, 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 waitting 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 target + , data_callback const& dcallback + , nodes_callback const& ncallback) + : find_data(dht_node, target, ncallback) + , m_data_callback(dcallback) + , m_immutable(true) +{ +} + +get_item::get_item( + node& dht_node + , char const* pk + , std::string const& salt + , data_callback const& dcallback + , nodes_callback const& ncallback) + : find_data(dht_node, item_target_id( + std::make_pair(salt.c_str(), int(salt.size())), pk) + , ncallback) + , m_data_callback(dcallback) + , m_data(pk, salt) + , m_immutable(false) +{ +} + +char const* get_item::name() const { return "get"; } + +observer_ptr get_item::new_observer(void* ptr + , udp::endpoint const& ep, node_id const& id) +{ + observer_ptr o(new (ptr) get_item_observer(this, ep, id)); +#if TORRENT_USE_ASSERTS + o->m_in_constructor = false; +#endif + return o; +} + +bool get_item::invoke(observer_ptr o) +{ + if (m_done) + { + m_invoke_count = -1; + return false; + } + + entry e; + e["y"] = "q"; + entry& a = e["a"]; + + e["q"] = "get"; + a["target"] = m_target.to_string(); + + 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(m_target + == item_target_id(std::pair(m_data.salt().c_str() + , m_data.salt().size()) + , m_data.pk().data())); + } +#endif + } + + find_data::done(); +} + +void get_item_observer::reply(msg const& m) +{ + char const* pk = NULL; + char const* sig = NULL; + boost::uint64_t seq = 0; + + bdecode_node 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 k = r.dict_find_string("k"); + if (k && k.string_length() == item_pk_len) + pk = k.string_ptr(); + + bdecode_node s = r.dict_find_string("sig"); + if (s && s.string_length() == item_sig_len) + sig = s.string_ptr(); + + bdecode_node q = r.dict_find_int("seq"); + if (q) + seq = q.int_value(); + else if (pk && sig) + { + 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..05a7237 --- /dev/null +++ b/src/kademlia/get_peers.cpp @@ -0,0 +1,338 @@ +/* + +Copyright (c) 2006-2016, Arvid Norberg & Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include +#include + +namespace libtorrent { namespace dht +{ + +using detail::read_endpoint_list; +using detail::read_v4_endpoint; +#if TORRENT_USE_IPV6 +using detail::read_v6_endpoint; +#endif + +void get_peers_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, "[%p] missing response dict" + , static_cast(algorithm())); +#endif + timeout(); + return; + } + + // look for peers + bdecode_node 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) + { + // 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 + bdecode_node id = r.dict_find_string("id"); + if (id && id.string_length() == 20) + { + get_observer()->log(dht_logger::traversal, "[%p] PEERS " + "invoke-count: %d branch-factor: %d addr: %s id: %s distance: %d p: %d" + , static_cast(algorithm()) + , algorithm()->invoke_count() + , algorithm()->branch_factor() + , print_endpoint(m.addr).c_str() + , to_hex(id.string_value()).c_str() + , distance_exp(algorithm()->target(), node_id(id.string_ptr())) + , int((end - peers) / 6)); + } +#endif + while (end - peers >= 6) + peer_list.push_back(read_v4_endpoint(peers)); + } + else + { + // assume it's uTorrent/libtorrent format + read_endpoint_list(n, peer_list); +#ifndef TORRENT_DISABLE_LOGGING + bdecode_node id = r.dict_find_string("id"); + if (id && id.string_length() == 20) + { + get_observer()->log(dht_logger::traversal, "[%p] PEERS " + "invoke-count: %d branch-factor: %d addr: %s id: %s distance: %d p: %d" + , static_cast(algorithm()) + , algorithm()->invoke_count() + , algorithm()->branch_factor() + , print_endpoint(m.addr).c_str() + , to_hex(id.string_value()).c_str() + , distance_exp(algorithm()->target(), node_id(id.string_ptr())) + , int(n.list_size())); + } +#endif + } + static_cast(algorithm())->got_peers(peer_list); + } + + find_data_observer::reply(m); +} + +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 target + , data_callback const& dcallback + , nodes_callback const& ncallback + , bool noseeds) + : find_data(dht_node, target, ncallback) + , m_data_callback(dcallback) + , m_noseeds(noseeds) +{ +} + +char const* get_peers::name() const { return "get_peers"; } + +bool get_peers::invoke(observer_ptr o) +{ + if (m_done) + { + m_invoke_count = -1; + return false; + } + + entry e; + e["y"] = "q"; + entry& a = e["a"]; + + e["q"] = "get_peers"; + a["info_hash"] = m_target.to_string(); + if (m_noseeds) a["noseed"] = 1; + + if (m_node.observer()) + { + m_node.observer()->outgoing_get_peers(m_target, m_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(void* ptr + , udp::endpoint const& ep, node_id const& id) +{ + observer_ptr o(new (ptr) get_peers_observer(this, ep, id)); +#if TORRENT_USE_ASSERTS + o->m_in_constructor = false; +#endif + return o; +} + +obfuscated_get_peers::obfuscated_get_peers( + node& dht_node + , node_id info_hash + , data_callback const& dcallback + , nodes_callback const& ncallback + , bool noseeds) + : get_peers(dht_node, info_hash, dcallback, ncallback, noseeds) + , m_obfuscated(true) +{ +} + +char const* obfuscated_get_peers::name() const +{ return !m_obfuscated ? get_peers::name() : "get_peers [obfuscated]"; } + +observer_ptr obfuscated_get_peers::new_observer(void* ptr + , udp::endpoint const& ep, node_id const& id) +{ + if (m_obfuscated) + { + observer_ptr o(new (ptr) obfuscated_get_peers_observer(this, ep, id)); +#if TORRENT_USE_ASSERTS + o->m_in_constructor = false; +#endif + return o; + } + else + { + observer_ptr o(new (ptr) get_peers_observer(this, ep, id)); +#if TORRENT_USE_ASSERTS + o->m_in_constructor = false; +#endif + return o; + } +} + +bool obfuscated_get_peers::invoke(observer_ptr o) +{ + if (!m_obfuscated) return get_peers::invoke(o); + + const node_id id = o->id(); + const int shared_prefix = 160 - distance_exp(id, m_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 (std::vector::iterator i = m_results.begin() + , end(m_results.end()); i != end; ++i) + { + observer* const node = i->get(); + // 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) == 0) 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 |= m_target & mask; + a["info_hash"] = obfuscated_target.to_string(); + + if (m_node.observer()) + { + m_node.observer()->outgoing_get_peers(m_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 + + boost::intrusive_ptr ta(new get_peers(m_node, m_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.clear(); + m_nodes_callback.clear(); + +#ifndef TORRENT_DISABLE_LOGGING + get_node().observer()->log(dht_logger::traversal, "[%p] obfuscated get_peers " + "phase 1 done, spawning get_peers [ %p ]" + , static_cast(this) + , static_cast(ta.get())); +#endif + + int num_added = 0; + for (std::vector::iterator 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) == 0) 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 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 id = r.dict_find_string("id"); + if (!id || id.string_length() != 20) + { +#ifndef TORRENT_DISABLE_LOGGING + get_observer()->log(dht_logger::traversal, "[%p] invalid id in response" + , static_cast(algorithm())); +#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..6a56ca0 --- /dev/null +++ b/src/kademlia/item.cpp @@ -0,0 +1,235 @@ +/* + +Copyright (c) 2013, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include + +#ifdef TORRENT_DEBUG +#include "libtorrent/bdecode.hpp" +#endif + +#ifdef TORRENT_USE_VALGRIND +#include +#endif + +namespace libtorrent { namespace dht +{ + +namespace +{ + enum { canonical_length = 1200 }; + int canonical_string(std::pair v, boost::uint64_t seq + , std::pair salt, char out[canonical_length]) + { + // v must be valid bencoding! +#ifdef TORRENT_DEBUG + bdecode_node e; + error_code ec; + TORRENT_ASSERT(bdecode(v.first, v.first + v.second, e, ec) == 0); +#endif + char* ptr = out; + + int left = canonical_length - (ptr - out); + if (salt.second > 0) + { + ptr += snprintf(ptr, left, "4:salt%d:", salt.second); + left = canonical_length - (ptr - out); + memcpy(ptr, salt.first, (std::min)(salt.second, left)); + ptr += (std::min)(salt.second, left); + left = canonical_length - (ptr - out); + } + ptr += snprintf(ptr, canonical_length - (ptr - out) + , "3:seqi%" PRId64 "e1:v", seq); + left = canonical_length - (ptr - out); + memcpy(ptr, v.first, (std::min)(v.second, left)); + ptr += (std::min)(v.second, left); + TORRENT_ASSERT((ptr - out) <= canonical_length); + return ptr - out; + } +} + +// calculate the target hash for an immutable item. +sha1_hash item_target_id(std::pair v) +{ + hasher h; + h.update(v.first, v.second); + return h.final(); +} + +// calculate the target hash for a mutable item. +sha1_hash item_target_id(std::pair salt + , char const* pk) +{ + hasher h; + h.update(pk, item_pk_len); + if (salt.second > 0) h.update(salt.first, salt.second); + return h.final(); +} + +bool verify_mutable_item( + std::pair v + , std::pair salt + , boost::uint64_t seq + , char const* pk + , char const* sig) +{ +#ifdef TORRENT_USE_VALGRIND + VALGRIND_CHECK_MEM_IS_DEFINED(v.first, v.second); + VALGRIND_CHECK_MEM_IS_DEFINED(pk, item_pk_len); + VALGRIND_CHECK_MEM_IS_DEFINED(sig, item_sig_len); +#endif + + char str[canonical_length]; + int len = canonical_string(v, seq, salt, str); + + return ed25519_verify(reinterpret_cast(sig) + , reinterpret_cast(str) + , len + , reinterpret_cast(pk)) == 1; +} + +// 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. +void sign_mutable_item( + std::pair v + , std::pair salt + , boost::uint64_t seq + , char const* pk + , char const* sk + , char* sig) +{ +#ifdef TORRENT_USE_VALGRIND + VALGRIND_CHECK_MEM_IS_DEFINED(v.first, v.second); + VALGRIND_CHECK_MEM_IS_DEFINED(sk, item_sk_len); + VALGRIND_CHECK_MEM_IS_DEFINED(pk, item_pk_len); +#endif + + char str[canonical_length]; + int len = canonical_string(v, seq, salt, str); + + ed25519_sign(reinterpret_cast(sig) + , reinterpret_cast(str) + , len + , reinterpret_cast(pk) + , reinterpret_cast(sk) + ); +} + +item::item(char const* pk, std::string const& salt) + : m_salt(salt) + , m_seq(0) + , m_mutable(true) +{ + memcpy(m_pk.data(), pk, item_pk_len); +} + +item::item(entry const& v + , std::pair salt + , boost::uint64_t seq, char const* pk, char const* sk) +{ + assign(v, salt, seq, pk, sk); +} + +void item::assign(entry const& v, std::pair salt + , boost::uint64_t seq, char const* pk, char const* sk) +{ + m_value = v; + if (pk && sk) + { + char buffer[1000]; + int bsize = bencode(buffer, v); + TORRENT_ASSERT(bsize <= 1000); + sign_mutable_item(std::make_pair(buffer, bsize) + , salt, seq, pk, sk, m_sig.c_array()); + m_salt.assign(salt.first, salt.second); + memcpy(m_pk.c_array(), pk, item_pk_len); + m_seq = seq; + m_mutable = true; + } + else + m_mutable = false; +} + +bool item::assign(bdecode_node const& v + , std::pair salt + , boost::uint64_t seq, char const* pk, char const* sig) +{ + TORRENT_ASSERT(v.data_section().second <= 1000); + if (pk && sig) + { + if (!verify_mutable_item(v.data_section(), salt, seq, pk, sig)) + return false; + memcpy(m_pk.c_array(), pk, item_pk_len); + memcpy(m_sig.c_array(), sig, item_sig_len); + if (salt.second > 0) + m_salt.assign(salt.first, salt.second); + else + m_salt.clear(); + m_seq = seq; + m_mutable = true; + } + else + m_mutable = false; + + m_value = v; + return true; +} + +void item::assign(entry const& v, std::string salt, boost::uint64_t seq + , char const* pk, char const* sig) +{ +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(pk && sig); + char buffer[1000]; + int bsize = bencode(buffer, v); + TORRENT_ASSERT(bsize <= 1000); + TORRENT_ASSERT(verify_mutable_item( + std::make_pair(buffer, bsize) + , std::make_pair(salt.data(), int(salt.size())) + , seq, pk, sig)); +#endif + + memcpy(m_pk.c_array(), pk, item_pk_len); + memcpy(m_sig.c_array(), sig, item_sig_len); + m_salt = salt; + m_seq = seq; + m_mutable = true; + m_value = v; +} + +} } // namespace libtorrent::dht diff --git a/src/kademlia/msg.cpp b/src/kademlia/msg.cpp new file mode 100644 index 0000000..53d1c7b --- /dev/null +++ b/src/kademlia/msg.cpp @@ -0,0 +1,145 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +* Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in +the documentation and/or other materials provided with the distribution. +* Neither the name of the author nor the names of its +contributors may be used to endorse or promote products derived +from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/kademlia/msg.hpp" +#include "libtorrent/bdecode.hpp" +#include "libtorrent/entry.hpp" + +namespace libtorrent { namespace dht { + +namespace dht_detail { + +bool verify_message(bdecode_node const& message, key_desc_t const desc[] + , bdecode_node ret[], int size, char* error, int error_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) + { + snprintf(error, 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]; + + // 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] == 0 && (k.flags & key_desc_t::optional) == 0) + { + // the key was not found, and it's not an optional key + snprintf(error, error_size, "missing '%s' key", k.name); + return false; + } + + if (k.size > 0 + && ret[i] + && k.type == bdecode_node::string_t) + { + bool invalid = false; + if (k.flags & key_desc_t::size_divisible) + invalid = (ret[i].string_length() % k.size) != 0; + else + invalid = 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) + { + snprintf(error, 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; +} + +} + +void incoming_error(entry& e, char const* msg, int error_code) +{ + e["y"] = "e"; + entry::list_type& l = e["e"].list(); + l.push_back(entry(error_code)); + l.push_back(entry(msg)); +} + +} } diff --git a/src/kademlia/node.cpp b/src/kademlia/node.cpp new file mode 100644 index 0000000..a268efd --- /dev/null +++ b/src/kademlia/node.cpp @@ -0,0 +1,1189 @@ +/* + +Copyright (c) 2006-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include + +#ifdef TORRENT_USE_VALGRIND +#include +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/io.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/aux_/session_impl.hpp" +#include "libtorrent/alert_types.hpp" // for dht_lookup +#include "libtorrent/performance_counters.hpp" // for counters + +#include "libtorrent/kademlia/node_id.hpp" +#include "libtorrent/kademlia/rpc_manager.hpp" +#include "libtorrent/kademlia/routing_table.hpp" +#include "libtorrent/kademlia/node.hpp" +#include "libtorrent/kademlia/dht_observer.hpp" +#include "libtorrent/kademlia/direct_request.hpp" + +#include "libtorrent/kademlia/refresh.hpp" +#include "libtorrent/kademlia/get_peers.hpp" +#include "libtorrent/kademlia/get_item.hpp" + +namespace libtorrent { namespace dht +{ + +using detail::write_endpoint; + +namespace { + +void nop() {} + +node_id calculate_node_id(node_id const& nid, dht_observer* observer) +{ + address external_address; + if (observer) external_address = observer->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 (!observer || external_address == address()) + { + return generate_random_id(); + } + + if (nid == (node_id::min)() || !verify_id(nid, external_address)) + return generate_id(external_address); + + return nid; +} + +} // anonymous namespace + +node::node(udp_socket_interface* sock + , dht_settings const& settings, node_id nid + , dht_observer* observer + , struct counters& cnt + , dht_storage_constructor_type storage_constructor) + : m_settings(settings) + , m_id(calculate_node_id(nid, observer)) + , m_table(m_id, 8, settings, observer) + , m_rpc(m_id, m_settings, m_table, sock, observer) + , m_observer(observer) + , m_last_tracker_tick(aux::time_now()) + , m_last_self_refresh(min_time()) + , m_sock(sock) + , m_counters(cnt) + , m_storage(storage_constructor(m_id, m_settings)) +{ + m_secret[0] = random(); + m_secret[1] = random(); + + TORRENT_ASSERT(m_storage.get() != NULL); +} + +node::~node() {} + +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) return; + + // 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, m_observer->external_address())) + return; + +#ifndef TORRENT_DISABLE_LOGGING + if (m_observer) m_observer->log(dht_logger::node + , "updating node ID (because external IP address changed)"); +#endif + + m_id = generate_id(m_observer->external_address()); + + m_table.update_node_id(m_id); + m_rpc.update_node_id(m_id); +} + +bool node::verify_token(std::string const& token, char const* info_hash + , udp::endpoint const& addr) const +{ + if (token.length() != 4) + { +#ifndef TORRENT_DISABLE_LOGGING + if (m_observer) + { + m_observer->log(dht_logger::node, "token of incorrect length: %d" + , int(token.length())); + } +#endif + return false; + } + + hasher h1; + error_code ec; + std::string address = addr.address().to_string(ec); + if (ec) return false; + h1.update(&address[0], address.length()); + h1.update(reinterpret_cast(&m_secret[0]), sizeof(m_secret[0])); + h1.update(reinterpret_cast(info_hash), sha1_hash::size); + + sha1_hash h = h1.final(); + if (std::equal(token.begin(), token.end(), reinterpret_cast(&h[0]))) + return true; + + hasher h2; + h2.update(&address[0], address.length()); + h2.update(reinterpret_cast(&m_secret[1]), sizeof(m_secret[1])); + h2.update(info_hash, sha1_hash::size); + h = h2.final(); + if (std::equal(token.begin(), token.end(), reinterpret_cast(&h[0]))) + return true; + return false; +} + +std::string node::generate_token(udp::endpoint const& addr, char const* info_hash) +{ + std::string token; + token.resize(4); + hasher h; + error_code ec; + std::string address = addr.address().to_string(ec); + TORRENT_ASSERT(!ec); + h.update(&address[0], address.length()); + h.update(reinterpret_cast(&m_secret[0]), sizeof(m_secret[0])); + h.update(info_hash, sha1_hash::size); + + sha1_hash hash = h.final(); + std::copy(hash.begin(), hash.begin() + 4, reinterpret_cast(&token[0])); + TORRENT_ASSERT(std::equal(token.begin(), token.end(), reinterpret_cast(&hash[0]))); + return token; +} + +void node::bootstrap(std::vector const& nodes + , find_data::nodes_callback const& f) +{ + node_id target = m_id; + make_id_secret(target); + + boost::intrusive_ptr r(new dht::bootstrap(*this, target, f)); + m_last_self_refresh = aux::time_now(); + +#ifndef TORRENT_DISABLE_LOGGING + int count = 0; +#endif + + for (std::vector::const_iterator i = nodes.begin() + , end(nodes.end()); i != end; ++i) + { +#ifndef TORRENT_DISABLE_LOGGING + ++count; +#endif + r->add_entry(node_id(0), *i, observer::flag_initial); + } + + // make us start as far away from our node ID as possible + r->trim_seed_nodes(); + +#ifndef TORRENT_DISABLE_LOGGING + if (m_observer) + m_observer->log(dht_logger::node, "bootstrapping with %d nodes", count); +#endif + r->start(); +} + +int node::bucket_size(int bucket) +{ + return m_table.bucket_size(bucket); +} + +void node::new_write_key() +{ + m_secret[1] = m_secret[0]; + m_secret[0] = random(); +} + +void node::unreachable(udp::endpoint const& ep) +{ + m_rpc.unreachable(ep); +} + +void node::incoming(msg const& m) +{ + // is this a reply? + bdecode_node y_ent = m.message.dict_find_string("y"); + if (!y_ent || y_ent.string_length() == 0) + { + // 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, 0); + return; + } + + char y = *(y_ent.string_ptr()); + + bdecode_node ext_ip = m.message.dict_find_string("ip"); + + // backwards compatibility + if (!ext_ip) + { + bdecode_node r = m.message.dict_find_dict("r"); + if (r) + ext_ip = r.dict_find_string("ip"); + } + +#if TORRENT_USE_IPV6 + if (ext_ip && ext_ip.string_length() >= 16) + { + // this node claims we use the wrong node-ID! + address_v6::bytes_type b; + memcpy(&b[0], ext_ip.string_ptr(), 16); + if (m_observer) + m_observer->set_external_address(address_v6(b) + , m.addr.address()); + } else +#endif + if (ext_ip && ext_ip.string_length() >= 4) + { + address_v4::bytes_type b; + memcpy(&b[0], ext_ip.string_ptr(), 4); + if (m_observer) + m_observer->set_external_address(address_v4(b) + , m.addr.address()); + } + + switch (y) + { + case 'r': + { + node_id id; + m_rpc.incoming(m, &id); + break; + } + case 'q': + { + TORRENT_ASSERT(m.message.dict_find_string_value("y") == "q"); + // When a DHT node enters the read-only state, it no longer + // responds to 'query' messages that it receives. + if (m_settings.read_only) break; + + entry e; + incoming_request(m, e); + m_sock->send_packet(e, m.addr, 0); + break; + } + case 'e': + { +#ifndef TORRENT_DISABLE_LOGGING + if (m_observer) + { + 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_observer) + { + m_observer->log(dht_logger::node, "INCOMING ERROR: (%" PRId64 ") %s" + , err.list_int_value_at(0) + , err.list_string_value_at(1).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 listen_port, sha1_hash const& ih, int flags) + { +#ifndef TORRENT_DISABLE_LOGGING + if (node.observer()) + { + char hex_ih[41]; + to_hex(reinterpret_cast(&ih[0]), 20, hex_ih); + node.observer()->log(dht_logger::node, "sending announce_peer [ ih: %s " + " p: %d nodes: %d ]", hex_ih, listen_port, int(v.size())); + } +#endif + + // create a dummy traversal_algorithm + boost::intrusive_ptr algo( + new traversal_algorithm(node, (node_id::min)())); + // store on the first k nodes + for (std::vector >::const_iterator i = v.begin() + , end(v.end()); i != end; ++i) + { +#ifndef TORRENT_DISABLE_LOGGING + if (node.observer()) + { + node.observer()->log(dht_logger::node, "announce-distance: %d" + , (160 - distance_exp(ih, i->first.id))); + } +#endif + + void* ptr = node.m_rpc.allocate_observer(); + if (ptr == 0) return; + observer_ptr o(new (ptr) announce_observer(algo, i->first.ep(), i->first.id)); +#if defined TORRENT_DEBUG || defined TORRENT_RELEASE_ASSERTS + o->m_in_constructor = false; +#endif + entry e; + e["y"] = "q"; + e["q"] = "announce_peer"; + entry& a = e["a"]; + a["info_hash"] = ih.to_string(); + a["port"] = listen_port; + a["token"] = i->second; + a["seed"] = (flags & node::flag_seed) ? 1 : 0; + if (flags & node::flag_implied_port) a["implied_port"] = 1; + node.stats_counters().inc_stats_counter(counters::dht_announce_peer_out); + node.m_rpc.invoke(e, i->first.ep(), o); + } + } +} + +void node::add_router_node(udp::endpoint router) +{ +#ifndef TORRENT_DISABLE_LOGGING + if (m_observer) + { + 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 node) +{ + // 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 + , boost::function const&)> dcallback + , boost::function > const&)> ncallback + , bool noseeds) +{ + // search for nodes with ids close to id or with peers + // for info-hash id. then send announce_peer to them. + + boost::intrusive_ptr ta; + if (m_settings.privacy_lookups) + { + ta.reset(new dht::obfuscated_get_peers(*this, info_hash, dcallback, ncallback, noseeds)); + } + else + { + ta.reset(new dht::get_peers(*this, info_hash, dcallback, ncallback, noseeds)); + } + + ta->start(); +} + +void node::announce(sha1_hash const& info_hash, int listen_port, int flags + , boost::function const&)> f) +{ +#ifndef TORRENT_DISABLE_LOGGING + if (m_observer) + { + char hex_ih[41]; + to_hex(reinterpret_cast(&info_hash[0]), 20, hex_ih); + m_observer->log(dht_logger::node, "announcing [ ih: %s p: %d ]" + , hex_ih, listen_port); + } +#endif + + get_peers(info_hash, f + , boost::bind(&announce_fun, _1, boost::ref(*this) + , listen_port, info_hash, flags), flags & node::flag_seed); +} + +void node::direct_request(udp::endpoint ep, entry& e + , boost::function f) +{ + // not really a traversal + boost::intrusive_ptr algo( + new direct_traversal(*this, (node_id::min)(), f)); + + void* ptr = m_rpc.allocate_observer(); + if (ptr == 0) return; + observer_ptr o(new (ptr) direct_observer(algo, ep, (node_id::min)())); +#if defined TORRENT_DEBUG || defined TORRENT_RELEASE_ASSERTS + o->m_in_constructor = false; +#endif + m_rpc.invoke(e, ep, o); +} + +void node::get_item(sha1_hash const& target + , boost::function f) +{ +#ifndef TORRENT_DISABLE_LOGGING + if (m_observer) + { + char hex_target[41]; + to_hex(reinterpret_cast(&target[0]), 20, hex_target); + m_observer->log(dht_logger::node, "starting get for [ hash: %s ]" + , hex_target); + } +#endif + + boost::intrusive_ptr ta; + ta.reset(new dht::get_item(*this, target, boost::bind(f, _1), find_data::nodes_callback())); + ta->start(); +} + +void node::get_item(char const* pk, std::string const& salt + , boost::function f) +{ +#ifndef TORRENT_DISABLE_LOGGING + if (m_observer) + { + char hex_key[65]; + to_hex(pk, 32, hex_key); + m_observer->log(dht_logger::node, "starting get for [ key: %s ]", hex_key); + } +#endif + + boost::intrusive_ptr ta; + ta.reset(new dht::get_item(*this, pk, salt, f, find_data::nodes_callback())); + ta->start(); +} + +namespace { + +void put(std::vector > const& nodes + , boost::intrusive_ptr ta) +{ + ta->set_targets(nodes); + ta->start(); +} + +void put_data_cb(item i, bool auth + , boost::intrusive_ptr ta + , boost::function f) +{ + // call data_callback only when we got authoritative data. + if (auth) + { + f(i); + ta->set_data(i); + } +} + +} // namespace + +void node::put_item(sha1_hash const& target, entry const& data, boost::function f) +{ +#ifndef TORRENT_DISABLE_LOGGING + if (m_observer) + { + char hex_target[41]; + to_hex(target.data(), 20, hex_target); + m_observer->log(dht_logger::node, "starting get for [ hash: %s ]" + , hex_target); + } +#endif + + item i; + i.assign(data); + boost::intrusive_ptr put_ta; + put_ta.reset(new dht::put_data(*this, boost::bind(f, _2))); + put_ta->set_data(i); + + boost::intrusive_ptr ta; + ta.reset(new dht::get_item(*this, target, get_item::data_callback(), + boost::bind(&put, _1, put_ta))); + ta->start(); +} + +void node::put_item(char const* pk, std::string const& salt + , boost::function f + , boost::function data_cb) +{ + #ifndef TORRENT_DISABLE_LOGGING + if (m_observer) + { + char hex_key[65]; + to_hex(pk, 32, hex_key); + m_observer->log(dht_logger::node, "starting get for [ key: %s ]", hex_key); + } + #endif + + boost::intrusive_ptr put_ta; + put_ta.reset(new dht::put_data(*this, f)); + + boost::intrusive_ptr ta; + ta.reset(new dht::get_item(*this, pk, salt + , boost::bind(&put_data_cb, _1, _2, put_ta, data_cb) + , boost::bind(&put, _1, put_ta))); + ta->start(); +} + +struct ping_observer : observer +{ + ping_observer( + boost::intrusive_ptr const& algorithm + , udp::endpoint const& ep, node_id const& id) + : observer(algorithm, ep, id) + {} + + // parses out "nodes" + virtual void reply(msg const& m) + { + flags |= flag_done; + + bdecode_node 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 + bdecode_node n = r.dict_find_string("nodes"); + if (n) + { + char const* nodes = n.string_ptr(); + char const* end = nodes + n.string_length(); + + while (end - nodes >= 26) + { + node_id id; + std::copy(nodes, nodes + 20, id.begin()); + nodes += 20; + algorithm()->get_node().m_table.heard_about(id + , detail::read_v4_endpoint(nodes)); + } + } + } +}; + +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 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); + boost::intrusive_ptr r(new dht::bootstrap(*this, target + , boost::bind(&nop))); + r->start(); + m_last_self_refresh = now; + return; + } + + node_entry const* ne = m_table.next_refresh(); + if (ne == NULL) return; + + // this shouldn't happen + TORRENT_ASSERT(m_id != ne->id); + if (ne->id == m_id) return; + + int 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 bucket + , node_id const& id) +{ + TORRENT_ASSERT(id != m_id); + void* ptr = m_rpc.allocate_observer(); + if (ptr == 0) return; + + 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 + // this is unfortunately necessary for the observer + // to free itself from the pool when it's being released + boost::intrusive_ptr algo( + new traversal_algorithm(*this, (node_id::min)())); + observer_ptr o(new (ptr) ping_observer(algo, ep, id)); +#if defined TORRENT_DEBUG || defined TORRENT_RELEASE_ASSERTS + o->m_in_constructor = false; +#endif + entry e; + e["y"] = "q"; + entry& a = e["a"]; + + 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"; + a["info_hash"] = target.to_string(); + m_counters.inc_stats_counter(counters::dht_get_peers_out); + } + + m_rpc.invoke(e, ep, o); +} + +time_duration node::connection_timeout() +{ + time_duration d = m_rpc.tick(); + time_point now(aux::time_now()); + if (now - minutes(2) < m_last_tracker_tick) return d; + m_last_tracker_tick = now; + + m_storage->tick(); + + return d; +} + +void node::status(std::vector& table + , std::vector& requests) +{ + mutex_t::scoped_lock l(m_mutex); + + m_table.status(table); + + for (std::set::iterator i = m_running_requests.begin() + , end(m_running_requests.end()); i != end; ++i) + { + requests.push_back(dht_lookup()); + dht_lookup& lookup = requests.back(); + (*i)->status(lookup); + } +} + +// TODO: in the future, this function should update all the +// dht related counter. For now, it just update the storage +// related ones. +void node::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); + + int nodes, replacements; + boost::tie(nodes, replacements, boost::tuples::ignore) = size(); + c.set_value(counters::dht_nodes, nodes); + c.set_value(counters::dht_node_cache, replacements); + c.set_value(counters::dht_allocated_observers, m_rpc.num_allocated_observers()); +} + +#ifndef TORRENT_NO_DEPRECATE +// TODO: 2 use the non deprecated function instead of this one +void node::status(session_status& s) +{ + mutex_t::scoped_lock l(m_mutex); + + m_table.status(s); + s.dht_torrents = int(m_storage->num_torrents()); + s.active_requests.clear(); + s.dht_total_allocations = m_rpc.num_allocated_observers(); + for (std::set::iterator i = m_running_requests.begin() + , end(m_running_requests.end()); i != end; ++i) + { + s.active_requests.push_back(dht_lookup()); + dht_lookup& lookup = s.active_requests.back(); + (*i)->status(lookup); + } +} +#endif + +void node::lookup_peers(sha1_hash const& info_hash, entry& reply + , bool noseed, bool scrape) const +{ + if (m_observer) + m_observer->get_peers(info_hash); + + m_storage->get_peers(info_hash, noseed, scrape, reply); +} + +void TORRENT_EXTRA_EXPORT write_nodes_entry(entry& r, nodes_t const& nodes) +{ + entry& n = r["nodes"]; + std::back_insert_iterator out(n.string()); + for (nodes_t::const_iterator i = nodes.begin() + , end(nodes.end()); i != end; ++i) + { + if (!i->addr().is_v4()) continue; + std::copy(i->id.begin(), i->id.end(), out); + write_endpoint(udp::endpoint(i->addr(), i->port()), out); + } +} + +// build response +void node::incoming_request(msg const& m, entry& e) +{ + if (!m_sock->has_quota()) + return; + + e = entry(entry::dictionary_t); + e["y"] = "r"; + e["t"] = m.message.dict_find_string_value("t"); + + key_desc_t 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 + , sizeof(error_string))) + { + incoming_error(e, error_string); + return; + } + + e["ip"] = endpoint_to_bytes(m.addr); + + bdecode_node arg_ent = top_level[2]; + bool read_only = top_level[1] && top_level[1].int_value() != 0; + node_id id(top_level[3].string_ptr()); + + // if this nodes ID doesn't match its IP, tell it what + // its IP is with an error + // don't enforce this yet + if (m_settings.enforce_node_id && !verify_id(id, m.addr.address())) + { + incoming_error(e, "invalid node ID"); + return; + } + + if (!read_only) + m_table.heard_about(id, m.addr); + + entry& reply = e["r"]; + m_rpc.add_our_id(reply); + + // mirror back the other node's external port + reply["p"] = m.addr.port(); + + char const* query = top_level[0].string_ptr(); + int query_len = top_level[0].string_length(); + + if (m_observer && m_observer->on_dht_request(query, query_len, m, e)) + return; + + if (query_len == 4 && memcmp(query, "ping", 4) == 0) + { + 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_len == 9 && memcmp(query, "get_peers", 9) == 0) + { + key_desc_t 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}, + }; + + bdecode_node msg_keys[3]; + if (!verify_message(arg_ent, msg_desc, msg_keys, error_string + , sizeof(error_string))) + { + m_counters.inc_stats_counter(counters::dht_invalid_get_peers); + incoming_error(e, error_string); + return; + } + + reply["token"] = generate_token(m.addr, msg_keys[0].string_ptr()); + + m_counters.inc_stats_counter(counters::dht_get_peers_in); + + sha1_hash info_hash(msg_keys[0].string_ptr()); + nodes_t n; + // always return nodes as well as peers + m_table.find_node(info_hash, n, 0); + write_nodes_entry(reply, n); + + bool noseed = false; + bool scrape = false; + if (msg_keys[1] && msg_keys[1].int_value() != 0) noseed = true; + if (msg_keys[2] && msg_keys[2].int_value() != 0) scrape = true; + lookup_peers(info_hash, reply, noseed, scrape); +#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_len == 9 && memcmp(query, "find_node", 9) == 0) + { + key_desc_t msg_desc[] = { + {"target", bdecode_node::string_t, 20, 0}, + }; + + bdecode_node msg_keys[1]; + if (!verify_message(arg_ent, msg_desc, msg_keys, error_string, sizeof(error_string))) + { + incoming_error(e, error_string); + return; + } + + m_counters.inc_stats_counter(counters::dht_find_node_in); + sha1_hash target(msg_keys[0].string_ptr()); + + // TODO: 2 find_node should write directly to the response entry + nodes_t n; + m_table.find_node(target, n, 0); + write_nodes_entry(reply, n); + } + else if (query_len == 13 && memcmp(query, "announce_peer", 13) == 0) + { + key_desc_t 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, sizeof(error_string))) + { + m_counters.inc_stats_counter(counters::dht_invalid_announce); + incoming_error(e, error_string); + return; + } + + int 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 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(), 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 addr = tcp::endpoint(m.addr.address(), port); + std::string name = msg_keys[3] ? msg_keys[3].string_value() : std::string(); + bool seed = msg_keys[4] && msg_keys[4].int_value(); + + m_storage->announce_peer(info_hash, addr, name, seed); + } + else if (query_len == 3 && memcmp(query, "put", 3) == 0) + { + // the first 2 entries are for both mutable and + // immutable puts + static const key_desc_t 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, item_pk_len, key_desc_t::optional}, + {"sig", bdecode_node::string_t, item_sig_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 + bdecode_node msg_keys[7]; + if (!verify_message(arg_ent, msg_desc, msg_keys, error_string, sizeof(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 mutable_put = (msg_keys[2] && msg_keys[3] && msg_keys[4]); + + // public key (only set if it's a mutable put) + char const* pk = NULL; + if (msg_keys[3]) pk = msg_keys[3].string_ptr(); + + // signature (only set if it's a mutable put) + char const* sig = NULL; + if (msg_keys[4]) sig = msg_keys[4].string_ptr(); + + // pointer and length to the whole entry + std::pair buf = msg_keys[1].data_section(); + if (buf.second > 1000 || buf.second <= 0) + { + m_counters.inc_stats_counter(counters::dht_invalid_put); + incoming_error(e, "message too big", 205); + return; + } + + std::pair salt(static_cast(NULL), 0); + if (msg_keys[6]) + salt = std::pair( + msg_keys[6].string_ptr(), msg_keys[6].string_length()); + if (salt.second > 64) + { + m_counters.inc_stats_counter(counters::dht_invalid_put); + incoming_error(e, "salt too big", 207); + return; + } + + sha1_hash target; + if (pk) + target = item_target_id(salt, pk); + else + target = item_target_id(buf); + +// fprintf(stderr, "%s PUT target: %s salt: %s key: %s\n" +// , mutable_put ? "mutable":"immutable" +// , to_hex(target.to_string()).c_str() +// , salt.second > 0 ? std::string(salt.first, salt.second).c_str() : "" +// , pk ? to_hex(std::string(pk, 32)).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() + , reinterpret_cast(&target[0]), 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.first, buf.second, m.addr.address()); + } + else + { + // mutable put, we must verify the signature + +#ifdef TORRENT_USE_VALGRIND + VALGRIND_CHECK_MEM_IS_DEFINED(msg_keys[4].string_ptr(), item_sig_len); + VALGRIND_CHECK_MEM_IS_DEFINED(pk, item_pk_len); +#endif + boost::int64_t seq = msg_keys[2].int_value(); + + if (seq < 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(item_sig_len == msg_keys[4].string_length()); + + boost::int64_t item_seq; + if (!m_storage->get_mutable_item_seq(target, item_seq)) + { + m_storage->put_mutable_item(target + , buf.first, buf.second + , sig, seq, pk + , salt.first, salt.second + , 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 != 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.first, buf.second + , sig, seq, pk + , salt.first, salt.second + , m.addr.address()); + } + } + + m_table.node_seen(id, m.addr, 0xffff); + } + else if (query_len == 3 && memcmp(query, "get", 3) == 0) + { + key_desc_t msg_desc[] = { + {"seq", bdecode_node::int_t, 0, key_desc_t::optional}, + {"target", bdecode_node::string_t, 20, 0}, + }; + + // k is not used for now + + // attempt to parse the message + bdecode_node msg_keys[2]; + if (!verify_message(arg_ent, msg_desc, msg_keys, error_string + , sizeof(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 target(msg_keys[1].string_ptr()); + +// fprintf(stderr, "%s GET target: %s\n" +// , msg_keys[1] ? "mutable":"immutable" +// , to_hex(target.to_string()).c_str()); + + reply["token"] = generate_token(m.addr, msg_keys[1].string_ptr()); + + nodes_t n; + // always return nodes as well as peers + m_table.find_node(target, n, 0); + write_nodes_entry(reply, n); + + // 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, 0, true, reply); + } + } + else + { + m_storage->get_mutable_item(target + , msg_keys[0].int_value(), false + , 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 target(target_ent.string_ptr()); + nodes_t n; + // always return nodes as well as peers + m_table.find_node(target, n, 0); + write_nodes_entry(reply, n); + return; + } +} + +} } // namespace libtorrent::dht diff --git a/src/kademlia/node_entry.cpp b/src/kademlia/node_entry.cpp new file mode 100644 index 0000000..00ac3b0 --- /dev/null +++ b/src/kademlia/node_entry.cpp @@ -0,0 +1,90 @@ +/* + +Copyright (c) 2006-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/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 ep + , int roundtriptime + , bool pinged) + : last_queried(pinged ? aux::time_now() : min_time()) + , id(id_) + , a(ep.address().to_v4().to_bytes()) + , p(ep.port()) + , rtt(roundtriptime & 0xffff) + , timeout_count(pinged ? 0 : 0xff) + { +#ifndef TORRENT_DISABLE_LOGGING + first_seen = aux::time_now(); +#endif + } + + node_entry::node_entry(udp::endpoint ep) + : last_queried(min_time()) + , id(0) + , a(ep.address().to_v4().to_bytes()) + , p(ep.port()) + , rtt(0xffff) + , timeout_count(0xff) + { +#ifndef TORRENT_DISABLE_LOGGING + first_seen = aux::time_now(); +#endif + } + + node_entry::node_entry() + : last_queried(min_time()) + , id(0) + , p(0) + , rtt(0xffff) + , timeout_count(0xff) + { +#ifndef TORRENT_DISABLE_LOGGING + first_seen = aux::time_now(); +#endif + } + + void node_entry::update_rtt(int new_rtt) + { + TORRENT_ASSERT(new_rtt <= 0xffff); + TORRENT_ASSERT(new_rtt >= 0); + if (new_rtt == 0xffff) return; + if (rtt == 0xffff) rtt = new_rtt; + else rtt = int(rtt) * 2 / 3 + int(new_rtt) / 3; + } + +}} + + diff --git a/src/kademlia/node_id.cpp b/src/kademlia/node_id.cpp new file mode 100644 index 0000000..e74d60c --- /dev/null +++ b/src/kademlia/node_id.cpp @@ -0,0 +1,238 @@ +/* + +Copyright (c) 2006-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 "libtorrent/kademlia/node_id.hpp" +#include "libtorrent/kademlia/node_entry.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/broadcast_socket.hpp" // for is_local et.al +#include "libtorrent/socket_io.hpp" // for hash_address +#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) +{ + node_id ret; + node_id::iterator k = ret.begin(); + // TODO: 3 the XORing should be done at full words instead of bytes + for (node_id::const_iterator i = n1.begin(), j = n2.begin() + , end(n1.end()); i != end; ++i, ++j, ++k) + { + *k = *i ^ *j; + } + return ret; +} + +// returns true if: distance(n1, ref) < distance(n2, ref) +bool compare_ref(node_id const& n1, node_id const& n2, node_id const& ref) +{ + // TODO: 3 the XORing should be done at full words instead of bytes + for (node_id::const_iterator i = n1.begin(), j = n2.begin() + , k = ref.begin(), end(n1.end()); i != end; ++i, ++j, ++k) + { + boost::uint8_t lhs = (*i ^ *k); + boost::uint8_t rhs = (*j ^ *k); + if (lhs < rhs) return true; + if (lhs > rhs) return false; + } + return false; +} + +// 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: 3 the xoring should be done at full words and _builtin_clz() could + // be used as the last step + int byte = node_id::size - 1; + for (node_id::const_iterator i = n1.begin(), j = n2.begin() + , end(n1.end()); i != end; ++i, ++j, --byte) + { + TORRENT_ASSERT(byte >= 0); + boost::uint8_t t = *i ^ *j; + if (t == 0) continue; + // we have found the first non-zero byte + // return the bit-number of the first bit + // that differs + int const bit = byte * 8; + for (int b = 7; b >= 0; --b) + if (t >= (1 << b)) return bit + b; + return bit; + } + + return 0; +} + +node_id generate_id_impl(address const& ip_, boost::uint32_t r) +{ + boost::uint8_t* ip = 0; + + static const boost::uint8_t v4mask[] = { 0x03, 0x0f, 0x3f, 0xff }; +#if TORRENT_USE_IPV6 + static const boost::uint8_t v6mask[] = { 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff }; +#endif + boost::uint8_t const* mask = 0; + int num_octets = 0; + + address_v4::bytes_type b4; +#if TORRENT_USE_IPV6 + address_v6::bytes_type b6; + if (ip_.is_v6()) + { + b6 = ip_.to_v6().to_bytes(); + ip = &b6[0]; + num_octets = 8; + mask = v6mask; + } + else +#endif + { + b4 = ip_.to_v4().to_bytes(); + ip = &b4[0]; + 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 + boost::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); + + for (int i = 3; i < 19; ++i) id[i] = random() & 0xff; + id[19] = r & 0xff; + + return id; +} + +static boost::uint32_t secret = 0; + +void make_id_secret(node_id& in) +{ + if (secret == 0) secret = (random() % 0xfffffffe) + 1; + + boost::uint32_t rand = random(); + + // 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 secret_hash = h.final(); + memcpy(&in[20-4], &secret_hash[0], 4); + memcpy(&in[20-8], &rand, 4); +} + +node_id generate_random_id() +{ + char r[20]; + for (int i = 0; i < 20; ++i) r[i] = random() & 0xff; + 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 memcmp(&nid[20-4], &secret_hash[0], 4) == 0; +} + +// verifies whether a node-id matches the IP it's used from +// returns true if the node-id is OK coming from this source +// and false otherwise. +bool verify_id(node_id const& nid, address const& source_ip) +{ + // no need to verify local IPs, they would be incorrect anyway + if (is_local(source_ip)) return true; + + node_id h = generate_id_impl(source_ip, nid[19]); + return nid[0] == h[0] && nid[1] == h[1] && (nid[2] & 0xf8) == (h[2] & 0xf8); +} + +node_id generate_id(address const& ip) +{ + return generate_id_impl(ip, random()); +} + +bool matching_prefix(node_entry const& n, int mask, int prefix, int bucket_index) +{ + node_id id = n.id; + id <<= bucket_index + 1; + return (id[0] & mask) == prefix; +} + +node_id generate_prefix_mask(int bits) +{ + TORRENT_ASSERT(bits >= 0); + TORRENT_ASSERT(bits <= 160); + node_id mask(0); + int b = 0; + for (; 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..393e340 --- /dev/null +++ b/src/kademlia/put_data.cpp @@ -0,0 +1,122 @@ +/* + +Copyright (c) 2006-2016, Arvid Norberg & Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include + +namespace libtorrent { namespace dht +{ + +put_data::put_data(node& dht_node, put_callback const& callback) + : traversal_algorithm(dht_node, (node_id::min)()) + , m_put_callback(callback) + , m_done(false) +{ +} + +char const* put_data::name() const { return "put_data"; } + +void put_data::start() +{ + // router nodes must not be added to puts + init(); + bool is_done = add_requests(); + if (is_done) done(); +} + +void put_data::set_targets(std::vector > const& targets) +{ + for (std::vector >::const_iterator i = targets.begin() + , end(targets.end()); i != end; ++i) + { + void* ptr = m_node.m_rpc.allocate_observer(); + if (ptr == 0) return; + + observer_ptr o(new (ptr) put_data_observer(this, i->first.ep() + , i->first.id, i->second)); + + #if defined TORRENT_DEBUG || defined TORRENT_RELEASE_ASSERTS + o->m_in_constructor = false; + #endif + m_results.push_back(o); + } +} + +void put_data::done() +{ + m_done = true; + +#ifndef TORRENT_DISABLE_LOGGING + get_node().observer()->log(dht_logger::traversal, "[%p] %s DONE, response %d, timeout %d" + , static_cast(this), name(), m_responses, m_timeouts); +#endif + + m_put_callback(m_data, m_responses); + traversal_algorithm::done(); +} + +bool put_data::invoke(observer_ptr o) +{ + if (m_done) + { + m_invoke_count = -1; + return false; + } + + // TODO: what if o is not an isntance of put_data_observer? This need to be + // redesigned for better type saftey. + put_data_observer* 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"] = std::string(m_data.pk().data(), item_pk_len); + a["seq"] = m_data.seq(); + a["sig"] = std::string(m_data.sig().data(), item_sig_len); + if (!m_data.salt().empty()) + { + a["salt"] = m_data.salt(); + } + } + + 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..998e6ef --- /dev/null +++ b/src/kademlia/refresh.cpp @@ -0,0 +1,111 @@ +/* + +Copyright (c) 2006-2016, Arvid Norberg & Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include +#include + +#include + +namespace libtorrent { namespace dht +{ + +observer_ptr bootstrap::new_observer(void* ptr + , udp::endpoint const& ep, node_id const& id) +{ + observer_ptr o(new (ptr) get_peers_observer(this, ep, id)); +#if defined TORRENT_DEBUG || defined TORRENT_RELEASE_ASSERTS + 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(); + +// 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 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::trim_seed_nodes() +{ + // when we're bootstrapping, we want to start as far away from our ID as + // possible, to cover as much as possible of the ID space. So, remove all + // nodes except for the 32 that are farthest away from us + if (m_results.size() > 32) + m_results.erase(m_results.begin(), m_results.end() - 32); +} + +void bootstrap::done() +{ +#ifndef TORRENT_DISABLE_LOGGING + get_node().observer()->log(dht_logger::traversal, "[%p] bootstrap done, pinging remaining nodes" + , static_cast(this)); +#endif + + for (std::vector::iterator i = m_results.begin() + , end(m_results.end()); i != end; ++i) + { + if ((*i)->flags & observer::flag_queried) continue; + // this will send a ping + m_node.add_node((*i)->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..faeb0c9 --- /dev/null +++ b/src/kademlia/routing_table.cpp @@ -0,0 +1,1314 @@ +/* + +Copyright (c) 2006-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 // std::distance() +#include // std::copy, std::remove_copy_if +#include +#include + +#include "libtorrent/config.hpp" + +#include "libtorrent/kademlia/routing_table.hpp" +#include "libtorrent/broadcast_socket.hpp" // for cidr_distance +#include "libtorrent/session_status.hpp" +#include "libtorrent/kademlia/node_id.hpp" +#include "libtorrent/kademlia/dht_observer.hpp" +#include "libtorrent/aux_/time.hpp" +#include "libtorrent/alert_types.hpp" // for dht_routing_bucket +#include "libtorrent/socket_io.hpp" // for print_endpoint +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/address.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +using boost::uint8_t; + +#if BOOST_VERSION <= 104700 +namespace boost { +size_t hash_value(libtorrent::address_v4::bytes_type ip) +{ + return boost::hash_value(*reinterpret_cast(&ip[0])); +} +} +#endif + +namespace libtorrent { namespace dht +{ +namespace +{ + template + void erase_one(T& container, K const& key) + { + typename T::iterator i = container.find(key); + TORRENT_ASSERT(i != container.end()); + container.erase(i); + } + + bool verify_node_address(dht_settings const& settings + , node_id const& id, address const& addr) + { + // only when the node_id pass the verification, add it to routing table. + if (settings.enforce_node_id && !verify_id(id, addr)) return false; + + return true; + } +} + +routing_table::routing_table(node_id const& id, int bucket_size + , dht_settings const& settings + , dht_logger* log) + : +#ifndef TORRENT_DISABLE_LOGGING + m_log(log), +#endif + m_settings(settings) + , m_id(id) + , m_depth(0) + , m_last_self_refresh(min_time()) + , m_bucket_size(bucket_size) +{ + TORRENT_UNUSED(log); + m_buckets.reserve(30); +} + +int routing_table::bucket_limit(int bucket) const +{ + if (!m_settings.extended_routing_table) return m_bucket_size; + + static const int size_exceptions[] = {16, 8, 4, 2}; + if (bucket < int(sizeof(size_exceptions)/sizeof(size_exceptions[0]))) + return m_bucket_size * size_exceptions[bucket]; + return m_bucket_size; +} + +void routing_table::status(std::vector& s) const +{ + for (table_t::const_iterator i = m_buckets.begin() + , end(m_buckets.end()); i != end; ++i) + { + dht_routing_bucket b; + b.num_nodes = i->live_nodes.size(); + b.num_replacements = i->replacements.size(); + s.push_back(b); + } +} + +#ifndef TORRENT_NO_DEPRECATE +// TODO: 2 use the non deprecated function instead of this one +void routing_table::status(session_status& s) const +{ + int ignore; + boost::tie(s.dht_nodes, s.dht_node_cache, ignore) = size(); + s.dht_global_nodes = num_global_nodes(); + + for (table_t::const_iterator i = m_buckets.begin() + , end(m_buckets.end()); i != end; ++i) + { + dht_routing_bucket b; + b.num_nodes = i->live_nodes.size(); + b.num_replacements = i->replacements.size(); +#ifndef TORRENT_NO_DEPRECATE + b.last_active = 0; +#endif + s.dht_routing_table.push_back(b); + } +} +#endif + +boost::tuple routing_table::size() const +{ + int nodes = 0; + int replacements = 0; + int confirmed = 0; + for (table_t::const_iterator i = m_buckets.begin() + , end(m_buckets.end()); i != end; ++i) + { + nodes += i->live_nodes.size(); + for (bucket_t::const_iterator k = i->live_nodes.begin() + , end2(i->live_nodes.end()); k != end2; ++k) + { + if (k->confirmed()) ++confirmed; + } + + replacements += i->replacements.size(); + } + return boost::make_tuple(nodes, replacements, confirmed); +} + +boost::int64_t routing_table::num_global_nodes() const +{ + int deepest_bucket = 0; + int deepest_size = 0; + for (table_t::const_iterator i = m_buckets.begin() + , end(m_buckets.end()); i != end; ++i) + { + deepest_size = i->live_nodes.size(); // + 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 (boost::int64_t(1) << deepest_bucket) * m_bucket_size; + else return (boost::int64_t(2) << deepest_bucket) * deepest_size; +} + +int routing_table::depth() const +{ + if (m_depth >= int(m_buckets.size())) + m_depth = 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; +} + +#if defined TORRENT_DEBUG && TORRENT_USE_IOSTREAM + +void routing_table::print_state(std::ostream& os) const +{ + std::vector buf(2048); + int cursor = 0; + + cursor += snprintf(&buf[cursor], buf.size() - cursor + , "kademlia routing table state\n" + "bucket_size: %d\n" + "global node count: %" PRId64 "\n" + "node_id: %s\n\n" + "number of nodes per bucket:\n" + , m_bucket_size + , num_global_nodes() + , to_hex(m_id.to_string()).c_str()); + if (cursor > buf.size() - 500) buf.resize(buf.size() * 3 / 2); + + int idx = 0; + + for (table_t::const_iterator i = m_buckets.begin(), end(m_buckets.end()); + i != end; ++i, ++idx) + { + cursor += snprintf(&buf[cursor], buf.size() - cursor + , "%2d: ", idx); + for (int k = 0; k < int(i->live_nodes.size()); ++k) + cursor += snprintf(&buf[cursor], buf.size() - cursor, "#"); + for (int k = 0; k < int(i->replacements.size()); ++k) + cursor += snprintf(&buf[cursor], buf.size() - cursor, "-"); + cursor += snprintf(&buf[cursor], buf.size() - cursor, "\n"); + + if (cursor > buf.size() - 500) buf.resize(buf.size() * 3 / 2); + } + + time_point now = aux::time_now(); + + cursor += snprintf(&buf[cursor], buf.size() - cursor + , "\nnodes:"); + + int bucket_index = 0; + for (table_t::const_iterator i = m_buckets.begin(), end(m_buckets.end()); + i != end; ++i, ++bucket_index) + { + cursor += snprintf(&buf[cursor], buf.size() - cursor + , "\n=== BUCKET == %d == %d|%d ==== \n" + , bucket_index, int(i->live_nodes.size()) + , int(i->replacements.size())); + if (cursor > buf.size() - 500) buf.resize(buf.size() * 3 / 2); + + int id_shift; + // the last bucket is special, since it hasn't been split yet, it + // includes that top bit as well + if (bucket_index + 1 == m_buckets.size()) + id_shift = bucket_index; + else + id_shift = bucket_index + 1; + + for (bucket_t::const_iterator j = i->live_nodes.begin() + , end2(i->live_nodes.end()); j != end2; ++j) + { + int bucket_size_limit = bucket_limit(bucket_index); + boost::uint32_t top_mask = bucket_size_limit - 1; + int mask_shift = 0; + TORRENT_ASSERT_VAL(bucket_size_limit > 0, bucket_size_limit); + while ((top_mask & 0x80) == 0) + { + top_mask <<= 1; + ++mask_shift; + } + top_mask = (0xff << mask_shift) & 0xff; + + node_id id = j->id; + id <<= id_shift; + + cursor += snprintf(&buf[cursor], buf.size() - cursor + , " prefix: %2x id: %s" + , ((id[0] & top_mask) >> mask_shift) + , to_hex(j->id.to_string()).c_str()); + + if (j->rtt == 0xffff) + { + cursor += snprintf(&buf[cursor], buf.size() - cursor + , " rtt: "); + } + else + { + cursor += snprintf(&buf[cursor], buf.size() - cursor + , " rtt: %4d", j->rtt); + } + + cursor += snprintf(&buf[cursor], buf.size() - cursor + , " fail: %4d ping: %d dist: %3d" + , j->fail_count() + , j->pinged() + , distance_exp(m_id, j->id)); + + if (j->last_queried == min_time()) + { + cursor += snprintf(&buf[cursor], buf.size() - cursor + , " query: "); + } + else + { + cursor += snprintf(&buf[cursor], buf.size() - cursor + , " query: %3d", int(total_seconds(now - j->last_queried))); + } + + cursor += snprintf(&buf[cursor], buf.size() - cursor + , " ip: %s\n", print_endpoint(j->ep()).c_str()); + if (cursor > buf.size() - 500) buf.resize(buf.size() * 3 / 2); + } + } + + cursor += snprintf(&buf[cursor], buf.size() - cursor + , "\nnode spread per bucket:\n"); + bucket_index = 0; + for (table_t::const_iterator i = m_buckets.begin(), end(m_buckets.end()); + i != end; ++i, ++bucket_index) + { + int bucket_size_limit = bucket_limit(bucket_index); + + // mask out the first 3 bits, or more depending + // on the bucket_size_limit + // we have all the lower bits set in (bucket_size_limit-1) + // but we want the left-most bits to be set. Shift it + // until the MSB is set + boost::uint32_t top_mask = bucket_size_limit - 1; + int mask_shift = 0; + TORRENT_ASSERT_VAL(bucket_size_limit > 0, bucket_size_limit); + while ((top_mask & 0x80) == 0) + { + top_mask <<= 1; + ++mask_shift; + } + top_mask = (0xff << mask_shift) & 0xff; + bucket_size_limit = (top_mask >> mask_shift) + 1; + TORRENT_ASSERT_VAL(bucket_size_limit <= 256, bucket_size_limit); + bool sub_buckets[256]; + memset(sub_buckets, 0, sizeof(sub_buckets)); + + int id_shift; + // the last bucket is special, since it hasn't been split yet, it + // includes that top bit as well + if (bucket_index + 1 == m_buckets.size()) + id_shift = bucket_index; + else + id_shift = bucket_index + 1; + + for (bucket_t::const_iterator j = i->live_nodes.begin() + , end2(i->live_nodes.end()); j != end2; ++j) + { + node_id id = j->id; + id <<= id_shift; + int b = (id[0] & top_mask) >> mask_shift; + TORRENT_ASSERT(b >= 0 && b < int(sizeof(sub_buckets)/sizeof(sub_buckets[0]))); + sub_buckets[b] = true; + } + + cursor += snprintf(&buf[cursor], buf.size() - cursor + , "%2d mask: %2x: [", bucket_index, (top_mask >> mask_shift)); + + for (int j = 0; j < bucket_size_limit; ++j) + { + cursor += snprintf(&buf[cursor], buf.size() - cursor + , (sub_buckets[j] ? "X" : " ")); + } + cursor += snprintf(&buf[cursor], buf.size() - cursor + , "]\n"); + if (cursor > buf.size() - 500) buf.resize(buf.size() * 3 / 2); + } + buf[cursor] = '\0'; + os << &buf[0]; +} + +#endif + +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 = NULL; + + // this will have a bias towards pinging nodes close to us first. + int idx = m_buckets.size() - 1; + for (table_t::reverse_iterator i = m_buckets.rbegin() + , end(m_buckets.rend()); i != end; ++i, --idx) + { + for (bucket_t::iterator j = i->live_nodes.begin() + , end2(i->live_nodes.end()); j != end2; ++j) + { + // this shouldn't happen + TORRENT_ASSERT(m_id != j->id); + if (j->id == m_id) continue; + + if (j->last_queried == min_time()) + { + candidate = &*j; + goto out; + } + + if (candidate == NULL || j->last_queried < candidate->last_queried) + { + candidate = &*j; + } + } + } +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; +} + +void routing_table::replacement_cache(bucket_t& nodes) const +{ + for (table_t::const_iterator i = m_buckets.begin() + , end(m_buckets.end()); i != end; ++i) + { + std::copy(i->replacements.begin(), i->replacements.end() + , std::back_inserter(nodes)); + } +} + +routing_table::table_t::iterator routing_table::find_bucket(node_id const& id) +{ +// TORRENT_ASSERT(id != m_id); + + int num_buckets = 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); + + table_t::iterator i = m_buckets.begin(); + std::advance(i, bucket_index); + return i; +} + +namespace { + +bool compare_ip_cidr(node_entry const& lhs, node_entry const& rhs) +{ + TORRENT_ASSERT(lhs.addr().is_v4() == rhs.addr().is_v4()); + // the number of bits in the IPs that may match. If + // more bits that this matches, something suspicious is + // going on and we shouldn't add the second one to our + // routing table + int cutoff = rhs.addr().is_v4() ? 8 : 64; + int dist = cidr_distance(lhs.addr(), rhs.addr()); + return dist <= cutoff; +} + +} // anonymous namespace + +node_entry* routing_table::find_node(udp::endpoint const& ep + , routing_table::table_t::iterator* bucket) +{ + for (table_t::iterator i = m_buckets.begin() + , end(m_buckets.end()); i != end; ++i) + { + for (bucket_t::iterator j = i->replacements.begin(); + j != i->replacements.end(); ++j) + { + if (j->addr() != ep.address()) continue; + if (j->port() != ep.port()) continue; + *bucket = i; + return &*j; + } + for (bucket_t::iterator j = i->live_nodes.begin(); + j != i->live_nodes.end(); ++j) + { + if (j->addr() != ep.address()) continue; + if (j->port() != ep.port()) continue; + *bucket = i; + return &*j; + } + } + *bucket = m_buckets.end(); + return 0; +} + +void routing_table::remove_node(node_entry* n + , routing_table::table_t::iterator bucket) +{ + INVARIANT_CHECK; + + if (!bucket->replacements.empty() + && n >= &bucket->replacements[0] + && n < &bucket->replacements[0] + bucket->replacements.size()) + { + int idx = n - &bucket->replacements[0]; + TORRENT_ASSERT(m_ips.count(n->a) > 0); + erase_one(m_ips, n->a); + bucket->replacements.erase(bucket->replacements.begin() + idx); + } + + if (!bucket->live_nodes.empty() + && n >= &bucket->live_nodes[0] + && n < &bucket->live_nodes[0] + bucket->live_nodes.size()) + { + int idx = n - &bucket->live_nodes[0]; + TORRENT_ASSERT(m_ips.count(n->a) > 0); + erase_one(m_ips, n->a); + bucket->live_nodes.erase(bucket->live_nodes.begin() + idx); + } +} + +bool routing_table::add_node(node_entry 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 resistence 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); + if (s == node_added) return true; + return false; + } + + // if the new bucket still has too many nodes in it, we need to keep + // splitting + if (m_buckets.back().live_nodes.size() > bucket_limit(m_buckets.size()-1)) + continue; + + s = add_node_impl(e); + if (s == failed_to_add) return false; + if (s == node_added) return true; + } + return false; +} + +routing_table::add_node_status_t routing_table::add_node_impl(node_entry e) +{ +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS +// INVARIANT_CHECK; +#endif + + // 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; + + // don't add ourself + if (e.id == m_id) return failed_to_add; + + // do we already have this IP in the table? + if (m_ips.count(e.addr().to_v4().to_bytes()) > 0) + { + // this exact IP already exists in the table. It might be the case + // that the node changed IP. If pinged is true, and the port also + // matches then we assume it's in fact the same node, and just update + // the routing table + // 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 + + table_t::iterator existing_bucket; + node_entry* existing = find_node(e.ep(), &existing_bucket); + if (existing == 0) + { + // the node we're trying to add is not a match with an existing node. we + // should ignore it, unless we allow duplicate IPs in our routing + // table. There could be a node with the same IP, but with a different + // port. m_ips just contain IP addresses, whereas the lookup we just + // performed was for full endpoints (address, port). + if (m_settings.restrict_routing_ips) + { +#ifndef TORRENT_DISABLE_LOGGING + char hex_id[41]; + to_hex(reinterpret_cast(&e.id[0]), 20, hex_id); + m_log->log(dht_logger::routing_table, "ignoring node (duplicate IP): %s %s" + , hex_id, 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; + } + return node_added; + } + else if (!e.pinged()) + { + // this may be a routing table poison attack. If we haven't confirmed + // that this peer actually exist with this new node ID yet, ignore it. + // we definitely don't want to replace the existing entry with this one + if (m_settings.restrict_routing_ips) + return failed_to_add; + } + else + { + TORRENT_ASSERT(existing->id != e.id); + // this is the same IP and port, but with + // a new node ID. remove the old entry and + // replace it with this new ID + remove_node(existing, existing_bucket); + } + } + + table_t::iterator i = find_bucket(e.id); + bucket_t& b = i->live_nodes; + bucket_t& rb = i->replacements; + int const bucket_index = 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); + int const next_bucket_size_limit = bucket_limit(bucket_index + 1); + + bucket_t::iterator j; + + // if the node already exists, we don't need it + j = std::find_if(b.begin(), b.end() + , boost::bind(&node_entry::id, _1) == 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(), boost::bind(&node_entry::id, _1) == 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; + erase_one(m_ips, j->addr().to_v4().to_bytes()); + rb.erase(j); + } + + if (m_settings.restrict_routing_ips) + { + // don't allow multiple entries from IPs very close to each other + // TODO: 3 the call to compare_ip_cidr here is expensive. peel off some + // layers of abstraction here to make it quicker. Look at xoring and using _builtin_ctz() + j = std::find_if(b.begin(), b.end(), boost::bind(&compare_ip_cidr, _1, e)); + if (j == b.end()) + { + j = std::find_if(rb.begin(), rb.end(), boost::bind(&compare_ip_cidr, _1, e)); + 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 + char hex_id1[41]; + to_hex(e.id.data(), 20, hex_id1); + char hex_id2[41]; + to_hex(j->id.data(), 20, hex_id2); + m_log->log(dht_logger::routing_table, "ignoring node: %s %s existing node: %s %s" + , hex_id1, print_address(e.addr()).c_str() + , hex_id2, print_address(j->addr()).c_str()); +#endif + return failed_to_add; + } +ip_ok: + + // can we split the bucket? + // only nodes that haven't failed can split the bucket, and we can only + // split the last bucket + bool const can_split = (boost::next(i) == m_buckets.end() + && m_buckets.size() < 159) + && e.fail_count() == 0 + && (i == m_buckets.begin() || boost::prior(i)->live_nodes.size() > 1); + + // 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 (int(b.size()) < (can_split ? next_bucket_size_limit : bucket_size_limit)) + { + if (b.empty()) b.reserve(bucket_size_limit); + b.push_back(e); + m_ips.insert(e.addr().to_v4().to_bytes()); + return node_added; + } + + // if there is no room, we look for nodes that are not 'pinged', + // i.e. we haven't confirmed that they respond to messages. + // Then we look for nodes marked as stale + // in the k-bucket. If we find one, we can replace it. + // 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. + + if (e.pinged() && e.fail_count() == 0) + { + // if the node we're trying to insert is considered pinged, + // we may replace other nodes that aren't pinged + + j = std::find_if(b.begin(), b.end(), boost::bind(&node_entry::pinged, _1) == false); + + if (j != b.end() && !j->pinged()) + { + // j points to a node that has not been pinged. + // Replace it with this new one + erase_one(m_ips, j->addr().to_v4().to_bytes()); + *j = e; + m_ips.insert(e.addr().to_v4().to_bytes()); + return node_added; + } + + // 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. + + j = std::max_element(b.begin(), b.end() + , boost::bind(&node_entry::fail_count, _1) + < boost::bind(&node_entry::fail_count, _2)); + 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 + erase_one(m_ips, j->addr().to_v4().to_bytes()); + *j = e; + m_ips.insert(e.addr().to_v4().to_bytes()); + return node_added; + } + + // 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 + + boost::uint32_t mask = bucket_size_limit - 1; + int mask_shift = 0; + TORRENT_ASSERT_VAL(mask > 0, mask); + while ((mask & 0x80) == 0) + { + mask <<= 1; + ++mask_shift; + } + + // in case bucket_size_limit is not an even power of 2 + mask = (0xff << mask_shift) & 0xff; + + // pick out all nodes that have the same prefix as the new node + std::vector nodes; + bool force_replace = false; + + { + node_id id = e.id; + // the last bucket is special, since it hasn't been split yet, it + // includes that top bit as well + if (bucket_index + 1 == m_buckets.size()) + id <<= bucket_index; + else + id <<= bucket_index + 1; + + for (j = b.begin(); j != b.end(); ++j) + { + if (!matching_prefix(*j, mask, id[0] & mask, bucket_index)) continue; + nodes.push_back(j); + } + } + + if (!nodes.empty()) + { + j = *std::max_element(nodes.begin(), nodes.end() + , boost::bind(&node_entry::rtt, boost::bind(&bucket_t::iterator::operator*, _1)) + < boost::bind(&node_entry::rtt, boost::bind(&bucket_t::iterator::operator*, _2))); + } + else + { + // there is no node in this prefix-slot, there may be some + // nodes sharing a prefix. Find all nodes that do not + // have a unique prefix + + // find node entries with duplicate prefixes in O(1) + std::vector prefix(1 << (8 - mask_shift), b.end()); + TORRENT_ASSERT(int(prefix.size()) >= bucket_size_limit); + + // the begin iterator from this object is used as a placeholder + // for an occupied slot whose node has already been added to the + // duplicate nodes list. + bucket_t placeholder; + + nodes.reserve(b.size()); + for (j = b.begin(); j != b.end(); ++j) + { + node_id id = j->id; + id <<= bucket_index + 1; + int this_prefix = (id[0] & mask) >> mask_shift; + TORRENT_ASSERT(this_prefix >= 0); + TORRENT_ASSERT(this_prefix < int(prefix.size())); + if (prefix[this_prefix] != b.end()) + { + // there's already a node with this prefix. Remember both + // duplicates. + nodes.push_back(j); + + if (prefix[this_prefix] != placeholder.begin()) + { + nodes.push_back(prefix[this_prefix]); + prefix[this_prefix] = placeholder.begin(); + } + } + } + + if (!nodes.empty()) + { + // from these nodes, pick the one with the highest RTT + // and replace it + + std::vector::iterator k = std::max_element(nodes.begin(), nodes.end() + , boost::bind(&node_entry::rtt, boost::bind(&bucket_t::iterator::operator*, _1)) + < boost::bind(&node_entry::rtt, boost::bind(&bucket_t::iterator::operator*, _2))); + + // in this case, we would really rather replace the node even if + // the new node has higher RTT, becase it fills a new prefix that we otherwise + // don't have. + force_replace = true; + j = *k; + } + else + { + j = std::max_element(b.begin(), b.end() + , boost::bind(&node_entry::rtt, _1) + < boost::bind(&node_entry::rtt, _2)); + } + } + + if (j != b.end() && (force_replace || j->rtt > e.rtt)) + { + erase_one(m_ips, j->addr().to_v4().to_bytes()); + *j = e; + m_ips.insert(e.addr().to_v4().to_bytes()); +#ifndef TORRENT_DISABLE_LOGGING + if (m_log) + { + char hex_id[41]; + to_hex(e.id.data(), sha1_hash::size, hex_id); + m_log->log(dht_logger::routing_table, "replacing node with higher RTT: %s %s" + , hex_id, print_address(e.addr()).c_str()); + } +#endif + return node_added; + } + // in order to keep lookup times small, prefer nodes with low RTTs + + } + + // if we can't split, try to insert into the replacement bucket + + if (!can_split) + { + // 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() + , boost::bind(&node_entry::id, _1) == 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(), boost::bind(&node_entry::pinged, _1) == false); + if (j == rb.end()) j = rb.begin(); + erase_one(m_ips, j->addr().to_v4().to_bytes()); + rb.erase(j); + } + + if (rb.empty()) rb.reserve(m_bucket_size); + rb.push_back(e); + m_ips.insert(e.addr().to_v4().to_bytes()); + return node_added; + } + + return need_bucket_split; +} + +void routing_table::split_bucket() +{ + INVARIANT_CHECK; + + int const bucket_index = 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 - distane_exp(m_id, id)) >= (i - m_buckets.begin()) + // to the new bucket + int const new_bucket_size = bucket_limit(bucket_index + 1); + for (bucket_t::iterator 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 (b.size() > bucket_size_limit) + { + // TODO: 2 move the lowest priority nodes to the replacement bucket + for (bucket_t::iterator 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 (bucket_t::iterator j = rb.begin(); j != rb.end();) + { + if (distance_exp(m_id, j->id) >= 159 - bucket_index) + { + if (int(b.size()) >= bucket_size_limit) + { + ++j; + continue; + } + b.push_back(*j); + } + else + { + // this entry belongs in the new bucket + if (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 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 (int i = 0; i < old_buckets.size(); ++i) + { + bucket_t const& bucket = old_buckets[i].live_nodes; + for (int j = 0; j < bucket.size(); ++j) + { + add_node(bucket[j]); + } + } + + // now add back the replacement nodes + for (int i = 0; i < old_buckets.size(); ++i) + { + bucket_t const& bucket = old_buckets[i].replacements; + for (int j = 0; j < bucket.size(); ++j) + { + add_node(bucket[j]); + } + } +} + +void routing_table::for_each_node( + void (*fun1)(void*, node_entry const&) + , void (*fun2)(void*, node_entry const&) + , void* userdata) const +{ + for (table_t::const_iterator i = m_buckets.begin() + , end(m_buckets.end()); i != end; ++i) + { + if (fun1) + { + for (bucket_t::const_iterator j = i->live_nodes.begin() + , end2(i->live_nodes.end()); j != end2; ++j) + fun1(userdata, *j); + } + if (fun2) + { + for (bucket_t::const_iterator j = i->replacements.begin() + , end2(i->replacements.end()); j != end2; ++j) + fun2(userdata, *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; + + table_t::iterator i = find_bucket(nid); + bucket_t& b = i->live_nodes; + bucket_t& rb = i->replacements; + + bucket_t::iterator j = std::find_if(b.begin(), b.end() + , boost::bind(&node_entry::id, _1) == nid); + + if (j == b.end()) + { + j = std::find_if(rb.begin(), rb.end() + , boost::bind(&node_entry::id, _1) == nid); + + if (j == rb.end() + || j->ep() != ep) return; + + j->timed_out(); + +#ifndef TORRENT_DISABLE_LOGGING + char hex_id[41]; + to_hex(nid.data(), 20, hex_id); + m_log->log(dht_logger::routing_table, "NODE FAILED id: %s ip: %s fails: %d pinged: %d up-time: %d" + , hex_id, print_endpoint(j->ep()).c_str() + , int(j->fail_count()) + , int(j->pinged()) + , int(total_seconds(aux::time_now() - j->first_seen))); +#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 + char hex_id[41]; + to_hex(nid.data(), 20, hex_id); + m_log->log(dht_logger::routing_table, "NODE FAILED id: %s ip: %s fails: %d pinged: %d up-time: %d" + , hex_id, print_endpoint(j->ep()).c_str() + , int(j->fail_count()) + , int(j->pinged()) + , int(total_seconds(aux::time_now() - j->first_seen))); +#endif + + // if this node has failed too many times, or if this node + // has never responded at all, remove it + if (j->fail_count() >= m_settings.max_fail_count || !j->pinged()) + { + erase_one(m_ips, j->addr().to_v4().to_bytes()); + b.erase(j); + } + return; + } + + erase_one(m_ips, j->a); + b.erase(j); + + // sort by RTT first, to find the node with the lowest + // RTT that is pinged + std::sort(rb.begin(), rb.end() + , boost::bind(&node_entry::rtt, _1) < boost::bind(&node_entry::rtt, _2)); + + j = std::find_if(rb.begin(), rb.end(), boost::bind(&node_entry::pinged, _1)); + if (j == rb.end()) j = rb.begin(); + b.push_back(*j); + rb.erase(j); +} + +void routing_table::add_router_node(udp::endpoint 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 ep, int rtt) +{ + if (!verify_node_address(m_settings, id, ep.address())) return false; + return add_node(node_entry(id, ep, rtt, true)); +} + +// fills the vector with the k nodes from our buckets that +// are nearest to the given id. +void routing_table::find_node(node_id const& target + , std::vector& l, int options, int count) +{ + l.clear(); + if (count == 0) count = m_bucket_size; + + table_t::iterator i = find_bucket(target); + int bucket_index = std::distance(m_buckets.begin(), i); + int bucket_size_limit = bucket_limit(bucket_index); + + l.reserve(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& b = j->live_nodes; + if (options & include_failed) + { + copy(b.begin(), b.end() + , std::back_inserter(l)); + } + else + { + std::remove_copy_if(b.begin(), b.end() + , std::back_inserter(l) + , !boost::bind(&node_entry::confirmed, _1)); + } + + if (int(l.size()) == count) return; + + if (int(l.size()) > count) + { + // sort the nodes by how close they are to the target + std::sort(l.begin() + unsorted_start_idx, l.end(), boost::bind(&compare_ref + , boost::bind(&node_entry::id, _1) + , boost::bind(&node_entry::id, _2), target)); + + l.resize(count); + return; + } + unsorted_start_idx = int(l.size()); + } + + // if we still don't have enough nodes, copy nodes + // further away from us + + if (i == m_buckets.begin()) + return; + + j = i; + + unsorted_start_idx = int(l.size()); + do + { + --j; + bucket_t& 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) + , !boost::bind(&node_entry::confirmed, _1)); + } + + if (int(l.size()) == count) return; + + if (int(l.size()) > count) + { + // sort the nodes by how close they are to the target + std::sort(l.begin() + unsorted_start_idx, l.end(), boost::bind(&compare_ref + , boost::bind(&node_entry::id, _1) + , boost::bind(&node_entry::id, _2), target)); + + l.resize(count); + return; + } + unsorted_start_idx = int(l.size()); + } + while (j != m_buckets.begin() && int(l.size()) < count); + + TORRENT_ASSERT(int(l.size()) <= count); +} + +#if TORRENT_USE_INVARIANT_CHECKS +void routing_table::check_invariant() const +{ + boost::unordered_multiset all_ips; + + for (table_t::const_iterator i = m_buckets.begin() + , end(m_buckets.end()); i != end; ++i) + { + for (bucket_t::const_iterator j = i->replacements.begin(); + j != i->replacements.end(); ++j) + { + all_ips.insert(j->addr().to_v4().to_bytes()); + } + for (bucket_t::const_iterator j = i->live_nodes.begin(); + j != i->live_nodes.end(); ++j) + { + all_ips.insert(j->addr().to_v4().to_bytes()); + } + } + + TORRENT_ASSERT(all_ips == m_ips); +} +#endif + +bool routing_table::is_full(int bucket) const +{ + int num_buckets = m_buckets.size(); + if (num_buckets == 0) return false; + if (bucket >= num_buckets) return false; + + table_t::const_iterator i = m_buckets.begin(); + std::advance(i, bucket); + return (i->live_nodes.size() >= bucket_limit(bucket) + && i->replacements.size() >= m_bucket_size); +} + +} } // namespace libtorrent::dht + diff --git a/src/kademlia/rpc_manager.cpp b/src/kademlia/rpc_manager.cpp new file mode 100644 index 0000000..63aaa4a --- /dev/null +++ b/src/kademlia/rpc_manager.cpp @@ -0,0 +1,502 @@ +/* + +Copyright (c) 2006-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/socket.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include +#include +#include +#include // for generate_random_id +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include // for print_endpoint +#include +#include // for dht_settings +#include +#include // for aux::time_now + +namespace libtorrent { namespace dht +{ + +namespace io = libtorrent::detail; + +void intrusive_ptr_add_ref(observer const* o) +{ + TORRENT_ASSERT(o != 0); + TORRENT_ASSERT(o->m_refs < 0xffff); + ++o->m_refs; +} + +void intrusive_ptr_release(observer const* o) +{ + TORRENT_ASSERT(o != 0); + TORRENT_ASSERT(o->m_refs > 0); + if (--o->m_refs == 0) + { + boost::intrusive_ptr ta = o->algorithm(); + (const_cast(o))->~observer(); + ta->free_observer(const_cast(o)); + } +} + +// TODO: 3 move this into it's own .cpp file +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 TORRENT_USE_IPV6 + if (ep.address().is_v6()) + { + flags |= flag_ipv6_address; + m_addr.v6 = ep.address().to_v6().to_bytes(); + } + else +#endif + { + flags &= ~flag_ipv6_address; + m_addr.v4 = ep.address().to_v4().to_bytes(); + } +} + +address observer::target_addr() const +{ +#if TORRENT_USE_IPV6 + if (flags & flag_ipv6_address) + return address_v6(m_addr.v6); + else +#endif + return address_v4(m_addr.v4); +} + +udp::endpoint observer::target_ep() const +{ + return udp::endpoint(target_addr(), m_port); +} + +void observer::abort() +{ + if (flags & flag_done) return; + flags |= flag_done; + m_algorithm->failed(observer_ptr(this), traversal_algorithm::prevent_request); +} + +void observer::done() +{ + if (flags & flag_done) return; + flags |= flag_done; + m_algorithm->finished(observer_ptr(this)); +} + +void observer::short_timeout() +{ + if (flags & flag_short_timeout) return; + m_algorithm->failed(observer_ptr(this), traversal_algorithm::short_timeout); +} + +void observer::timeout() +{ + if (flags & flag_done) return; + flags |= flag_done; + m_algorithm->failed(observer_ptr(this)); +} + +void observer::set_id(node_id const& id) +{ + if (m_id == id) return; + m_id = id; + if (m_algorithm) m_algorithm->resort_results(); +} + +enum { observer_size = max9< + sizeof(find_data_observer) + , sizeof(announce_observer) + , sizeof(put_data_observer) + , sizeof(direct_observer) + , sizeof(get_item_observer) + , sizeof(get_peers_observer) + , sizeof(obfuscated_get_peers_observer) + , sizeof(null_observer) + , sizeof(traversal_observer) + >::value +}; + +rpc_manager::rpc_manager(node_id const& our_id + , dht_settings const& settings + , routing_table& table, udp_socket_interface* sock + , dht_logger* log) + : m_pool_allocator(observer_size, 10) + , m_sock(sock) + , m_log(log) + , m_settings(settings) + , m_table(table) + , m_timer(aux::time_now()) + , m_our_id(our_id) + , m_allocated_observers(0) + , m_destructing(false) +{} + +rpc_manager::~rpc_manager() +{ + TORRENT_ASSERT(!m_destructing); + m_destructing = true; + + for (transactions_t::iterator i = m_transactions.begin() + , end(m_transactions.end()); i != end; ++i) + { + i->second->abort(); + } +} + +void* rpc_manager::allocate_observer() +{ + m_pool_allocator.set_next_size(10); + void* ret = m_pool_allocator.malloc(); + if (ret) ++m_allocated_observers; + return ret; +} + +void rpc_manager::free_observer(void* ptr) +{ + if (!ptr) return; + --m_allocated_observers; + TORRENT_ASSERT(reinterpret_cast(ptr)->m_in_use == false); + m_pool_allocator.free(ptr); +} + +#if TORRENT_USE_ASSERTS +size_t rpc_manager::allocation_size() const +{ + return observer_size; +} +#endif +#if TORRENT_USE_INVARIANT_CHECKS +void rpc_manager::check_invariant() const +{ + for (transactions_t::const_iterator i = m_transactions.begin() + , end(m_transactions.end()); i != end; ++i) + { + TORRENT_ASSERT(i->second); + } +} +#endif + +void rpc_manager::unreachable(udp::endpoint const& ep) +{ +#ifndef TORRENT_DISABLE_LOGGING + m_log->log(dht_logger::rpc_manager, "PORT_UNREACHABLE [ ip: %s ]" + , print_endpoint(ep).c_str()); +#endif + + for (transactions_t::iterator i = m_transactions.begin(); + i != m_transactions.end();) + { + TORRENT_ASSERT(i->second); + observer_ptr const& o = i->second; + if (o->target_ep() != ep) { ++i; continue; } + observer_ptr ptr = i->second; + i = m_transactions.erase(i); +#ifndef TORRENT_DISABLE_LOGGING + m_log->log(dht_logger::rpc_manager, "found transaction [ tid: %d ]" + , int(ptr->transaction_id())); +#endif + ptr->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 + + std::string transaction_id = m.message.dict_find_string_value("t"); + if (transaction_id.empty()) return false; + + std::string::const_iterator ptr = transaction_id.begin(); + int tid = transaction_id.size() != 2 ? -1 : io::read_uint16(ptr); + + observer_ptr o; + std::pair range = m_transactions.equal_range(tid); + for (transactions_t::iterator 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 + 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, 0); + return false; + } + + time_point now = clock_type::now(); + +#ifndef TORRENT_DISABLE_LOGGING + m_log->log(dht_logger::rpc_manager, "round trip time(ms): %" PRId64 " from %s" + , 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 + 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, "reply with error from %s: (%" PRId64 ") %s" + , print_endpoint(m.addr).c_str() + , err.list_int_value_at(0) + , err.list_string_value_at(1).c_str()); + } + else + { + m_log->log(dht_logger::rpc_manager, "reply with (malformed) error from %s" + , 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 ret_ent = m.message.dict_find_dict("r"); + if (!ret_ent) + { + o->timeout(); + return false; + } + + bdecode_node 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 nid = node_id(node_id_ent.string_ptr()); + if (m_settings.enforce_node_id && !verify_id(nid, m.addr.address())) + { + o->timeout(); + return false; + } + +#ifndef TORRENT_DISABLE_LOGGING + m_log->log(dht_logger::rpc_manager, "[%p] reply with transaction id: %d from %s" + , static_cast(o->algorithm()), 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; + + static const int short_timeout = 1; + static const int timeout = 15; + + // look for observers that have timed out + + if (m_transactions.empty()) return seconds(short_timeout); + + std::vector timeouts; + std::vector short_timeouts; + + time_duration ret = seconds(short_timeout); + time_point now = aux::time_now(); + + for (transactions_t::iterator i = m_transactions.begin(); + i != m_transactions.end();) + { + observer_ptr o = i->second; + + time_duration diff = now - o->sent(); + if (diff >= seconds(timeout)) + { +#ifndef TORRENT_DISABLE_LOGGING + m_log->log(dht_logger::rpc_manager, "[%p] timing out transaction id: %d from: %s" + , static_cast(o->algorithm()), o->transaction_id() + , print_endpoint(o->target_ep()).c_str()); +#endif + m_transactions.erase(i++); + timeouts.push_back(o); + continue; + } + + // don't call short_timeout() again if we've + // already called it once + if (diff >= seconds(short_timeout) && !o->has_short_timeout()) + { +#ifndef TORRENT_DISABLE_LOGGING + m_log->log(dht_logger::rpc_manager, "[%p] short-timing out transaction id: %d from: %s" + , static_cast(o->algorithm()), o->transaction_id() + , print_endpoint(o->target_ep()).c_str()); +#endif + ++i; + + short_timeouts.push_back(o); + continue; + } + + ret = std::min(seconds(timeout) - diff, ret); + ++i; + } + + std::for_each(timeouts.begin(), timeouts.end(), boost::bind(&observer::timeout, _1)); + std::for_each(short_timeouts.begin(), short_timeouts.end(), boost::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 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]; + int tid = (random() ^ (random() << 5)) & 0xffff; + io::write_uint16(tid, out); + e["t"] = transaction_id; + + // When a DHT node enters the read-only state, in each outgoing query message, + // places a 'ro' key in the top-level message dictionary and sets its value to 1. + if (m_settings.read_only) e["ro"] = 1; + + o->set_target(target_addr); + o->set_transaction_id(tid); + +#ifndef TORRENT_DISABLE_LOGGING + m_log->log(dht_logger::rpc_manager, "[%p] invoking %s -> %s" + , static_cast(o->algorithm()), e["q"].string().c_str() + , print_endpoint(target_addr).c_str()); +#endif + + if (m_sock->send_packet(e, target_addr, 1)) + { + m_transactions.insert(std::make_pair(tid, o)); +#if TORRENT_USE_ASSERTS + o->m_was_sent = true; +#endif + return true; + } + return false; +} + +observer::~observer() +{ + // if the message was sent, it must have been + // reported back to the traversal_algorithm as + // well. If it wasn't sent, it cannot have been + // reported back + TORRENT_ASSERT(m_was_sent == bool(flags & flag_done) || m_was_abandoned); + TORRENT_ASSERT(!m_in_constructor); +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(m_in_use); + m_in_use = false; +#endif +} + +} } // namespace libtorrent::dht + diff --git a/src/kademlia/traversal_algorithm.cpp b/src/kademlia/traversal_algorithm.cpp new file mode 100644 index 0000000..d7d380a --- /dev/null +++ b/src/kademlia/traversal_algorithm.cpp @@ -0,0 +1,638 @@ +/* + +Copyright (c) 2006-2016, Arvid Norberg & Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/time.hpp" // for total_seconds + +#include +#include +#include +#include +#include // for dht_logger +#include +#include // for read_*_endpoint +#include // for dht_lookup +#include + +#include + +namespace libtorrent { namespace dht +{ +using detail::read_v4_endpoint; +#if TORRENT_USE_IPV6 +using detail::read_v6_endpoint; +#endif + +#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(void* ptr + , udp::endpoint const& ep, node_id const& id) +{ + observer_ptr o(new (ptr) null_observer(boost::intrusive_ptr(this), ep, id)); +#if TORRENT_USE_ASSERTS + o->m_in_constructor = false; +#endif + return o; +} + +traversal_algorithm::traversal_algorithm( + node& dht_node + , node_id target) + : m_node(dht_node) + , m_target(target) + , m_ref_count(0) + , m_invoke_count(0) + , m_branch_factor(3) + , m_responses(0) + , m_timeouts(0) +{ +#ifndef TORRENT_DISABLE_LOGGING + if (get_node().observer()) + { + char hex_target[41]; + to_hex(reinterpret_cast(&target[0]), 20, hex_target); + get_node().observer()->log(dht_logger::traversal, "[%p] NEW target: %s k: %d" + , static_cast(this), hex_target, int(m_node.m_table.bucket_size())); + } +#endif +} + +void traversal_algorithm::resort_results() +{ + std::sort( + m_results.begin() + , m_results.end() + , boost::bind( + compare_ref + , boost::bind(&observer::id, _1) + , boost::bind(&observer::id, _2) + , m_target + ) + ); +} + +void traversal_algorithm::add_entry(node_id const& id, udp::endpoint addr, unsigned char flags) +{ + TORRENT_ASSERT(m_node.m_rpc.allocation_size() >= sizeof(find_data_observer)); + void* ptr = m_node.m_rpc.allocate_observer(); + if (ptr == 0) + { +#ifndef TORRENT_DISABLE_LOGGING + if (get_node().observer()) + { + get_node().observer()->log(dht_logger::traversal, "[%p] failed to allocate memory or observer. aborting!" + , static_cast(this)); + } +#endif + done(); + return; + } + observer_ptr o = new_observer(ptr, addr, id); + if (id.is_all_zeros()) + { + o->set_id(generate_random_id()); + o->flags |= observer::flag_no_id; + } + + o->flags |= flags; + + TORRENT_ASSERT(libtorrent::dht::is_sorted(m_results.begin(), m_results.end() + , boost::bind( + compare_ref + , boost::bind(&observer::id, _1) + , boost::bind(&observer::id, _2) + , m_target) + )); + + std::vector::iterator iter = std::lower_bound( + m_results.begin() + , m_results.end() + , o + , boost::bind( + compare_ref + , boost::bind(&observer::id, _1) + , boost::bind(&observer::id, _2) + , m_target + ) + ); + + if (iter == m_results.end() || (*iter)->id() != id) + { + if (m_node.settings().restrict_search_ips + && !(flags & observer::flag_initial)) + { + // mask the lower octet + boost::uint32_t prefix4 = o->target_addr().to_v4().to_ulong(); + prefix4 &= 0xffffff00; + + if (m_peer4_prefixes.count(prefix4) > 0) + { + // 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 + if (get_node().observer()) + { + char hex_id[41]; + to_hex(reinterpret_cast(&o->id()[0]), 20, hex_id); + get_node().observer()->log(dht_logger::traversal + , "[%p] traversal DUPLICATE node. id: %s addr: %s type: %s" + , static_cast(this), hex_id, print_address(o->target_addr()).c_str(), name()); + } +#endif + return; + } + + m_peer4_prefixes.insert(prefix4); + } + + TORRENT_ASSERT((o->flags & observer::flag_no_id) + || std::find_if(m_results.begin(), m_results.end() + , boost::bind(&observer::id, _1) == id) == m_results.end()); + +#ifndef TORRENT_DISABLE_LOGGING + if (get_node().observer()) + { + char hex_id[41]; + to_hex(reinterpret_cast(&id[0]), 20, hex_id); + get_node().observer()->log(dht_logger::traversal + , "[%p] ADD id: %s addr: %s distance: %d invoke-count: %d type: %s" + , static_cast(this), hex_id, print_endpoint(addr).c_str() + , distance_exp(m_target, id), m_invoke_count, name()); + } +#endif + iter = m_results.insert(iter, o); + + TORRENT_ASSERT(libtorrent::dht::is_sorted(m_results.begin(), m_results.end() + , boost::bind( + compare_ref + , boost::bind(&observer::id, _1) + , boost::bind(&observer::id, _2) + , m_target) + )); + } + + if (m_results.size() > 100) + { + for (int i = 100; i < int(m_results.size()); ++i) + { + if ((m_results[i]->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() + m_results[i]->flags |= observer::flag_done; + TORRENT_ASSERT(m_invoke_count > 0); + --m_invoke_count; + } + +#if TORRENT_USE_ASSERTS + m_results[i]->m_was_abandoned = true; +#endif + } + m_results.resize(100); + } +} + +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 is_done = add_requests(); + if (is_done) done(); +} + +void* traversal_algorithm::allocate_observer() +{ + return m_node.m_rpc.allocate_observer(); +} + +void traversal_algorithm::free_observer(void* ptr) +{ + m_node.m_rpc.free_observer(ptr); +} + +char const* traversal_algorithm::name() const +{ + return "traversal_algorithm"; +} + +void traversal_algorithm::traverse(node_id const& id, udp::endpoint addr) +{ +#ifndef TORRENT_DISABLE_LOGGING + if (id.is_all_zeros() && get_node().observer()) + { + get_node().observer()->log(dht_logger::traversal + , "[%p] WARNING node returned a list which included a node with id 0" + , static_cast(this)); + } +#endif + + // let the routing table know this node may exist + m_node.m_table.heard_about(id, addr); + + add_entry(id, addr, 0); +} + +void traversal_algorithm::finished(observer_ptr o) +{ +#if TORRENT_USE_ASSERTS + std::vector::iterator 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 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, int flags) +{ + // don't tell the routing table about + // node ids that we just generated ourself + if ((o->flags & observer::flag_no_id) == 0) + m_node.m_table.node_failed(o->id(), o->target_ep()); + + if (m_results.empty()) return; + + 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) == 0) + ++m_branch_factor; + o->flags |= observer::flag_short_timeout; +#ifndef TORRENT_DISABLE_LOGGING + if (get_node().observer()) + { + char hex_id[41]; + to_hex(reinterpret_cast(&o->id()[0]), 20, hex_id); + get_node().observer()->log(dht_logger::traversal + , "[%p] 1ST_TIMEOUT id: %s distance: %d addr: %s branch-factor: %d " + "invoke-count: %d type: %s" + , static_cast(this), hex_id, distance_exp(m_target, o->id()) + , print_address(o->target_addr()).c_str(), m_branch_factor + , m_invoke_count, name()); + } +#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 + if (o->flags & observer::flag_short_timeout) + --m_branch_factor; + +#ifndef TORRENT_DISABLE_LOGGING + if (get_node().observer()) + { + char hex_id[41]; + to_hex(reinterpret_cast(&o->id()[0]), 20, hex_id); + get_node().observer()->log(dht_logger::traversal + , "[%p] TIMEOUT id: %s distance: %d addr: %s branch-factor: %d " + "invoke-count: %d type: %s" + , static_cast(this), hex_id, distance_exp(m_target, o->id()) + , print_address(o->target_addr()).c_str(), m_branch_factor + , m_invoke_count, name()); + } +#endif + + ++m_timeouts; + TORRENT_ASSERT(m_invoke_count > 0); + --m_invoke_count; + } + + if (flags & prevent_request) + { + --m_branch_factor; + if (m_branch_factor <= 0) m_branch_factor = 1; + } + bool is_done = add_requests(); + if (is_done) done(); +} + +void traversal_algorithm::done() +{ +#ifndef TORRENT_DISABLE_LOGGING + int results_target = m_node.m_table.bucket_size(); + int closest_target = 160; +#endif + + for (std::vector::iterator i = m_results.begin() + , end(m_results.end()); i != end; ++i) + { + boost::intrusive_ptr o = *i; + 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 + if (results_target > 0 && (o->flags & observer::flag_alive) && get_node().observer()) + { + TORRENT_ASSERT(o->flags & observer::flag_queried); + char hex_id[41]; + to_hex(reinterpret_cast(&o->id()[0]), 20, hex_id); + get_node().observer()->log(dht_logger::traversal + , "[%p] id: %s distance: %d addr: %s" + , static_cast(this), hex_id, closest_target + , print_endpoint(o->target_ep()).c_str()); + + --results_target; + int dist = distance_exp(m_target, o->id()); + if (dist < closest_target) closest_target = dist; + } +#endif + } + +#ifndef TORRENT_DISABLE_LOGGING + if (get_node().observer()) + { + get_node().observer()->log(dht_logger::traversal + , "[%p] COMPLETED distance: %d type: %s" + , static_cast(this), 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_invoke_count = 0; +} + +bool traversal_algorithm::add_requests() +{ + 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 agg = m_node.settings().aggressive_lookups; + + // Find the first node that hasn't already been queried. + // and make sure that the 'm_branch_factor' top nodes + // stay queried at all times (obviously ignoring failed nodes) + // and without surpassing the 'result_target' nodes (i.e. k=8) + // this is a slight variation of the original paper which instead + // limits the number of outstanding requests, this limits the + // number of good outstanding requests. It will use more traffic, + // but is intended to speed up lookups + for (std::vector::iterator 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) == 0) + ++outstanding; + + continue; + } + +#ifndef TORRENT_DISABLE_LOGGING + if (get_node().observer()) + { + char hex_id[41]; + to_hex(reinterpret_cast(&o->id()[0]), 20, hex_id); + get_node().observer()->log(dht_logger::traversal + , "[%p] INVOKE nodes-left: %d top-invoke-count: %d " + "invoke-count: %d branch-factor: %d " + "distance: %d id: %s addr: %s type: %s" + , static_cast(this), int(m_results.end() - i), outstanding, int(m_invoke_count) + , int(m_branch_factor), distance_exp(m_target, o->id()), hex_id + , 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 + if (get_node().observer()) + { + get_node().observer()->log(dht_logger::traversal + , "[%p] using router nodes to initiate traversal algorithm %d routers" + , static_cast(this), int(std::distance(m_node.m_table.router_begin(), m_node.m_table.router_end()))); + } +#endif + for (routing_table::router_iterator i = m_node.m_table.router_begin() + , end(m_node.m_table.router_end()); i != end; ++i) + { + add_entry(node_id(0), *i, observer::flag_initial); + } +} + +void traversal_algorithm::init() +{ + m_branch_factor = 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; + + int last_sent = INT_MAX; + time_point now = aux::time_now(); + for (std::vector::iterator i = m_results.begin() + , end(m_results.end()); i != end; ++i) + { + observer& o = **i; + 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 traversal_observer::reply(msg const& m) +{ + bdecode_node r = m.message.dict_find_dict("r"); + if (!r) + { +#ifndef TORRENT_DISABLE_LOGGING + if (get_observer()) + { + get_observer()->log(dht_logger::traversal + , "[%p] missing response dict" + , static_cast(algorithm())); + } +#endif + return; + } + +#ifndef TORRENT_DISABLE_LOGGING + if (get_observer()) + { + bdecode_node nid = r.dict_find_string("id"); + char hex_id[41]; + to_hex(nid.string_ptr(), 20, hex_id); + get_observer()->log(dht_logger::traversal + , "[%p] RESPONSE id: %s invoke-count: %d addr: %s type: %s" + , static_cast(algorithm()), hex_id, algorithm()->invoke_count() + , print_endpoint(target_ep()).c_str(), algorithm()->name()); + } +#endif + // look for nodes + bdecode_node n = r.dict_find_string("nodes"); + if (n) + { + char const* nodes = n.string_ptr(); + char const* end = nodes + n.string_length(); + + while (end - nodes >= 26) + { + node_id id; + std::copy(nodes, nodes + 20, id.begin()); + nodes += 20; + algorithm()->traverse(id, read_v4_endpoint(nodes)); + } + } + + bdecode_node id = r.dict_find_string("id"); + if (!id || id.string_length() != 20) + { +#ifndef TORRENT_DISABLE_LOGGING + if (get_observer()) + { + get_observer()->log(dht_logger::traversal, "[%p] invalid id in response" + , static_cast(algorithm())); + } +#endif + return; + } + + // in case we didn't know the id of this peer when we sent the message to + // it. For instance if it's a bootstrap node. + set_id(node_id(id.string_ptr())); +} + +} } // namespace libtorrent::dht + diff --git a/src/lazy_bdecode.cpp b/src/lazy_bdecode.cpp new file mode 100644 index 0000000..fc3461b --- /dev/null +++ b/src/lazy_bdecode.cpp @@ -0,0 +1,662 @@ +/* + +Copyright (c) 2008-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_NO_DEPRECATE + +#include "libtorrent/config.hpp" +#include "libtorrent/lazy_entry.hpp" +#include "libtorrent/bdecode.hpp" // for error codes +#include +#include // for numeric_limits + +namespace +{ + const int lazy_entry_grow_factor = 150; // percent + const int lazy_entry_dict_init = 5; + const int lazy_entry_list_init = 5; +} + +namespace libtorrent +{ + + namespace + { + int fail(int* error_pos + , std::vector& stack + , char const* start + , char const* orig_start) + { + while (!stack.empty()) { + lazy_entry* top = stack.back(); + if (top->type() == lazy_entry::dict_t || top->type() == lazy_entry::list_t) + { + top->pop(); + break; + } + stack.pop_back(); + } + if (error_pos) *error_pos = start - orig_start; + return -1; + } + +#define TORRENT_FAIL_BDECODE(code) do { ec = make_error_code(code); return fail(error_pos, stack, start, orig_start); } TORRENT_WHILE_0 + + bool numeric(char c) { return c >= '0' && c <= '9'; } + + char const* find_char(char const* start, char const* end, char delimiter) + { + while (start < end && *start != delimiter) ++start; + return start; + } + + } // anonymous namespace + +#ifndef TORRENT_NO_DEPRECATE + int lazy_bdecode(char const* start, char const* end + , lazy_entry& ret, int depth_limit, int item_limit) + { + error_code ec; + int pos; + return lazy_bdecode(start, end, ret, ec, &pos, depth_limit, item_limit); + } +#endif + + // return 0 = success + int lazy_bdecode(char const* start, char const* end, lazy_entry& ret + , error_code& ec, int* error_pos, int depth_limit, int item_limit) + { + char const* const orig_start = start; + ret.clear(); + + std::vector stack; + + if (start == end) + TORRENT_FAIL_BDECODE(bdecode_errors::unexpected_eof); + + stack.push_back(&ret); + while (start <= end) + { + if (stack.empty()) break; // done! + + lazy_entry* top = stack.back(); + + if (int(stack.size()) > depth_limit) TORRENT_FAIL_BDECODE(bdecode_errors::depth_exceeded); + if (start >= end) TORRENT_FAIL_BDECODE(bdecode_errors::unexpected_eof); + char t = *start; + ++start; + if (start >= end && t != 'e') TORRENT_FAIL_BDECODE(bdecode_errors::unexpected_eof); + + switch (top->type()) + { + case lazy_entry::dict_t: + { + if (t == 'e') + { + top->set_end(start); + stack.pop_back(); + continue; + } + if (!numeric(t)) TORRENT_FAIL_BDECODE(bdecode_errors::expected_digit); + boost::int64_t len = t - '0'; + bdecode_errors::error_code_enum e = bdecode_errors::no_error; + start = parse_int(start, end, ':', len, e); + if (e) + TORRENT_FAIL_BDECODE(e); + + // remaining buffer size excluding ':' + const ptrdiff_t 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); + + ++start; + if (start == end) TORRENT_FAIL_BDECODE(bdecode_errors::unexpected_eof); + lazy_entry* ent = top->dict_append(start); + if (ent == 0) TORRENT_FAIL_BDECODE(boost::system::errc::not_enough_memory); + start += len; + if (start >= end) TORRENT_FAIL_BDECODE(bdecode_errors::unexpected_eof); + stack.push_back(ent); + t = *start; + ++start; + break; + } + case lazy_entry::list_t: + { + if (t == 'e') + { + top->set_end(start); + stack.pop_back(); + continue; + } + lazy_entry* ent = top->list_append(); + if (ent == 0) TORRENT_FAIL_BDECODE(boost::system::errc::not_enough_memory); + stack.push_back(ent); + break; + } + case lazy_entry::int_t: + case lazy_entry::string_t: + case lazy_entry::none_t: + break; + } + + --item_limit; + if (item_limit <= 0) TORRENT_FAIL_BDECODE(bdecode_errors::limit_exceeded); + + top = stack.back(); + switch (t) + { + case 'd': + top->construct_dict(start - 1); + break; + case 'l': + top->construct_list(start - 1); + break; + case 'i': + { + char const* int_start = start; + start = find_char(start, end, 'e'); + top->construct_int(int_start, start - int_start); + if (start == end) TORRENT_FAIL_BDECODE(bdecode_errors::unexpected_eof); + TORRENT_ASSERT(*start == 'e'); + ++start; + stack.pop_back(); + break; + } + default: + { + if (!numeric(t)) + TORRENT_FAIL_BDECODE(bdecode_errors::expected_value); + + boost::int64_t len = t - '0'; + bdecode_errors::error_code_enum e = bdecode_errors::no_error; + start = parse_int(start, end, ':', len, e); + if (e) + TORRENT_FAIL_BDECODE(e); + + // remaining buffer size excluding ':' + const ptrdiff_t 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); + + ++start; + if (start == end) TORRENT_FAIL_BDECODE(bdecode_errors::unexpected_eof); + top->construct_string(start, int(len)); + start += len; + stack.pop_back(); + break; + } + } + } + return 0; + } + + int lazy_entry::capacity() const + { + TORRENT_ASSERT(m_type == dict_t || m_type == list_t); + if (m_data.list == NULL) return 0; + if (m_type == dict_t) + return m_data.dict[0].val.m_len; + else + return m_data.list[0].m_len; + } + + boost::int64_t lazy_entry::int_value() const + { + TORRENT_ASSERT(m_type == int_t); + boost::int64_t val = 0; + bool negative = false; + if (*m_data.start == '-') negative = true; + bdecode_errors::error_code_enum ec = bdecode_errors::no_error; + parse_int(m_data.start + negative + , m_data.start + m_size, 'e', val, ec); + if (ec) return 0; + if (negative) val = -val; + return val; + } + + lazy_entry* lazy_entry::dict_append(char const* name) + { + TORRENT_ASSERT(m_type == dict_t); + TORRENT_ASSERT(m_size <= this->capacity()); + if (m_data.dict == NULL) + { + int capacity = lazy_entry_dict_init; + m_data.dict = new (std::nothrow) lazy_dict_entry[capacity+1]; + if (m_data.dict == NULL) return NULL; + m_data.dict[0].val.m_len = capacity; + } + else if (m_size == this->capacity()) + { + int capacity = this->capacity() * lazy_entry_grow_factor / 100; + lazy_dict_entry* tmp = new (std::nothrow) lazy_dict_entry[capacity+1]; + if (tmp == NULL) return NULL; + std::memcpy(tmp, m_data.dict, sizeof(lazy_dict_entry) * (m_size + 1)); + for (int i = 0; i < int(m_size); ++i) m_data.dict[i+1].val.release(); + + delete[] m_data.dict; + m_data.dict = tmp; + m_data.dict[0].val.m_len = capacity; + } + + TORRENT_ASSERT(m_size < this->capacity()); + lazy_dict_entry& ret = m_data.dict[1+m_size++]; + ret.name = name; + return &ret.val; + } + + void lazy_entry::pop() + { + if (m_size > 0) --m_size; + } + + namespace + { + // the number of decimal digits needed + // to represent the given value + int num_digits(int val) + { + int ret = 1; + while (val >= 10) + { + ++ret; + val /= 10; + } + return ret; + } + } + + void lazy_entry::construct_string(char const* start, int length) + { + TORRENT_ASSERT(m_type == none_t); + m_type = string_t; + m_data.start = start; + m_size = length; + m_begin = start - 1 - num_digits(length); + m_len = start - m_begin + length; + } + + namespace + { + // str1 is null-terminated + // str2 is not, str2 is len2 chars + bool string_equal(char const* str1, char const* str2, int len2) + { + while (len2 > 0) + { + if (*str1 != *str2) return false; + if (*str1 == 0) return false; + ++str1; + ++str2; + --len2; + } + return *str1 == 0; + } + } + + std::pair lazy_entry::dict_at(int i) const + { + TORRENT_ASSERT(m_type == dict_t); + TORRENT_ASSERT(i < int(m_size)); + lazy_dict_entry const& e = m_data.dict[i+1]; + return std::make_pair(std::string(e.name, e.val.m_begin - e.name), &e.val); + } + + std::string lazy_entry::dict_find_string_value(char const* name) const + { + lazy_entry const* e = dict_find(name); + if (e == 0 || e->type() != lazy_entry::string_t) return std::string(); + return e->string_value(); + } + + pascal_string lazy_entry::dict_find_pstr(char const* name) const + { + lazy_entry const* e = dict_find(name); + if (e == 0 || e->type() != lazy_entry::string_t) return pascal_string(0, 0); + return e->string_pstr(); + } + + lazy_entry const* lazy_entry::dict_find_string(char const* name) const + { + lazy_entry const* e = dict_find(name); + if (e == 0 || e->type() != lazy_entry::string_t) return 0; + return e; + } + + lazy_entry const* lazy_entry::dict_find_int(char const* name) const + { + lazy_entry const* e = dict_find(name); + if (e == 0 || e->type() != lazy_entry::int_t) return 0; + return e; + } + + boost::int64_t lazy_entry::dict_find_int_value(char const* name + , boost::int64_t default_val) const + { + lazy_entry const* e = dict_find(name); + if (e == 0 || e->type() != lazy_entry::int_t) return default_val; + return e->int_value(); + } + + lazy_entry const* lazy_entry::dict_find_dict(char const* name) const + { + lazy_entry const* e = dict_find(name); + if (e == 0 || e->type() != lazy_entry::dict_t) return 0; + return e; + } + + lazy_entry const* lazy_entry::dict_find_dict(std::string const& name) const + { + lazy_entry const* e = dict_find(name); + if (e == 0 || e->type() != lazy_entry::dict_t) return 0; + return e; + } + + lazy_entry const* lazy_entry::dict_find_list(char const* name) const + { + lazy_entry const* e = dict_find(name); + if (e == 0 || e->type() != lazy_entry::list_t) return 0; + return e; + } + + lazy_entry* lazy_entry::dict_find(char const* name) + { + TORRENT_ASSERT(m_type == dict_t); + for (int i = 0; i < int(m_size); ++i) + { + lazy_dict_entry& e = m_data.dict[i+1]; + if (string_equal(name, e.name, e.val.m_begin - e.name)) + return &e.val; + } + return 0; + } + + lazy_entry* lazy_entry::dict_find(std::string const& name) + { + TORRENT_ASSERT(m_type == dict_t); + for (int i = 0; i < int(m_size); ++i) + { + lazy_dict_entry& e = m_data.dict[i+1]; + if (name.size() != e.val.m_begin - e.name) continue; + if (std::equal(name.begin(), name.end(), e.name)) + return &e.val; + } + return 0; + } + + lazy_entry* lazy_entry::list_append() + { + TORRENT_ASSERT(m_type == list_t); + TORRENT_ASSERT(m_size <= this->capacity()); + if (m_data.start == NULL) + { + int capacity = lazy_entry_list_init; + m_data.list = new (std::nothrow) lazy_entry[capacity+1]; + if (m_data.list == 0) return 0; + m_data.list[0].m_len = capacity; + } + else if (m_size == this->capacity()) + { + int capacity = this->capacity() * lazy_entry_grow_factor / 100; + lazy_entry* tmp = new (std::nothrow) lazy_entry[capacity+1]; + if (tmp == NULL) return NULL; + std::memcpy(tmp, m_data.list, sizeof(lazy_entry) * (m_size+1)); + for (int i = 0; i < int(m_size); ++i) m_data.list[i+1].release(); + + delete[] m_data.list; + m_data.list = tmp; + m_data.list[0].m_len = capacity; + } + + TORRENT_ASSERT(m_size < this->capacity()); + return &m_data.list[1 + (m_size++)]; + } + + std::string lazy_entry::list_string_value_at(int i) const + { + lazy_entry const* e = list_at(i); + if (e == 0 || e->type() != lazy_entry::string_t) return std::string(); + return e->string_value(); + } + + pascal_string lazy_entry::list_pstr_at(int i) const + { + lazy_entry const* e = list_at(i); + if (e == 0 || e->type() != lazy_entry::string_t) return pascal_string(0, 0); + return e->string_pstr(); + } + + boost::int64_t lazy_entry::list_int_value_at(int i, boost::int64_t default_val) const + { + lazy_entry const* e = list_at(i); + if (e == 0 || e->type() != lazy_entry::int_t) return default_val; + return e->int_value(); + } + + void lazy_entry::clear() + { + switch (m_type) + { + case list_t: + delete[] m_data.list; + break; + case dict_t: + delete[] m_data.dict; + break; + default: break; + } + m_data.start = NULL; + m_size = 0; + m_type = none_t; + } + + std::pair lazy_entry::data_section() const + { + typedef std::pair return_t; + return return_t(m_begin, m_len); + } + + namespace { + + int line_longer_than(lazy_entry const& e, int limit) + { + int line_len = 0; + switch (e.type()) + { + case lazy_entry::list_t: + line_len += 4; + if (line_len > limit) return -1; + for (int i = 0; i < e.list_size(); ++i) + { + int ret = line_longer_than(*e.list_at(i), limit - line_len); + if (ret == -1) return -1; + line_len += ret + 2; + } + break; + case lazy_entry::dict_t: + line_len += 4; + if (line_len > limit) return -1; + for (int i = 0; i < e.dict_size(); ++i) + { + line_len += 4 + e.dict_at(i).first.size(); + if (line_len > limit) return -1; + int ret = line_longer_than(*e.dict_at(i).second, limit - line_len); + if (ret == -1) return -1; + line_len += ret + 1; + } + break; + case lazy_entry::string_t: + line_len += 3 + e.string_length(); + break; + case lazy_entry::int_t: + { + boost::int64_t val = e.int_value(); + while (val > 0) + { + ++line_len; + val /= 10; + } + line_len += 2; + } + break; + case lazy_entry::none_t: + line_len += 4; + break; + } + + if (line_len > limit) return -1; + return line_len; + } + + void escape_string(std::string& ret, char const* str, int len) + { + for (int i = 0; i < len; ++i) + { + if (str[i] >= 32 && str[i] < 127) + { + ret += str[i]; + } + else + { + char tmp[5]; + snprintf(tmp, sizeof(tmp), "\\x%02x", boost::uint8_t(str[i])); + ret += tmp; + } + } + } + + void print_string(std::string& ret, char const* str, int len, bool single_line) + { + bool printable = true; + for (int i = 0; i < len; ++i) + { + char c = str[i]; + if (c >= 32 && c < 127) continue; + printable = false; + break; + } + ret += "'"; + if (printable) + { + if (single_line && len > 30) + { + ret.append(str, 14); + ret += "..."; + ret.append(str + len-14, 14); + } + else + ret.append(str, len); + ret += "'"; + return; + } + if (single_line && len > 20) + { + escape_string(ret, str, 9); + ret += "..."; + escape_string(ret, str + len - 9, 9); + } + else + { + escape_string(ret, str, len); + } + ret += "'"; + } + } // anonymous namespace + + std::string print_entry(lazy_entry const& e, bool single_line, int indent) + { + char indent_str[200]; + memset(indent_str, ' ', 200); + indent_str[0] = ','; + indent_str[1] = '\n'; + indent_str[199] = 0; + if (indent < 197 && indent >= 0) indent_str[indent+2] = 0; + std::string ret; + switch (e.type()) + { + case lazy_entry::none_t: return "none"; + case lazy_entry::int_t: + { + char str[100]; + snprintf(str, sizeof(str), "%" PRId64, e.int_value()); + return str; + } + case lazy_entry::string_t: + { + print_string(ret, e.string_ptr(), e.string_length(), single_line); + return ret; + } + case lazy_entry::list_t: + { + ret += '['; + bool one_liner = line_longer_than(e, 200) != -1 || single_line; + + if (!one_liner) ret += indent_str + 1; + for (int i = 0; i < e.list_size(); ++i) + { + if (i == 0 && one_liner) ret += " "; + ret += print_entry(*e.list_at(i), single_line, indent + 2); + if (i < e.list_size() - 1) ret += (one_liner?", ":indent_str); + else ret += (one_liner?" ":indent_str+1); + } + ret += "]"; + return ret; + } + case lazy_entry::dict_t: + { + ret += "{"; + bool one_liner = line_longer_than(e, 200) != -1 || single_line; + + if (!one_liner) ret += indent_str+1; + for (int i = 0; i < e.dict_size(); ++i) + { + if (i == 0 && one_liner) ret += " "; + std::pair ent = e.dict_at(i); + print_string(ret, ent.first.c_str(), ent.first.size(), true); + ret += ": "; + ret += print_entry(*ent.second, single_line, indent + 2); + if (i < e.dict_size() - 1) ret += (one_liner?", ":indent_str); + else ret += (one_liner?" ":indent_str+1); + } + ret += "}"; + return ret; + } + } + return ret; + } +} + +#endif // TORRENT_NO_DEPRECATE + diff --git a/src/lsd.cpp b/src/lsd.cpp new file mode 100644 index 0000000..a46e2a4 --- /dev/null +++ b/src/lsd.cpp @@ -0,0 +1,316 @@ +/* + +Copyright (c) 2007-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/lsd.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/http_tracker_connection.hpp" +#include "libtorrent/buffer.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/http_parser.hpp" +#include "libtorrent/socket_io.hpp" // for print_address + +#if defined TORRENT_ASIO_DEBUGGING +#include "libtorrent/debug.hpp" +#endif + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +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 snprintf(dst, 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 + +static error_code dummy; + +lsd::lsd(io_service& ios, peer_callback_t const& cb +#ifndef TORRENT_DISABLE_LOGGING + , log_callback_t const& log +#endif + ) + : m_callback(cb) + , m_socket(udp::endpoint(address_v4::from_string("239.192.152.143", dummy), 6771)) +#if TORRENT_USE_IPV6 + , m_socket6(udp::endpoint(address_v6::from_string("ff15::efc0:988f", dummy), 6771)) +#endif +#ifndef TORRENT_DISABLE_LOGGING + , m_log_cb(log) +#endif + , m_broadcast_timer(ios) + , m_cookie((random() ^ boost::uintptr_t(this)) & 0x7fffffff) + , m_disabled(false) +#if TORRENT_USE_IPV6 + , m_disabled6(false) +#endif +{ +} + +#ifndef TORRENT_DISABLE_LOGGING +TORRENT_FORMAT(2,3) +void lsd::debug_log(char const* fmt, ...) const +{ + va_list v; + va_start(v, fmt); + + char buf[1024]; + vsnprintf(buf, sizeof(buf), fmt, v); + va_end(v); + m_log_cb(buf); +} +#endif + +void lsd::start(error_code& ec) +{ + m_socket.open(boost::bind(&lsd::on_announce, self(), _1, _2, _3) + , m_broadcast_timer.get_io_service(), ec); + if (ec) return; + +#if TORRENT_USE_IPV6 + m_socket6.open(boost::bind(&lsd::on_announce, self(), _1, _2, _3) + , m_broadcast_timer.get_io_service(), ec); +#endif +} + +lsd::~lsd() {} + +void lsd::announce(sha1_hash const& ih, int listen_port, bool broadcast) +{ + announce_impl(ih, listen_port, broadcast, 0); +} + +void lsd::announce_impl(sha1_hash const& ih, int const listen_port + , bool const broadcast, int retry_count) +{ +#if TORRENT_USE_IPV6 + if (m_disabled && m_disabled6) return; +#else + if (m_disabled) return; +#endif + + char ih_hex[41]; + to_hex(ih.data(), 20, ih_hex); + char msg[200]; + +#ifndef TORRENT_DISABLE_LOGGING + debug_log("==> LSD: ih: %s port: %u\n", ih_hex, listen_port); +#endif + + error_code ec; + if (!m_disabled) + { + int const msg_len = render_lsd_packet(msg, sizeof(msg), listen_port, ih_hex + , m_cookie, "239.192.152.143"); + m_socket.send(msg, msg_len, ec, broadcast ? broadcast_socket::broadcast : 0); + if (ec) + { + m_disabled = true; +#ifndef TORRENT_DISABLE_LOGGING + debug_log("*** LSD: failed to send message: (%d) %s", ec.value() + , ec.message().c_str()); +#endif + } + } + +#if TORRENT_USE_IPV6 + if (!m_disabled6) + { + int const msg_len = render_lsd_packet(msg, sizeof(msg), listen_port, ih_hex + , m_cookie, "[ff15::efc0:988f]"); + m_socket6.send(msg, msg_len, ec, broadcast ? broadcast_socket::broadcast : 0); + if (ec) + { + m_disabled6 = true; +#ifndef TORRENT_DISABLE_LOGGING + debug_log("*** LSD: failed to send message6: (%d) %s", ec.value() + , ec.message().c_str()); +#endif + } + } +#endif + + ++retry_count; + if (retry_count >= 3) return; + +#if TORRENT_USE_IPV6 + if (m_disabled && m_disabled6) return; +#else + if (m_disabled) return; +#endif + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("lsd::resend_announce"); +#endif + m_broadcast_timer.expires_from_now(seconds(2 * retry_count), ec); + m_broadcast_timer.async_wait(boost::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) +{ +#if defined TORRENT_ASIO_DEBUGGING + complete_async("lsd::resend_announce"); +#endif + if (e) return; + + announce_impl(info_hash, listen_port, false, retry_count); +} + +void lsd::on_announce(udp::endpoint const& from, char* buf + , std::size_t bytes_transferred) +{ + using namespace libtorrent::detail; + + http_parser p; + + bool error = false; + p.incoming(buffer::const_interval(buf, buf + bytes_transferred) + , 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; + } + + int port = std::atoi(port_str.c_str()); + + typedef std::multimap headers_t; + headers_t const& headers = p.headers(); + + headers_t::const_iterator 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 + boost::int32_t const cookie = strtol(cookie_iter->second.c_str(), NULL, 16); + if (cookie == m_cookie) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("<== LSD: ignoring packet (cookie matched our own): %x" + , cookie); +#endif + return; + } + } + + std::pair ihs + = headers.equal_range("infohash"); + + for (headers_t::const_iterator 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(0); + from_hex(ih_str.c_str(), 40, ih.data()); + + if (!ih.is_all_zeros() && port != 0) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("<== LSD: %s:%d ih: %s" + , print_address(from.address()).c_str() + , port, ih_str.c_str()); +#endif + // we got an announce, pass it on through the callback + TORRENT_TRY { + m_callback(tcp::endpoint(from.address(), port), ih); + } TORRENT_CATCH(std::exception&) {} + } + } +} + +void lsd::close() +{ + m_socket.close(); +#if TORRENT_USE_IPV6 + m_socket6.close(); +#endif + error_code ec; + m_broadcast_timer.cancel(ec); + m_disabled = true; +#if TORRENT_USE_IPV6 + m_disabled6 = true; +#endif + m_callback.clear(); +} + +} // libtorrent namespace + + diff --git a/src/lt_trackers.cpp b/src/lt_trackers.cpp new file mode 100644 index 0000000..e019435 --- /dev/null +++ b/src/lt_trackers.cpp @@ -0,0 +1,395 @@ +/* + +Copyright (c) 2008-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_NO_DEPRECATE +#ifndef TORRENT_DISABLE_EXTENSIONS + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include + +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/bt_peer_connection.hpp" +#include "libtorrent/peer_connection_handle.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/extensions.hpp" +#include "libtorrent/extensions/lt_trackers.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/parse_url.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/announce_entry.hpp" + +namespace libtorrent { namespace +{ + + bool send_tracker(announce_entry const& e) + { + // max_fails == 0 means that it's one + // of the trackers from the trackers + // from the torrent file + return e.fail_limit == 0 || e.verified; + } + + struct lt_tracker_plugin : torrent_plugin + { + lt_tracker_plugin(torrent& t) + : m_torrent(t) + , m_updates(0) + , m_2_minutes(110) + , m_num_trackers(0) + { + m_old_trackers = t.trackers(); + update_list_hash(); + } + + virtual boost::shared_ptr new_connection( + peer_connection_handle const& pc); + + virtual void tick() + { + if (m_2_minutes++ < 120) return; + m_2_minutes = 0; + + // build tracker diff + entry tex; + entry::list_type& added = tex["added"].list(); + std::vector const& trackers = m_torrent.trackers(); + for (std::vector::const_iterator i = trackers.begin() + , end(trackers.end()); i != end; ++i) + { + std::vector::const_iterator k = std::find_if( + m_old_trackers.begin(), m_old_trackers.end() + , boost::bind(&announce_entry::url, _1) == i->url); + if (k != m_old_trackers.end()) continue; + if (!send_tracker(*i)) continue; + m_old_trackers.push_back(*i); + ++m_updates; + added.push_back(i->url); + } + m_lt_trackers_msg.clear(); + bencode(std::back_inserter(m_lt_trackers_msg), tex); + if (m_updates > 0) update_list_hash(); + } + + void update_list_hash() + { + std::vector canonical_list; + std::transform(m_old_trackers.begin(), m_old_trackers.end(), back_inserter(canonical_list) + , boost::bind(&announce_entry::url, _1)); + std::sort(canonical_list.begin(), canonical_list.end()); + + hasher h; + std::for_each(canonical_list.begin(), canonical_list.end() + , boost::bind(&hasher::update, &h, _1)); + m_list_hash = h.final(); + } + + int num_updates() const { return m_updates; } + + std::vector const& get_lt_tex_msg() const { return m_lt_trackers_msg; } + + sha1_hash const& list_hash() const { return m_list_hash; } + + std::vector const& trackers() const { return m_old_trackers; } + + void increment_tracker_counter() { m_num_trackers++; } + int num_tex_trackers() const { return m_num_trackers; } + + private: + torrent& m_torrent; + std::vector m_old_trackers; + int m_updates; + int m_2_minutes; + std::vector m_lt_trackers_msg; + sha1_hash m_list_hash; + int m_num_trackers; + }; + + + struct lt_tracker_peer_plugin : peer_plugin + { + lt_tracker_peer_plugin(torrent& t, bt_peer_connection& pc, lt_tracker_plugin& tp) + : m_message_index(0) + , m_torrent(t) + , m_pc(pc) + , m_tp(tp) + , m_2_minutes(115) + , m_full_list(true) + {} + + // can add entries to the extension handshake + virtual void add_handshake(entry& h) + { + entry& messages = h["m"]; + messages["lt_tex"] = 19; + h["tr"] = m_tp.list_hash().to_string(); + } + + // called when the extension handshake from the other end is received + virtual bool on_extension_handshake(bdecode_node const& h) + { + m_message_index = 0; + if (h.type() != bdecode_node::dict_t) return false; + bdecode_node messages = h.dict_find("m"); + if (!messages || messages.type() != bdecode_node::dict_t) return false; + + int index = int(messages.dict_find_int_value("lt_tex", -1)); + if (index == -1) return false; + m_message_index = index; + + // if we have the same tracker list, don't bother sending the + // full list. Just send deltas + std::string tracker_list_hash = h.dict_find_string_value("tr"); + if (tracker_list_hash.size() == 20 + && sha1_hash(tracker_list_hash) == m_tp.list_hash()) + { + m_full_list = false; + } + return true; + } + + virtual bool on_extended(int /* length */ + , int extended_msg, buffer::const_interval body) + { + if (extended_msg != 19) return false; + if (m_message_index == 0) return false; + if (!m_pc.packet_finished()) return true; + + bdecode_node msg; + error_code ec; + int ret = bdecode(body.begin, body.end, msg, ec); + if (ret != 0 || msg.type() != bdecode_node::dict_t) + { + m_pc.disconnect(errors::invalid_lt_tracker_message, op_bittorrent, 2); + return true; + } + + bdecode_node added = msg.dict_find_list("added"); + + // invalid tex message + if (added == 0) + { +#ifndef TORRENT_DISABLE_LOGGING + m_pc.peer_log(peer_log_alert::incoming_message, "LT_TEX" + , "NOT A DICTIONARY"); +#endif + return true; + } + + if (m_tp.num_tex_trackers() >= 50) + { +#ifndef TORRENT_DISABLE_LOGGING + m_pc.peer_log(peer_log_alert::incoming_message, "LT_TEX" + , "we already have %d trackers from tex, don't add any more" + , m_tp.num_tex_trackers()); +#endif + return true; + } + +#ifndef TORRENT_DISABLE_LOGGING + m_pc.peer_log(peer_log_alert::incoming_message, "LT_TEX"); +#endif + + for (int i = 0; i < added.list_size(); ++i) + { + announce_entry e(added.list_string_value_at(i)); + if (e.url.empty()) continue; + + // ignore urls with binary data in them + if (need_encoding(e.url.c_str(), e.url.size())) continue; + + // ignore invalid URLs + error_code err; + std::string protocol; + std::string auth; + std::string hostname; + int port; + std::string path; + boost::tie(protocol, auth, hostname, port, path) + = parse_url_components(e.url, err); + if (err) continue; + + // ignore unknown protocols + if (protocol != "udp" && protocol != "http" && protocol != "https") + continue; + + // ignore invalid ports + if (port == 0) + continue; + + if (m_tp.num_tex_trackers() >= 50) + break; + +#ifndef TORRENT_DISABLE_LOGGING + m_pc.peer_log(peer_log_alert::info, "LT_TEX", "adding: %s", e.url.c_str()); +#endif + e.fail_limit = 1; + e.send_stats = false; + e.source = announce_entry::source_tex; + if (m_torrent.add_tracker(e)) + m_tp.increment_tracker_counter(); + } + return true; + } + + virtual void tick() + { + // no handshake yet + if (!m_message_index) return; + if (++m_2_minutes <= 120) return; + m_2_minutes = 0; + + if (m_full_list) + { + if (send_full_tex_list()) + m_full_list = false; + } + else + { + send_lt_tex_diff(); + } + } + + private: + + void send_lt_tex_diff() + { + // if there's no change in out tracker set, don't send anything + if (m_tp.num_updates() == 0) return; + + if (!m_torrent.valid_metadata() || m_torrent.torrent_file().priv()) + return; + + std::vector const& tex_msg = m_tp.get_lt_tex_msg(); + + char msg[6]; + char* ptr = msg; + + detail::write_uint32(1 + 1 + tex_msg.size(), ptr); + detail::write_uint8(bt_peer_connection::msg_extended, ptr); + detail::write_uint8(m_message_index, ptr); + m_pc.send_buffer(msg, sizeof(msg)); + m_pc.send_buffer(&tex_msg[0], tex_msg.size()); + m_pc.setup_send(); + } + + bool send_full_tex_list() const + { + if (m_tp.trackers().empty()) return false; + + if (!m_torrent.valid_metadata() || m_torrent.torrent_file().priv()) + return false; + +#ifndef TORRENT_DISABLE_LOGGING + m_pc.peer_log(peer_log_alert::outgoing_message, "LT_TEX"); +#endif + entry tex; + entry::list_type& added = tex["added"].list(); + for (std::vector::const_iterator i = m_tp.trackers().begin() + , end(m_tp.trackers().end()); i != end; ++i) + { + if (!send_tracker(*i)) continue; + added.push_back(i->url); +#ifndef TORRENT_DISABLE_LOGGING + m_pc.peer_log(peer_log_alert::info, "LT_TEX" + , "sending: %s", i->url.c_str()); +#endif + } + std::vector tex_msg; + bencode(std::back_inserter(tex_msg), tex); + + char msg[6]; + char* ptr = msg; + + detail::write_uint32(1 + 1 + tex_msg.size(), ptr); + detail::write_uint8(bt_peer_connection::msg_extended, ptr); + detail::write_uint8(m_message_index, ptr); + m_pc.send_buffer(msg, sizeof(msg)); + m_pc.send_buffer(&tex_msg[0], tex_msg.size()); + m_pc.setup_send(); + + return true; + } + + // this is the message index the remote peer uses + // for metadata extension messages. + int m_message_index; + + torrent& m_torrent; + bt_peer_connection& m_pc; + lt_tracker_plugin& m_tp; + + int m_2_minutes; + bool m_full_list; + }; + + boost::shared_ptr lt_tracker_plugin::new_connection( + peer_connection_handle const& pc) + { + if (pc.type() != peer_connection::bittorrent_connection) + return boost::shared_ptr(); + + if (m_torrent.valid_metadata() && m_torrent.torrent_file().priv()) + return boost::shared_ptr(); + + bt_peer_connection* c = static_cast(pc.native_handle().get()); + return boost::shared_ptr(new lt_tracker_peer_plugin(m_torrent, *c, *this)); + } + +} } + +namespace libtorrent +{ + + boost::shared_ptr TORRENT_EXPORT create_lt_trackers_plugin(torrent_handle const& th, void*) + { + torrent* t = th.native_handle().get(); + if (t->valid_metadata() && t->torrent_file().priv()) return boost::shared_ptr(); + return boost::shared_ptr(new lt_tracker_plugin(*t)); + } + +} + +#endif +#endif + diff --git a/src/magnet_uri.cpp b/src/magnet_uri.cpp new file mode 100644 index 0000000..4ea45fd --- /dev/null +++ b/src/magnet_uri.cpp @@ -0,0 +1,294 @@ +/* + +Copyright (c) 2007-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/magnet_uri.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/aux_/escape_string.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/torrent_status.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/announce_entry.hpp" + +#include +#include "libtorrent/socket_io.hpp" + +namespace libtorrent +{ + std::string make_magnet_uri(torrent_handle const& handle) + { + if (!handle.is_valid()) return ""; + + std::string ret; + sha1_hash const& ih = handle.info_hash(); + ret += "magnet:?xt=urn:btih:"; + ret += to_hex(ih.to_string()); + + torrent_status st = handle.status(torrent_handle::query_name); + if (!st.name.empty()) + { + ret += "&dn="; + ret += escape_string(st.name.c_str(), int(st.name.length())); + } + + std::vector const& tr = handle.trackers(); + for (std::vector::const_iterator i = tr.begin(), end(tr.end()); i != end; ++i) + { + ret += "&tr="; + ret += escape_string(i->url.c_str(), int(i->url.length())); + } + + std::set seeds = handle.url_seeds(); + for (std::set::iterator i = seeds.begin() + , end(seeds.end()); i != end; ++i) + { + ret += "&ws="; + ret += escape_string(i->c_str(), int(i->length())); + } + + return ret; + } + + std::string make_magnet_uri(torrent_info const& info) + { + std::string ret; + sha1_hash const& ih = info.info_hash(); + ret += "magnet:?xt=urn:btih:"; + ret += to_hex(ih.to_string()); + + std::string const& name = info.name(); + + if (!name.empty()) + { + ret += "&dn="; + ret += escape_string(name.c_str(), name.length()); + } + + std::vector const& tr = info.trackers(); + + for (std::vector::const_iterator i = tr.begin(), end(tr.end()); i != end; ++i) + { + ret += "&tr="; + ret += escape_string(i->url.c_str(), i->url.length()); + } + + std::vector const& seeds = info.web_seeds(); + for (std::vector::const_iterator i = seeds.begin() + , end(seeds.end()); i != end; ++i) + { + if (i->type != web_seed_entry::url_seed) continue; + + ret += "&ws="; + ret += escape_string(i->url.c_str(), i->url.length()); + } + + return ret; + } + +#ifndef TORRENT_NO_DEPRECATE + + namespace { + torrent_handle add_magnet_uri_deprecated(session& ses, std::string const& uri + , add_torrent_params p, error_code& ec) + { + parse_magnet_uri(uri, p, ec); + if (ec) return torrent_handle(); + return ses.add_torrent(p, ec); + } + } + + torrent_handle add_magnet_uri(session& ses, std::string const& uri + , add_torrent_params const& p, error_code& ec) + { + return add_magnet_uri_deprecated(ses, uri, p, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + torrent_handle add_magnet_uri(session& ses, std::string const& uri + , std::string const& save_path + , storage_mode_t storage_mode + , bool paused + , storage_constructor_type sc + , void* userdata) + { + add_torrent_params params(sc); + params.storage_mode = storage_mode; + params.userdata = userdata; + params.save_path = save_path; + + if (paused) params.flags |= add_torrent_params::flag_paused; + else params.flags &= ~add_torrent_params::flag_paused; + + error_code ec; + std::string display_name = url_has_argument(uri, "dn"); + if (!display_name.empty()) params.name = unescape_string(display_name.c_str(), ec); + std::string tracker_string = url_has_argument(uri, "tr"); + if (!tracker_string.empty()) params.trackers.push_back(unescape_string(tracker_string.c_str(), ec)); + + std::string btih = url_has_argument(uri, "xt"); + if (btih.empty()) return torrent_handle(); + + if (btih.compare(0, 9, "urn:btih:") != 0) return torrent_handle(); + + if (btih.size() == 40 + 9) from_hex(&btih[9], 40, params.info_hash.data()); + else params.info_hash.assign(base32decode(btih.substr(9))); + + return ses.add_torrent(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) throw libtorrent_exception(ec); + return ret; + } +#endif // BOOST_NO_EXCEPTIONS +#endif // TORRENT_NO_DEPRECATE + + void parse_magnet_uri(std::string const& uri, add_torrent_params& p, error_code& ec) + { + ec.clear(); + std::string name; + + { + error_code e; + std::string display_name = url_has_argument(uri, "dn"); + if (!display_name.empty()) name = unescape_string(display_name.c_str(), e); + } + + // parse trackers out of the magnet link + std::string::size_type pos = std::string::npos; + std::string url = url_has_argument(uri, "tr", &pos); + while (pos != std::string::npos) + { + error_code e; + url = unescape_string(url, e); + if (e) continue; + p.trackers.push_back(url); + pos = uri.find("&tr=", pos); + if (pos == std::string::npos) break; + pos += 4; + url = uri.substr(pos, uri.find('&', pos) - pos); + } + + // parse web seeds out of the magnet link + pos = std::string::npos; + url = url_has_argument(uri, "ws", &pos); + while (pos != std::string::npos) + { + error_code e; + url = unescape_string(url, e); + if (e) continue; + p.url_seeds.push_back(url); + pos = uri.find("&ws=", pos); + if (pos == std::string::npos) break; + pos += 4; + url = uri.substr(pos, uri.find('&', pos) - pos); + } + + std::string btih = url_has_argument(uri, "xt"); + if (btih.empty()) + { + ec = errors::missing_info_hash_in_uri; + return; + } + + if (btih.compare(0, 9, "urn:btih:") != 0) + { + ec = errors::missing_info_hash_in_uri; + return; + } + +#ifndef TORRENT_DISABLE_DHT + std::string::size_type node_pos = std::string::npos; + std::string node = url_has_argument(uri, "dht", &node_pos); + while (!node.empty()) + { + std::string::size_type divider = node.find_last_of(':'); + if (divider != std::string::npos) + { + int port = atoi(node.c_str()+divider+1); + if (port != 0) + p.dht_nodes.push_back(std::make_pair(node.substr(0, divider), port)); + } + + node_pos = uri.find("&dht=", node_pos); + if (node_pos == std::string::npos) break; + node_pos += 5; + node = uri.substr(node_pos, uri.find('&', node_pos) - node_pos); + } +#endif + + sha1_hash info_hash; + if (btih.size() == 40 + 9) from_hex(&btih[9], 40, info_hash.data()); + else if (btih.size() == 32 + 9) + { + std::string ih = base32decode(btih.substr(9)); + if (ih.size() != 20) + { + ec = errors::invalid_info_hash; + return; + } + info_hash.assign(ih); + } + else + { + ec = errors::invalid_info_hash; + return; + } + + p.info_hash = info_hash; + if (!name.empty()) p.name = name; + } + + void parse_magnet_uri_peers(std::string const& uri, std::vector& peers) + { + std::string::size_type peer_pos = std::string::npos; + std::string peer = url_has_argument(uri, "x.pe", &peer_pos); + while (!peer.empty()) + { + error_code e; + tcp::endpoint endp = parse_endpoint(peer, e); + if (!e) + peers.push_back(endp); + + peer_pos = uri.find("&x.pe=", peer_pos); + if (peer_pos == std::string::npos) break; + peer_pos += 6; + peer = uri.substr(peer_pos, uri.find('&', peer_pos) - peer_pos); + } + } +} + + diff --git a/src/merkle.cpp b/src/merkle.cpp new file mode 100644 index 0000000..71771a2 --- /dev/null +++ b/src/merkle.cpp @@ -0,0 +1,69 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/aux_/merkle.hpp" + +namespace libtorrent +{ + int merkle_get_parent(int tree_node) + { + // node 0 doesn't have a parent + TORRENT_ASSERT(tree_node > 0); + return (tree_node - 1) / 2; + } + + int merkle_get_sibling(int tree_node) + { + // node 0 doesn't have a sibling + TORRENT_ASSERT(tree_node > 0); + // even numbers have their sibling to the left + // odd numbers have their sibling to the right + return tree_node + ((tree_node&1)?1:-1); + } + + int merkle_num_nodes(int leafs) + { + TORRENT_ASSERT(leafs > 0); + return (leafs << 1) - 1; + } + + int merkle_num_leafs(int pieces) + { + TORRENT_ASSERT(pieces > 0); + // round up to nearest 2 exponent + int ret = 1; + while (pieces > ret) ret <<= 1; + return ret; + } + +} + diff --git a/src/metadata_transfer.cpp b/src/metadata_transfer.cpp new file mode 100644 index 0000000..d671483 --- /dev/null +++ b/src/metadata_transfer.cpp @@ -0,0 +1,617 @@ +/* + +Copyright (c) 2006-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_NO_DEPRECATE +#ifndef TORRENT_DISABLE_EXTENSIONS + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include + +#include +#include +#include +#include // count + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/bt_peer_connection.hpp" +#include "libtorrent/peer_connection_handle.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/extensions.hpp" +#include "libtorrent/extensions/metadata_transfer.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/buffer.hpp" +#include "libtorrent/io.hpp" + +namespace libtorrent { namespace +{ + int div_round_up(int numerator, int denominator) + { + return (numerator + denominator - 1) / denominator; + } + + std::pair req_to_offset(std::pair req, int total_size) + { + TORRENT_ASSERT(req.first >= 0); + TORRENT_ASSERT(req.second > 0); + TORRENT_ASSERT(req.second <= 256); + TORRENT_ASSERT(req.first + req.second <= 256); + + int start = div_round_up(req.first * total_size, 256); + int size = div_round_up((req.first + req.second) * total_size, 256) - start; + return std::make_pair(start, size); + } + + std::pair offset_to_req(std::pair offset, int total_size) + { + int start = offset.first * 256 / total_size; + int size = (offset.first + offset.second) * 256 / total_size - start; + + std::pair ret(start, size); + + TORRENT_ASSERT(start >= 0); + TORRENT_ASSERT(size > 0); + TORRENT_ASSERT(start <= 256); + TORRENT_ASSERT(start + size <= 256); + + // assert the identity of this function +#if TORRENT_USE_ASSERTS + std::pair identity = req_to_offset(ret, total_size); + TORRENT_ASSERT(offset == identity); +#endif + return ret; + } + + struct metadata_plugin TORRENT_FINAL + : torrent_plugin + { + metadata_plugin(torrent& t) + : m_torrent(t) + , m_metadata_progress(0) + , m_metadata_size(0) + { + m_requested_metadata.resize(256, 0); + } + +/* + bool need_loaded() + { return m_torrent.need_loaded(); } +*/ + virtual void on_unload() TORRENT_OVERRIDE + { + m_metadata.reset(); + } + + virtual void on_load() TORRENT_OVERRIDE + { + // initialize m_metadata_size + TORRENT_ASSERT(m_torrent.is_loaded()); + metadata(); + } + + virtual void on_files_checked() TORRENT_OVERRIDE + { + // if the torrent is a seed, make a reference to + // the metadata from the torrent before it is deallocated + if (m_torrent.is_seed()) metadata(); + } + + virtual boost::shared_ptr new_connection( + peer_connection_handle const& pc) TORRENT_OVERRIDE; + + buffer::const_interval metadata() const + { + if (!m_metadata) + { + m_metadata = m_torrent.torrent_file().metadata(); + m_metadata_size = m_torrent.torrent_file().metadata_size(); + TORRENT_ASSERT(hasher(m_metadata.get(), m_metadata_size).final() + == m_torrent.torrent_file().info_hash()); + } + return buffer::const_interval(m_metadata.get(), m_metadata.get() + + m_metadata_size); + } + + bool received_metadata(char const* buf, int size, int offset, int total_size) + { + if (m_torrent.valid_metadata()) return false; + + if (!m_metadata || m_metadata_size < total_size) + { + m_metadata.reset(new char[total_size]); + m_metadata_size = total_size; + } + std::copy(buf, buf + size, &m_metadata[offset]); + + if (m_have_metadata.empty()) + m_have_metadata.resize(256, false); + + std::pair req = offset_to_req(std::make_pair(offset, size) + , total_size); + + TORRENT_ASSERT(req.first + req.second <= int(m_have_metadata.size())); + + std::fill( + m_have_metadata.begin() + req.first + , m_have_metadata.begin() + req.first + req.second + , true); + + bool have_all = std::count( + m_have_metadata.begin() + , m_have_metadata.end() + , true) == 256; + + if (!have_all) return false; + + if (!m_torrent.set_metadata(&m_metadata[0], m_metadata_size)) + { + std::fill( + m_have_metadata.begin() + , m_have_metadata.begin() + req.first + req.second + , false); + m_metadata_progress = 0; + m_metadata_size = 0; + return false; + } + + // clear the storage for the bitfield + std::vector().swap(m_have_metadata); + std::vector().swap(m_requested_metadata); + + return true; + } + + // returns a range of the metadata that + // we should request. + std::pair metadata_request(); + + void cancel_metadata_request(std::pair req) + { + for (int i = req.first; i < req.first + req.second; ++i) + { + TORRENT_ASSERT(m_requested_metadata[i] > 0); + if (m_requested_metadata[i] > 0) + --m_requested_metadata[i]; + } + } + + // this is called from the peer_connection for + // each piece of metadata it receives + void metadata_progress(int total_size, int received) + { + m_metadata_progress += received; + m_metadata_size = total_size; + m_torrent.set_progress_ppm(boost::int64_t(m_metadata_progress) * 1000000 / m_metadata_size); + } + + void on_piece_pass(int) TORRENT_OVERRIDE + { + // if we became a seed, copy the metadata from + // the torrent before it is deallocated + if (m_torrent.is_seed()) + metadata(); + } + +/* + int metadata_size() const { return m_metadata_size; } +*/ + + private: + torrent& m_torrent; + + // this buffer is filled with the info-section of + // the metadata file while downloading it from + // peers, and while sending it. + // it is mutable because it's generated lazily + mutable boost::shared_array m_metadata; + + int m_metadata_progress; + mutable int m_metadata_size; + + // this is a bitfield of size 256, each bit represents + // a piece of the metadata. It is set to one if we + // have that piece. This vector may be empty + // (size 0) if we haven't received any metadata + // or if we already have all metadata + std::vector m_have_metadata; + // this vector keeps track of how many times each meatdata + // block has been requested + std::vector m_requested_metadata; + + // explicitly disallow assignment, to silence msvc warning + metadata_plugin& operator=(metadata_plugin const&); + }; + + struct metadata_peer_plugin TORRENT_FINAL + : peer_plugin + { + metadata_peer_plugin(torrent& t, peer_connection& pc + , metadata_plugin& tp) + : m_waiting_metadata_request(false) + , m_message_index(0) + , m_metadata_progress(0) + , m_no_metadata(min_time()) + , m_metadata_request(min_time()) + , m_torrent(t) + , m_pc(pc) + , m_tp(tp) + {} + + virtual char const* type() const TORRENT_OVERRIDE { return "LT_metadata"; } + + // can add entries to the extension handshake + virtual void add_handshake(entry& h) TORRENT_OVERRIDE + { + entry& messages = h["m"]; + messages["LT_metadata"] = 14; + } + + // called when the extension handshake from the other end is received + virtual bool on_extension_handshake(bdecode_node const& h) TORRENT_OVERRIDE + { + m_message_index = 0; + if (h.type() != bdecode_node::dict_t) return false; + bdecode_node messages = h.dict_find("m"); + if (!messages || messages.type() != bdecode_node::dict_t) return false; + + int index = int(messages.dict_find_int_value("LT_metadata", -1)); + if (index == -1) return false; + m_message_index = index; + return true; + } + + void write_metadata_request(std::pair req) + { + TORRENT_ASSERT(req.first >= 0); + TORRENT_ASSERT(req.second > 0); + TORRENT_ASSERT(req.first + req.second <= 256); + TORRENT_ASSERT(!m_pc.associated_torrent().expired()); + TORRENT_ASSERT(!m_pc.associated_torrent().lock()->valid_metadata()); + + int start = req.first; + int size = req.second; + + // abort if the peer doesn't support the metadata extension + if (m_message_index == 0) return; + +#ifndef TORRENT_DISABLE_LOGGING + m_pc.peer_log(peer_log_alert::outgoing_message, "METADATA_REQUEST" + , "start: %d size: %d", start, size); +#endif + + char msg[9]; + char* ptr = msg; + + detail::write_uint32(1 + 1 + 3, ptr); + detail::write_uint8(bt_peer_connection::msg_extended, ptr); + detail::write_uint8(m_message_index, ptr); + // means 'request data' + detail::write_uint8(0, ptr); + detail::write_uint8(start, ptr); + detail::write_uint8(size - 1, ptr); + m_pc.send_buffer(msg, sizeof(msg)); + m_pc.setup_send(); + } + + void write_metadata(std::pair req) + { + TORRENT_ASSERT(req.first >= 0); + TORRENT_ASSERT(req.second > 0); + TORRENT_ASSERT(req.second <= 256); + TORRENT_ASSERT(req.first + req.second <= 256); + TORRENT_ASSERT(!m_pc.associated_torrent().expired()); + + // abort if the peer doesn't support the metadata extension + if (m_message_index == 0) return; + + if (m_torrent.valid_metadata()) + { + std::pair offset + = req_to_offset(req, int(m_tp.metadata().left())); + + char msg[15]; + char* ptr = msg; + +#ifndef TORRENT_DISABLE_LOGGING + m_pc.peer_log(peer_log_alert::outgoing_message, "METADATA" + , "start: %d total_size: %d offset: %d data_size: %d" + , req.first, req.second, offset.first, offset.second); +#endif + // yes, we have metadata, send it + detail::write_uint32(11 + offset.second, ptr); + detail::write_uint8(bt_peer_connection::msg_extended, ptr); + detail::write_uint8(m_message_index, ptr); + // means 'data packet' + detail::write_uint8(1, ptr); + detail::write_uint32(int(m_tp.metadata().left()), ptr); + detail::write_uint32(offset.first, ptr); + m_pc.send_buffer(msg, sizeof(msg)); + + // TODO: this is not safe. The torrent could be unloaded while + // we're still sending the metadata + char const* metadata = m_tp.metadata().begin; + m_pc.append_const_send_buffer(metadata + offset.first, offset.second); + } + else + { +#ifndef TORRENT_DISABLE_LOGGING + m_pc.peer_log(peer_log_alert::outgoing_message, "METADATA" + , "don't have metadata"); +#endif + char msg[4+3]; + char* ptr = msg; + + // we don't have the metadata, reply with + // don't have-message + detail::write_uint32(1 + 2, ptr); + detail::write_uint8(bt_peer_connection::msg_extended, ptr); + detail::write_uint8(m_message_index, ptr); + // means 'have no data' + detail::write_uint8(2, ptr); + m_pc.send_buffer(msg, sizeof(msg)); + } + m_pc.setup_send(); + } + + virtual bool on_extended(int length + , int msg, buffer::const_interval body) TORRENT_OVERRIDE + { + if (msg != 14) return false; + if (m_message_index == 0) return false; + + if (length > 500 * 1024) + { + m_pc.disconnect(errors::metadata_too_large, op_bittorrent, 2); + return true; + } + + if (body.left() < 1) return true; + int type = detail::read_uint8(body.begin); + + switch (type) + { + case 0: // request + { + if (body.left() < 2) return true; + int start = detail::read_uint8(body.begin); + int size = detail::read_uint8(body.begin) + 1; + +#ifndef TORRENT_DISABLE_LOGGING + m_pc.peer_log(peer_log_alert::incoming_message, "METADATA_REQUEST" + , "start: %d size: %d", start, size); +#endif + + if (length != 3) + { + // invalid metadata request + m_pc.disconnect(errors::invalid_metadata_request, op_bittorrent, 2); + return true; + } + + write_metadata(std::make_pair(start, size)); + } + break; + case 1: // data + { + if (body.left() < 8) return true; + + int total_size = detail::read_int32(body.begin); + int offset = detail::read_int32(body.begin); + int data_size = length - 9; + +#ifndef TORRENT_DISABLE_LOGGING + m_pc.peer_log(peer_log_alert::incoming_message, "METADATA" + , "total_size: %d | offset: %d | data_size: %d" + ,total_size, offset, data_size); +#endif + + if (total_size > m_torrent.session().settings().get_int(settings_pack::max_metadata_size)) + { + m_pc.disconnect(errors::metadata_too_large, op_bittorrent, 2); + return true; + } + if (total_size <= 0) + { + m_pc.disconnect(errors::invalid_metadata_size, op_bittorrent, 2); + return true; + } + if (offset > total_size || offset < 0) + { + m_pc.disconnect(errors::invalid_metadata_offset, op_bittorrent, 2); + return true; + } + if (offset + data_size > total_size) + { + m_pc.disconnect(errors::invalid_metadata_message, op_bittorrent, 2); + return true; + } + + m_tp.metadata_progress(total_size + , body.left() - m_metadata_progress); + m_metadata_progress = body.left(); + + if (body.left() < data_size) return true; + + m_waiting_metadata_request = false; + m_tp.received_metadata(body.begin, data_size + , offset, total_size); + m_metadata_progress = 0; + } + break; + case 2: // have no data + m_no_metadata = aux::time_now(); + if (m_waiting_metadata_request) + m_tp.cancel_metadata_request(m_last_metadata_request); + m_waiting_metadata_request = false; +#ifndef TORRENT_DISABLE_LOGGING + m_pc.peer_log(peer_log_alert::incoming_message, "METADATA" + , "don't have metadata"); +#endif + break; + default: + { + m_pc.disconnect(errors::invalid_metadata_message, op_bittorrent, 2); + } + } + return true; + } + + virtual void tick() TORRENT_OVERRIDE + { + 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_waiting_metadata_request + && has_metadata()) + { + m_last_metadata_request = m_tp.metadata_request(); + write_metadata_request(m_last_metadata_request); + m_waiting_metadata_request = true; + m_metadata_request = aux::time_now(); + } + } + + bool has_metadata() const + { + return aux::time_now() - minutes(5) > m_no_metadata; + } + + private: + + // this is set to true when we send a metadata + // request to this peer, and reset to false when + // we receive a reply to our request. + bool m_waiting_metadata_request; + + // this is the message index the remote peer uses + // for metadata extension messages. + int m_message_index; + + // the number of bytes of metadata we have received + // so far from this per, only counting the current + // request. Any previously finished requests + // that have been forwarded to the torrent object + // do not count. + int m_metadata_progress; + + // this is set to the current time each time we get a + // "I don't have metadata" message. + time_point m_no_metadata; + + // this is set to the time when we last sent + // a request for metadata to this peer + time_point m_metadata_request; + + // if we're waiting for a metadata request + // this was the request we sent + std::pair m_last_metadata_request; + + torrent& m_torrent; + peer_connection& m_pc; + metadata_plugin& m_tp; + + // explicitly disallow assignment, to silence msvc warning + metadata_peer_plugin& operator=(metadata_peer_plugin const&); + }; + + boost::shared_ptr metadata_plugin::new_connection( + peer_connection_handle const& pc) + { + if (pc.type() != peer_connection::bittorrent_connection) + return boost::shared_ptr(); + + return boost::shared_ptr(new metadata_peer_plugin(m_torrent, *pc.native_handle().get(), *this)); + } + + std::pair metadata_plugin::metadata_request() + { + // the number of blocks to request + int num_blocks = 256 / 4; + TORRENT_ASSERT(num_blocks <= 128); + + int min_element = (std::numeric_limits::max)(); + int best_index = 0; + for (int i = 0; i < 256 - num_blocks + 1; ++i) + { + int min = *std::min_element(m_requested_metadata.begin() + i + , m_requested_metadata.begin() + i + num_blocks); + min += std::accumulate(m_requested_metadata.begin() + i + , m_requested_metadata.begin() + i + num_blocks, int(0)); + + if (min_element > min) + { + best_index = i; + min_element = min; + } + } + + std::pair ret(best_index, num_blocks); + for (int i = ret.first; i < ret.first + ret.second; ++i) + m_requested_metadata[i]++; + + TORRENT_ASSERT(ret.first >= 0); + TORRENT_ASSERT(ret.second > 0); + TORRENT_ASSERT(ret.second <= 256); + TORRENT_ASSERT(ret.first + ret.second <= 256); + + return ret; + } + +} } + +namespace libtorrent +{ + + boost::shared_ptr create_metadata_plugin(torrent_handle const& th, void*) + { + torrent* t = th.native_handle().get(); + // don't add this extension if the torrent is private + if (t->valid_metadata() && t->torrent_file().priv()) return boost::shared_ptr(); + return boost::shared_ptr(new metadata_plugin(*t)); + } + +} + +#endif +#endif + diff --git a/src/mpi.cpp b/src/mpi.cpp new file mode 100644 index 0000000..b8104f8 --- /dev/null +++ b/src/mpi.cpp @@ -0,0 +1,10089 @@ +#include "libtorrent/aux_/disable_warnings_push.hpp" + +/* Start: bn_error.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_ERROR_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +static const struct { + int code; + const char *msg; +} msgs[] = { + { MP_OKAY, "Successful" }, + { MP_MEM, "Out of heap" }, + { MP_VAL, "Value out of range" } +}; + +/* return a char * string for a given code */ +const char *mp_error_to_string(int code) +{ + int x; + + /* scan the lookup table for the given message */ + for (x = 0; x < (int)(sizeof(msgs) / sizeof(msgs[0])); x++) { + if (msgs[x].code == code) { + return msgs[x].msg; + } + } + + /* generic reply for invalid code */ + return "Invalid error code"; +} + +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_error.c */ + +/* Start: bn_fast_mp_invmod.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_FAST_MP_INVMOD_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* computes the modular inverse via binary extended euclidean algorithm, + * that is c = 1/a mod b + * + * Based on slow invmod except this is optimized for the case where b is + * odd as per HAC Note 14.64 on pp. 610 + */ +int fast_mp_invmod (mp_int * a, mp_int * b, mp_int * c) +{ + mp_int x, y, u, v, B, D; + int res, neg; + + /* 2. [modified] b must be odd */ + if (mp_iseven (b) == MP_YES) { + return MP_VAL; + } + + /* init all our temps */ + if ((res = mp_init_multi(&x, &y, &u, &v, &B, &D, NULL)) != MP_OKAY) { + return res; + } + + /* x == modulus, y == value to invert */ + if ((res = mp_copy (b, &x)) != MP_OKAY) { + goto LBL_ERR; + } + + /* we need y = |a| */ + if ((res = mp_mod (a, b, &y)) != MP_OKAY) { + goto LBL_ERR; + } + + /* 3. u=x, v=y, A=1, B=0, C=0,D=1 */ + if ((res = mp_copy (&x, &u)) != MP_OKAY) { + goto LBL_ERR; + } + if ((res = mp_copy (&y, &v)) != MP_OKAY) { + goto LBL_ERR; + } + mp_set (&D, 1); + +top: + /* 4. while u is even do */ + while (mp_iseven (&u) == MP_YES) { + /* 4.1 u = u/2 */ + if ((res = mp_div_2 (&u, &u)) != MP_OKAY) { + goto LBL_ERR; + } + /* 4.2 if B is odd then */ + if (mp_isodd (&B) == MP_YES) { + if ((res = mp_sub (&B, &x, &B)) != MP_OKAY) { + goto LBL_ERR; + } + } + /* B = B/2 */ + if ((res = mp_div_2 (&B, &B)) != MP_OKAY) { + goto LBL_ERR; + } + } + + /* 5. while v is even do */ + while (mp_iseven (&v) == MP_YES) { + /* 5.1 v = v/2 */ + if ((res = mp_div_2 (&v, &v)) != MP_OKAY) { + goto LBL_ERR; + } + /* 5.2 if D is odd then */ + if (mp_isodd (&D) == MP_YES) { + /* D = (D-x)/2 */ + if ((res = mp_sub (&D, &x, &D)) != MP_OKAY) { + goto LBL_ERR; + } + } + /* D = D/2 */ + if ((res = mp_div_2 (&D, &D)) != MP_OKAY) { + goto LBL_ERR; + } + } + + /* 6. if u >= v then */ + if (mp_cmp (&u, &v) != MP_LT) { + /* u = u - v, B = B - D */ + if ((res = mp_sub (&u, &v, &u)) != MP_OKAY) { + goto LBL_ERR; + } + + if ((res = mp_sub (&B, &D, &B)) != MP_OKAY) { + goto LBL_ERR; + } + } else { + /* v - v - u, D = D - B */ + if ((res = mp_sub (&v, &u, &v)) != MP_OKAY) { + goto LBL_ERR; + } + + if ((res = mp_sub (&D, &B, &D)) != MP_OKAY) { + goto LBL_ERR; + } + } + + /* if not zero goto step 4 */ + if (mp_iszero (&u) == MP_NO) { + goto top; + } + + /* now a = C, b = D, gcd == g*v */ + + /* if v != 1 then there is no inverse */ + if (mp_cmp_d (&v, 1) != MP_EQ) { + res = MP_VAL; + goto LBL_ERR; + } + + /* b is now the inverse */ + neg = a->sign; + while (D.sign == MP_NEG) { + if ((res = mp_add (&D, b, &D)) != MP_OKAY) { + goto LBL_ERR; + } + } + mp_exch (&D, c); + c->sign = neg; + res = MP_OKAY; + +LBL_ERR:mp_clear_multi (&x, &y, &u, &v, &B, &D, NULL); + return res; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_fast_mp_invmod.c */ + +/* Start: bn_fast_mp_montgomery_reduce.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_FAST_MP_MONTGOMERY_REDUCE_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* computes xR**-1 == x (mod N) via Montgomery Reduction + * + * This is an optimized implementation of montgomery_reduce + * which uses the comba method to quickly calculate the columns of the + * reduction. + * + * Based on Algorithm 14.32 on pp.601 of HAC. +*/ +int fast_mp_montgomery_reduce (mp_int * x, mp_int * n, mp_digit rho) +{ + int ix, res, olduse; + mp_word W[MP_WARRAY]; + + /* get old used count */ + olduse = x->used; + + /* grow a as required */ + if (x->alloc < (n->used + 1)) { + if ((res = mp_grow (x, n->used + 1)) != MP_OKAY) { + return res; + } + } + + /* first we have to get the digits of the input into + * an array of double precision words W[...] + */ + { + mp_word *_W; + mp_digit *tmpx; + + /* alias for the W[] array */ + _W = W; + + /* alias for the digits of x*/ + tmpx = x->dp; + + /* copy the digits of a into W[0..a->used-1] */ + for (ix = 0; ix < x->used; ix++) { + *_W++ = *tmpx++; + } + + /* zero the high words of W[a->used..m->used*2] */ + for (; ix < ((n->used * 2) + 1); ix++) { + *_W++ = 0; + } + } + + /* now we proceed to zero successive digits + * from the least significant upwards + */ + for (ix = 0; ix < n->used; ix++) { + /* mu = ai * m' mod b + * + * We avoid a double precision multiplication (which isn't required) + * by casting the value down to a mp_digit. Note this requires + * that W[ix-1] have the carry cleared (see after the inner loop) + */ + mp_digit mu; + mu = (mp_digit) (((W[ix] & MP_MASK) * rho) & MP_MASK); + + /* a = a + mu * m * b**i + * + * This is computed in place and on the fly. The multiplication + * by b**i is handled by offseting which columns the results + * are added to. + * + * Note the comba method normally doesn't handle carries in the + * inner loop In this case we fix the carry from the previous + * column since the Montgomery reduction requires digits of the + * result (so far) [see above] to work. This is + * handled by fixing up one carry after the inner loop. The + * carry fixups are done in order so after these loops the + * first m->used words of W[] have the carries fixed + */ + { + int iy; + mp_digit *tmpn; + mp_word *_W; + + /* alias for the digits of the modulus */ + tmpn = n->dp; + + /* Alias for the columns set by an offset of ix */ + _W = W + ix; + + /* inner loop */ + for (iy = 0; iy < n->used; iy++) { + *_W++ += ((mp_word)mu) * ((mp_word)*tmpn++); + } + } + + /* now fix carry for next digit, W[ix+1] */ + W[ix + 1] += W[ix] >> ((mp_word) DIGIT_BIT); + } + + /* now we have to propagate the carries and + * shift the words downward [all those least + * significant digits we zeroed]. + */ + { + mp_digit *tmpx; + mp_word *_W, *_W1; + + /* nox fix rest of carries */ + + /* alias for current word */ + _W1 = W + ix; + + /* alias for next word, where the carry goes */ + _W = W + ++ix; + + for (; ix <= ((n->used * 2) + 1); ix++) { + *_W++ += *_W1++ >> ((mp_word) DIGIT_BIT); + } + + /* copy out, A = A/b**n + * + * The result is A/b**n but instead of converting from an + * array of mp_word to mp_digit than calling mp_rshd + * we just copy them in the right order + */ + + /* alias for destination word */ + tmpx = x->dp; + + /* alias for shifted double precision result */ + _W = W + n->used; + + for (ix = 0; ix < (n->used + 1); ix++) { + *tmpx++ = (mp_digit)(*_W++ & ((mp_word) MP_MASK)); + } + + /* zero oldused digits, if the input a was larger than + * m->used+1 we'll have to clear the digits + */ + for (; ix < olduse; ix++) { + *tmpx++ = 0; + } + } + + /* set the max used and clamp */ + x->used = n->used + 1; + mp_clamp (x); + + /* if A >= m then A = A - m */ + if (mp_cmp_mag (x, n) != MP_LT) { + return s_mp_sub (x, n, x); + } + return MP_OKAY; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_fast_mp_montgomery_reduce.c */ + +/* Start: bn_fast_s_mp_mul_digs.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_FAST_S_MP_MUL_DIGS_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* Fast (comba) multiplier + * + * This is the fast column-array [comba] multiplier. It is + * designed to compute the columns of the product first + * then handle the carries afterwards. This has the effect + * of making the nested loops that compute the columns very + * simple and schedulable on super-scalar processors. + * + * This has been modified to produce a variable number of + * digits of output so if say only a half-product is required + * you don't have to compute the upper half (a feature + * required for fast Barrett reduction). + * + * Based on Algorithm 14.12 on pp.595 of HAC. + * + */ +int fast_s_mp_mul_digs (mp_int * a, mp_int * b, mp_int * c, int digs) +{ + int olduse, res, pa, ix, iz; + mp_digit W[MP_WARRAY]; + mp_word _W; + + /* grow the destination as required */ + if (c->alloc < digs) { + if ((res = mp_grow (c, digs)) != MP_OKAY) { + return res; + } + } + + /* number of output digits to produce */ + pa = MIN(digs, a->used + b->used); + + /* clear the carry */ + _W = 0; + for (ix = 0; ix < pa; ix++) { + int tx, ty; + int iy; + mp_digit *tmpx, *tmpy; + + /* get offsets into the two bignums */ + ty = MIN(b->used-1, ix); + tx = ix - ty; + + /* setup temp aliases */ + tmpx = a->dp + tx; + tmpy = b->dp + ty; + + /* this is the number of times the loop will iterrate, essentially + while (tx++ < a->used && ty-- >= 0) { ... } + */ + iy = MIN(a->used-tx, ty+1); + + /* execute loop */ + for (iz = 0; iz < iy; ++iz) { + _W += ((mp_word)*tmpx++)*((mp_word)*tmpy--); + + } + + /* store term */ + W[ix] = ((mp_digit)_W) & MP_MASK; + + /* make next carry */ + _W = _W >> ((mp_word)DIGIT_BIT); + } + + /* setup dest */ + olduse = c->used; + c->used = pa; + + { + mp_digit *tmpc; + tmpc = c->dp; + for (ix = 0; ix < (pa + 1); ix++) { + /* now extract the previous digit [below the carry] */ + *tmpc++ = W[ix]; + } + + /* clear unused digits [that existed in the old copy of c] */ + for (; ix < olduse; ix++) { + *tmpc++ = 0; + } + } + mp_clamp (c); + return MP_OKAY; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_fast_s_mp_mul_digs.c */ + +/* Start: bn_fast_s_mp_mul_high_digs.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_FAST_S_MP_MUL_HIGH_DIGS_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* this is a modified version of fast_s_mul_digs that only produces + * output digits *above* digs. See the comments for fast_s_mul_digs + * to see how it works. + * + * This is used in the Barrett reduction since for one of the multiplications + * only the higher digits were needed. This essentially halves the work. + * + * Based on Algorithm 14.12 on pp.595 of HAC. + */ +int fast_s_mp_mul_high_digs (mp_int * a, mp_int * b, mp_int * c, int digs) +{ + int olduse, res, pa, ix, iz; + mp_digit W[MP_WARRAY]; + mp_word _W; + + /* grow the destination as required */ + pa = a->used + b->used; + if (c->alloc < pa) { + if ((res = mp_grow (c, pa)) != MP_OKAY) { + return res; + } + } + + /* number of output digits to produce */ + pa = a->used + b->used; + _W = 0; + for (ix = digs; ix < pa; ix++) { + int tx, ty, iy; + mp_digit *tmpx, *tmpy; + + /* get offsets into the two bignums */ + ty = MIN(b->used-1, ix); + tx = ix - ty; + + /* setup temp aliases */ + tmpx = a->dp + tx; + tmpy = b->dp + ty; + + /* this is the number of times the loop will iterrate, essentially its + while (tx++ < a->used && ty-- >= 0) { ... } + */ + iy = MIN(a->used-tx, ty+1); + + /* execute loop */ + for (iz = 0; iz < iy; iz++) { + _W += ((mp_word)*tmpx++)*((mp_word)*tmpy--); + } + + /* store term */ + W[ix] = ((mp_digit)_W) & MP_MASK; + + /* make next carry */ + _W = _W >> ((mp_word)DIGIT_BIT); + } + + /* setup dest */ + olduse = c->used; + c->used = pa; + + { + mp_digit *tmpc; + + tmpc = c->dp + digs; + for (ix = digs; ix < pa; ix++) { + /* now extract the previous digit [below the carry] */ + *tmpc++ = W[ix]; + } + + /* clear unused digits [that existed in the old copy of c] */ + for (; ix < olduse; ix++) { + *tmpc++ = 0; + } + } + mp_clamp (c); + return MP_OKAY; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_fast_s_mp_mul_high_digs.c */ + +/* Start: bn_fast_s_mp_sqr.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_FAST_S_MP_SQR_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* the jist of squaring... + * you do like mult except the offset of the tmpx [one that + * starts closer to zero] can't equal the offset of tmpy. + * So basically you set up iy like before then you min it with + * (ty-tx) so that it never happens. You double all those + * you add in the inner loop + +After that loop you do the squares and add them in. +*/ + +int fast_s_mp_sqr (mp_int * a, mp_int * b) +{ + int olduse, res, pa, ix, iz; + mp_digit W[MP_WARRAY], *tmpx; + mp_word W1; + + /* grow the destination as required */ + pa = a->used + a->used; + if (b->alloc < pa) { + if ((res = mp_grow (b, pa)) != MP_OKAY) { + return res; + } + } + + /* number of output digits to produce */ + W1 = 0; + for (ix = 0; ix < pa; ix++) { + int tx, ty, iy; + mp_word _W; + mp_digit *tmpy; + + /* clear counter */ + _W = 0; + + /* get offsets into the two bignums */ + ty = MIN(a->used-1, ix); + tx = ix - ty; + + /* setup temp aliases */ + tmpx = a->dp + tx; + tmpy = a->dp + ty; + + /* this is the number of times the loop will iterrate, essentially + while (tx++ < a->used && ty-- >= 0) { ... } + */ + iy = MIN(a->used-tx, ty+1); + + /* now for squaring tx can never equal ty + * we halve the distance since they approach at a rate of 2x + * and we have to round because odd cases need to be executed + */ + iy = MIN(iy, ((ty-tx)+1)>>1); + + /* execute loop */ + for (iz = 0; iz < iy; iz++) { + _W += ((mp_word)*tmpx++)*((mp_word)*tmpy--); + } + + /* double the inner product and add carry */ + _W = _W + _W + W1; + + /* even columns have the square term in them */ + if ((ix&1) == 0) { + _W += ((mp_word)a->dp[ix>>1])*((mp_word)a->dp[ix>>1]); + } + + /* store it */ + W[ix] = (mp_digit)(_W & MP_MASK); + + /* make next carry */ + W1 = _W >> ((mp_word)DIGIT_BIT); + } + + /* setup dest */ + olduse = b->used; + b->used = a->used+a->used; + + { + mp_digit *tmpb; + tmpb = b->dp; + for (ix = 0; ix < pa; ix++) { + *tmpb++ = W[ix] & MP_MASK; + } + + /* clear unused digits [that existed in the old copy of c] */ + for (; ix < olduse; ix++) { + *tmpb++ = 0; + } + } + mp_clamp (b); + return MP_OKAY; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_fast_s_mp_sqr.c */ + +/* Start: bn_mp_2expt.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_2EXPT_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* computes a = 2**b + * + * Simple algorithm which zeroes the int, grows it then just sets one bit + * as required. + */ +int +mp_2expt (mp_int * a, int b) +{ + int res; + + /* zero a as per default */ + mp_zero (a); + + /* grow a to accomodate the single bit */ + if ((res = mp_grow (a, (b / DIGIT_BIT) + 1)) != MP_OKAY) { + return res; + } + + /* set the used count of where the bit will go */ + a->used = (b / DIGIT_BIT) + 1; + + /* put the single bit in its place */ + a->dp[b / DIGIT_BIT] = ((mp_digit)1) << (b % DIGIT_BIT); + + return MP_OKAY; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_2expt.c */ + +/* Start: bn_mp_abs.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_ABS_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* b = |a| + * + * Simple function copies the input and fixes the sign to positive + */ +int +mp_abs (mp_int * a, mp_int * b) +{ + int res; + + /* copy a to b */ + if (a != b) { + if ((res = mp_copy (a, b)) != MP_OKAY) { + return res; + } + } + + /* force the sign of b to positive */ + b->sign = MP_ZPOS; + + return MP_OKAY; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_abs.c */ + +/* Start: bn_mp_add.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_ADD_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* high level addition (handles signs) */ +int mp_add (mp_int * a, mp_int * b, mp_int * c) +{ + int sa, sb, res; + + /* get sign of both inputs */ + sa = a->sign; + sb = b->sign; + + /* handle two cases, not four */ + if (sa == sb) { + /* both positive or both negative */ + /* add their magnitudes, copy the sign */ + c->sign = sa; + res = s_mp_add (a, b, c); + } else { + /* one positive, the other negative */ + /* subtract the one with the greater magnitude from */ + /* the one of the lesser magnitude. The result gets */ + /* the sign of the one with the greater magnitude. */ + if (mp_cmp_mag (a, b) == MP_LT) { + c->sign = sb; + res = s_mp_sub (b, a, c); + } else { + c->sign = sa; + res = s_mp_sub (a, b, c); + } + } + return res; +} + +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_add.c */ + +/* Start: bn_mp_add_d.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_ADD_D_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* single digit addition */ +int +mp_add_d (mp_int * a, mp_digit b, mp_int * c) +{ + int res, ix, oldused; + mp_digit *tmpa, *tmpc, mu; + + /* grow c as required */ + if (c->alloc < (a->used + 1)) { + if ((res = mp_grow(c, a->used + 1)) != MP_OKAY) { + return res; + } + } + + /* if a is negative and |a| >= b, call c = |a| - b */ + if ((a->sign == MP_NEG) && ((a->used > 1) || (a->dp[0] >= b))) { + /* temporarily fix sign of a */ + a->sign = MP_ZPOS; + + /* c = |a| - b */ + res = mp_sub_d(a, b, c); + + /* fix sign */ + a->sign = c->sign = MP_NEG; + + /* clamp */ + mp_clamp(c); + + return res; + } + + /* old number of used digits in c */ + oldused = c->used; + + /* sign always positive */ + c->sign = MP_ZPOS; + + /* source alias */ + tmpa = a->dp; + + /* destination alias */ + tmpc = c->dp; + + /* if a is positive */ + if (a->sign == MP_ZPOS) { + /* add digit, after this we're propagating + * the carry. + */ + *tmpc = *tmpa++ + b; + mu = *tmpc >> DIGIT_BIT; + *tmpc++ &= MP_MASK; + + /* now handle rest of the digits */ + for (ix = 1; ix < a->used; ix++) { + *tmpc = *tmpa++ + mu; + mu = *tmpc >> DIGIT_BIT; + *tmpc++ &= MP_MASK; + } + /* set final carry */ + ix++; + *tmpc++ = mu; + + /* setup size */ + c->used = a->used + 1; + } else { + /* a was negative and |a| < b */ + c->used = 1; + + /* the result is a single digit */ + if (a->used == 1) { + *tmpc++ = b - a->dp[0]; + } else { + *tmpc++ = b; + } + + /* setup count so the clearing of oldused + * can fall through correctly + */ + ix = 1; + } + + /* now zero to oldused */ + while (ix++ < oldused) { + *tmpc++ = 0; + } + mp_clamp(c); + + return MP_OKAY; +} + +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_add_d.c */ + +/* Start: bn_mp_addmod.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_ADDMOD_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* d = a + b (mod c) */ +int +mp_addmod (mp_int * a, mp_int * b, mp_int * c, mp_int * d) +{ + int res; + mp_int t; + + if ((res = mp_init (&t)) != MP_OKAY) { + return res; + } + + if ((res = mp_add (a, b, &t)) != MP_OKAY) { + mp_clear (&t); + return res; + } + res = mp_mod (&t, c, d); + mp_clear (&t); + return res; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_addmod.c */ + +/* Start: bn_mp_and.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_AND_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* AND two ints together */ +int +mp_and (mp_int * a, mp_int * b, mp_int * c) +{ + int res, ix, px; + mp_int t, *x; + + if (a->used > b->used) { + if ((res = mp_init_copy (&t, a)) != MP_OKAY) { + return res; + } + px = b->used; + x = b; + } else { + if ((res = mp_init_copy (&t, b)) != MP_OKAY) { + return res; + } + px = a->used; + x = a; + } + + for (ix = 0; ix < px; ix++) { + t.dp[ix] &= x->dp[ix]; + } + + /* zero digits above the last from the smallest mp_int */ + for (; ix < t.used; ix++) { + t.dp[ix] = 0; + } + + mp_clamp (&t); + mp_exch (c, &t); + mp_clear (&t); + return MP_OKAY; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_and.c */ + +/* Start: bn_mp_clamp.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_CLAMP_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* trim unused digits + * + * This is used to ensure that leading zero digits are + * trimed and the leading "used" digit will be non-zero + * Typically very fast. Also fixes the sign if there + * are no more leading digits + */ +void +mp_clamp (mp_int * a) +{ + /* decrease used while the most significant digit is + * zero. + */ + while ((a->used > 0) && (a->dp[a->used - 1] == 0)) { + --(a->used); + } + + /* reset the sign flag if used == 0 */ + if (a->used == 0) { + a->sign = MP_ZPOS; + } +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_clamp.c */ + +/* Start: bn_mp_clear.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_CLEAR_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* clear one (frees) */ +void +mp_clear (mp_int * a) +{ + int i; + + /* only do anything if a hasn't been freed previously */ + if (a->dp != NULL) { + /* first zero the digits */ + for (i = 0; i < a->used; i++) { + a->dp[i] = 0; + } + + /* free ram */ + XFREE(a->dp); + + /* reset members to make debugging easier */ + a->dp = NULL; + a->alloc = a->used = 0; + a->sign = MP_ZPOS; + } +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_clear.c */ + +/* Start: bn_mp_clear_multi.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_CLEAR_MULTI_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ +#include + +void mp_clear_multi(mp_int *mp, ...) +{ + mp_int* next_mp = mp; + va_list args; + va_start(args, mp); + while (next_mp != NULL) { + mp_clear(next_mp); + next_mp = va_arg(args, mp_int*); + } + va_end(args); +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_clear_multi.c */ + +/* Start: bn_mp_cmp.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_CMP_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* compare two ints (signed)*/ +int +mp_cmp (mp_int * a, mp_int * b) +{ + /* compare based on sign */ + if (a->sign != b->sign) { + if (a->sign == MP_NEG) { + return MP_LT; + } else { + return MP_GT; + } + } + + /* compare digits */ + if (a->sign == MP_NEG) { + /* if negative compare opposite direction */ + return mp_cmp_mag(b, a); + } else { + return mp_cmp_mag(a, b); + } +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_cmp.c */ + +/* Start: bn_mp_cmp_d.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_CMP_D_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* compare a digit */ +int mp_cmp_d(mp_int * a, mp_digit b) +{ + /* compare based on sign */ + if (a->sign == MP_NEG) { + return MP_LT; + } + + /* compare based on magnitude */ + if (a->used > 1) { + return MP_GT; + } + + /* compare the only digit of a to b */ + if (a->dp[0] > b) { + return MP_GT; + } else if (a->dp[0] < b) { + return MP_LT; + } else { + return MP_EQ; + } +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_cmp_d.c */ + +/* Start: bn_mp_cmp_mag.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_CMP_MAG_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* compare maginitude of two ints (unsigned) */ +int mp_cmp_mag (mp_int * a, mp_int * b) +{ + int n; + mp_digit *tmpa, *tmpb; + + /* compare based on # of non-zero digits */ + if (a->used > b->used) { + return MP_GT; + } + + if (a->used < b->used) { + return MP_LT; + } + + /* alias for a */ + tmpa = a->dp + (a->used - 1); + + /* alias for b */ + tmpb = b->dp + (a->used - 1); + + /* compare based on digits */ + for (n = 0; n < a->used; ++n, --tmpa, --tmpb) { + if (*tmpa > *tmpb) { + return MP_GT; + } + + if (*tmpa < *tmpb) { + return MP_LT; + } + } + return MP_EQ; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_cmp_mag.c */ + +/* Start: bn_mp_cnt_lsb.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_CNT_LSB_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +static const int lnz[16] = { + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0 +}; + +/* Counts the number of lsbs which are zero before the first zero bit */ +int mp_cnt_lsb(mp_int *a) +{ + int x; + mp_digit q, qq; + + /* easy out */ + if (mp_iszero(a) == MP_YES) { + return 0; + } + + /* scan lower digits until non-zero */ + for (x = 0; (x < a->used) && (a->dp[x] == 0); x++) {} + q = a->dp[x]; + x *= DIGIT_BIT; + + /* now scan this digit until a 1 is found */ + if ((q & 1) == 0) { + do { + qq = q & 15; + x += lnz[qq]; + q >>= 4; + } while (qq == 0); + } + return x; +} + +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_cnt_lsb.c */ + +/* Start: bn_mp_copy.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_COPY_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* copy, b = a */ +int +mp_copy (mp_int * a, mp_int * b) +{ + int res, n; + + /* if dst == src do nothing */ + if (a == b) { + return MP_OKAY; + } + + /* grow dest */ + if (b->alloc < a->used) { + if ((res = mp_grow (b, a->used)) != MP_OKAY) { + return res; + } + } + + /* zero b and copy the parameters over */ + { + mp_digit *tmpa, *tmpb; + + /* pointer aliases */ + + /* source */ + tmpa = a->dp; + + /* destination */ + tmpb = b->dp; + + /* copy all the digits */ + for (n = 0; n < a->used; n++) { + *tmpb++ = *tmpa++; + } + + /* clear high digits */ + for (; n < b->used; n++) { + *tmpb++ = 0; + } + } + + /* copy used count and sign */ + b->used = a->used; + b->sign = a->sign; + return MP_OKAY; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_copy.c */ + +/* Start: bn_mp_count_bits.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_COUNT_BITS_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* returns the number of bits in an int */ +int +mp_count_bits (mp_int * a) +{ + int r; + mp_digit q; + + /* shortcut */ + if (a->used == 0) { + return 0; + } + + /* get number of digits and add that */ + r = (a->used - 1) * DIGIT_BIT; + + /* take the last digit and count the bits in it */ + q = a->dp[a->used - 1]; + while (q > ((mp_digit) 0)) { + ++r; + q >>= ((mp_digit) 1); + } + return r; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_count_bits.c */ + +/* Start: bn_mp_div.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_DIV_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +#ifdef BN_MP_DIV_SMALL + +/* slower bit-bang division... also smaller */ +int mp_div(mp_int * a, mp_int * b, mp_int * c, mp_int * d) +{ + mp_int ta, tb, tq, q; + int res, n, n2; + + /* is divisor zero ? */ + if (mp_iszero (b) == MP_YES) { + return MP_VAL; + } + + /* if a < b then q=0, r = a */ + if (mp_cmp_mag (a, b) == MP_LT) { + if (d != NULL) { + res = mp_copy (a, d); + } else { + res = MP_OKAY; + } + if (c != NULL) { + mp_zero (c); + } + return res; + } + + /* init our temps */ + if ((res = mp_init_multi(&ta, &tb, &tq, &q, NULL)) != MP_OKAY) { + return res; + } + + + mp_set(&tq, 1); + n = mp_count_bits(a) - mp_count_bits(b); + if (((res = mp_abs(a, &ta)) != MP_OKAY) || + ((res = mp_abs(b, &tb)) != MP_OKAY) || + ((res = mp_mul_2d(&tb, n, &tb)) != MP_OKAY) || + ((res = mp_mul_2d(&tq, n, &tq)) != MP_OKAY)) { + goto LBL_ERR; + } + + while (n-- >= 0) { + if (mp_cmp(&tb, &ta) != MP_GT) { + if (((res = mp_sub(&ta, &tb, &ta)) != MP_OKAY) || + ((res = mp_add(&q, &tq, &q)) != MP_OKAY)) { + goto LBL_ERR; + } + } + if (((res = mp_div_2d(&tb, 1, &tb, NULL)) != MP_OKAY) || + ((res = mp_div_2d(&tq, 1, &tq, NULL)) != MP_OKAY)) { + goto LBL_ERR; + } + } + + /* now q == quotient and ta == remainder */ + n = a->sign; + n2 = (a->sign == b->sign) ? MP_ZPOS : MP_NEG; + if (c != NULL) { + mp_exch(c, &q); + c->sign = (mp_iszero(c) == MP_YES) ? MP_ZPOS : n2; + } + if (d != NULL) { + mp_exch(d, &ta); + d->sign = (mp_iszero(d) == MP_YES) ? MP_ZPOS : n; + } +LBL_ERR: + mp_clear_multi(&ta, &tb, &tq, &q, NULL); + return res; +} + +#else + +/* integer signed division. + * c*b + d == a [e.g. a/b, c=quotient, d=remainder] + * HAC pp.598 Algorithm 14.20 + * + * Note that the description in HAC is horribly + * incomplete. For example, it doesn't consider + * the case where digits are removed from 'x' in + * the inner loop. It also doesn't consider the + * case that y has fewer than three digits, etc.. + * + * The overall algorithm is as described as + * 14.20 from HAC but fixed to treat these cases. +*/ +int mp_div (mp_int * a, mp_int * b, mp_int * c, mp_int * d) +{ + mp_int q, x, y, t1, t2; + int res, n, t, i, norm, neg; + + /* is divisor zero ? */ + if (mp_iszero (b) == MP_YES) { + return MP_VAL; + } + + /* if a < b then q=0, r = a */ + if (mp_cmp_mag (a, b) == MP_LT) { + if (d != NULL) { + res = mp_copy (a, d); + } else { + res = MP_OKAY; + } + if (c != NULL) { + mp_zero (c); + } + return res; + } + + if ((res = mp_init_size (&q, a->used + 2)) != MP_OKAY) { + return res; + } + q.used = a->used + 2; + + if ((res = mp_init (&t1)) != MP_OKAY) { + goto LBL_Q; + } + + if ((res = mp_init (&t2)) != MP_OKAY) { + goto LBL_T1; + } + + if ((res = mp_init_copy (&x, a)) != MP_OKAY) { + goto LBL_T2; + } + + if ((res = mp_init_copy (&y, b)) != MP_OKAY) { + goto LBL_X; + } + + /* fix the sign */ + neg = (a->sign == b->sign) ? MP_ZPOS : MP_NEG; + x.sign = y.sign = MP_ZPOS; + + /* normalize both x and y, ensure that y >= b/2, [b == 2**DIGIT_BIT] */ + norm = mp_count_bits(&y) % DIGIT_BIT; + if (norm < (int)(DIGIT_BIT-1)) { + norm = (DIGIT_BIT-1) - norm; + if ((res = mp_mul_2d (&x, norm, &x)) != MP_OKAY) { + goto LBL_Y; + } + if ((res = mp_mul_2d (&y, norm, &y)) != MP_OKAY) { + goto LBL_Y; + } + } else { + norm = 0; + } + + /* note hac does 0 based, so if used==5 then its 0,1,2,3,4, e.g. use 4 */ + n = x.used - 1; + t = y.used - 1; + + /* while (x >= y*b**n-t) do { q[n-t] += 1; x -= y*b**{n-t} } */ + if ((res = mp_lshd (&y, n - t)) != MP_OKAY) { /* y = y*b**{n-t} */ + goto LBL_Y; + } + + while (mp_cmp (&x, &y) != MP_LT) { + ++(q.dp[n - t]); + if ((res = mp_sub (&x, &y, &x)) != MP_OKAY) { + goto LBL_Y; + } + } + + /* reset y by shifting it back down */ + mp_rshd (&y, n - t); + + /* step 3. for i from n down to (t + 1) */ + for (i = n; i >= (t + 1); i--) { + if (i > x.used) { + continue; + } + + /* step 3.1 if xi == yt then set q{i-t-1} to b-1, + * otherwise set q{i-t-1} to (xi*b + x{i-1})/yt */ + if (x.dp[i] == y.dp[t]) { + q.dp[(i - t) - 1] = ((((mp_digit)1) << DIGIT_BIT) - 1); + } else { + mp_word tmp; + tmp = ((mp_word) x.dp[i]) << ((mp_word) DIGIT_BIT); + tmp |= ((mp_word) x.dp[i - 1]); + tmp /= ((mp_word) y.dp[t]); + if (tmp > (mp_word) MP_MASK) { + tmp = MP_MASK; + } + q.dp[(i - t) - 1] = (mp_digit) (tmp & (mp_word) (MP_MASK)); + } + + /* while (q{i-t-1} * (yt * b + y{t-1})) > + xi * b**2 + xi-1 * b + xi-2 + + do q{i-t-1} -= 1; + */ + q.dp[(i - t) - 1] = (q.dp[(i - t) - 1] + 1) & MP_MASK; + do { + q.dp[(i - t) - 1] = (q.dp[(i - t) - 1] - 1) & MP_MASK; + + /* find left hand */ + mp_zero (&t1); + t1.dp[0] = ((t - 1) < 0) ? 0 : y.dp[t - 1]; + t1.dp[1] = y.dp[t]; + t1.used = 2; + if ((res = mp_mul_d (&t1, q.dp[(i - t) - 1], &t1)) != MP_OKAY) { + goto LBL_Y; + } + + /* find right hand */ + t2.dp[0] = ((i - 2) < 0) ? 0 : x.dp[i - 2]; + t2.dp[1] = ((i - 1) < 0) ? 0 : x.dp[i - 1]; + t2.dp[2] = x.dp[i]; + t2.used = 3; + } while (mp_cmp_mag(&t1, &t2) == MP_GT); + + /* step 3.3 x = x - q{i-t-1} * y * b**{i-t-1} */ + if ((res = mp_mul_d (&y, q.dp[(i - t) - 1], &t1)) != MP_OKAY) { + goto LBL_Y; + } + + if ((res = mp_lshd (&t1, (i - t) - 1)) != MP_OKAY) { + goto LBL_Y; + } + + if ((res = mp_sub (&x, &t1, &x)) != MP_OKAY) { + goto LBL_Y; + } + + /* if x < 0 then { x = x + y*b**{i-t-1}; q{i-t-1} -= 1; } */ + if (x.sign == MP_NEG) { + if ((res = mp_copy (&y, &t1)) != MP_OKAY) { + goto LBL_Y; + } + if ((res = mp_lshd (&t1, (i - t) - 1)) != MP_OKAY) { + goto LBL_Y; + } + if ((res = mp_add (&x, &t1, &x)) != MP_OKAY) { + goto LBL_Y; + } + + q.dp[(i - t) - 1] = (q.dp[(i - t) - 1] - 1UL) & MP_MASK; + } + } + + /* now q is the quotient and x is the remainder + * [which we have to normalize] + */ + + /* get sign before writing to c */ + x.sign = (x.used == 0) ? MP_ZPOS : a->sign; + + if (c != NULL) { + mp_clamp (&q); + mp_exch (&q, c); + c->sign = neg; + } + + if (d != NULL) { + if ((res = mp_div_2d (&x, norm, &x, NULL)) != MP_OKAY) { + goto LBL_Y; + } + mp_exch (&x, d); + } + + res = MP_OKAY; + +LBL_Y:mp_clear (&y); +LBL_X:mp_clear (&x); +LBL_T2:mp_clear (&t2); +LBL_T1:mp_clear (&t1); +LBL_Q:mp_clear (&q); + return res; +} + +#endif + +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_div.c */ + +/* Start: bn_mp_div_2.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_DIV_2_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* b = a/2 */ +int mp_div_2(mp_int * a, mp_int * b) +{ + int x, res, oldused; + + /* copy */ + if (b->alloc < a->used) { + if ((res = mp_grow (b, a->used)) != MP_OKAY) { + return res; + } + } + + oldused = b->used; + b->used = a->used; + { + mp_digit r, rr, *tmpa, *tmpb; + + /* source alias */ + tmpa = a->dp + b->used - 1; + + /* dest alias */ + tmpb = b->dp + b->used - 1; + + /* carry */ + r = 0; + for (x = b->used - 1; x >= 0; x--) { + /* get the carry for the next iteration */ + rr = *tmpa & 1; + + /* shift the current digit, add in carry and store */ + *tmpb-- = (*tmpa-- >> 1) | (r << (DIGIT_BIT - 1)); + + /* forward carry to next iteration */ + r = rr; + } + + /* zero excess digits */ + tmpb = b->dp + b->used; + for (x = b->used; x < oldused; x++) { + *tmpb++ = 0; + } + } + b->sign = a->sign; + mp_clamp (b); + return MP_OKAY; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_div_2.c */ + +/* Start: bn_mp_div_2d.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_DIV_2D_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* shift right by a certain bit count (store quotient in c, optional remainder in d) */ +int mp_div_2d (mp_int * a, int b, mp_int * c, mp_int * d) +{ + mp_digit D, r, rr; + int x, res; + mp_int t; + + + /* if the shift count is <= 0 then we do no work */ + if (b <= 0) { + res = mp_copy (a, c); + if (d != NULL) { + mp_zero (d); + } + return res; + } + + if ((res = mp_init (&t)) != MP_OKAY) { + return res; + } + + /* get the remainder */ + if (d != NULL) { + if ((res = mp_mod_2d (a, b, &t)) != MP_OKAY) { + mp_clear (&t); + return res; + } + } + + /* copy */ + if ((res = mp_copy (a, c)) != MP_OKAY) { + mp_clear (&t); + return res; + } + + /* shift by as many digits in the bit count */ + if (b >= (int)DIGIT_BIT) { + mp_rshd (c, b / DIGIT_BIT); + } + + /* shift any bit count < DIGIT_BIT */ + D = (mp_digit) (b % DIGIT_BIT); + if (D != 0) { + mp_digit *tmpc, mask, shift; + + /* mask */ + mask = (((mp_digit)1) << D) - 1; + + /* shift for lsb */ + shift = DIGIT_BIT - D; + + /* alias */ + tmpc = c->dp + (c->used - 1); + + /* carry */ + r = 0; + for (x = c->used - 1; x >= 0; x--) { + /* get the lower bits of this word in a temp */ + rr = *tmpc & mask; + + /* shift the current word and mix in the carry bits from the previous word */ + *tmpc = (*tmpc >> D) | (r << shift); + --tmpc; + + /* set the carry to the carry bits of the current word found above */ + r = rr; + } + } + mp_clamp (c); + if (d != NULL) { + mp_exch (&t, d); + } + mp_clear (&t); + return MP_OKAY; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_div_2d.c */ + +/* Start: bn_mp_div_3.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_DIV_3_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* divide by three (based on routine from MPI and the GMP manual) */ +int +mp_div_3 (mp_int * a, mp_int *c, mp_digit * d) +{ + mp_int q; + mp_word w, t; + mp_digit b; + int res, ix; + + /* b = 2**DIGIT_BIT / 3 */ + b = (((mp_word)1) << ((mp_word)DIGIT_BIT)) / ((mp_word)3); + + if ((res = mp_init_size(&q, a->used)) != MP_OKAY) { + return res; + } + + q.used = a->used; + q.sign = a->sign; + w = 0; + for (ix = a->used - 1; ix >= 0; ix--) { + w = (w << ((mp_word)DIGIT_BIT)) | ((mp_word)a->dp[ix]); + + if (w >= 3) { + /* multiply w by [1/3] */ + t = (w * ((mp_word)b)) >> ((mp_word)DIGIT_BIT); + + /* now subtract 3 * [w/3] from w, to get the remainder */ + w -= t+t+t; + + /* fixup the remainder as required since + * the optimization is not exact. + */ + while (w >= 3) { + t += 1; + w -= 3; + } + } else { + t = 0; + } + q.dp[ix] = (mp_digit)t; + } + + /* [optional] store the remainder */ + if (d != NULL) { + *d = (mp_digit)w; + } + + /* [optional] store the quotient */ + if (c != NULL) { + mp_clamp(&q); + mp_exch(&q, c); + } + mp_clear(&q); + + return res; +} + +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_div_3.c */ + +/* Start: bn_mp_div_d.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_DIV_D_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +static int s_is_power_of_two(mp_digit b, int *p) +{ + int x; + + /* fast return if no power of two */ + if ((b == 0) || ((b & (b-1)) != 0)) { + return 0; + } + + for (x = 0; x < DIGIT_BIT; x++) { + if (b == (((mp_digit)1)<dp[0] & ((((mp_digit)1)<used)) != MP_OKAY) { + return res; + } + + q.used = a->used; + q.sign = a->sign; + w = 0; + for (ix = a->used - 1; ix >= 0; ix--) { + w = (w << ((mp_word)DIGIT_BIT)) | ((mp_word)a->dp[ix]); + + if (w >= b) { + t = (mp_digit)(w / b); + w -= ((mp_word)t) * ((mp_word)b); + } else { + t = 0; + } + q.dp[ix] = (mp_digit)t; + } + + if (d != NULL) { + *d = (mp_digit)w; + } + + if (c != NULL) { + mp_clamp(&q); + mp_exch(&q, c); + } + mp_clear(&q); + + return res; +} + +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_div_d.c */ + +/* Start: bn_mp_dr_is_modulus.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_DR_IS_MODULUS_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* determines if a number is a valid DR modulus */ +int mp_dr_is_modulus(mp_int *a) +{ + int ix; + + /* must be at least two digits */ + if (a->used < 2) { + return 0; + } + + /* must be of the form b**k - a [a <= b] so all + * but the first digit must be equal to -1 (mod b). + */ + for (ix = 1; ix < a->used; ix++) { + if (a->dp[ix] != MP_MASK) { + return 0; + } + } + return 1; +} + +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_dr_is_modulus.c */ + +/* Start: bn_mp_dr_reduce.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_DR_REDUCE_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* reduce "x" in place modulo "n" using the Diminished Radix algorithm. + * + * Based on algorithm from the paper + * + * "Generating Efficient Primes for Discrete Log Cryptosystems" + * Chae Hoon Lim, Pil Joong Lee, + * POSTECH Information Research Laboratories + * + * The modulus must be of a special format [see manual] + * + * Has been modified to use algorithm 7.10 from the LTM book instead + * + * Input x must be in the range 0 <= x <= (n-1)**2 + */ +int +mp_dr_reduce (mp_int * x, mp_int * n, mp_digit k) +{ + int err, i, m; + mp_word r; + mp_digit mu, *tmpx1, *tmpx2; + + /* m = digits in modulus */ + m = n->used; + + /* ensure that "x" has at least 2m digits */ + if (x->alloc < (m + m)) { + if ((err = mp_grow (x, m + m)) != MP_OKAY) { + return err; + } + } + +/* top of loop, this is where the code resumes if + * another reduction pass is required. + */ +top: + /* aliases for digits */ + /* alias for lower half of x */ + tmpx1 = x->dp; + + /* alias for upper half of x, or x/B**m */ + tmpx2 = x->dp + m; + + /* set carry to zero */ + mu = 0; + + /* compute (x mod B**m) + k * [x/B**m] inline and inplace */ + for (i = 0; i < m; i++) { + r = (((mp_word)*tmpx2++) * (mp_word)k) + *tmpx1 + mu; + *tmpx1++ = (mp_digit)(r & MP_MASK); + mu = (mp_digit)(r >> ((mp_word)DIGIT_BIT)); + } + + /* set final carry */ + *tmpx1++ = mu; + + /* zero words above m */ + for (i = m + 1; i < x->used; i++) { + *tmpx1++ = 0; + } + + /* clamp, sub and return */ + mp_clamp (x); + + /* if x >= n then subtract and reduce again + * Each successive "recursion" makes the input smaller and smaller. + */ + if (mp_cmp_mag (x, n) != MP_LT) { + if ((err = s_mp_sub(x, n, x)) != MP_OKAY) { + return err; + } + goto top; + } + return MP_OKAY; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_dr_reduce.c */ + +/* Start: bn_mp_dr_setup.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_DR_SETUP_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* determines the setup value */ +void mp_dr_setup(mp_int *a, mp_digit *d) +{ + /* the casts are required if DIGIT_BIT is one less than + * the number of bits in a mp_digit [e.g. DIGIT_BIT==31] + */ + *d = (mp_digit)((((mp_word)1) << ((mp_word)DIGIT_BIT)) - + ((mp_word)a->dp[0])); +} + +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_dr_setup.c */ + +/* Start: bn_mp_exch.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_EXCH_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* swap the elements of two integers, for cases where you can't simply swap the + * mp_int pointers around + */ +void +mp_exch (mp_int * a, mp_int * b) +{ + mp_int t; + + t = *a; + *a = *b; + *b = t; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_exch.c */ + +/* Start: bn_mp_export.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_EXPORT_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* based on gmp's mpz_export. + * see http://gmplib.org/manual/Integer-Import-and-Export.html + */ +int mp_export(void* rop, size_t* countp, int order, size_t size, + int endian, size_t nails, mp_int* op) { + int result; + size_t odd_nails, nail_bytes, i, j, bits, count; + unsigned char odd_nail_mask; + + mp_int t; + + if ((result = mp_init_copy(&t, op)) != MP_OKAY) { + return result; + } + + if (endian == 0) { + union { + unsigned int i; + char c[4]; + } lint; + lint.i = 0x01020304; + + endian = (lint.c[0] == 4) ? -1 : 1; + } + + odd_nails = (nails % 8); + odd_nail_mask = 0xff; + for (i = 0; i < odd_nails; ++i) { + odd_nail_mask ^= (1 << (7 - i)); + } + nail_bytes = nails / 8; + + bits = mp_count_bits(&t); + count = (bits / ((size * 8) - nails)) + (((bits % ((size * 8) - nails)) != 0) ? 1 : 0); + + for (i = 0; i < count; ++i) { + for (j = 0; j < size; ++j) { + unsigned char* byte = ( + (unsigned char*)rop + + (((order == -1) ? i : ((count - 1) - i)) * size) + + ((endian == -1) ? j : ((size - 1) - j)) + ); + + if (j >= (size - nail_bytes)) { + *byte = 0; + continue; + } + + *byte = (unsigned char)((j == ((size - nail_bytes) - 1)) ? (t.dp[0] & odd_nail_mask) : (t.dp[0] & 0xFF)); + + if ((result = mp_div_2d(&t, ((j == ((size - nail_bytes) - 1)) ? (8 - odd_nails) : 8), &t, NULL)) != MP_OKAY) { + mp_clear(&t); + return result; + } + } + } + + mp_clear(&t); + + if (countp != NULL) { + *countp = count; + } + + return MP_OKAY; +} + +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_export.c */ + +/* Start: bn_mp_expt_d.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_EXPT_D_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* wrapper function for mp_expt_d_ex() */ +int mp_expt_d (mp_int * a, mp_digit b, mp_int * c) +{ + return mp_expt_d_ex(a, b, c, 0); +} + +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_expt_d.c */ + +/* Start: bn_mp_expt_d_ex.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_EXPT_D_EX_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* calculate c = a**b using a square-multiply algorithm */ +int mp_expt_d_ex (mp_int * a, mp_digit b, mp_int * c, int fast) +{ + int res; + unsigned int x; + + mp_int g; + + if ((res = mp_init_copy (&g, a)) != MP_OKAY) { + return res; + } + + /* set initial result */ + mp_set (c, 1); + + if (fast != 0) { + while (b > 0) { + /* if the bit is set multiply */ + if ((b & 1) != 0) { + if ((res = mp_mul (c, &g, c)) != MP_OKAY) { + mp_clear (&g); + return res; + } + } + + /* square */ + if (b > 1) { + if ((res = mp_sqr (&g, &g)) != MP_OKAY) { + mp_clear (&g); + return res; + } + } + + /* shift to next bit */ + b >>= 1; + } + } + else { + for (x = 0; x < DIGIT_BIT; x++) { + /* square */ + if ((res = mp_sqr (c, c)) != MP_OKAY) { + mp_clear (&g); + return res; + } + + /* if the bit is set multiply */ + if ((b & (mp_digit) (((mp_digit)1) << (DIGIT_BIT - 1))) != 0) { + if ((res = mp_mul (c, &g, c)) != MP_OKAY) { + mp_clear (&g); + return res; + } + } + + /* shift to next bit */ + b <<= 1; + } + } /* if ... else */ + + mp_clear (&g); + return MP_OKAY; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_expt_d_ex.c */ + +/* Start: bn_mp_exptmod.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_EXPTMOD_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + + +/* this is a shell function that calls either the normal or Montgomery + * exptmod functions. Originally the call to the montgomery code was + * embedded in the normal function but that wasted alot of stack space + * for nothing (since 99% of the time the Montgomery code would be called) + */ +int mp_exptmod (mp_int * G, mp_int * X, mp_int * P, mp_int * Y) +{ + int dr; + + /* modulus P must be positive */ + if (P->sign == MP_NEG) { + return MP_VAL; + } + + /* if exponent X is negative we have to recurse */ + if (X->sign == MP_NEG) { +#ifdef BN_MP_INVMOD_C + mp_int tmpG, tmpX; + int err; + + /* first compute 1/G mod P */ + if ((err = mp_init(&tmpG)) != MP_OKAY) { + return err; + } + if ((err = mp_invmod(G, P, &tmpG)) != MP_OKAY) { + mp_clear(&tmpG); + return err; + } + + /* now get |X| */ + if ((err = mp_init(&tmpX)) != MP_OKAY) { + mp_clear(&tmpG); + return err; + } + if ((err = mp_abs(X, &tmpX)) != MP_OKAY) { + mp_clear_multi(&tmpG, &tmpX, NULL); + return err; + } + + /* and now compute (1/G)**|X| instead of G**X [X < 0] */ + err = mp_exptmod(&tmpG, &tmpX, P, Y); + mp_clear_multi(&tmpG, &tmpX, NULL); + return err; +#else + /* no invmod */ + return MP_VAL; +#endif + } + +/* modified diminished radix reduction */ +#if defined(BN_MP_REDUCE_IS_2K_L_C) && defined(BN_MP_REDUCE_2K_L_C) && defined(BN_S_MP_EXPTMOD_C) + if (mp_reduce_is_2k_l(P) == MP_YES) { + return s_mp_exptmod(G, X, P, Y, 1); + } +#endif + +#ifdef BN_MP_DR_IS_MODULUS_C + /* is it a DR modulus? */ + dr = mp_dr_is_modulus(P); +#else + /* default to no */ + dr = 0; +#endif + +#ifdef BN_MP_REDUCE_IS_2K_C + /* if not, is it a unrestricted DR modulus? */ + if (dr == 0) { + dr = mp_reduce_is_2k(P) << 1; + } +#endif + + /* if the modulus is odd or dr != 0 use the montgomery method */ +#ifdef BN_MP_EXPTMOD_FAST_C + if ((mp_isodd (P) == MP_YES) || (dr != 0)) { + return mp_exptmod_fast (G, X, P, Y, dr); + } else { +#endif +#ifdef BN_S_MP_EXPTMOD_C + /* otherwise use the generic Barrett reduction technique */ + return s_mp_exptmod (G, X, P, Y, 0); +#else + /* no exptmod for evens */ + return MP_VAL; +#endif +#ifdef BN_MP_EXPTMOD_FAST_C + } +#endif +} + +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_exptmod.c */ + +/* Start: bn_mp_exptmod_fast.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_EXPTMOD_FAST_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* computes Y == G**X mod P, HAC pp.616, Algorithm 14.85 + * + * Uses a left-to-right k-ary sliding window to compute the modular exponentiation. + * The value of k changes based on the size of the exponent. + * + * Uses Montgomery or Diminished Radix reduction [whichever appropriate] + */ + +#ifdef MP_LOW_MEM + #define TAB_SIZE 32 +#else + #define TAB_SIZE 256 +#endif + +int mp_exptmod_fast (mp_int * G, mp_int * X, mp_int * P, mp_int * Y, int redmode) +{ + mp_int M[TAB_SIZE], res; + mp_digit buf, mp; + int err, bitbuf, bitcpy, bitcnt, mode, digidx, x, y, winsize; + + /* use a pointer to the reduction algorithm. This allows us to use + * one of many reduction algorithms without modding the guts of + * the code with if statements everywhere. + */ + int (*redux)(mp_int*,mp_int*,mp_digit); + + /* find window size */ + x = mp_count_bits (X); + if (x <= 7) { + winsize = 2; + } else if (x <= 36) { + winsize = 3; + } else if (x <= 140) { + winsize = 4; + } else if (x <= 450) { + winsize = 5; + } else if (x <= 1303) { + winsize = 6; + } else if (x <= 3529) { + winsize = 7; + } else { + winsize = 8; + } + +#ifdef MP_LOW_MEM + if (winsize > 5) { + winsize = 5; + } +#endif + + /* init M array */ + /* init first cell */ + if ((err = mp_init(&M[1])) != MP_OKAY) { + return err; + } + + /* now init the second half of the array */ + for (x = 1<<(winsize-1); x < (1 << winsize); x++) { + if ((err = mp_init(&M[x])) != MP_OKAY) { + for (y = 1<<(winsize-1); y < x; y++) { + mp_clear (&M[y]); + } + mp_clear(&M[1]); + return err; + } + } + + /* determine and setup reduction code */ + if (redmode == 0) { +#ifdef BN_MP_MONTGOMERY_SETUP_C + /* now setup montgomery */ + if ((err = mp_montgomery_setup (P, &mp)) != MP_OKAY) { + goto LBL_M; + } +#else + err = MP_VAL; + goto LBL_M; +#endif + + /* automatically pick the comba one if available (saves quite a few calls/ifs) */ +#ifdef BN_FAST_MP_MONTGOMERY_REDUCE_C + if ((((P->used * 2) + 1) < MP_WARRAY) && + (P->used < (1 << ((CHAR_BIT * sizeof(mp_word)) - (2 * DIGIT_BIT))))) { + redux = fast_mp_montgomery_reduce; + } else +#endif + { +#ifdef BN_MP_MONTGOMERY_REDUCE_C + /* use slower baseline Montgomery method */ + redux = mp_montgomery_reduce; +#else + err = MP_VAL; + goto LBL_M; +#endif + } + } else if (redmode == 1) { +#if defined(BN_MP_DR_SETUP_C) && defined(BN_MP_DR_REDUCE_C) + /* setup DR reduction for moduli of the form B**k - b */ + mp_dr_setup(P, &mp); + redux = mp_dr_reduce; +#else + err = MP_VAL; + goto LBL_M; +#endif + } else { +#if defined(BN_MP_REDUCE_2K_SETUP_C) && defined(BN_MP_REDUCE_2K_C) + /* setup DR reduction for moduli of the form 2**k - b */ + if ((err = mp_reduce_2k_setup(P, &mp)) != MP_OKAY) { + goto LBL_M; + } + redux = mp_reduce_2k; +#else + err = MP_VAL; + goto LBL_M; +#endif + } + + /* setup result */ + if ((err = mp_init (&res)) != MP_OKAY) { + goto LBL_M; + } + + /* create M table + * + + * + * The first half of the table is not computed though accept for M[0] and M[1] + */ + + if (redmode == 0) { +#ifdef BN_MP_MONTGOMERY_CALC_NORMALIZATION_C + /* now we need R mod m */ + if ((err = mp_montgomery_calc_normalization (&res, P)) != MP_OKAY) { + goto LBL_RES; + } +#else + err = MP_VAL; + goto LBL_RES; +#endif + + /* now set M[1] to G * R mod m */ + if ((err = mp_mulmod (G, &res, P, &M[1])) != MP_OKAY) { + goto LBL_RES; + } + } else { + mp_set(&res, 1); + if ((err = mp_mod(G, P, &M[1])) != MP_OKAY) { + goto LBL_RES; + } + } + + /* compute the value at M[1<<(winsize-1)] by squaring M[1] (winsize-1) times */ + if ((err = mp_copy (&M[1], &M[1 << (winsize - 1)])) != MP_OKAY) { + goto LBL_RES; + } + + for (x = 0; x < (winsize - 1); x++) { + if ((err = mp_sqr (&M[1 << (winsize - 1)], &M[1 << (winsize - 1)])) != MP_OKAY) { + goto LBL_RES; + } + if ((err = redux (&M[1 << (winsize - 1)], P, mp)) != MP_OKAY) { + goto LBL_RES; + } + } + + /* create upper table */ + for (x = (1 << (winsize - 1)) + 1; x < (1 << winsize); x++) { + if ((err = mp_mul (&M[x - 1], &M[1], &M[x])) != MP_OKAY) { + goto LBL_RES; + } + if ((err = redux (&M[x], P, mp)) != MP_OKAY) { + goto LBL_RES; + } + } + + /* set initial mode and bit cnt */ + mode = 0; + bitcnt = 1; + buf = 0; + digidx = X->used - 1; + bitcpy = 0; + bitbuf = 0; + + for (;;) { + /* grab next digit as required */ + if (--bitcnt == 0) { + /* if digidx == -1 we are out of digits so break */ + if (digidx == -1) { + break; + } + /* read next digit and reset bitcnt */ + buf = X->dp[digidx--]; + bitcnt = (int)DIGIT_BIT; + } + + /* grab the next msb from the exponent */ + y = (mp_digit)(buf >> (DIGIT_BIT - 1)) & 1; + buf <<= (mp_digit)1; + + /* if the bit is zero and mode == 0 then we ignore it + * These represent the leading zero bits before the first 1 bit + * in the exponent. Technically this opt is not required but it + * does lower the # of trivial squaring/reductions used + */ + if ((mode == 0) && (y == 0)) { + continue; + } + + /* if the bit is zero and mode == 1 then we square */ + if ((mode == 1) && (y == 0)) { + if ((err = mp_sqr (&res, &res)) != MP_OKAY) { + goto LBL_RES; + } + if ((err = redux (&res, P, mp)) != MP_OKAY) { + goto LBL_RES; + } + continue; + } + + /* else we add it to the window */ + bitbuf |= (y << (winsize - ++bitcpy)); + mode = 2; + + if (bitcpy == winsize) { + /* ok window is filled so square as required and multiply */ + /* square first */ + for (x = 0; x < winsize; x++) { + if ((err = mp_sqr (&res, &res)) != MP_OKAY) { + goto LBL_RES; + } + if ((err = redux (&res, P, mp)) != MP_OKAY) { + goto LBL_RES; + } + } + + /* then multiply */ + if ((err = mp_mul (&res, &M[bitbuf], &res)) != MP_OKAY) { + goto LBL_RES; + } + if ((err = redux (&res, P, mp)) != MP_OKAY) { + goto LBL_RES; + } + + /* empty window and reset */ + bitcpy = 0; + bitbuf = 0; + mode = 1; + } + } + + /* if bits remain then square/multiply */ + if ((mode == 2) && (bitcpy > 0)) { + /* square then multiply if the bit is set */ + for (x = 0; x < bitcpy; x++) { + if ((err = mp_sqr (&res, &res)) != MP_OKAY) { + goto LBL_RES; + } + if ((err = redux (&res, P, mp)) != MP_OKAY) { + goto LBL_RES; + } + + /* get next bit of the window */ + bitbuf <<= 1; + if ((bitbuf & (1 << winsize)) != 0) { + /* then multiply */ + if ((err = mp_mul (&res, &M[1], &res)) != MP_OKAY) { + goto LBL_RES; + } + if ((err = redux (&res, P, mp)) != MP_OKAY) { + goto LBL_RES; + } + } + } + } + + if (redmode == 0) { + /* fixup result if Montgomery reduction is used + * recall that any value in a Montgomery system is + * actually multiplied by R mod n. So we have + * to reduce one more time to cancel out the factor + * of R. + */ + if ((err = redux(&res, P, mp)) != MP_OKAY) { + goto LBL_RES; + } + } + + /* swap res with Y */ + mp_exch (&res, Y); + err = MP_OKAY; +LBL_RES:mp_clear (&res); +LBL_M: + mp_clear(&M[1]); + for (x = 1<<(winsize-1); x < (1 << winsize); x++) { + mp_clear (&M[x]); + } + return err; +} +#endif + + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_exptmod_fast.c */ + +/* Start: bn_mp_exteuclid.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_EXTEUCLID_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* Extended euclidean algorithm of (a, b) produces + a*u1 + b*u2 = u3 + */ +int mp_exteuclid(mp_int *a, mp_int *b, mp_int *U1, mp_int *U2, mp_int *U3) +{ + mp_int u1,u2,u3,v1,v2,v3,t1,t2,t3,q,tmp; + int err; + + if ((err = mp_init_multi(&u1, &u2, &u3, &v1, &v2, &v3, &t1, &t2, &t3, &q, &tmp, NULL)) != MP_OKAY) { + return err; + } + + /* initialize, (u1,u2,u3) = (1,0,a) */ + mp_set(&u1, 1); + if ((err = mp_copy(a, &u3)) != MP_OKAY) { goto _ERR; } + + /* initialize, (v1,v2,v3) = (0,1,b) */ + mp_set(&v2, 1); + if ((err = mp_copy(b, &v3)) != MP_OKAY) { goto _ERR; } + + /* loop while v3 != 0 */ + while (mp_iszero(&v3) == MP_NO) { + /* q = u3/v3 */ + if ((err = mp_div(&u3, &v3, &q, NULL)) != MP_OKAY) { goto _ERR; } + + /* (t1,t2,t3) = (u1,u2,u3) - (v1,v2,v3)q */ + if ((err = mp_mul(&v1, &q, &tmp)) != MP_OKAY) { goto _ERR; } + if ((err = mp_sub(&u1, &tmp, &t1)) != MP_OKAY) { goto _ERR; } + if ((err = mp_mul(&v2, &q, &tmp)) != MP_OKAY) { goto _ERR; } + if ((err = mp_sub(&u2, &tmp, &t2)) != MP_OKAY) { goto _ERR; } + if ((err = mp_mul(&v3, &q, &tmp)) != MP_OKAY) { goto _ERR; } + if ((err = mp_sub(&u3, &tmp, &t3)) != MP_OKAY) { goto _ERR; } + + /* (u1,u2,u3) = (v1,v2,v3) */ + if ((err = mp_copy(&v1, &u1)) != MP_OKAY) { goto _ERR; } + if ((err = mp_copy(&v2, &u2)) != MP_OKAY) { goto _ERR; } + if ((err = mp_copy(&v3, &u3)) != MP_OKAY) { goto _ERR; } + + /* (v1,v2,v3) = (t1,t2,t3) */ + if ((err = mp_copy(&t1, &v1)) != MP_OKAY) { goto _ERR; } + if ((err = mp_copy(&t2, &v2)) != MP_OKAY) { goto _ERR; } + if ((err = mp_copy(&t3, &v3)) != MP_OKAY) { goto _ERR; } + } + + /* make sure U3 >= 0 */ + if (u3.sign == MP_NEG) { + if ((err = mp_neg(&u1, &u1)) != MP_OKAY) { goto _ERR; } + if ((err = mp_neg(&u2, &u2)) != MP_OKAY) { goto _ERR; } + if ((err = mp_neg(&u3, &u3)) != MP_OKAY) { goto _ERR; } + } + + /* copy result out */ + if (U1 != NULL) { mp_exch(U1, &u1); } + if (U2 != NULL) { mp_exch(U2, &u2); } + if (U3 != NULL) { mp_exch(U3, &u3); } + + err = MP_OKAY; +_ERR: mp_clear_multi(&u1, &u2, &u3, &v1, &v2, &v3, &t1, &t2, &t3, &q, &tmp, NULL); + return err; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_exteuclid.c */ + +/* Start: bn_mp_fread.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_FREAD_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* read a bigint from a file stream in ASCII */ +int mp_fread(mp_int *a, int radix, FILE *stream) +{ + int err, ch, neg, y; + + /* clear a */ + mp_zero(a); + + /* if first digit is - then set negative */ + ch = fgetc(stream); + if (ch == '-') { + neg = MP_NEG; + ch = fgetc(stream); + } else { + neg = MP_ZPOS; + } + + for (;;) { + /* find y in the radix map */ + for (y = 0; y < radix; y++) { + if (mp_s_rmap[y] == ch) { + break; + } + } + if (y == radix) { + break; + } + + /* shift up and add */ + if ((err = mp_mul_d(a, radix, a)) != MP_OKAY) { + return err; + } + if ((err = mp_add_d(a, y, a)) != MP_OKAY) { + return err; + } + + ch = fgetc(stream); + } + if (mp_cmp_d(a, 0) != MP_EQ) { + a->sign = neg; + } + + return MP_OKAY; +} + +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_fread.c */ + +/* Start: bn_mp_fwrite.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_FWRITE_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +int mp_fwrite(mp_int *a, int radix, FILE *stream) +{ + char *buf; + int err, len, x; + + if ((err = mp_radix_size(a, radix, &len)) != MP_OKAY) { + return err; + } + + buf = OPT_CAST(char) XMALLOC (len); + if (buf == NULL) { + return MP_MEM; + } + + if ((err = mp_toradix(a, buf, radix)) != MP_OKAY) { + XFREE (buf); + return err; + } + + for (x = 0; x < len; x++) { + if (fputc(buf[x], stream) == EOF) { + XFREE (buf); + return MP_VAL; + } + } + + XFREE (buf); + return MP_OKAY; +} + +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_fwrite.c */ + +/* Start: bn_mp_gcd.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_GCD_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* Greatest Common Divisor using the binary method */ +int mp_gcd (mp_int * a, mp_int * b, mp_int * c) +{ + mp_int u, v; + int k, u_lsb, v_lsb, res; + + /* either zero than gcd is the largest */ + if (mp_iszero (a) == MP_YES) { + return mp_abs (b, c); + } + if (mp_iszero (b) == MP_YES) { + return mp_abs (a, c); + } + + /* get copies of a and b we can modify */ + if ((res = mp_init_copy (&u, a)) != MP_OKAY) { + return res; + } + + if ((res = mp_init_copy (&v, b)) != MP_OKAY) { + goto LBL_U; + } + + /* must be positive for the remainder of the algorithm */ + u.sign = v.sign = MP_ZPOS; + + /* B1. Find the common power of two for u and v */ + u_lsb = mp_cnt_lsb(&u); + v_lsb = mp_cnt_lsb(&v); + k = MIN(u_lsb, v_lsb); + + if (k > 0) { + /* divide the power of two out */ + if ((res = mp_div_2d(&u, k, &u, NULL)) != MP_OKAY) { + goto LBL_V; + } + + if ((res = mp_div_2d(&v, k, &v, NULL)) != MP_OKAY) { + goto LBL_V; + } + } + + /* divide any remaining factors of two out */ + if (u_lsb != k) { + if ((res = mp_div_2d(&u, u_lsb - k, &u, NULL)) != MP_OKAY) { + goto LBL_V; + } + } + + if (v_lsb != k) { + if ((res = mp_div_2d(&v, v_lsb - k, &v, NULL)) != MP_OKAY) { + goto LBL_V; + } + } + + while (mp_iszero(&v) == MP_NO) { + /* make sure v is the largest */ + if (mp_cmp_mag(&u, &v) == MP_GT) { + /* swap u and v to make sure v is >= u */ + mp_exch(&u, &v); + } + + /* subtract smallest from largest */ + if ((res = s_mp_sub(&v, &u, &v)) != MP_OKAY) { + goto LBL_V; + } + + /* Divide out all factors of two */ + if ((res = mp_div_2d(&v, mp_cnt_lsb(&v), &v, NULL)) != MP_OKAY) { + goto LBL_V; + } + } + + /* multiply by 2**k which we divided out at the beginning */ + if ((res = mp_mul_2d (&u, k, c)) != MP_OKAY) { + goto LBL_V; + } + c->sign = MP_ZPOS; + res = MP_OKAY; +LBL_V:mp_clear (&u); +LBL_U:mp_clear (&v); + return res; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_gcd.c */ + +/* Start: bn_mp_get_int.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_GET_INT_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* get the lower 32-bits of an mp_int */ +unsigned long mp_get_int(mp_int * a) +{ + int i; + mp_min_u32 res; + + if (a->used == 0) { + return 0; + } + + /* get number of digits of the lsb we have to read */ + i = MIN(a->used,(int)(((sizeof(unsigned long) * CHAR_BIT) + DIGIT_BIT - 1) / DIGIT_BIT)) - 1; + + /* get most significant digit of result */ + res = DIGIT(a,i); + + while (--i >= 0) { + res = (res << DIGIT_BIT) | DIGIT(a,i); + } + + /* force result to 32-bits always so it is consistent on non 32-bit platforms */ + return res & 0xFFFFFFFFUL; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_get_int.c */ + +/* Start: bn_mp_get_long.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_GET_LONG_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* get the lower unsigned long of an mp_int, platform dependent */ +unsigned long mp_get_long(mp_int * a) +{ + int i; + unsigned long res; + + if (a->used == 0) { + return 0; + } + + /* get number of digits of the lsb we have to read */ + i = MIN(a->used,(int)(((sizeof(unsigned long) * CHAR_BIT) + DIGIT_BIT - 1) / DIGIT_BIT)) - 1; + + /* get most significant digit of result */ + res = DIGIT(a,i); + +#if (ULONG_MAX != 0xffffffffuL) || (DIGIT_BIT < 32) + while (--i >= 0) { + res = (res << DIGIT_BIT) | DIGIT(a,i); + } +#endif + return res; +} +#endif + +/* End: bn_mp_get_long.c */ + +/* Start: bn_mp_get_long_long.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_GET_LONG_LONG_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* get the lower unsigned long long of an mp_int, platform dependent */ +unsigned long long mp_get_long_long (mp_int * a) +{ + int i; + unsigned long long res; + + if (a->used == 0) { + return 0; + } + + /* get number of digits of the lsb we have to read */ + i = MIN(a->used,(int)(((sizeof(unsigned long long) * CHAR_BIT) + DIGIT_BIT - 1) / DIGIT_BIT)) - 1; + + /* get most significant digit of result */ + res = DIGIT(a,i); + +#if DIGIT_BIT < 64 + while (--i >= 0) { + res = (res << DIGIT_BIT) | DIGIT(a,i); + } +#endif + return res; +} +#endif + +/* End: bn_mp_get_long_long.c */ + +/* Start: bn_mp_grow.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_GROW_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* grow as required */ +int mp_grow (mp_int * a, int size) +{ + int i; + mp_digit *tmp; + + /* if the alloc size is smaller alloc more ram */ + if (a->alloc < size) { + /* ensure there are always at least MP_PREC digits extra on top */ + size += (MP_PREC * 2) - (size % MP_PREC); + + /* reallocate the array a->dp + * + * We store the return in a temporary variable + * in case the operation failed we don't want + * to overwrite the dp member of a. + */ + tmp = OPT_CAST(mp_digit) XREALLOC (a->dp, sizeof (mp_digit) * size); + if (tmp == NULL) { + /* reallocation failed but "a" is still valid [can be freed] */ + return MP_MEM; + } + + /* reallocation succeeded so set a->dp */ + a->dp = tmp; + + /* zero excess digits */ + i = a->alloc; + a->alloc = size; + for (; i < a->alloc; i++) { + a->dp[i] = 0; + } + } + return MP_OKAY; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_grow.c */ + +/* Start: bn_mp_import.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_IMPORT_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* based on gmp's mpz_import. + * see http://gmplib.org/manual/Integer-Import-and-Export.html + */ +int mp_import(mp_int* rop, size_t count, int order, size_t size, + int endian, size_t nails, const void* op) { + int result; + size_t odd_nails, nail_bytes, i, j; + unsigned char odd_nail_mask; + + mp_zero(rop); + + if (endian == 0) { + union { + unsigned int i; + char c[4]; + } lint; + lint.i = 0x01020304; + + endian = (lint.c[0] == 4) ? -1 : 1; + } + + odd_nails = (nails % 8); + odd_nail_mask = 0xff; + for (i = 0; i < odd_nails; ++i) { + odd_nail_mask ^= (1 << (7 - i)); + } + nail_bytes = nails / 8; + + for (i = 0; i < count; ++i) { + for (j = 0; j < (size - nail_bytes); ++j) { + unsigned char byte = *( + (unsigned char*)op + + (((order == 1) ? i : ((count - 1) - i)) * size) + + ((endian == 1) ? (j + nail_bytes) : (((size - 1) - j) - nail_bytes)) + ); + + if ( + (result = mp_mul_2d(rop, ((j == 0) ? (8 - odd_nails) : 8), rop)) != MP_OKAY) { + return result; + } + + rop->dp[0] |= (j == 0) ? (byte & odd_nail_mask) : byte; + rop->used += 1; + } + } + + mp_clamp(rop); + + return MP_OKAY; +} + +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_import.c */ + +/* Start: bn_mp_init.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_INIT_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* init a new mp_int */ +int mp_init (mp_int * a) +{ + int i; + + /* allocate memory required and clear it */ + a->dp = OPT_CAST(mp_digit) XMALLOC (sizeof (mp_digit) * MP_PREC); + if (a->dp == NULL) { + return MP_MEM; + } + + /* set the digits to zero */ + for (i = 0; i < MP_PREC; i++) { + a->dp[i] = 0; + } + + /* set the used to zero, allocated digits to the default precision + * and sign to positive */ + a->used = 0; + a->alloc = MP_PREC; + a->sign = MP_ZPOS; + + return MP_OKAY; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_init.c */ + +/* Start: bn_mp_init_copy.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_INIT_COPY_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* creates "a" then copies b into it */ +int mp_init_copy (mp_int * a, mp_int * b) +{ + int res; + + if ((res = mp_init_size (a, b->used)) != MP_OKAY) { + return res; + } + return mp_copy (b, a); +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_init_copy.c */ + +/* Start: bn_mp_init_multi.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_INIT_MULTI_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ +#include + +int mp_init_multi(mp_int *mp, ...) +{ + mp_err res = MP_OKAY; /* Assume ok until proven otherwise */ + int n = 0; /* Number of ok inits */ + mp_int* cur_arg = mp; + va_list args; + + va_start(args, mp); /* init args to next argument from caller */ + while (cur_arg != NULL) { + if (mp_init(cur_arg) != MP_OKAY) { + /* Oops - error! Back-track and mp_clear what we already + succeeded in init-ing, then return error. + */ + va_list clean_args; + + /* end the current list */ + va_end(args); + + /* now start cleaning up */ + cur_arg = mp; + va_start(clean_args, mp); + while (n-- != 0) { + mp_clear(cur_arg); + cur_arg = va_arg(clean_args, mp_int*); + } + va_end(clean_args); + res = MP_MEM; + break; + } + n++; + cur_arg = va_arg(args, mp_int*); + } + va_end(args); + return res; /* Assumed ok, if error flagged above. */ +} + +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_init_multi.c */ + +/* Start: bn_mp_init_set.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_INIT_SET_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* initialize and set a digit */ +int mp_init_set (mp_int * a, mp_digit b) +{ + int err; + if ((err = mp_init(a)) != MP_OKAY) { + return err; + } + mp_set(a, b); + return err; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_init_set.c */ + +/* Start: bn_mp_init_set_int.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_INIT_SET_INT_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* initialize and set a digit */ +int mp_init_set_int (mp_int * a, unsigned long b) +{ + int err; + if ((err = mp_init(a)) != MP_OKAY) { + return err; + } + return mp_set_int(a, b); +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_init_set_int.c */ + +/* Start: bn_mp_init_size.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_INIT_SIZE_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* init an mp_init for a given size */ +int mp_init_size (mp_int * a, int size) +{ + int x; + + /* pad size so there are always extra digits */ + size += (MP_PREC * 2) - (size % MP_PREC); + + /* alloc mem */ + a->dp = OPT_CAST(mp_digit) XMALLOC (sizeof (mp_digit) * size); + if (a->dp == NULL) { + return MP_MEM; + } + + /* set the members */ + a->used = 0; + a->alloc = size; + a->sign = MP_ZPOS; + + /* zero the digits */ + for (x = 0; x < size; x++) { + a->dp[x] = 0; + } + + return MP_OKAY; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_init_size.c */ + +/* Start: bn_mp_invmod.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_INVMOD_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* hac 14.61, pp608 */ +int mp_invmod (mp_int * a, mp_int * b, mp_int * c) +{ + /* b cannot be negative */ + if ((b->sign == MP_NEG) || (mp_iszero(b) == MP_YES)) { + return MP_VAL; + } + +#ifdef BN_FAST_MP_INVMOD_C + /* if the modulus is odd we can use a faster routine instead */ + if (mp_isodd (b) == MP_YES) { + return fast_mp_invmod (a, b, c); + } +#endif + +#ifdef BN_MP_INVMOD_SLOW_C + return mp_invmod_slow(a, b, c); +#else + return MP_VAL; +#endif +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_invmod.c */ + +/* Start: bn_mp_invmod_slow.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_INVMOD_SLOW_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* hac 14.61, pp608 */ +int mp_invmod_slow (mp_int * a, mp_int * b, mp_int * c) +{ + mp_int x, y, u, v, A, B, C, D; + int res; + + /* b cannot be negative */ + if ((b->sign == MP_NEG) || (mp_iszero(b) == MP_YES)) { + return MP_VAL; + } + + /* init temps */ + if ((res = mp_init_multi(&x, &y, &u, &v, + &A, &B, &C, &D, NULL)) != MP_OKAY) { + return res; + } + + /* x = a, y = b */ + if ((res = mp_mod(a, b, &x)) != MP_OKAY) { + goto LBL_ERR; + } + if ((res = mp_copy (b, &y)) != MP_OKAY) { + goto LBL_ERR; + } + + /* 2. [modified] if x,y are both even then return an error! */ + if ((mp_iseven (&x) == MP_YES) && (mp_iseven (&y) == MP_YES)) { + res = MP_VAL; + goto LBL_ERR; + } + + /* 3. u=x, v=y, A=1, B=0, C=0,D=1 */ + if ((res = mp_copy (&x, &u)) != MP_OKAY) { + goto LBL_ERR; + } + if ((res = mp_copy (&y, &v)) != MP_OKAY) { + goto LBL_ERR; + } + mp_set (&A, 1); + mp_set (&D, 1); + +top: + /* 4. while u is even do */ + while (mp_iseven (&u) == MP_YES) { + /* 4.1 u = u/2 */ + if ((res = mp_div_2 (&u, &u)) != MP_OKAY) { + goto LBL_ERR; + } + /* 4.2 if A or B is odd then */ + if ((mp_isodd (&A) == MP_YES) || (mp_isodd (&B) == MP_YES)) { + /* A = (A+y)/2, B = (B-x)/2 */ + if ((res = mp_add (&A, &y, &A)) != MP_OKAY) { + goto LBL_ERR; + } + if ((res = mp_sub (&B, &x, &B)) != MP_OKAY) { + goto LBL_ERR; + } + } + /* A = A/2, B = B/2 */ + if ((res = mp_div_2 (&A, &A)) != MP_OKAY) { + goto LBL_ERR; + } + if ((res = mp_div_2 (&B, &B)) != MP_OKAY) { + goto LBL_ERR; + } + } + + /* 5. while v is even do */ + while (mp_iseven (&v) == MP_YES) { + /* 5.1 v = v/2 */ + if ((res = mp_div_2 (&v, &v)) != MP_OKAY) { + goto LBL_ERR; + } + /* 5.2 if C or D is odd then */ + if ((mp_isodd (&C) == MP_YES) || (mp_isodd (&D) == MP_YES)) { + /* C = (C+y)/2, D = (D-x)/2 */ + if ((res = mp_add (&C, &y, &C)) != MP_OKAY) { + goto LBL_ERR; + } + if ((res = mp_sub (&D, &x, &D)) != MP_OKAY) { + goto LBL_ERR; + } + } + /* C = C/2, D = D/2 */ + if ((res = mp_div_2 (&C, &C)) != MP_OKAY) { + goto LBL_ERR; + } + if ((res = mp_div_2 (&D, &D)) != MP_OKAY) { + goto LBL_ERR; + } + } + + /* 6. if u >= v then */ + if (mp_cmp (&u, &v) != MP_LT) { + /* u = u - v, A = A - C, B = B - D */ + if ((res = mp_sub (&u, &v, &u)) != MP_OKAY) { + goto LBL_ERR; + } + + if ((res = mp_sub (&A, &C, &A)) != MP_OKAY) { + goto LBL_ERR; + } + + if ((res = mp_sub (&B, &D, &B)) != MP_OKAY) { + goto LBL_ERR; + } + } else { + /* v - v - u, C = C - A, D = D - B */ + if ((res = mp_sub (&v, &u, &v)) != MP_OKAY) { + goto LBL_ERR; + } + + if ((res = mp_sub (&C, &A, &C)) != MP_OKAY) { + goto LBL_ERR; + } + + if ((res = mp_sub (&D, &B, &D)) != MP_OKAY) { + goto LBL_ERR; + } + } + + /* if not zero goto step 4 */ + if (mp_iszero (&u) == MP_NO) + goto top; + + /* now a = C, b = D, gcd == g*v */ + + /* if v != 1 then there is no inverse */ + if (mp_cmp_d (&v, 1) != MP_EQ) { + res = MP_VAL; + goto LBL_ERR; + } + + /* if its too low */ + while (mp_cmp_d(&C, 0) == MP_LT) { + if ((res = mp_add(&C, b, &C)) != MP_OKAY) { + goto LBL_ERR; + } + } + + /* too big */ + while (mp_cmp_mag(&C, b) != MP_LT) { + if ((res = mp_sub(&C, b, &C)) != MP_OKAY) { + goto LBL_ERR; + } + } + + /* C is now the inverse */ + mp_exch (&C, c); + res = MP_OKAY; +LBL_ERR:mp_clear_multi (&x, &y, &u, &v, &A, &B, &C, &D, NULL); + return res; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_invmod_slow.c */ + +/* Start: bn_mp_is_square.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_IS_SQUARE_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* Check if remainders are possible squares - fast exclude non-squares */ +static const char rem_128[128] = { + 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, + 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, + 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, + 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, + 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, + 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, + 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1 +}; + +static const char rem_105[105] = { + 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, + 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, + 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, + 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, + 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1 +}; + +/* Store non-zero to ret if arg is square, and zero if not */ +int mp_is_square(mp_int *arg,int *ret) +{ + int res; + mp_digit c; + mp_int t; + unsigned long r; + + /* Default to Non-square :) */ + *ret = MP_NO; + + if (arg->sign == MP_NEG) { + return MP_VAL; + } + + /* digits used? (TSD) */ + if (arg->used == 0) { + return MP_OKAY; + } + + /* First check mod 128 (suppose that DIGIT_BIT is at least 7) */ + if (rem_128[127 & DIGIT(arg,0)] == 1) { + return MP_OKAY; + } + + /* Next check mod 105 (3*5*7) */ + if ((res = mp_mod_d(arg,105,&c)) != MP_OKAY) { + return res; + } + if (rem_105[c] == 1) { + return MP_OKAY; + } + + + if ((res = mp_init_set_int(&t,11L*13L*17L*19L*23L*29L*31L)) != MP_OKAY) { + return res; + } + if ((res = mp_mod(arg,&t,&t)) != MP_OKAY) { + goto ERR; + } + r = mp_get_int(&t); + /* Check for other prime modules, note it's not an ERROR but we must + * free "t" so the easiest way is to goto ERR. We know that res + * is already equal to MP_OKAY from the mp_mod call + */ + if (((1L<<(r%11)) & 0x5C4L) != 0L) goto ERR; + if (((1L<<(r%13)) & 0x9E4L) != 0L) goto ERR; + if (((1L<<(r%17)) & 0x5CE8L) != 0L) goto ERR; + if (((1L<<(r%19)) & 0x4F50CL) != 0L) goto ERR; + if (((1L<<(r%23)) & 0x7ACCA0L) != 0L) goto ERR; + if (((1L<<(r%29)) & 0xC2EDD0CL) != 0L) goto ERR; + if (((1L<<(r%31)) & 0x6DE2B848L) != 0L) goto ERR; + + /* Final check - is sqr(sqrt(arg)) == arg ? */ + if ((res = mp_sqrt(arg,&t)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_sqr(&t,&t)) != MP_OKAY) { + goto ERR; + } + + *ret = (mp_cmp_mag(&t,arg) == MP_EQ) ? MP_YES : MP_NO; +ERR:mp_clear(&t); + return res; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_is_square.c */ + +/* Start: bn_mp_jacobi.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_JACOBI_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* computes the jacobi c = (a | n) (or Legendre if n is prime) + * HAC pp. 73 Algorithm 2.149 + * HAC is wrong here, as the special case of (0 | 1) is not + * handled correctly. + */ +int mp_jacobi (mp_int * a, mp_int * n, int *c) +{ + mp_int a1, p1; + int k, s, r, res; + mp_digit residue; + + /* if a < 0 return MP_VAL */ + if (mp_isneg(a) == MP_YES) { + return MP_VAL; + } + + /* if n <= 0 return MP_VAL */ + if (mp_cmp_d(n, 0) != MP_GT) { + return MP_VAL; + } + + /* step 1. handle case of a == 0 */ + if (mp_iszero (a) == MP_YES) { + /* special case of a == 0 and n == 1 */ + if (mp_cmp_d (n, 1) == MP_EQ) { + *c = 1; + } else { + *c = 0; + } + return MP_OKAY; + } + + /* step 2. if a == 1, return 1 */ + if (mp_cmp_d (a, 1) == MP_EQ) { + *c = 1; + return MP_OKAY; + } + + /* default */ + s = 0; + + /* step 3. write a = a1 * 2**k */ + if ((res = mp_init_copy (&a1, a)) != MP_OKAY) { + return res; + } + + if ((res = mp_init (&p1)) != MP_OKAY) { + goto LBL_A1; + } + + /* divide out larger power of two */ + k = mp_cnt_lsb(&a1); + if ((res = mp_div_2d(&a1, k, &a1, NULL)) != MP_OKAY) { + goto LBL_P1; + } + + /* step 4. if e is even set s=1 */ + if ((k & 1) == 0) { + s = 1; + } else { + /* else set s=1 if p = 1/7 (mod 8) or s=-1 if p = 3/5 (mod 8) */ + residue = n->dp[0] & 7; + + if ((residue == 1) || (residue == 7)) { + s = 1; + } else if ((residue == 3) || (residue == 5)) { + s = -1; + } + } + + /* step 5. if p == 3 (mod 4) *and* a1 == 3 (mod 4) then s = -s */ + if ( ((n->dp[0] & 3) == 3) && ((a1.dp[0] & 3) == 3)) { + s = -s; + } + + /* if a1 == 1 we're done */ + if (mp_cmp_d (&a1, 1) == MP_EQ) { + *c = s; + } else { + /* n1 = n mod a1 */ + if ((res = mp_mod (n, &a1, &p1)) != MP_OKAY) { + goto LBL_P1; + } + if ((res = mp_jacobi (&p1, &a1, &r)) != MP_OKAY) { + goto LBL_P1; + } + *c = s * r; + } + + /* done */ + res = MP_OKAY; +LBL_P1:mp_clear (&p1); +LBL_A1:mp_clear (&a1); + return res; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_jacobi.c */ + +/* Start: bn_mp_karatsuba_mul.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_KARATSUBA_MUL_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* c = |a| * |b| using Karatsuba Multiplication using + * three half size multiplications + * + * Let B represent the radix [e.g. 2**DIGIT_BIT] and + * let n represent half of the number of digits in + * the min(a,b) + * + * a = a1 * B**n + a0 + * b = b1 * B**n + b0 + * + * Then, a * b => + a1b1 * B**2n + ((a1 + a0)(b1 + b0) - (a0b0 + a1b1)) * B + a0b0 + * + * Note that a1b1 and a0b0 are used twice and only need to be + * computed once. So in total three half size (half # of + * digit) multiplications are performed, a0b0, a1b1 and + * (a1+b1)(a0+b0) + * + * Note that a multiplication of half the digits requires + * 1/4th the number of single precision multiplications so in + * total after one call 25% of the single precision multiplications + * are saved. Note also that the call to mp_mul can end up back + * in this function if the a0, a1, b0, or b1 are above the threshold. + * This is known as divide-and-conquer and leads to the famous + * O(N**lg(3)) or O(N**1.584) work which is asymptopically lower than + * the standard O(N**2) that the baseline/comba methods use. + * Generally though the overhead of this method doesn't pay off + * until a certain size (N ~ 80) is reached. + */ +int mp_karatsuba_mul (mp_int * a, mp_int * b, mp_int * c) +{ + mp_int x0, x1, y0, y1, t1, x0y0, x1y1; + int B, err; + + /* default the return code to an error */ + err = MP_MEM; + + /* min # of digits */ + B = MIN (a->used, b->used); + + /* now divide in two */ + B = B >> 1; + + /* init copy all the temps */ + if (mp_init_size (&x0, B) != MP_OKAY) + goto ERR; + if (mp_init_size (&x1, a->used - B) != MP_OKAY) + goto X0; + if (mp_init_size (&y0, B) != MP_OKAY) + goto X1; + if (mp_init_size (&y1, b->used - B) != MP_OKAY) + goto Y0; + + /* init temps */ + if (mp_init_size (&t1, B * 2) != MP_OKAY) + goto Y1; + if (mp_init_size (&x0y0, B * 2) != MP_OKAY) + goto T1; + if (mp_init_size (&x1y1, B * 2) != MP_OKAY) + goto X0Y0; + + /* now shift the digits */ + x0.used = y0.used = B; + x1.used = a->used - B; + y1.used = b->used - B; + + { + int x; + mp_digit *tmpa, *tmpb, *tmpx, *tmpy; + + /* we copy the digits directly instead of using higher level functions + * since we also need to shift the digits + */ + tmpa = a->dp; + tmpb = b->dp; + + tmpx = x0.dp; + tmpy = y0.dp; + for (x = 0; x < B; x++) { + *tmpx++ = *tmpa++; + *tmpy++ = *tmpb++; + } + + tmpx = x1.dp; + for (x = B; x < a->used; x++) { + *tmpx++ = *tmpa++; + } + + tmpy = y1.dp; + for (x = B; x < b->used; x++) { + *tmpy++ = *tmpb++; + } + } + + /* only need to clamp the lower words since by definition the + * upper words x1/y1 must have a known number of digits + */ + mp_clamp (&x0); + mp_clamp (&y0); + + /* now calc the products x0y0 and x1y1 */ + /* after this x0 is no longer required, free temp [x0==t2]! */ + if (mp_mul (&x0, &y0, &x0y0) != MP_OKAY) + goto X1Y1; /* x0y0 = x0*y0 */ + if (mp_mul (&x1, &y1, &x1y1) != MP_OKAY) + goto X1Y1; /* x1y1 = x1*y1 */ + + /* now calc x1+x0 and y1+y0 */ + if (s_mp_add (&x1, &x0, &t1) != MP_OKAY) + goto X1Y1; /* t1 = x1 - x0 */ + if (s_mp_add (&y1, &y0, &x0) != MP_OKAY) + goto X1Y1; /* t2 = y1 - y0 */ + if (mp_mul (&t1, &x0, &t1) != MP_OKAY) + goto X1Y1; /* t1 = (x1 + x0) * (y1 + y0) */ + + /* add x0y0 */ + if (mp_add (&x0y0, &x1y1, &x0) != MP_OKAY) + goto X1Y1; /* t2 = x0y0 + x1y1 */ + if (s_mp_sub (&t1, &x0, &t1) != MP_OKAY) + goto X1Y1; /* t1 = (x1+x0)*(y1+y0) - (x1y1 + x0y0) */ + + /* shift by B */ + if (mp_lshd (&t1, B) != MP_OKAY) + goto X1Y1; /* t1 = (x0y0 + x1y1 - (x1-x0)*(y1-y0))<used; + + /* now divide in two */ + B = B >> 1; + + /* init copy all the temps */ + if (mp_init_size (&x0, B) != MP_OKAY) + goto ERR; + if (mp_init_size (&x1, a->used - B) != MP_OKAY) + goto X0; + + /* init temps */ + if (mp_init_size (&t1, a->used * 2) != MP_OKAY) + goto X1; + if (mp_init_size (&t2, a->used * 2) != MP_OKAY) + goto T1; + if (mp_init_size (&x0x0, B * 2) != MP_OKAY) + goto T2; + if (mp_init_size (&x1x1, (a->used - B) * 2) != MP_OKAY) + goto X0X0; + + { + int x; + mp_digit *dst, *src; + + src = a->dp; + + /* now shift the digits */ + dst = x0.dp; + for (x = 0; x < B; x++) { + *dst++ = *src++; + } + + dst = x1.dp; + for (x = B; x < a->used; x++) { + *dst++ = *src++; + } + } + + x0.used = B; + x1.used = a->used - B; + + mp_clamp (&x0); + + /* now calc the products x0*x0 and x1*x1 */ + if (mp_sqr (&x0, &x0x0) != MP_OKAY) + goto X1X1; /* x0x0 = x0*x0 */ + if (mp_sqr (&x1, &x1x1) != MP_OKAY) + goto X1X1; /* x1x1 = x1*x1 */ + + /* now calc (x1+x0)**2 */ + if (s_mp_add (&x1, &x0, &t1) != MP_OKAY) + goto X1X1; /* t1 = x1 - x0 */ + if (mp_sqr (&t1, &t1) != MP_OKAY) + goto X1X1; /* t1 = (x1 - x0) * (x1 - x0) */ + + /* add x0y0 */ + if (s_mp_add (&x0x0, &x1x1, &t2) != MP_OKAY) + goto X1X1; /* t2 = x0x0 + x1x1 */ + if (s_mp_sub (&t1, &t2, &t1) != MP_OKAY) + goto X1X1; /* t1 = (x1+x0)**2 - (x0x0 + x1x1) */ + + /* shift by B */ + if (mp_lshd (&t1, B) != MP_OKAY) + goto X1X1; /* t1 = (x0x0 + x1x1 - (x1-x0)*(x1-x0))<sign = MP_ZPOS; + +LBL_T: + mp_clear_multi (&t1, &t2, NULL); + return res; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_lcm.c */ + +/* Start: bn_mp_lshd.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_LSHD_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* shift left a certain amount of digits */ +int mp_lshd (mp_int * a, int b) +{ + int x, res; + + /* if its less than zero return */ + if (b <= 0) { + return MP_OKAY; + } + + /* grow to fit the new digits */ + if (a->alloc < (a->used + b)) { + if ((res = mp_grow (a, a->used + b)) != MP_OKAY) { + return res; + } + } + + { + mp_digit *top, *bottom; + + /* increment the used by the shift amount then copy upwards */ + a->used += b; + + /* top */ + top = a->dp + a->used - 1; + + /* base */ + bottom = (a->dp + a->used - 1) - b; + + /* much like mp_rshd this is implemented using a sliding window + * except the window goes the otherway around. Copying from + * the bottom to the top. see bn_mp_rshd.c for more info. + */ + for (x = a->used - 1; x >= b; x--) { + *top-- = *bottom--; + } + + /* zero the lower digits */ + top = a->dp; + for (x = 0; x < b; x++) { + *top++ = 0; + } + } + return MP_OKAY; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_lshd.c */ + +/* Start: bn_mp_mod.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_MOD_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* c = a mod b, 0 <= c < b if b > 0, b < c <= 0 if b < 0 */ +int +mp_mod (mp_int * a, mp_int * b, mp_int * c) +{ + mp_int t; + int res; + + if ((res = mp_init (&t)) != MP_OKAY) { + return res; + } + + if ((res = mp_div (a, b, NULL, &t)) != MP_OKAY) { + mp_clear (&t); + return res; + } + + if ((mp_iszero(&t) != MP_NO) || (t.sign == b->sign)) { + res = MP_OKAY; + mp_exch (&t, c); + } else { + res = mp_add (b, &t, c); + } + + mp_clear (&t); + return res; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_mod.c */ + +/* Start: bn_mp_mod_2d.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_MOD_2D_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* calc a value mod 2**b */ +int +mp_mod_2d (mp_int * a, int b, mp_int * c) +{ + int x, res; + + /* if b is <= 0 then zero the int */ + if (b <= 0) { + mp_zero (c); + return MP_OKAY; + } + + /* if the modulus is larger than the value than return */ + if (b >= (int) (a->used * DIGIT_BIT)) { + res = mp_copy (a, c); + return res; + } + + /* copy */ + if ((res = mp_copy (a, c)) != MP_OKAY) { + return res; + } + + /* zero digits above the last digit of the modulus */ + for (x = (b / DIGIT_BIT) + (((b % DIGIT_BIT) == 0) ? 0 : 1); x < c->used; x++) { + c->dp[x] = 0; + } + /* clear the digit that is not completely outside/inside the modulus */ + c->dp[b / DIGIT_BIT] &= + (mp_digit) ((((mp_digit) 1) << (((mp_digit) b) % DIGIT_BIT)) - ((mp_digit) 1)); + mp_clamp (c); + return MP_OKAY; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_mod_2d.c */ + +/* Start: bn_mp_mod_d.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_MOD_D_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +int +mp_mod_d (mp_int * a, mp_digit b, mp_digit * c) +{ + return mp_div_d(a, b, NULL, c); +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_mod_d.c */ + +/* Start: bn_mp_montgomery_calc_normalization.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_MONTGOMERY_CALC_NORMALIZATION_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* + * shifts with subtractions when the result is greater than b. + * + * The method is slightly modified to shift B unconditionally upto just under + * the leading bit of b. This saves alot of multiple precision shifting. + */ +int mp_montgomery_calc_normalization (mp_int * a, mp_int * b) +{ + int x, bits, res; + + /* how many bits of last digit does b use */ + bits = mp_count_bits (b) % DIGIT_BIT; + + if (b->used > 1) { + if ((res = mp_2expt (a, ((b->used - 1) * DIGIT_BIT) + bits - 1)) != MP_OKAY) { + return res; + } + } else { + mp_set(a, 1); + bits = 1; + } + + + /* now compute C = A * B mod b */ + for (x = bits - 1; x < (int)DIGIT_BIT; x++) { + if ((res = mp_mul_2 (a, a)) != MP_OKAY) { + return res; + } + if (mp_cmp_mag (a, b) != MP_LT) { + if ((res = s_mp_sub (a, b, a)) != MP_OKAY) { + return res; + } + } + } + + return MP_OKAY; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_montgomery_calc_normalization.c */ + +/* Start: bn_mp_montgomery_reduce.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_MONTGOMERY_REDUCE_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* computes xR**-1 == x (mod N) via Montgomery Reduction */ +int +mp_montgomery_reduce (mp_int * x, mp_int * n, mp_digit rho) +{ + int ix, res, digs; + mp_digit mu; + + /* can the fast reduction [comba] method be used? + * + * Note that unlike in mul you're safely allowed *less* + * than the available columns [255 per default] since carries + * are fixed up in the inner loop. + */ + digs = (n->used * 2) + 1; + if ((digs < MP_WARRAY) && + (n->used < + (1 << ((CHAR_BIT * sizeof(mp_word)) - (2 * DIGIT_BIT))))) { + return fast_mp_montgomery_reduce (x, n, rho); + } + + /* grow the input as required */ + if (x->alloc < digs) { + if ((res = mp_grow (x, digs)) != MP_OKAY) { + return res; + } + } + x->used = digs; + + for (ix = 0; ix < n->used; ix++) { + /* mu = ai * rho mod b + * + * The value of rho must be precalculated via + * montgomery_setup() such that + * it equals -1/n0 mod b this allows the + * following inner loop to reduce the + * input one digit at a time + */ + mu = (mp_digit) (((mp_word)x->dp[ix] * (mp_word)rho) & MP_MASK); + + /* a = a + mu * m * b**i */ + { + int iy; + mp_digit *tmpn, *tmpx, u; + mp_word r; + + /* alias for digits of the modulus */ + tmpn = n->dp; + + /* alias for the digits of x [the input] */ + tmpx = x->dp + ix; + + /* set the carry to zero */ + u = 0; + + /* Multiply and add in place */ + for (iy = 0; iy < n->used; iy++) { + /* compute product and sum */ + r = ((mp_word)mu * (mp_word)*tmpn++) + + (mp_word) u + (mp_word) *tmpx; + + /* get carry */ + u = (mp_digit)(r >> ((mp_word) DIGIT_BIT)); + + /* fix digit */ + *tmpx++ = (mp_digit)(r & ((mp_word) MP_MASK)); + } + /* At this point the ix'th digit of x should be zero */ + + + /* propagate carries upwards as required*/ + while (u != 0) { + *tmpx += u; + u = *tmpx >> DIGIT_BIT; + *tmpx++ &= MP_MASK; + } + } + } + + /* at this point the n.used'th least + * significant digits of x are all zero + * which means we can shift x to the + * right by n.used digits and the + * residue is unchanged. + */ + + /* x = x/b**n.used */ + mp_clamp(x); + mp_rshd (x, n->used); + + /* if x >= n then x = x - n */ + if (mp_cmp_mag (x, n) != MP_LT) { + return s_mp_sub (x, n, x); + } + + return MP_OKAY; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_montgomery_reduce.c */ + +/* Start: bn_mp_montgomery_setup.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_MONTGOMERY_SETUP_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* setups the montgomery reduction stuff */ +int +mp_montgomery_setup (mp_int * n, mp_digit * rho) +{ + mp_digit x, b; + +/* fast inversion mod 2**k + * + * Based on the fact that + * + * XA = 1 (mod 2**n) => (X(2-XA)) A = 1 (mod 2**2n) + * => 2*X*A - X*X*A*A = 1 + * => 2*(1) - (1) = 1 + */ + b = n->dp[0]; + + if ((b & 1) == 0) { + return MP_VAL; + } + + x = (((b + 2) & 4) << 1) + b; /* here x*a==1 mod 2**4 */ + x *= 2 - (b * x); /* here x*a==1 mod 2**8 */ +#if !defined(MP_8BIT) + x *= 2 - (b * x); /* here x*a==1 mod 2**16 */ +#endif +#if defined(MP_64BIT) || !(defined(MP_8BIT) || defined(MP_16BIT)) + x *= 2 - (b * x); /* here x*a==1 mod 2**32 */ +#endif +#ifdef MP_64BIT + x *= 2 - (b * x); /* here x*a==1 mod 2**64 */ +#endif + + /* rho = -1/m mod b */ + *rho = (mp_digit)(((mp_word)1 << ((mp_word) DIGIT_BIT)) - x) & MP_MASK; + + return MP_OKAY; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_montgomery_setup.c */ + +/* Start: bn_mp_mul.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_MUL_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* high level multiplication (handles sign) */ +int mp_mul (mp_int * a, mp_int * b, mp_int * c) +{ + int res, neg; + neg = (a->sign == b->sign) ? MP_ZPOS : MP_NEG; + + /* use Toom-Cook? */ +#ifdef BN_MP_TOOM_MUL_C + if (MIN (a->used, b->used) >= TOOM_MUL_CUTOFF) { + res = mp_toom_mul(a, b, c); + } else +#endif +#ifdef BN_MP_KARATSUBA_MUL_C + /* use Karatsuba? */ + if (MIN (a->used, b->used) >= KARATSUBA_MUL_CUTOFF) { + res = mp_karatsuba_mul (a, b, c); + } else +#endif + { + /* can we use the fast multiplier? + * + * The fast multiplier can be used if the output will + * have less than MP_WARRAY digits and the number of + * digits won't affect carry propagation + */ + int digs = a->used + b->used + 1; + +#ifdef BN_FAST_S_MP_MUL_DIGS_C + if ((digs < MP_WARRAY) && + (MIN(a->used, b->used) <= + (1 << ((CHAR_BIT * sizeof(mp_word)) - (2 * DIGIT_BIT))))) { + res = fast_s_mp_mul_digs (a, b, c, digs); + } else +#endif + { +#ifdef BN_S_MP_MUL_DIGS_C + res = s_mp_mul (a, b, c); /* uses s_mp_mul_digs */ +#else + res = MP_VAL; +#endif + } + } + c->sign = (c->used > 0) ? neg : MP_ZPOS; + return res; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_mul.c */ + +/* Start: bn_mp_mul_2.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_MUL_2_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* b = a*2 */ +int mp_mul_2(mp_int * a, mp_int * b) +{ + int x, res, oldused; + + /* grow to accomodate result */ + if (b->alloc < (a->used + 1)) { + if ((res = mp_grow (b, a->used + 1)) != MP_OKAY) { + return res; + } + } + + oldused = b->used; + b->used = a->used; + + { + mp_digit r, rr, *tmpa, *tmpb; + + /* alias for source */ + tmpa = a->dp; + + /* alias for dest */ + tmpb = b->dp; + + /* carry */ + r = 0; + for (x = 0; x < a->used; x++) { + + /* get what will be the *next* carry bit from the + * MSB of the current digit + */ + rr = *tmpa >> ((mp_digit)(DIGIT_BIT - 1)); + + /* now shift up this digit, add in the carry [from the previous] */ + *tmpb++ = ((*tmpa++ << ((mp_digit)1)) | r) & MP_MASK; + + /* copy the carry that would be from the source + * digit into the next iteration + */ + r = rr; + } + + /* new leading digit? */ + if (r != 0) { + /* add a MSB which is always 1 at this point */ + *tmpb = 1; + ++(b->used); + } + + /* now zero any excess digits on the destination + * that we didn't write to + */ + tmpb = b->dp + b->used; + for (x = b->used; x < oldused; x++) { + *tmpb++ = 0; + } + } + b->sign = a->sign; + return MP_OKAY; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_mul_2.c */ + +/* Start: bn_mp_mul_2d.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_MUL_2D_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* shift left by a certain bit count */ +int mp_mul_2d (mp_int * a, int b, mp_int * c) +{ + mp_digit d; + int res; + + /* copy */ + if (a != c) { + if ((res = mp_copy (a, c)) != MP_OKAY) { + return res; + } + } + + if (c->alloc < (int)(c->used + (b / DIGIT_BIT) + 1)) { + if ((res = mp_grow (c, c->used + (b / DIGIT_BIT) + 1)) != MP_OKAY) { + return res; + } + } + + /* shift by as many digits in the bit count */ + if (b >= (int)DIGIT_BIT) { + if ((res = mp_lshd (c, b / DIGIT_BIT)) != MP_OKAY) { + return res; + } + } + + /* shift any bit count < DIGIT_BIT */ + d = (mp_digit) (b % DIGIT_BIT); + if (d != 0) { + mp_digit *tmpc, shift, mask, r, rr; + int x; + + /* bitmask for carries */ + mask = (((mp_digit)1) << d) - 1; + + /* shift for msbs */ + shift = DIGIT_BIT - d; + + /* alias */ + tmpc = c->dp; + + /* carry */ + r = 0; + for (x = 0; x < c->used; x++) { + /* get the higher bits of the current word */ + rr = (*tmpc >> shift) & mask; + + /* shift the current word and OR in the carry */ + *tmpc = ((*tmpc << d) | r) & MP_MASK; + ++tmpc; + + /* set the carry to the carry bits of the current word */ + r = rr; + } + + /* set final carry */ + if (r != 0) { + c->dp[(c->used)++] = r; + } + } + mp_clamp (c); + return MP_OKAY; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_mul_2d.c */ + +/* Start: bn_mp_mul_d.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_MUL_D_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* multiply by a digit */ +int +mp_mul_d (mp_int * a, mp_digit b, mp_int * c) +{ + mp_digit u, *tmpa, *tmpc; + mp_word r; + int ix, res, olduse; + + /* make sure c is big enough to hold a*b */ + if (c->alloc < (a->used + 1)) { + if ((res = mp_grow (c, a->used + 1)) != MP_OKAY) { + return res; + } + } + + /* get the original destinations used count */ + olduse = c->used; + + /* set the sign */ + c->sign = a->sign; + + /* alias for a->dp [source] */ + tmpa = a->dp; + + /* alias for c->dp [dest] */ + tmpc = c->dp; + + /* zero carry */ + u = 0; + + /* compute columns */ + for (ix = 0; ix < a->used; ix++) { + /* compute product and carry sum for this term */ + r = (mp_word)u + ((mp_word)*tmpa++ * (mp_word)b); + + /* mask off higher bits to get a single digit */ + *tmpc++ = (mp_digit) (r & ((mp_word) MP_MASK)); + + /* send carry into next iteration */ + u = (mp_digit) (r >> ((mp_word) DIGIT_BIT)); + } + + /* store final carry [if any] and increment ix offset */ + *tmpc++ = u; + ++ix; + + /* now zero digits above the top */ + while (ix++ < olduse) { + *tmpc++ = 0; + } + + /* set used count */ + c->used = a->used + 1; + mp_clamp(c); + + return MP_OKAY; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_mul_d.c */ + +/* Start: bn_mp_mulmod.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_MULMOD_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* d = a * b (mod c) */ +int mp_mulmod (mp_int * a, mp_int * b, mp_int * c, mp_int * d) +{ + int res; + mp_int t; + + if ((res = mp_init (&t)) != MP_OKAY) { + return res; + } + + if ((res = mp_mul (a, b, &t)) != MP_OKAY) { + mp_clear (&t); + return res; + } + res = mp_mod (&t, c, d); + mp_clear (&t); + return res; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_mulmod.c */ + +/* Start: bn_mp_n_root.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_N_ROOT_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* wrapper function for mp_n_root_ex() + * computes c = (a)**(1/b) such that (c)**b <= a and (c+1)**b > a + */ +int mp_n_root (mp_int * a, mp_digit b, mp_int * c) +{ + return mp_n_root_ex(a, b, c, 0); +} + +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_n_root.c */ + +/* Start: bn_mp_n_root_ex.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_N_ROOT_EX_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* find the n'th root of an integer + * + * Result found such that (c)**b <= a and (c+1)**b > a + * + * This algorithm uses Newton's approximation + * x[i+1] = x[i] - f(x[i])/f'(x[i]) + * which will find the root in log(N) time where + * each step involves a fair bit. This is not meant to + * find huge roots [square and cube, etc]. + */ +int mp_n_root_ex (mp_int * a, mp_digit b, mp_int * c, int fast) +{ + mp_int t1, t2, t3; + int res, neg; + + /* input must be positive if b is even */ + if (((b & 1) == 0) && (a->sign == MP_NEG)) { + return MP_VAL; + } + + if ((res = mp_init (&t1)) != MP_OKAY) { + return res; + } + + if ((res = mp_init (&t2)) != MP_OKAY) { + goto LBL_T1; + } + + if ((res = mp_init (&t3)) != MP_OKAY) { + goto LBL_T2; + } + + /* if a is negative fudge the sign but keep track */ + neg = a->sign; + a->sign = MP_ZPOS; + + /* t2 = 2 */ + mp_set (&t2, 2); + + do { + /* t1 = t2 */ + if ((res = mp_copy (&t2, &t1)) != MP_OKAY) { + goto LBL_T3; + } + + /* t2 = t1 - ((t1**b - a) / (b * t1**(b-1))) */ + + /* t3 = t1**(b-1) */ + if ((res = mp_expt_d_ex (&t1, b - 1, &t3, fast)) != MP_OKAY) { + goto LBL_T3; + } + + /* numerator */ + /* t2 = t1**b */ + if ((res = mp_mul (&t3, &t1, &t2)) != MP_OKAY) { + goto LBL_T3; + } + + /* t2 = t1**b - a */ + if ((res = mp_sub (&t2, a, &t2)) != MP_OKAY) { + goto LBL_T3; + } + + /* denominator */ + /* t3 = t1**(b-1) * b */ + if ((res = mp_mul_d (&t3, b, &t3)) != MP_OKAY) { + goto LBL_T3; + } + + /* t3 = (t1**b - a)/(b * t1**(b-1)) */ + if ((res = mp_div (&t2, &t3, &t3, NULL)) != MP_OKAY) { + goto LBL_T3; + } + + if ((res = mp_sub (&t1, &t3, &t2)) != MP_OKAY) { + goto LBL_T3; + } + } while (mp_cmp (&t1, &t2) != MP_EQ); + + /* result can be off by a few so check */ + for (;;) { + if ((res = mp_expt_d_ex (&t1, b, &t2, fast)) != MP_OKAY) { + goto LBL_T3; + } + + if (mp_cmp (&t2, a) == MP_GT) { + if ((res = mp_sub_d (&t1, 1, &t1)) != MP_OKAY) { + goto LBL_T3; + } + } else { + break; + } + } + + /* reset the sign of a first */ + a->sign = neg; + + /* set the result */ + mp_exch (&t1, c); + + /* set the sign of the result */ + c->sign = neg; + + res = MP_OKAY; + +LBL_T3:mp_clear (&t3); +LBL_T2:mp_clear (&t2); +LBL_T1:mp_clear (&t1); + return res; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_n_root_ex.c */ + +/* Start: bn_mp_neg.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_NEG_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* b = -a */ +int mp_neg (mp_int * a, mp_int * b) +{ + int res; + if (a != b) { + if ((res = mp_copy (a, b)) != MP_OKAY) { + return res; + } + } + + if (mp_iszero(b) != MP_YES) { + b->sign = (a->sign == MP_ZPOS) ? MP_NEG : MP_ZPOS; + } else { + b->sign = MP_ZPOS; + } + + return MP_OKAY; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_neg.c */ + +/* Start: bn_mp_or.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_OR_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* OR two ints together */ +int mp_or (mp_int * a, mp_int * b, mp_int * c) +{ + int res, ix, px; + mp_int t, *x; + + if (a->used > b->used) { + if ((res = mp_init_copy (&t, a)) != MP_OKAY) { + return res; + } + px = b->used; + x = b; + } else { + if ((res = mp_init_copy (&t, b)) != MP_OKAY) { + return res; + } + px = a->used; + x = a; + } + + for (ix = 0; ix < px; ix++) { + t.dp[ix] |= x->dp[ix]; + } + mp_clamp (&t); + mp_exch (c, &t); + mp_clear (&t); + return MP_OKAY; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_or.c */ + +/* Start: bn_mp_prime_fermat.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_PRIME_FERMAT_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* performs one Fermat test. + * + * If "a" were prime then b**a == b (mod a) since the order of + * the multiplicative sub-group would be phi(a) = a-1. That means + * it would be the same as b**(a mod (a-1)) == b**1 == b (mod a). + * + * Sets result to 1 if the congruence holds, or zero otherwise. + */ +int mp_prime_fermat (mp_int * a, mp_int * b, int *result) +{ + mp_int t; + int err; + + /* default to composite */ + *result = MP_NO; + + /* ensure b > 1 */ + if (mp_cmp_d(b, 1) != MP_GT) { + return MP_VAL; + } + + /* init t */ + if ((err = mp_init (&t)) != MP_OKAY) { + return err; + } + + /* compute t = b**a mod a */ + if ((err = mp_exptmod (b, a, a, &t)) != MP_OKAY) { + goto LBL_T; + } + + /* is it equal to b? */ + if (mp_cmp (&t, b) == MP_EQ) { + *result = MP_YES; + } + + err = MP_OKAY; +LBL_T:mp_clear (&t); + return err; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_prime_fermat.c */ + +/* Start: bn_mp_prime_is_divisible.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_PRIME_IS_DIVISIBLE_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* determines if an integers is divisible by one + * of the first PRIME_SIZE primes or not + * + * sets result to 0 if not, 1 if yes + */ +int mp_prime_is_divisible (mp_int * a, int *result) +{ + int err, ix; + mp_digit res; + + /* default to not */ + *result = MP_NO; + + for (ix = 0; ix < PRIME_SIZE; ix++) { + /* what is a mod LBL_prime_tab[ix] */ + if ((err = mp_mod_d (a, ltm_prime_tab[ix], &res)) != MP_OKAY) { + return err; + } + + /* is the residue zero? */ + if (res == 0) { + *result = MP_YES; + return MP_OKAY; + } + } + + return MP_OKAY; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_prime_is_divisible.c */ + +/* Start: bn_mp_prime_is_prime.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_PRIME_IS_PRIME_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* performs a variable number of rounds of Miller-Rabin + * + * Probability of error after t rounds is no more than + + * + * Sets result to 1 if probably prime, 0 otherwise + */ +int mp_prime_is_prime (mp_int * a, int t, int *result) +{ + mp_int b; + int ix, err, res; + + /* default to no */ + *result = MP_NO; + + /* valid value of t? */ + if ((t <= 0) || (t > PRIME_SIZE)) { + return MP_VAL; + } + + /* is the input equal to one of the primes in the table? */ + for (ix = 0; ix < PRIME_SIZE; ix++) { + if (mp_cmp_d(a, ltm_prime_tab[ix]) == MP_EQ) { + *result = 1; + return MP_OKAY; + } + } + + /* first perform trial division */ + if ((err = mp_prime_is_divisible (a, &res)) != MP_OKAY) { + return err; + } + + /* return if it was trivially divisible */ + if (res == MP_YES) { + return MP_OKAY; + } + + /* now perform the miller-rabin rounds */ + if ((err = mp_init (&b)) != MP_OKAY) { + return err; + } + + for (ix = 0; ix < t; ix++) { + /* set the prime */ + mp_set (&b, ltm_prime_tab[ix]); + + if ((err = mp_prime_miller_rabin (a, &b, &res)) != MP_OKAY) { + goto LBL_B; + } + + if (res == MP_NO) { + goto LBL_B; + } + } + + /* passed the test */ + *result = MP_YES; +LBL_B:mp_clear (&b); + return err; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_prime_is_prime.c */ + +/* Start: bn_mp_prime_miller_rabin.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_PRIME_MILLER_RABIN_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* Miller-Rabin test of "a" to the base of "b" as described in + * HAC pp. 139 Algorithm 4.24 + * + * Sets result to 0 if definitely composite or 1 if probably prime. + * Randomly the chance of error is no more than 1/4 and often + * very much lower. + */ +int mp_prime_miller_rabin (mp_int * a, mp_int * b, int *result) +{ + mp_int n1, y, r; + int s, j, err; + + /* default */ + *result = MP_NO; + + /* ensure b > 1 */ + if (mp_cmp_d(b, 1) != MP_GT) { + return MP_VAL; + } + + /* get n1 = a - 1 */ + if ((err = mp_init_copy (&n1, a)) != MP_OKAY) { + return err; + } + if ((err = mp_sub_d (&n1, 1, &n1)) != MP_OKAY) { + goto LBL_N1; + } + + /* set 2**s * r = n1 */ + if ((err = mp_init_copy (&r, &n1)) != MP_OKAY) { + goto LBL_N1; + } + + /* count the number of least significant bits + * which are zero + */ + s = mp_cnt_lsb(&r); + + /* now divide n - 1 by 2**s */ + if ((err = mp_div_2d (&r, s, &r, NULL)) != MP_OKAY) { + goto LBL_R; + } + + /* compute y = b**r mod a */ + if ((err = mp_init (&y)) != MP_OKAY) { + goto LBL_R; + } + if ((err = mp_exptmod (b, &r, a, &y)) != MP_OKAY) { + goto LBL_Y; + } + + /* if y != 1 and y != n1 do */ + if ((mp_cmp_d (&y, 1) != MP_EQ) && (mp_cmp (&y, &n1) != MP_EQ)) { + j = 1; + /* while j <= s-1 and y != n1 */ + while ((j <= (s - 1)) && (mp_cmp (&y, &n1) != MP_EQ)) { + if ((err = mp_sqrmod (&y, a, &y)) != MP_OKAY) { + goto LBL_Y; + } + + /* if y == 1 then composite */ + if (mp_cmp_d (&y, 1) == MP_EQ) { + goto LBL_Y; + } + + ++j; + } + + /* if y != n1 then composite */ + if (mp_cmp (&y, &n1) != MP_EQ) { + goto LBL_Y; + } + } + + /* probably prime now */ + *result = MP_YES; +LBL_Y:mp_clear (&y); +LBL_R:mp_clear (&r); +LBL_N1:mp_clear (&n1); + return err; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_prime_miller_rabin.c */ + +/* Start: bn_mp_prime_next_prime.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_PRIME_NEXT_PRIME_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* finds the next prime after the number "a" using "t" trials + * of Miller-Rabin. + * + * bbs_style = 1 means the prime must be congruent to 3 mod 4 + */ +int mp_prime_next_prime(mp_int *a, int t, int bbs_style) +{ + int err, res = MP_NO, x, y; + mp_digit res_tab[PRIME_SIZE], step, kstep; + mp_int b; + + /* ensure t is valid */ + if ((t <= 0) || (t > PRIME_SIZE)) { + return MP_VAL; + } + + /* force positive */ + a->sign = MP_ZPOS; + + /* simple algo if a is less than the largest prime in the table */ + if (mp_cmp_d(a, ltm_prime_tab[PRIME_SIZE-1]) == MP_LT) { + /* find which prime it is bigger than */ + for (x = PRIME_SIZE - 2; x >= 0; x--) { + if (mp_cmp_d(a, ltm_prime_tab[x]) != MP_LT) { + if (bbs_style == 1) { + /* ok we found a prime smaller or + * equal [so the next is larger] + * + * however, the prime must be + * congruent to 3 mod 4 + */ + if ((ltm_prime_tab[x + 1] & 3) != 3) { + /* scan upwards for a prime congruent to 3 mod 4 */ + for (y = x + 1; y < PRIME_SIZE; y++) { + if ((ltm_prime_tab[y] & 3) == 3) { + mp_set(a, ltm_prime_tab[y]); + return MP_OKAY; + } + } + } + } else { + mp_set(a, ltm_prime_tab[x + 1]); + return MP_OKAY; + } + } + } + /* at this point a maybe 1 */ + if (mp_cmp_d(a, 1) == MP_EQ) { + mp_set(a, 2); + return MP_OKAY; + } + /* fall through to the sieve */ + } + + /* generate a prime congruent to 3 mod 4 or 1/3 mod 4? */ + if (bbs_style == 1) { + kstep = 4; + } else { + kstep = 2; + } + + /* at this point we will use a combination of a sieve and Miller-Rabin */ + + if (bbs_style == 1) { + /* if a mod 4 != 3 subtract the correct value to make it so */ + if ((a->dp[0] & 3) != 3) { + if ((err = mp_sub_d(a, (a->dp[0] & 3) + 1, a)) != MP_OKAY) { return err; }; + } + } else { + if (mp_iseven(a) == MP_YES) { + /* force odd */ + if ((err = mp_sub_d(a, 1, a)) != MP_OKAY) { + return err; + } + } + } + + /* generate the restable */ + for (x = 1; x < PRIME_SIZE; x++) { + if ((err = mp_mod_d(a, ltm_prime_tab[x], res_tab + x)) != MP_OKAY) { + return err; + } + } + + /* init temp used for Miller-Rabin Testing */ + if ((err = mp_init(&b)) != MP_OKAY) { + return err; + } + + for (;;) { + /* skip to the next non-trivially divisible candidate */ + step = 0; + do { + /* y == 1 if any residue was zero [e.g. cannot be prime] */ + y = 0; + + /* increase step to next candidate */ + step += kstep; + + /* compute the new residue without using division */ + for (x = 1; x < PRIME_SIZE; x++) { + /* add the step to each residue */ + res_tab[x] += kstep; + + /* subtract the modulus [instead of using division] */ + if (res_tab[x] >= ltm_prime_tab[x]) { + res_tab[x] -= ltm_prime_tab[x]; + } + + /* set flag if zero */ + if (res_tab[x] == 0) { + y = 1; + } + } + } while ((y == 1) && (step < ((((mp_digit)1) << DIGIT_BIT) - kstep))); + + /* add the step */ + if ((err = mp_add_d(a, step, a)) != MP_OKAY) { + goto LBL_ERR; + } + + /* if didn't pass sieve and step == MAX then skip test */ + if ((y == 1) && (step >= ((((mp_digit)1) << DIGIT_BIT) - kstep))) { + continue; + } + + /* is this prime? */ + for (x = 0; x < t; x++) { + mp_set(&b, ltm_prime_tab[x]); + if ((err = mp_prime_miller_rabin(a, &b, &res)) != MP_OKAY) { + goto LBL_ERR; + } + if (res == MP_NO) { + break; + } + } + + if (res == MP_YES) { + break; + } + } + + err = MP_OKAY; +LBL_ERR: + mp_clear(&b); + return err; +} + +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_prime_next_prime.c */ + +/* Start: bn_mp_prime_rabin_miller_trials.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_PRIME_RABIN_MILLER_TRIALS_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + + +static const struct { + int k, t; +} sizes[] = { +{ 128, 28 }, +{ 256, 16 }, +{ 384, 10 }, +{ 512, 7 }, +{ 640, 6 }, +{ 768, 5 }, +{ 896, 4 }, +{ 1024, 4 } +}; + +/* returns # of RM trials required for a given bit size */ +int mp_prime_rabin_miller_trials(int size) +{ + int x; + + for (x = 0; x < (int)(sizeof(sizes)/(sizeof(sizes[0]))); x++) { + if (sizes[x].k == size) { + return sizes[x].t; + } else if (sizes[x].k > size) { + return (x == 0) ? sizes[0].t : sizes[x - 1].t; + } + } + return sizes[x-1].t + 1; +} + + +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_prime_rabin_miller_trials.c */ + +/* Start: bn_mp_prime_random_ex.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_PRIME_RANDOM_EX_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* makes a truly random prime of a given size (bits), + * + * Flags are as follows: + * + * LTM_PRIME_BBS - make prime congruent to 3 mod 4 + * LTM_PRIME_SAFE - make sure (p-1)/2 is prime as well (implies LTM_PRIME_BBS) + * LTM_PRIME_2MSB_ON - make the 2nd highest bit one + * + * You have to supply a callback which fills in a buffer with random bytes. "dat" is a parameter you can + * have passed to the callback (e.g. a state or something). This function doesn't use "dat" itself + * so it can be NULL + * + */ + +/* This is possibly the mother of all prime generation functions, muahahahahaha! */ +int mp_prime_random_ex(mp_int *a, int t, int size, int flags, ltm_prime_callback cb, void *dat) +{ + unsigned char *tmp, maskAND, maskOR_msb, maskOR_lsb; + int res, err, bsize, maskOR_msb_offset; + + /* sanity check the input */ + if ((size <= 1) || (t <= 0)) { + return MP_VAL; + } + + /* LTM_PRIME_SAFE implies LTM_PRIME_BBS */ + if ((flags & LTM_PRIME_SAFE) != 0) { + flags |= LTM_PRIME_BBS; + } + + /* calc the byte size */ + bsize = (size>>3) + ((size&7)?1:0); + + /* we need a buffer of bsize bytes */ + tmp = OPT_CAST(unsigned char) XMALLOC(bsize); + if (tmp == NULL) { + return MP_MEM; + } + + /* calc the maskAND value for the MSbyte*/ + maskAND = ((size&7) == 0) ? 0xFF : (0xFF >> (8 - (size & 7))); + + /* calc the maskOR_msb */ + maskOR_msb = 0; + maskOR_msb_offset = ((size & 7) == 1) ? 1 : 0; + if ((flags & LTM_PRIME_2MSB_ON) != 0) { + maskOR_msb |= 0x80 >> ((9 - size) & 7); + } + + /* get the maskOR_lsb */ + maskOR_lsb = 1; + if ((flags & LTM_PRIME_BBS) != 0) { + maskOR_lsb |= 3; + } + + do { + /* read the bytes */ + if (cb(tmp, bsize, dat) != bsize) { + err = MP_VAL; + goto error; + } + + /* work over the MSbyte */ + tmp[0] &= maskAND; + tmp[0] |= 1 << ((size - 1) & 7); + + /* mix in the maskORs */ + tmp[maskOR_msb_offset] |= maskOR_msb; + tmp[bsize-1] |= maskOR_lsb; + + /* read it in */ + if ((err = mp_read_unsigned_bin(a, tmp, bsize)) != MP_OKAY) { goto error; } + + /* is it prime? */ + if ((err = mp_prime_is_prime(a, t, &res)) != MP_OKAY) { goto error; } + if (res == MP_NO) { + continue; + } + + if ((flags & LTM_PRIME_SAFE) != 0) { + /* see if (a-1)/2 is prime */ + if ((err = mp_sub_d(a, 1, a)) != MP_OKAY) { goto error; } + if ((err = mp_div_2(a, a)) != MP_OKAY) { goto error; } + + /* is it prime? */ + if ((err = mp_prime_is_prime(a, t, &res)) != MP_OKAY) { goto error; } + } + } while (res == MP_NO); + + if ((flags & LTM_PRIME_SAFE) != 0) { + /* restore a to the original value */ + if ((err = mp_mul_2(a, a)) != MP_OKAY) { goto error; } + if ((err = mp_add_d(a, 1, a)) != MP_OKAY) { goto error; } + } + + err = MP_OKAY; +error: + XFREE(tmp); + return err; +} + + +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_prime_random_ex.c */ + +/* Start: bn_mp_radix_size.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_RADIX_SIZE_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* returns size of ASCII reprensentation */ +int mp_radix_size (mp_int * a, int radix, int *size) +{ + int res, digs; + mp_int t; + mp_digit d; + + *size = 0; + + /* make sure the radix is in range */ + if ((radix < 2) || (radix > 64)) { + return MP_VAL; + } + + if (mp_iszero(a) == MP_YES) { + *size = 2; + return MP_OKAY; + } + + /* special case for binary */ + if (radix == 2) { + *size = mp_count_bits (a) + ((a->sign == MP_NEG) ? 1 : 0) + 1; + return MP_OKAY; + } + + /* digs is the digit count */ + digs = 0; + + /* if it's negative add one for the sign */ + if (a->sign == MP_NEG) { + ++digs; + } + + /* init a copy of the input */ + if ((res = mp_init_copy (&t, a)) != MP_OKAY) { + return res; + } + + /* force temp to positive */ + t.sign = MP_ZPOS; + + /* fetch out all of the digits */ + while (mp_iszero (&t) == MP_NO) { + if ((res = mp_div_d (&t, (mp_digit) radix, &t, &d)) != MP_OKAY) { + mp_clear (&t); + return res; + } + ++digs; + } + mp_clear (&t); + + /* return digs + 1, the 1 is for the NULL byte that would be required. */ + *size = digs + 1; + return MP_OKAY; +} + +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_radix_size.c */ + +/* Start: bn_mp_radix_smap.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_RADIX_SMAP_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* chars used in radix conversions */ +const char *mp_s_rmap = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/"; +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_radix_smap.c */ + +/* Start: bn_mp_rand.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_RAND_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* makes a pseudo-random int of a given size */ +int +mp_rand (mp_int * a, int digits) +{ + int res; + mp_digit d; + + mp_zero (a); + if (digits <= 0) { + return MP_OKAY; + } + + /* first place a random non-zero digit */ + do { + d = ((mp_digit) abs (MP_GEN_RANDOM())) & MP_MASK; + } while (d == 0); + + if ((res = mp_add_d (a, d, a)) != MP_OKAY) { + return res; + } + + while (--digits > 0) { + if ((res = mp_lshd (a, 1)) != MP_OKAY) { + return res; + } + + if ((res = mp_add_d (a, ((mp_digit) abs (MP_GEN_RANDOM())), a)) != MP_OKAY) { + return res; + } + } + + return MP_OKAY; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_rand.c */ + +/* Start: bn_mp_read_radix.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_READ_RADIX_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* read a string [ASCII] in a given radix */ +int mp_read_radix (mp_int * a, const char *str, int radix) +{ + int y, res, neg; + char ch; + + /* zero the digit bignum */ + mp_zero(a); + + /* make sure the radix is ok */ + if ((radix < 2) || (radix > 64)) { + return MP_VAL; + } + + /* if the leading digit is a + * minus set the sign to negative. + */ + if (*str == '-') { + ++str; + neg = MP_NEG; + } else { + neg = MP_ZPOS; + } + + /* set the integer to the default of zero */ + mp_zero (a); + + /* process each digit of the string */ + while (*str != '\0') { + /* if the radix <= 36 the conversion is case insensitive + * this allows numbers like 1AB and 1ab to represent the same value + * [e.g. in hex] + */ + ch = (radix <= 36) ? (char)toupper((int)*str) : *str; + for (y = 0; y < 64; y++) { + if (ch == mp_s_rmap[y]) { + break; + } + } + + /* if the char was found in the map + * and is less than the given radix add it + * to the number, otherwise exit the loop. + */ + if (y < radix) { + if ((res = mp_mul_d (a, (mp_digit) radix, a)) != MP_OKAY) { + return res; + } + if ((res = mp_add_d (a, (mp_digit) y, a)) != MP_OKAY) { + return res; + } + } else { + break; + } + ++str; + } + + /* set the sign only if a != 0 */ + if (mp_iszero(a) != MP_YES) { + a->sign = neg; + } + return MP_OKAY; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_read_radix.c */ + +/* Start: bn_mp_read_signed_bin.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_READ_SIGNED_BIN_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* read signed bin, big endian, first byte is 0==positive or 1==negative */ +int mp_read_signed_bin (mp_int * a, const unsigned char *b, int c) +{ + int res; + + /* read magnitude */ + if ((res = mp_read_unsigned_bin (a, b + 1, c - 1)) != MP_OKAY) { + return res; + } + + /* first byte is 0 for positive, non-zero for negative */ + if (b[0] == 0) { + a->sign = MP_ZPOS; + } else { + a->sign = MP_NEG; + } + + return MP_OKAY; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_read_signed_bin.c */ + +/* Start: bn_mp_read_unsigned_bin.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_READ_UNSIGNED_BIN_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* reads a unsigned char array, assumes the msb is stored first [big endian] */ +int mp_read_unsigned_bin (mp_int * a, const unsigned char *b, int c) +{ + int res; + + /* make sure there are at least two digits */ + if (a->alloc < 2) { + if ((res = mp_grow(a, 2)) != MP_OKAY) { + return res; + } + } + + /* zero the int */ + mp_zero (a); + + /* read the bytes in */ + while (c-- > 0) { + if ((res = mp_mul_2d (a, 8, a)) != MP_OKAY) { + return res; + } + +#ifndef MP_8BIT + a->dp[0] |= *b++; + a->used += 1; +#else + a->dp[0] = (*b & MP_MASK); + a->dp[1] |= ((*b++ >> 7U) & 1); + a->used += 2; +#endif + } + mp_clamp (a); + return MP_OKAY; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_read_unsigned_bin.c */ + +/* Start: bn_mp_reduce.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_REDUCE_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* reduces x mod m, assumes 0 < x < m**2, mu is + * precomputed via mp_reduce_setup. + * From HAC pp.604 Algorithm 14.42 + */ +int mp_reduce (mp_int * x, mp_int * m, mp_int * mu) +{ + mp_int q; + int res, um = m->used; + + /* q = x */ + if ((res = mp_init_copy (&q, x)) != MP_OKAY) { + return res; + } + + /* q1 = x / b**(k-1) */ + mp_rshd (&q, um - 1); + + /* according to HAC this optimization is ok */ + if (((mp_digit) um) > (((mp_digit)1) << (DIGIT_BIT - 1))) { + if ((res = mp_mul (&q, mu, &q)) != MP_OKAY) { + goto CLEANUP; + } + } else { +#ifdef BN_S_MP_MUL_HIGH_DIGS_C + if ((res = s_mp_mul_high_digs (&q, mu, &q, um)) != MP_OKAY) { + goto CLEANUP; + } +#elif defined(BN_FAST_S_MP_MUL_HIGH_DIGS_C) + if ((res = fast_s_mp_mul_high_digs (&q, mu, &q, um)) != MP_OKAY) { + goto CLEANUP; + } +#else + { + res = MP_VAL; + goto CLEANUP; + } +#endif + } + + /* q3 = q2 / b**(k+1) */ + mp_rshd (&q, um + 1); + + /* x = x mod b**(k+1), quick (no division) */ + if ((res = mp_mod_2d (x, DIGIT_BIT * (um + 1), x)) != MP_OKAY) { + goto CLEANUP; + } + + /* q = q * m mod b**(k+1), quick (no division) */ + if ((res = s_mp_mul_digs (&q, m, &q, um + 1)) != MP_OKAY) { + goto CLEANUP; + } + + /* x = x - q */ + if ((res = mp_sub (x, &q, x)) != MP_OKAY) { + goto CLEANUP; + } + + /* If x < 0, add b**(k+1) to it */ + if (mp_cmp_d (x, 0) == MP_LT) { + mp_set (&q, 1); + if ((res = mp_lshd (&q, um + 1)) != MP_OKAY) + goto CLEANUP; + if ((res = mp_add (x, &q, x)) != MP_OKAY) + goto CLEANUP; + } + + /* Back off if it's too big */ + while (mp_cmp (x, m) != MP_LT) { + if ((res = s_mp_sub (x, m, x)) != MP_OKAY) { + goto CLEANUP; + } + } + +CLEANUP: + mp_clear (&q); + + return res; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_reduce.c */ + +/* Start: bn_mp_reduce_2k.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_REDUCE_2K_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* reduces a modulo n where n is of the form 2**p - d */ +int mp_reduce_2k(mp_int *a, mp_int *n, mp_digit d) +{ + mp_int q; + int p, res; + + if ((res = mp_init(&q)) != MP_OKAY) { + return res; + } + + p = mp_count_bits(n); +top: + /* q = a/2**p, a = a mod 2**p */ + if ((res = mp_div_2d(a, p, &q, a)) != MP_OKAY) { + goto ERR; + } + + if (d != 1) { + /* q = q * d */ + if ((res = mp_mul_d(&q, d, &q)) != MP_OKAY) { + goto ERR; + } + } + + /* a = a + q */ + if ((res = s_mp_add(a, &q, a)) != MP_OKAY) { + goto ERR; + } + + if (mp_cmp_mag(a, n) != MP_LT) { + if ((res = s_mp_sub(a, n, a)) != MP_OKAY) { + goto ERR; + } + goto top; + } + +ERR: + mp_clear(&q); + return res; +} + +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_reduce_2k.c */ + +/* Start: bn_mp_reduce_2k_l.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_REDUCE_2K_L_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* reduces a modulo n where n is of the form 2**p - d + This differs from reduce_2k since "d" can be larger + than a single digit. +*/ +int mp_reduce_2k_l(mp_int *a, mp_int *n, mp_int *d) +{ + mp_int q; + int p, res; + + if ((res = mp_init(&q)) != MP_OKAY) { + return res; + } + + p = mp_count_bits(n); +top: + /* q = a/2**p, a = a mod 2**p */ + if ((res = mp_div_2d(a, p, &q, a)) != MP_OKAY) { + goto ERR; + } + + /* q = q * d */ + if ((res = mp_mul(&q, d, &q)) != MP_OKAY) { + goto ERR; + } + + /* a = a + q */ + if ((res = s_mp_add(a, &q, a)) != MP_OKAY) { + goto ERR; + } + + if (mp_cmp_mag(a, n) != MP_LT) { + if ((res = s_mp_sub(a, n, a)) != MP_OKAY) { + goto ERR; + } + goto top; + } + +ERR: + mp_clear(&q); + return res; +} + +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_reduce_2k_l.c */ + +/* Start: bn_mp_reduce_2k_setup.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_REDUCE_2K_SETUP_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* determines the setup value */ +int mp_reduce_2k_setup(mp_int *a, mp_digit *d) +{ + int res, p; + mp_int tmp; + + if ((res = mp_init(&tmp)) != MP_OKAY) { + return res; + } + + p = mp_count_bits(a); + if ((res = mp_2expt(&tmp, p)) != MP_OKAY) { + mp_clear(&tmp); + return res; + } + + if ((res = s_mp_sub(&tmp, a, &tmp)) != MP_OKAY) { + mp_clear(&tmp); + return res; + } + + *d = tmp.dp[0]; + mp_clear(&tmp); + return MP_OKAY; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_reduce_2k_setup.c */ + +/* Start: bn_mp_reduce_2k_setup_l.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_REDUCE_2K_SETUP_L_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* determines the setup value */ +int mp_reduce_2k_setup_l(mp_int *a, mp_int *d) +{ + int res; + mp_int tmp; + + if ((res = mp_init(&tmp)) != MP_OKAY) { + return res; + } + + if ((res = mp_2expt(&tmp, mp_count_bits(a))) != MP_OKAY) { + goto ERR; + } + + if ((res = s_mp_sub(&tmp, a, d)) != MP_OKAY) { + goto ERR; + } + +ERR: + mp_clear(&tmp); + return res; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_reduce_2k_setup_l.c */ + +/* Start: bn_mp_reduce_is_2k.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_REDUCE_IS_2K_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* determines if mp_reduce_2k can be used */ +int mp_reduce_is_2k(mp_int *a) +{ + int ix, iy, iw; + mp_digit iz; + + if (a->used == 0) { + return MP_NO; + } else if (a->used == 1) { + return MP_YES; + } else if (a->used > 1) { + iy = mp_count_bits(a); + iz = 1; + iw = 1; + + /* Test every bit from the second digit up, must be 1 */ + for (ix = DIGIT_BIT; ix < iy; ix++) { + if ((a->dp[iw] & iz) == 0) { + return MP_NO; + } + iz <<= 1; + if (iz > (mp_digit)MP_MASK) { + ++iw; + iz = 1; + } + } + } + return MP_YES; +} + +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_reduce_is_2k.c */ + +/* Start: bn_mp_reduce_is_2k_l.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_REDUCE_IS_2K_L_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* determines if reduce_2k_l can be used */ +int mp_reduce_is_2k_l(mp_int *a) +{ + int ix, iy; + + if (a->used == 0) { + return MP_NO; + } else if (a->used == 1) { + return MP_YES; + } else if (a->used > 1) { + /* if more than half of the digits are -1 we're sold */ + for (iy = ix = 0; ix < a->used; ix++) { + if (a->dp[ix] == MP_MASK) { + ++iy; + } + } + return (iy >= (a->used/2)) ? MP_YES : MP_NO; + + } + return MP_NO; +} + +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_reduce_is_2k_l.c */ + +/* Start: bn_mp_reduce_setup.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_REDUCE_SETUP_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* pre-calculate the value required for Barrett reduction + * For a given modulus "b" it calulates the value required in "a" + */ +int mp_reduce_setup (mp_int * a, mp_int * b) +{ + int res; + + if ((res = mp_2expt (a, b->used * 2 * DIGIT_BIT)) != MP_OKAY) { + return res; + } + return mp_div (a, b, a, NULL); +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_reduce_setup.c */ + +/* Start: bn_mp_rshd.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_RSHD_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* shift right a certain amount of digits */ +void mp_rshd (mp_int * a, int b) +{ + int x; + + /* if b <= 0 then ignore it */ + if (b <= 0) { + return; + } + + /* if b > used then simply zero it and return */ + if (a->used <= b) { + mp_zero (a); + return; + } + + { + mp_digit *bottom, *top; + + /* shift the digits down */ + + /* bottom */ + bottom = a->dp; + + /* top [offset into digits] */ + top = a->dp + b; + + /* this is implemented as a sliding window where + * the window is b-digits long and digits from + * the top of the window are copied to the bottom + * + * e.g. + + b-2 | b-1 | b0 | b1 | b2 | ... | bb | ----> + /\ | ----> + \-------------------/ ----> + */ + for (x = 0; x < (a->used - b); x++) { + *bottom++ = *top++; + } + + /* zero the top digits */ + for (; x < a->used; x++) { + *bottom++ = 0; + } + } + + /* remove excess digits */ + a->used -= b; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_rshd.c */ + +/* Start: bn_mp_set.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_SET_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* set to a digit */ +void mp_set (mp_int * a, mp_digit b) +{ + mp_zero (a); + a->dp[0] = b & MP_MASK; + a->used = (a->dp[0] != 0) ? 1 : 0; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_set.c */ + +/* Start: bn_mp_set_int.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_SET_INT_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* set a 32-bit const */ +int mp_set_int (mp_int * a, unsigned long b) +{ + int x, res; + + mp_zero (a); + + /* set four bits at a time */ + for (x = 0; x < 8; x++) { + /* shift the number up four bits */ + if ((res = mp_mul_2d (a, 4, a)) != MP_OKAY) { + return res; + } + + /* OR in the top four bits of the source */ + a->dp[0] |= (b >> 28) & 15; + + /* shift the source up to the next four bits */ + b <<= 4; + + /* ensure that digits are not clamped off */ + a->used += 1; + } + mp_clamp (a); + return MP_OKAY; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_set_int.c */ + +/* Start: bn_mp_set_long.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_SET_LONG_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* set a platform dependent unsigned long int */ +MP_SET_XLONG(mp_set_long, unsigned long) +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_set_long.c */ + +/* Start: bn_mp_set_long_long.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_SET_LONG_LONG_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* set a platform dependent unsigned long long int */ +MP_SET_XLONG(mp_set_long_long, unsigned long long) +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_set_long_long.c */ + +/* Start: bn_mp_shrink.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_SHRINK_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* shrink a bignum */ +int mp_shrink (mp_int * a) +{ + mp_digit *tmp; + int used = 1; + + if(a->used > 0) { + used = a->used; + } + + if (a->alloc != used) { + if ((tmp = OPT_CAST(mp_digit) XREALLOC (a->dp, sizeof (mp_digit) * used)) == NULL) { + return MP_MEM; + } + a->dp = tmp; + a->alloc = used; + } + return MP_OKAY; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_shrink.c */ + +/* Start: bn_mp_signed_bin_size.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_SIGNED_BIN_SIZE_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* get the size for an signed equivalent */ +int mp_signed_bin_size (mp_int * a) +{ + return 1 + mp_unsigned_bin_size (a); +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_signed_bin_size.c */ + +/* Start: bn_mp_sqr.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_SQR_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* computes b = a*a */ +int +mp_sqr (mp_int * a, mp_int * b) +{ + int res; + +#ifdef BN_MP_TOOM_SQR_C + /* use Toom-Cook? */ + if (a->used >= TOOM_SQR_CUTOFF) { + res = mp_toom_sqr(a, b); + /* Karatsuba? */ + } else +#endif +#ifdef BN_MP_KARATSUBA_SQR_C + if (a->used >= KARATSUBA_SQR_CUTOFF) { + res = mp_karatsuba_sqr (a, b); + } else +#endif + { +#ifdef BN_FAST_S_MP_SQR_C + /* can we use the fast comba multiplier? */ + if ((((a->used * 2) + 1) < MP_WARRAY) && + (a->used < + (1 << (((sizeof(mp_word) * CHAR_BIT) - (2 * DIGIT_BIT)) - 1)))) { + res = fast_s_mp_sqr (a, b); + } else +#endif + { +#ifdef BN_S_MP_SQR_C + res = s_mp_sqr (a, b); +#else + res = MP_VAL; +#endif + } + } + b->sign = MP_ZPOS; + return res; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_sqr.c */ + +/* Start: bn_mp_sqrmod.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_SQRMOD_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* c = a * a (mod b) */ +int +mp_sqrmod (mp_int * a, mp_int * b, mp_int * c) +{ + int res; + mp_int t; + + if ((res = mp_init (&t)) != MP_OKAY) { + return res; + } + + if ((res = mp_sqr (a, &t)) != MP_OKAY) { + mp_clear (&t); + return res; + } + res = mp_mod (&t, b, c); + mp_clear (&t); + return res; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_sqrmod.c */ + +/* Start: bn_mp_sqrt.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_SQRT_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* this function is less generic than mp_n_root, simpler and faster */ +int mp_sqrt(mp_int *arg, mp_int *ret) +{ + int res; + mp_int t1,t2; + + /* must be positive */ + if (arg->sign == MP_NEG) { + return MP_VAL; + } + + /* easy out */ + if (mp_iszero(arg) == MP_YES) { + mp_zero(ret); + return MP_OKAY; + } + + if ((res = mp_init_copy(&t1, arg)) != MP_OKAY) { + return res; + } + + if ((res = mp_init(&t2)) != MP_OKAY) { + goto E2; + } + + /* First approx. (not very bad for large arg) */ + mp_rshd (&t1,t1.used/2); + + /* t1 > 0 */ + if ((res = mp_div(arg,&t1,&t2,NULL)) != MP_OKAY) { + goto E1; + } + if ((res = mp_add(&t1,&t2,&t1)) != MP_OKAY) { + goto E1; + } + if ((res = mp_div_2(&t1,&t1)) != MP_OKAY) { + goto E1; + } + /* And now t1 > sqrt(arg) */ + do { + if ((res = mp_div(arg,&t1,&t2,NULL)) != MP_OKAY) { + goto E1; + } + if ((res = mp_add(&t1,&t2,&t1)) != MP_OKAY) { + goto E1; + } + if ((res = mp_div_2(&t1,&t1)) != MP_OKAY) { + goto E1; + } + /* t1 >= sqrt(arg) >= t2 at this point */ + } while (mp_cmp_mag(&t1,&t2) == MP_GT); + + mp_exch(&t1,ret); + +E1: mp_clear(&t2); +E2: mp_clear(&t1); + return res; +} + +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_sqrt.c */ + +/* Start: bn_mp_sqrtmod_prime.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_SQRTMOD_PRIME_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library is free for all purposes without any express + * guarantee it works. + */ + +/* Tonelli-Shanks algorithm + * https://en.wikipedia.org/wiki/Tonelli%E2%80%93Shanks_algorithm + * https://gmplib.org/list-archives/gmp-discuss/2013-April/005300.html + * + */ + +int mp_sqrtmod_prime(mp_int *n, mp_int *prime, mp_int *ret) +{ + int res, legendre; + mp_int t1, C, Q, S, Z, M, T, R, two; + mp_digit i; + + /* first handle the simple cases */ + if (mp_cmp_d(n, 0) == MP_EQ) { + mp_zero(ret); + return MP_OKAY; + } + if (mp_cmp_d(prime, 2) == MP_EQ) return MP_VAL; /* prime must be odd */ + if ((res = mp_jacobi(n, prime, &legendre)) != MP_OKAY) return res; + if (legendre == -1) return MP_VAL; /* quadratic non-residue mod prime */ + + if ((res = mp_init_multi(&t1, &C, &Q, &S, &Z, &M, &T, &R, &two, NULL)) != MP_OKAY) { + return res; + } + + /* SPECIAL CASE: if prime mod 4 == 3 + * compute directly: res = n^(prime+1)/4 mod prime + * Handbook of Applied Cryptography algorithm 3.36 + */ + if ((res = mp_mod_d(prime, 4, &i)) != MP_OKAY) goto cleanup; + if (i == 3) { + if ((res = mp_add_d(prime, 1, &t1)) != MP_OKAY) goto cleanup; + if ((res = mp_div_2(&t1, &t1)) != MP_OKAY) goto cleanup; + if ((res = mp_div_2(&t1, &t1)) != MP_OKAY) goto cleanup; + if ((res = mp_exptmod(n, &t1, prime, ret)) != MP_OKAY) goto cleanup; + res = MP_OKAY; + goto cleanup; + } + + /* NOW: Tonelli-Shanks algorithm */ + + /* factor out powers of 2 from prime-1, defining Q and S as: prime-1 = Q*2^S */ + if ((res = mp_copy(prime, &Q)) != MP_OKAY) goto cleanup; + if ((res = mp_sub_d(&Q, 1, &Q)) != MP_OKAY) goto cleanup; + /* Q = prime - 1 */ + mp_zero(&S); + /* S = 0 */ + while (mp_iseven(&Q) != MP_NO) { + if ((res = mp_div_2(&Q, &Q)) != MP_OKAY) goto cleanup; + /* Q = Q / 2 */ + if ((res = mp_add_d(&S, 1, &S)) != MP_OKAY) goto cleanup; + /* S = S + 1 */ + } + + /* find a Z such that the Legendre symbol (Z|prime) == -1 */ + if ((res = mp_set_int(&Z, 2)) != MP_OKAY) goto cleanup; + /* Z = 2 */ + while(1) { + if ((res = mp_jacobi(&Z, prime, &legendre)) != MP_OKAY) goto cleanup; + if (legendre == -1) break; + if ((res = mp_add_d(&Z, 1, &Z)) != MP_OKAY) goto cleanup; + /* Z = Z + 1 */ + } + + if ((res = mp_exptmod(&Z, &Q, prime, &C)) != MP_OKAY) goto cleanup; + /* C = Z ^ Q mod prime */ + if ((res = mp_add_d(&Q, 1, &t1)) != MP_OKAY) goto cleanup; + if ((res = mp_div_2(&t1, &t1)) != MP_OKAY) goto cleanup; + /* t1 = (Q + 1) / 2 */ + if ((res = mp_exptmod(n, &t1, prime, &R)) != MP_OKAY) goto cleanup; + /* R = n ^ ((Q + 1) / 2) mod prime */ + if ((res = mp_exptmod(n, &Q, prime, &T)) != MP_OKAY) goto cleanup; + /* T = n ^ Q mod prime */ + if ((res = mp_copy(&S, &M)) != MP_OKAY) goto cleanup; + /* M = S */ + if ((res = mp_set_int(&two, 2)) != MP_OKAY) goto cleanup; + + res = MP_VAL; + while (1) { + if ((res = mp_copy(&T, &t1)) != MP_OKAY) goto cleanup; + i = 0; + while (1) { + if (mp_cmp_d(&t1, 1) == MP_EQ) break; + if ((res = mp_exptmod(&t1, &two, prime, &t1)) != MP_OKAY) goto cleanup; + i++; + } + if (i == 0) { + if ((res = mp_copy(&R, ret)) != MP_OKAY) goto cleanup; + res = MP_OKAY; + goto cleanup; + } + if ((res = mp_sub_d(&M, i, &t1)) != MP_OKAY) goto cleanup; + if ((res = mp_sub_d(&t1, 1, &t1)) != MP_OKAY) goto cleanup; + if ((res = mp_exptmod(&two, &t1, prime, &t1)) != MP_OKAY) goto cleanup; + /* t1 = 2 ^ (M - i - 1) */ + if ((res = mp_exptmod(&C, &t1, prime, &t1)) != MP_OKAY) goto cleanup; + /* t1 = C ^ (2 ^ (M - i - 1)) mod prime */ + if ((res = mp_sqrmod(&t1, prime, &C)) != MP_OKAY) goto cleanup; + /* C = (t1 * t1) mod prime */ + if ((res = mp_mulmod(&R, &t1, prime, &R)) != MP_OKAY) goto cleanup; + /* R = (R * t1) mod prime */ + if ((res = mp_mulmod(&T, &C, prime, &T)) != MP_OKAY) goto cleanup; + /* T = (T * C) mod prime */ + mp_set(&M, i); + /* M = i */ + } + +cleanup: + mp_clear_multi(&t1, &C, &Q, &S, &Z, &M, &T, &R, &two, NULL); + return res; +} + +#endif + +/* End: bn_mp_sqrtmod_prime.c */ + +/* Start: bn_mp_sub.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_SUB_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* high level subtraction (handles signs) */ +int +mp_sub (mp_int * a, mp_int * b, mp_int * c) +{ + int sa, sb, res; + + sa = a->sign; + sb = b->sign; + + if (sa != sb) { + /* subtract a negative from a positive, OR */ + /* subtract a positive from a negative. */ + /* In either case, ADD their magnitudes, */ + /* and use the sign of the first number. */ + c->sign = sa; + res = s_mp_add (a, b, c); + } else { + /* subtract a positive from a positive, OR */ + /* subtract a negative from a negative. */ + /* First, take the difference between their */ + /* magnitudes, then... */ + if (mp_cmp_mag (a, b) != MP_LT) { + /* Copy the sign from the first */ + c->sign = sa; + /* The first has a larger or equal magnitude */ + res = s_mp_sub (a, b, c); + } else { + /* The result has the *opposite* sign from */ + /* the first number. */ + c->sign = (sa == MP_ZPOS) ? MP_NEG : MP_ZPOS; + /* The second has a larger magnitude */ + res = s_mp_sub (b, a, c); + } + } + return res; +} + +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_sub.c */ + +/* Start: bn_mp_sub_d.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_SUB_D_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* single digit subtraction */ +int +mp_sub_d (mp_int * a, mp_digit b, mp_int * c) +{ + mp_digit *tmpa, *tmpc, mu; + int res, ix, oldused; + + /* grow c as required */ + if (c->alloc < (a->used + 1)) { + if ((res = mp_grow(c, a->used + 1)) != MP_OKAY) { + return res; + } + } + + /* if a is negative just do an unsigned + * addition [with fudged signs] + */ + if (a->sign == MP_NEG) { + a->sign = MP_ZPOS; + res = mp_add_d(a, b, c); + a->sign = c->sign = MP_NEG; + + /* clamp */ + mp_clamp(c); + + return res; + } + + /* setup regs */ + oldused = c->used; + tmpa = a->dp; + tmpc = c->dp; + + /* if a <= b simply fix the single digit */ + if (((a->used == 1) && (a->dp[0] <= b)) || (a->used == 0)) { + if (a->used == 1) { + *tmpc++ = b - *tmpa; + } else { + *tmpc++ = b; + } + ix = 1; + + /* negative/1digit */ + c->sign = MP_NEG; + c->used = 1; + } else { + /* positive/size */ + c->sign = MP_ZPOS; + c->used = a->used; + + /* subtract first digit */ + *tmpc = *tmpa++ - b; + mu = *tmpc >> ((sizeof(mp_digit) * CHAR_BIT) - 1); + *tmpc++ &= MP_MASK; + + /* handle rest of the digits */ + for (ix = 1; ix < a->used; ix++) { + *tmpc = *tmpa++ - mu; + mu = *tmpc >> ((sizeof(mp_digit) * CHAR_BIT) - 1); + *tmpc++ &= MP_MASK; + } + } + + /* zero excess digits */ + while (ix++ < oldused) { + *tmpc++ = 0; + } + mp_clamp(c); + return MP_OKAY; +} + +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_sub_d.c */ + +/* Start: bn_mp_submod.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_SUBMOD_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* d = a - b (mod c) */ +int +mp_submod (mp_int * a, mp_int * b, mp_int * c, mp_int * d) +{ + int res; + mp_int t; + + + if ((res = mp_init (&t)) != MP_OKAY) { + return res; + } + + if ((res = mp_sub (a, b, &t)) != MP_OKAY) { + mp_clear (&t); + return res; + } + res = mp_mod (&t, c, d); + mp_clear (&t); + return res; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_submod.c */ + +/* Start: bn_mp_to_signed_bin.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_TO_SIGNED_BIN_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* store in signed [big endian] format */ +int mp_to_signed_bin (mp_int * a, unsigned char *b) +{ + int res; + + if ((res = mp_to_unsigned_bin (a, b + 1)) != MP_OKAY) { + return res; + } + b[0] = (a->sign == MP_ZPOS) ? (unsigned char)0 : (unsigned char)1; + return MP_OKAY; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_to_signed_bin.c */ + +/* Start: bn_mp_to_signed_bin_n.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_TO_SIGNED_BIN_N_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* store in signed [big endian] format */ +int mp_to_signed_bin_n (mp_int * a, unsigned char *b, unsigned long *outlen) +{ + if (*outlen < (unsigned long)mp_signed_bin_size(a)) { + return MP_VAL; + } + *outlen = mp_signed_bin_size(a); + return mp_to_signed_bin(a, b); +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_to_signed_bin_n.c */ + +/* Start: bn_mp_to_unsigned_bin.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_TO_UNSIGNED_BIN_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* store in unsigned [big endian] format */ +int mp_to_unsigned_bin (mp_int * a, unsigned char *b) +{ + int x, res; + mp_int t; + + if ((res = mp_init_copy (&t, a)) != MP_OKAY) { + return res; + } + + x = 0; + while (mp_iszero (&t) == MP_NO) { +#ifndef MP_8BIT + b[x++] = (unsigned char) (t.dp[0] & 255); +#else + b[x++] = (unsigned char) (t.dp[0] | ((t.dp[1] & 0x01) << 7)); +#endif + if ((res = mp_div_2d (&t, 8, &t, NULL)) != MP_OKAY) { + mp_clear (&t); + return res; + } + } + bn_reverse (b, x); + mp_clear (&t); + return MP_OKAY; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_to_unsigned_bin.c */ + +/* Start: bn_mp_to_unsigned_bin_n.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_TO_UNSIGNED_BIN_N_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* store in unsigned [big endian] format */ +int mp_to_unsigned_bin_n (mp_int * a, unsigned char *b, unsigned long *outlen) +{ + if (*outlen < (unsigned long)mp_unsigned_bin_size(a)) { + return MP_VAL; + } + *outlen = mp_unsigned_bin_size(a); + return mp_to_unsigned_bin(a, b); +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_to_unsigned_bin_n.c */ + +/* Start: bn_mp_toom_mul.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_TOOM_MUL_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* multiplication using the Toom-Cook 3-way algorithm + * + * Much more complicated than Karatsuba but has a lower + * asymptotic running time of O(N**1.464). This algorithm is + * only particularly useful on VERY large inputs + * (we're talking 1000s of digits here...). +*/ +int mp_toom_mul(mp_int *a, mp_int *b, mp_int *c) +{ + mp_int w0, w1, w2, w3, w4, tmp1, tmp2, a0, a1, a2, b0, b1, b2; + int res, B; + + /* init temps */ + if ((res = mp_init_multi(&w0, &w1, &w2, &w3, &w4, + &a0, &a1, &a2, &b0, &b1, + &b2, &tmp1, &tmp2, NULL)) != MP_OKAY) { + return res; + } + + /* B */ + B = MIN(a->used, b->used) / 3; + + /* a = a2 * B**2 + a1 * B + a0 */ + if ((res = mp_mod_2d(a, DIGIT_BIT * B, &a0)) != MP_OKAY) { + goto ERR; + } + + if ((res = mp_copy(a, &a1)) != MP_OKAY) { + goto ERR; + } + mp_rshd(&a1, B); + if ((res = mp_mod_2d(&a1, DIGIT_BIT * B, &a1)) != MP_OKAY) { + goto ERR; + } + + if ((res = mp_copy(a, &a2)) != MP_OKAY) { + goto ERR; + } + mp_rshd(&a2, B*2); + + /* b = b2 * B**2 + b1 * B + b0 */ + if ((res = mp_mod_2d(b, DIGIT_BIT * B, &b0)) != MP_OKAY) { + goto ERR; + } + + if ((res = mp_copy(b, &b1)) != MP_OKAY) { + goto ERR; + } + mp_rshd(&b1, B); + (void)mp_mod_2d(&b1, DIGIT_BIT * B, &b1); + + if ((res = mp_copy(b, &b2)) != MP_OKAY) { + goto ERR; + } + mp_rshd(&b2, B*2); + + /* w0 = a0*b0 */ + if ((res = mp_mul(&a0, &b0, &w0)) != MP_OKAY) { + goto ERR; + } + + /* w4 = a2 * b2 */ + if ((res = mp_mul(&a2, &b2, &w4)) != MP_OKAY) { + goto ERR; + } + + /* w1 = (a2 + 2(a1 + 2a0))(b2 + 2(b1 + 2b0)) */ + if ((res = mp_mul_2(&a0, &tmp1)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_add(&tmp1, &a1, &tmp1)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_mul_2(&tmp1, &tmp1)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_add(&tmp1, &a2, &tmp1)) != MP_OKAY) { + goto ERR; + } + + if ((res = mp_mul_2(&b0, &tmp2)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_add(&tmp2, &b1, &tmp2)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_mul_2(&tmp2, &tmp2)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_add(&tmp2, &b2, &tmp2)) != MP_OKAY) { + goto ERR; + } + + if ((res = mp_mul(&tmp1, &tmp2, &w1)) != MP_OKAY) { + goto ERR; + } + + /* w3 = (a0 + 2(a1 + 2a2))(b0 + 2(b1 + 2b2)) */ + if ((res = mp_mul_2(&a2, &tmp1)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_add(&tmp1, &a1, &tmp1)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_mul_2(&tmp1, &tmp1)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_add(&tmp1, &a0, &tmp1)) != MP_OKAY) { + goto ERR; + } + + if ((res = mp_mul_2(&b2, &tmp2)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_add(&tmp2, &b1, &tmp2)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_mul_2(&tmp2, &tmp2)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_add(&tmp2, &b0, &tmp2)) != MP_OKAY) { + goto ERR; + } + + if ((res = mp_mul(&tmp1, &tmp2, &w3)) != MP_OKAY) { + goto ERR; + } + + + /* w2 = (a2 + a1 + a0)(b2 + b1 + b0) */ + if ((res = mp_add(&a2, &a1, &tmp1)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_add(&tmp1, &a0, &tmp1)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_add(&b2, &b1, &tmp2)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_add(&tmp2, &b0, &tmp2)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_mul(&tmp1, &tmp2, &w2)) != MP_OKAY) { + goto ERR; + } + + /* now solve the matrix + + 0 0 0 0 1 + 1 2 4 8 16 + 1 1 1 1 1 + 16 8 4 2 1 + 1 0 0 0 0 + + using 12 subtractions, 4 shifts, + 2 small divisions and 1 small multiplication + */ + + /* r1 - r4 */ + if ((res = mp_sub(&w1, &w4, &w1)) != MP_OKAY) { + goto ERR; + } + /* r3 - r0 */ + if ((res = mp_sub(&w3, &w0, &w3)) != MP_OKAY) { + goto ERR; + } + /* r1/2 */ + if ((res = mp_div_2(&w1, &w1)) != MP_OKAY) { + goto ERR; + } + /* r3/2 */ + if ((res = mp_div_2(&w3, &w3)) != MP_OKAY) { + goto ERR; + } + /* r2 - r0 - r4 */ + if ((res = mp_sub(&w2, &w0, &w2)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_sub(&w2, &w4, &w2)) != MP_OKAY) { + goto ERR; + } + /* r1 - r2 */ + if ((res = mp_sub(&w1, &w2, &w1)) != MP_OKAY) { + goto ERR; + } + /* r3 - r2 */ + if ((res = mp_sub(&w3, &w2, &w3)) != MP_OKAY) { + goto ERR; + } + /* r1 - 8r0 */ + if ((res = mp_mul_2d(&w0, 3, &tmp1)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_sub(&w1, &tmp1, &w1)) != MP_OKAY) { + goto ERR; + } + /* r3 - 8r4 */ + if ((res = mp_mul_2d(&w4, 3, &tmp1)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_sub(&w3, &tmp1, &w3)) != MP_OKAY) { + goto ERR; + } + /* 3r2 - r1 - r3 */ + if ((res = mp_mul_d(&w2, 3, &w2)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_sub(&w2, &w1, &w2)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_sub(&w2, &w3, &w2)) != MP_OKAY) { + goto ERR; + } + /* r1 - r2 */ + if ((res = mp_sub(&w1, &w2, &w1)) != MP_OKAY) { + goto ERR; + } + /* r3 - r2 */ + if ((res = mp_sub(&w3, &w2, &w3)) != MP_OKAY) { + goto ERR; + } + /* r1/3 */ + if ((res = mp_div_3(&w1, &w1, NULL)) != MP_OKAY) { + goto ERR; + } + /* r3/3 */ + if ((res = mp_div_3(&w3, &w3, NULL)) != MP_OKAY) { + goto ERR; + } + + /* at this point shift W[n] by B*n */ + if ((res = mp_lshd(&w1, 1*B)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_lshd(&w2, 2*B)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_lshd(&w3, 3*B)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_lshd(&w4, 4*B)) != MP_OKAY) { + goto ERR; + } + + if ((res = mp_add(&w0, &w1, c)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_add(&w2, &w3, &tmp1)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_add(&w4, &tmp1, &tmp1)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_add(&tmp1, c, c)) != MP_OKAY) { + goto ERR; + } + +ERR: + mp_clear_multi(&w0, &w1, &w2, &w3, &w4, + &a0, &a1, &a2, &b0, &b1, + &b2, &tmp1, &tmp2, NULL); + return res; +} + +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_toom_mul.c */ + +/* Start: bn_mp_toom_sqr.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_TOOM_SQR_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* squaring using Toom-Cook 3-way algorithm */ +int +mp_toom_sqr(mp_int *a, mp_int *b) +{ + mp_int w0, w1, w2, w3, w4, tmp1, a0, a1, a2; + int res, B; + + /* init temps */ + if ((res = mp_init_multi(&w0, &w1, &w2, &w3, &w4, &a0, &a1, &a2, &tmp1, NULL)) != MP_OKAY) { + return res; + } + + /* B */ + B = a->used / 3; + + /* a = a2 * B**2 + a1 * B + a0 */ + if ((res = mp_mod_2d(a, DIGIT_BIT * B, &a0)) != MP_OKAY) { + goto ERR; + } + + if ((res = mp_copy(a, &a1)) != MP_OKAY) { + goto ERR; + } + mp_rshd(&a1, B); + if ((res = mp_mod_2d(&a1, DIGIT_BIT * B, &a1)) != MP_OKAY) { + goto ERR; + } + + if ((res = mp_copy(a, &a2)) != MP_OKAY) { + goto ERR; + } + mp_rshd(&a2, B*2); + + /* w0 = a0*a0 */ + if ((res = mp_sqr(&a0, &w0)) != MP_OKAY) { + goto ERR; + } + + /* w4 = a2 * a2 */ + if ((res = mp_sqr(&a2, &w4)) != MP_OKAY) { + goto ERR; + } + + /* w1 = (a2 + 2(a1 + 2a0))**2 */ + if ((res = mp_mul_2(&a0, &tmp1)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_add(&tmp1, &a1, &tmp1)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_mul_2(&tmp1, &tmp1)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_add(&tmp1, &a2, &tmp1)) != MP_OKAY) { + goto ERR; + } + + if ((res = mp_sqr(&tmp1, &w1)) != MP_OKAY) { + goto ERR; + } + + /* w3 = (a0 + 2(a1 + 2a2))**2 */ + if ((res = mp_mul_2(&a2, &tmp1)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_add(&tmp1, &a1, &tmp1)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_mul_2(&tmp1, &tmp1)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_add(&tmp1, &a0, &tmp1)) != MP_OKAY) { + goto ERR; + } + + if ((res = mp_sqr(&tmp1, &w3)) != MP_OKAY) { + goto ERR; + } + + + /* w2 = (a2 + a1 + a0)**2 */ + if ((res = mp_add(&a2, &a1, &tmp1)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_add(&tmp1, &a0, &tmp1)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_sqr(&tmp1, &w2)) != MP_OKAY) { + goto ERR; + } + + /* now solve the matrix + + 0 0 0 0 1 + 1 2 4 8 16 + 1 1 1 1 1 + 16 8 4 2 1 + 1 0 0 0 0 + + using 12 subtractions, 4 shifts, 2 small divisions and 1 small multiplication. + */ + + /* r1 - r4 */ + if ((res = mp_sub(&w1, &w4, &w1)) != MP_OKAY) { + goto ERR; + } + /* r3 - r0 */ + if ((res = mp_sub(&w3, &w0, &w3)) != MP_OKAY) { + goto ERR; + } + /* r1/2 */ + if ((res = mp_div_2(&w1, &w1)) != MP_OKAY) { + goto ERR; + } + /* r3/2 */ + if ((res = mp_div_2(&w3, &w3)) != MP_OKAY) { + goto ERR; + } + /* r2 - r0 - r4 */ + if ((res = mp_sub(&w2, &w0, &w2)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_sub(&w2, &w4, &w2)) != MP_OKAY) { + goto ERR; + } + /* r1 - r2 */ + if ((res = mp_sub(&w1, &w2, &w1)) != MP_OKAY) { + goto ERR; + } + /* r3 - r2 */ + if ((res = mp_sub(&w3, &w2, &w3)) != MP_OKAY) { + goto ERR; + } + /* r1 - 8r0 */ + if ((res = mp_mul_2d(&w0, 3, &tmp1)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_sub(&w1, &tmp1, &w1)) != MP_OKAY) { + goto ERR; + } + /* r3 - 8r4 */ + if ((res = mp_mul_2d(&w4, 3, &tmp1)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_sub(&w3, &tmp1, &w3)) != MP_OKAY) { + goto ERR; + } + /* 3r2 - r1 - r3 */ + if ((res = mp_mul_d(&w2, 3, &w2)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_sub(&w2, &w1, &w2)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_sub(&w2, &w3, &w2)) != MP_OKAY) { + goto ERR; + } + /* r1 - r2 */ + if ((res = mp_sub(&w1, &w2, &w1)) != MP_OKAY) { + goto ERR; + } + /* r3 - r2 */ + if ((res = mp_sub(&w3, &w2, &w3)) != MP_OKAY) { + goto ERR; + } + /* r1/3 */ + if ((res = mp_div_3(&w1, &w1, NULL)) != MP_OKAY) { + goto ERR; + } + /* r3/3 */ + if ((res = mp_div_3(&w3, &w3, NULL)) != MP_OKAY) { + goto ERR; + } + + /* at this point shift W[n] by B*n */ + if ((res = mp_lshd(&w1, 1*B)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_lshd(&w2, 2*B)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_lshd(&w3, 3*B)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_lshd(&w4, 4*B)) != MP_OKAY) { + goto ERR; + } + + if ((res = mp_add(&w0, &w1, b)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_add(&w2, &w3, &tmp1)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_add(&w4, &tmp1, &tmp1)) != MP_OKAY) { + goto ERR; + } + if ((res = mp_add(&tmp1, b, b)) != MP_OKAY) { + goto ERR; + } + +ERR: + mp_clear_multi(&w0, &w1, &w2, &w3, &w4, &a0, &a1, &a2, &tmp1, NULL); + return res; +} + +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_toom_sqr.c */ + +/* Start: bn_mp_toradix.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_TORADIX_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* stores a bignum as a ASCII string in a given radix (2..64) */ +int mp_toradix (mp_int * a, char *str, int radix) +{ + int res, digs; + mp_int t; + mp_digit d; + char *_s = str; + + /* check range of the radix */ + if ((radix < 2) || (radix > 64)) { + return MP_VAL; + } + + /* quick out if its zero */ + if (mp_iszero(a) == MP_YES) { + *str++ = '0'; + *str = '\0'; + return MP_OKAY; + } + + if ((res = mp_init_copy (&t, a)) != MP_OKAY) { + return res; + } + + /* if it is negative output a - */ + if (t.sign == MP_NEG) { + ++_s; + *str++ = '-'; + t.sign = MP_ZPOS; + } + + digs = 0; + while (mp_iszero (&t) == MP_NO) { + if ((res = mp_div_d (&t, (mp_digit) radix, &t, &d)) != MP_OKAY) { + mp_clear (&t); + return res; + } + *str++ = mp_s_rmap[d]; + ++digs; + } + + /* reverse the digits of the string. In this case _s points + * to the first digit [exluding the sign] of the number] + */ + bn_reverse ((unsigned char *)_s, digs); + + /* append a NULL so the string is properly terminated */ + *str = '\0'; + + mp_clear (&t); + return MP_OKAY; +} + +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_toradix.c */ + +/* Start: bn_mp_toradix_n.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_TORADIX_N_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* stores a bignum as a ASCII string in a given radix (2..64) + * + * Stores upto maxlen-1 chars and always a NULL byte + */ +int mp_toradix_n(mp_int * a, char *str, int radix, int maxlen) +{ + int res, digs; + mp_int t; + mp_digit d; + char *_s = str; + + /* check range of the maxlen, radix */ + if ((maxlen < 2) || (radix < 2) || (radix > 64)) { + return MP_VAL; + } + + /* quick out if its zero */ + if (mp_iszero(a) == MP_YES) { + *str++ = '0'; + *str = '\0'; + return MP_OKAY; + } + + if ((res = mp_init_copy (&t, a)) != MP_OKAY) { + return res; + } + + /* if it is negative output a - */ + if (t.sign == MP_NEG) { + /* we have to reverse our digits later... but not the - sign!! */ + ++_s; + + /* store the flag and mark the number as positive */ + *str++ = '-'; + t.sign = MP_ZPOS; + + /* subtract a char */ + --maxlen; + } + + digs = 0; + while (mp_iszero (&t) == MP_NO) { + if (--maxlen < 1) { + /* no more room */ + break; + } + if ((res = mp_div_d (&t, (mp_digit) radix, &t, &d)) != MP_OKAY) { + mp_clear (&t); + return res; + } + *str++ = mp_s_rmap[d]; + ++digs; + } + + /* reverse the digits of the string. In this case _s points + * to the first digit [exluding the sign] of the number + */ + bn_reverse ((unsigned char *)_s, digs); + + /* append a NULL so the string is properly terminated */ + *str = '\0'; + + mp_clear (&t); + return MP_OKAY; +} + +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_toradix_n.c */ + +/* Start: bn_mp_unsigned_bin_size.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_UNSIGNED_BIN_SIZE_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* get the size for an unsigned equivalent */ +int mp_unsigned_bin_size (mp_int * a) +{ + int size = mp_count_bits (a); + return (size / 8) + (((size & 7) != 0) ? 1 : 0); +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_unsigned_bin_size.c */ + +/* Start: bn_mp_xor.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_XOR_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* XOR two ints together */ +int +mp_xor (mp_int * a, mp_int * b, mp_int * c) +{ + int res, ix, px; + mp_int t, *x; + + if (a->used > b->used) { + if ((res = mp_init_copy (&t, a)) != MP_OKAY) { + return res; + } + px = b->used; + x = b; + } else { + if ((res = mp_init_copy (&t, b)) != MP_OKAY) { + return res; + } + px = a->used; + x = a; + } + + for (ix = 0; ix < px; ix++) { + t.dp[ix] ^= x->dp[ix]; + } + mp_clamp (&t); + mp_exch (c, &t); + mp_clear (&t); + return MP_OKAY; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_xor.c */ + +/* Start: bn_mp_zero.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_MP_ZERO_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* set to zero */ +void mp_zero (mp_int * a) +{ + int n; + mp_digit *tmp; + + a->sign = MP_ZPOS; + a->used = 0; + + tmp = a->dp; + for (n = 0; n < a->alloc; n++) { + *tmp++ = 0; + } +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_mp_zero.c */ + +/* Start: bn_prime_tab.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_PRIME_TAB_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ +const mp_digit ltm_prime_tab[] = { + 0x0002, 0x0003, 0x0005, 0x0007, 0x000B, 0x000D, 0x0011, 0x0013, + 0x0017, 0x001D, 0x001F, 0x0025, 0x0029, 0x002B, 0x002F, 0x0035, + 0x003B, 0x003D, 0x0043, 0x0047, 0x0049, 0x004F, 0x0053, 0x0059, + 0x0061, 0x0065, 0x0067, 0x006B, 0x006D, 0x0071, 0x007F, +#ifndef MP_8BIT + 0x0083, + 0x0089, 0x008B, 0x0095, 0x0097, 0x009D, 0x00A3, 0x00A7, 0x00AD, + 0x00B3, 0x00B5, 0x00BF, 0x00C1, 0x00C5, 0x00C7, 0x00D3, 0x00DF, + 0x00E3, 0x00E5, 0x00E9, 0x00EF, 0x00F1, 0x00FB, 0x0101, 0x0107, + 0x010D, 0x010F, 0x0115, 0x0119, 0x011B, 0x0125, 0x0133, 0x0137, + + 0x0139, 0x013D, 0x014B, 0x0151, 0x015B, 0x015D, 0x0161, 0x0167, + 0x016F, 0x0175, 0x017B, 0x017F, 0x0185, 0x018D, 0x0191, 0x0199, + 0x01A3, 0x01A5, 0x01AF, 0x01B1, 0x01B7, 0x01BB, 0x01C1, 0x01C9, + 0x01CD, 0x01CF, 0x01D3, 0x01DF, 0x01E7, 0x01EB, 0x01F3, 0x01F7, + 0x01FD, 0x0209, 0x020B, 0x021D, 0x0223, 0x022D, 0x0233, 0x0239, + 0x023B, 0x0241, 0x024B, 0x0251, 0x0257, 0x0259, 0x025F, 0x0265, + 0x0269, 0x026B, 0x0277, 0x0281, 0x0283, 0x0287, 0x028D, 0x0293, + 0x0295, 0x02A1, 0x02A5, 0x02AB, 0x02B3, 0x02BD, 0x02C5, 0x02CF, + + 0x02D7, 0x02DD, 0x02E3, 0x02E7, 0x02EF, 0x02F5, 0x02F9, 0x0301, + 0x0305, 0x0313, 0x031D, 0x0329, 0x032B, 0x0335, 0x0337, 0x033B, + 0x033D, 0x0347, 0x0355, 0x0359, 0x035B, 0x035F, 0x036D, 0x0371, + 0x0373, 0x0377, 0x038B, 0x038F, 0x0397, 0x03A1, 0x03A9, 0x03AD, + 0x03B3, 0x03B9, 0x03C7, 0x03CB, 0x03D1, 0x03D7, 0x03DF, 0x03E5, + 0x03F1, 0x03F5, 0x03FB, 0x03FD, 0x0407, 0x0409, 0x040F, 0x0419, + 0x041B, 0x0425, 0x0427, 0x042D, 0x043F, 0x0443, 0x0445, 0x0449, + 0x044F, 0x0455, 0x045D, 0x0463, 0x0469, 0x047F, 0x0481, 0x048B, + + 0x0493, 0x049D, 0x04A3, 0x04A9, 0x04B1, 0x04BD, 0x04C1, 0x04C7, + 0x04CD, 0x04CF, 0x04D5, 0x04E1, 0x04EB, 0x04FD, 0x04FF, 0x0503, + 0x0509, 0x050B, 0x0511, 0x0515, 0x0517, 0x051B, 0x0527, 0x0529, + 0x052F, 0x0551, 0x0557, 0x055D, 0x0565, 0x0577, 0x0581, 0x058F, + 0x0593, 0x0595, 0x0599, 0x059F, 0x05A7, 0x05AB, 0x05AD, 0x05B3, + 0x05BF, 0x05C9, 0x05CB, 0x05CF, 0x05D1, 0x05D5, 0x05DB, 0x05E7, + 0x05F3, 0x05FB, 0x0607, 0x060D, 0x0611, 0x0617, 0x061F, 0x0623, + 0x062B, 0x062F, 0x063D, 0x0641, 0x0647, 0x0649, 0x064D, 0x0653 +#endif +}; +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_prime_tab.c */ + +/* Start: bn_reverse.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_REVERSE_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* reverse an array, used for radix code */ +void +bn_reverse (unsigned char *s, int len) +{ + int ix, iy; + unsigned char t; + + ix = 0; + iy = len - 1; + while (ix < iy) { + t = s[ix]; + s[ix] = s[iy]; + s[iy] = t; + ++ix; + --iy; + } +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_reverse.c */ + +/* Start: bn_s_mp_add.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_S_MP_ADD_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* low level addition, based on HAC pp.594, Algorithm 14.7 */ +int +s_mp_add (mp_int * a, mp_int * b, mp_int * c) +{ + mp_int *x; + int olduse, res, min, max; + + /* find sizes, we let |a| <= |b| which means we have to sort + * them. "x" will point to the input with the most digits + */ + if (a->used > b->used) { + min = b->used; + max = a->used; + x = a; + } else { + min = a->used; + max = b->used; + x = b; + } + + /* init result */ + if (c->alloc < (max + 1)) { + if ((res = mp_grow (c, max + 1)) != MP_OKAY) { + return res; + } + } + + /* get old used digit count and set new one */ + olduse = c->used; + c->used = max + 1; + + { + mp_digit u, *tmpa, *tmpb, *tmpc; + int i; + + /* alias for digit pointers */ + + /* first input */ + tmpa = a->dp; + + /* second input */ + tmpb = b->dp; + + /* destination */ + tmpc = c->dp; + + /* zero the carry */ + u = 0; + for (i = 0; i < min; i++) { + /* Compute the sum at one digit, T[i] = A[i] + B[i] + U */ + *tmpc = *tmpa++ + *tmpb++ + u; + + /* U = carry bit of T[i] */ + u = *tmpc >> ((mp_digit)DIGIT_BIT); + + /* take away carry bit from T[i] */ + *tmpc++ &= MP_MASK; + } + + /* now copy higher words if any, that is in A+B + * if A or B has more digits add those in + */ + if (min != max) { + for (; i < max; i++) { + /* T[i] = X[i] + U */ + *tmpc = x->dp[i] + u; + + /* U = carry bit of T[i] */ + u = *tmpc >> ((mp_digit)DIGIT_BIT); + + /* take away carry bit from T[i] */ + *tmpc++ &= MP_MASK; + } + } + + /* add carry */ + *tmpc++ = u; + + /* clear digits above oldused */ + for (i = c->used; i < olduse; i++) { + *tmpc++ = 0; + } + } + + mp_clamp (c); + return MP_OKAY; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_s_mp_add.c */ + +/* Start: bn_s_mp_exptmod.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_S_MP_EXPTMOD_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ +#ifdef MP_LOW_MEM + #define TAB_SIZE 32 +#else + #define TAB_SIZE 256 +#endif + +int s_mp_exptmod (mp_int * G, mp_int * X, mp_int * P, mp_int * Y, int redmode) +{ + mp_int M[TAB_SIZE], res, mu; + mp_digit buf; + int err, bitbuf, bitcpy, bitcnt, mode, digidx, x, y, winsize; + int (*redux)(mp_int*,mp_int*,mp_int*); + + /* find window size */ + x = mp_count_bits (X); + if (x <= 7) { + winsize = 2; + } else if (x <= 36) { + winsize = 3; + } else if (x <= 140) { + winsize = 4; + } else if (x <= 450) { + winsize = 5; + } else if (x <= 1303) { + winsize = 6; + } else if (x <= 3529) { + winsize = 7; + } else { + winsize = 8; + } + +#ifdef MP_LOW_MEM + if (winsize > 5) { + winsize = 5; + } +#endif + + /* init M array */ + /* init first cell */ + if ((err = mp_init(&M[1])) != MP_OKAY) { + return err; + } + + /* now init the second half of the array */ + for (x = 1<<(winsize-1); x < (1 << winsize); x++) { + if ((err = mp_init(&M[x])) != MP_OKAY) { + for (y = 1<<(winsize-1); y < x; y++) { + mp_clear (&M[y]); + } + mp_clear(&M[1]); + return err; + } + } + + /* create mu, used for Barrett reduction */ + if ((err = mp_init (&mu)) != MP_OKAY) { + goto LBL_M; + } + + if (redmode == 0) { + if ((err = mp_reduce_setup (&mu, P)) != MP_OKAY) { + goto LBL_MU; + } + redux = mp_reduce; + } else { + if ((err = mp_reduce_2k_setup_l (P, &mu)) != MP_OKAY) { + goto LBL_MU; + } + redux = mp_reduce_2k_l; + } + + /* create M table + * + * The M table contains powers of the base, + * e.g. M[x] = G**x mod P + * + * The first half of the table is not + * computed though accept for M[0] and M[1] + */ + if ((err = mp_mod (G, P, &M[1])) != MP_OKAY) { + goto LBL_MU; + } + + /* compute the value at M[1<<(winsize-1)] by squaring + * M[1] (winsize-1) times + */ + if ((err = mp_copy (&M[1], &M[1 << (winsize - 1)])) != MP_OKAY) { + goto LBL_MU; + } + + for (x = 0; x < (winsize - 1); x++) { + /* square it */ + if ((err = mp_sqr (&M[1 << (winsize - 1)], + &M[1 << (winsize - 1)])) != MP_OKAY) { + goto LBL_MU; + } + + /* reduce modulo P */ + if ((err = redux (&M[1 << (winsize - 1)], P, &mu)) != MP_OKAY) { + goto LBL_MU; + } + } + + /* create upper table, that is M[x] = M[x-1] * M[1] (mod P) + * for x = (2**(winsize - 1) + 1) to (2**winsize - 1) + */ + for (x = (1 << (winsize - 1)) + 1; x < (1 << winsize); x++) { + if ((err = mp_mul (&M[x - 1], &M[1], &M[x])) != MP_OKAY) { + goto LBL_MU; + } + if ((err = redux (&M[x], P, &mu)) != MP_OKAY) { + goto LBL_MU; + } + } + + /* setup result */ + if ((err = mp_init (&res)) != MP_OKAY) { + goto LBL_MU; + } + mp_set (&res, 1); + + /* set initial mode and bit cnt */ + mode = 0; + bitcnt = 1; + buf = 0; + digidx = X->used - 1; + bitcpy = 0; + bitbuf = 0; + + for (;;) { + /* grab next digit as required */ + if (--bitcnt == 0) { + /* if digidx == -1 we are out of digits */ + if (digidx == -1) { + break; + } + /* read next digit and reset the bitcnt */ + buf = X->dp[digidx--]; + bitcnt = (int) DIGIT_BIT; + } + + /* grab the next msb from the exponent */ + y = (buf >> (mp_digit)(DIGIT_BIT - 1)) & 1; + buf <<= (mp_digit)1; + + /* if the bit is zero and mode == 0 then we ignore it + * These represent the leading zero bits before the first 1 bit + * in the exponent. Technically this opt is not required but it + * does lower the # of trivial squaring/reductions used + */ + if ((mode == 0) && (y == 0)) { + continue; + } + + /* if the bit is zero and mode == 1 then we square */ + if ((mode == 1) && (y == 0)) { + if ((err = mp_sqr (&res, &res)) != MP_OKAY) { + goto LBL_RES; + } + if ((err = redux (&res, P, &mu)) != MP_OKAY) { + goto LBL_RES; + } + continue; + } + + /* else we add it to the window */ + bitbuf |= (y << (winsize - ++bitcpy)); + mode = 2; + + if (bitcpy == winsize) { + /* ok window is filled so square as required and multiply */ + /* square first */ + for (x = 0; x < winsize; x++) { + if ((err = mp_sqr (&res, &res)) != MP_OKAY) { + goto LBL_RES; + } + if ((err = redux (&res, P, &mu)) != MP_OKAY) { + goto LBL_RES; + } + } + + /* then multiply */ + if ((err = mp_mul (&res, &M[bitbuf], &res)) != MP_OKAY) { + goto LBL_RES; + } + if ((err = redux (&res, P, &mu)) != MP_OKAY) { + goto LBL_RES; + } + + /* empty window and reset */ + bitcpy = 0; + bitbuf = 0; + mode = 1; + } + } + + /* if bits remain then square/multiply */ + if ((mode == 2) && (bitcpy > 0)) { + /* square then multiply if the bit is set */ + for (x = 0; x < bitcpy; x++) { + if ((err = mp_sqr (&res, &res)) != MP_OKAY) { + goto LBL_RES; + } + if ((err = redux (&res, P, &mu)) != MP_OKAY) { + goto LBL_RES; + } + + bitbuf <<= 1; + if ((bitbuf & (1 << winsize)) != 0) { + /* then multiply */ + if ((err = mp_mul (&res, &M[1], &res)) != MP_OKAY) { + goto LBL_RES; + } + if ((err = redux (&res, P, &mu)) != MP_OKAY) { + goto LBL_RES; + } + } + } + } + + mp_exch (&res, Y); + err = MP_OKAY; +LBL_RES:mp_clear (&res); +LBL_MU:mp_clear (&mu); +LBL_M: + mp_clear(&M[1]); + for (x = 1<<(winsize-1); x < (1 << winsize); x++) { + mp_clear (&M[x]); + } + return err; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_s_mp_exptmod.c */ + +/* Start: bn_s_mp_mul_digs.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_S_MP_MUL_DIGS_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* multiplies |a| * |b| and only computes upto digs digits of result + * HAC pp. 595, Algorithm 14.12 Modified so you can control how + * many digits of output are created. + */ +int s_mp_mul_digs (mp_int * a, mp_int * b, mp_int * c, int digs) +{ + mp_int t; + int res, pa, pb, ix, iy; + mp_digit u; + mp_word r; + mp_digit tmpx, *tmpt, *tmpy; + + /* can we use the fast multiplier? */ + if (((digs) < MP_WARRAY) && + (MIN (a->used, b->used) < + (1 << ((CHAR_BIT * sizeof(mp_word)) - (2 * DIGIT_BIT))))) { + return fast_s_mp_mul_digs (a, b, c, digs); + } + + if ((res = mp_init_size (&t, digs)) != MP_OKAY) { + return res; + } + t.used = digs; + + /* compute the digits of the product directly */ + pa = a->used; + for (ix = 0; ix < pa; ix++) { + /* set the carry to zero */ + u = 0; + + /* limit ourselves to making digs digits of output */ + pb = MIN (b->used, digs - ix); + + /* setup some aliases */ + /* copy of the digit from a used within the nested loop */ + tmpx = a->dp[ix]; + + /* an alias for the destination shifted ix places */ + tmpt = t.dp + ix; + + /* an alias for the digits of b */ + tmpy = b->dp; + + /* compute the columns of the output and propagate the carry */ + for (iy = 0; iy < pb; iy++) { + /* compute the column as a mp_word */ + r = (mp_word)*tmpt + + ((mp_word)tmpx * (mp_word)*tmpy++) + + (mp_word)u; + + /* the new column is the lower part of the result */ + *tmpt++ = (mp_digit) (r & ((mp_word) MP_MASK)); + + /* get the carry word from the result */ + u = (mp_digit) (r >> ((mp_word) DIGIT_BIT)); + } + /* set carry if it is placed below digs */ + if ((ix + iy) < digs) { + *tmpt = u; + } + } + + mp_clamp (&t); + mp_exch (&t, c); + + mp_clear (&t); + return MP_OKAY; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_s_mp_mul_digs.c */ + +/* Start: bn_s_mp_mul_high_digs.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_S_MP_MUL_HIGH_DIGS_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* multiplies |a| * |b| and does not compute the lower digs digits + * [meant to get the higher part of the product] + */ +int +s_mp_mul_high_digs (mp_int * a, mp_int * b, mp_int * c, int digs) +{ + mp_int t; + int res, pa, pb, ix, iy; + mp_digit u; + mp_word r; + mp_digit tmpx, *tmpt, *tmpy; + + /* can we use the fast multiplier? */ +#ifdef BN_FAST_S_MP_MUL_HIGH_DIGS_C + if (((a->used + b->used + 1) < MP_WARRAY) + && (MIN (a->used, b->used) < (1 << ((CHAR_BIT * sizeof(mp_word)) - (2 * DIGIT_BIT))))) { + return fast_s_mp_mul_high_digs (a, b, c, digs); + } +#endif + + if ((res = mp_init_size (&t, a->used + b->used + 1)) != MP_OKAY) { + return res; + } + t.used = a->used + b->used + 1; + + pa = a->used; + pb = b->used; + for (ix = 0; ix < pa; ix++) { + /* clear the carry */ + u = 0; + + /* left hand side of A[ix] * B[iy] */ + tmpx = a->dp[ix]; + + /* alias to the address of where the digits will be stored */ + tmpt = &(t.dp[digs]); + + /* alias for where to read the right hand side from */ + tmpy = b->dp + (digs - ix); + + for (iy = digs - ix; iy < pb; iy++) { + /* calculate the double precision result */ + r = (mp_word)*tmpt + + ((mp_word)tmpx * (mp_word)*tmpy++) + + (mp_word)u; + + /* get the lower part */ + *tmpt++ = (mp_digit) (r & ((mp_word) MP_MASK)); + + /* carry the carry */ + u = (mp_digit) (r >> ((mp_word) DIGIT_BIT)); + } + *tmpt = u; + } + mp_clamp (&t); + mp_exch (&t, c); + mp_clear (&t); + return MP_OKAY; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_s_mp_mul_high_digs.c */ + +/* Start: bn_s_mp_sqr.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_S_MP_SQR_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* low level squaring, b = a*a, HAC pp.596-597, Algorithm 14.16 */ +int s_mp_sqr (mp_int * a, mp_int * b) +{ + mp_int t; + int res, ix, iy, pa; + mp_word r; + mp_digit u, tmpx, *tmpt; + + pa = a->used; + if ((res = mp_init_size (&t, (2 * pa) + 1)) != MP_OKAY) { + return res; + } + + /* default used is maximum possible size */ + t.used = (2 * pa) + 1; + + for (ix = 0; ix < pa; ix++) { + /* first calculate the digit at 2*ix */ + /* calculate double precision result */ + r = (mp_word)t.dp[2*ix] + + ((mp_word)a->dp[ix] * (mp_word)a->dp[ix]); + + /* store lower part in result */ + t.dp[ix+ix] = (mp_digit) (r & ((mp_word) MP_MASK)); + + /* get the carry */ + u = (mp_digit)(r >> ((mp_word) DIGIT_BIT)); + + /* left hand side of A[ix] * A[iy] */ + tmpx = a->dp[ix]; + + /* alias for where to store the results */ + tmpt = t.dp + ((2 * ix) + 1); + + for (iy = ix + 1; iy < pa; iy++) { + /* first calculate the product */ + r = ((mp_word)tmpx) * ((mp_word)a->dp[iy]); + + /* now calculate the double precision result, note we use + * addition instead of *2 since it's easier to optimize + */ + r = ((mp_word) *tmpt) + r + r + ((mp_word) u); + + /* store lower part */ + *tmpt++ = (mp_digit) (r & ((mp_word) MP_MASK)); + + /* get carry */ + u = (mp_digit)(r >> ((mp_word) DIGIT_BIT)); + } + /* propagate upwards */ + while (u != ((mp_digit) 0)) { + r = ((mp_word) *tmpt) + ((mp_word) u); + *tmpt++ = (mp_digit) (r & ((mp_word) MP_MASK)); + u = (mp_digit)(r >> ((mp_word) DIGIT_BIT)); + } + } + + mp_clamp (&t); + mp_exch (&t, b); + mp_clear (&t); + return MP_OKAY; +} +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_s_mp_sqr.c */ + +/* Start: bn_s_mp_sub.c */ +#include "libtorrent/tommath_private.h" +#ifdef BN_S_MP_SUB_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* low level subtraction (assumes |a| > |b|), HAC pp.595 Algorithm 14.9 */ +int +s_mp_sub (mp_int * a, mp_int * b, mp_int * c) +{ + int olduse, res, min, max; + + /* find sizes */ + min = b->used; + max = a->used; + + /* init result */ + if (c->alloc < max) { + if ((res = mp_grow (c, max)) != MP_OKAY) { + return res; + } + } + olduse = c->used; + c->used = max; + + { + mp_digit u, *tmpa, *tmpb, *tmpc; + int i; + + /* alias for digit pointers */ + tmpa = a->dp; + tmpb = b->dp; + tmpc = c->dp; + + /* set carry to zero */ + u = 0; + for (i = 0; i < min; i++) { + /* T[i] = A[i] - B[i] - U */ + *tmpc = (*tmpa++ - *tmpb++) - u; + + /* U = carry bit of T[i] + * Note this saves performing an AND operation since + * if a carry does occur it will propagate all the way to the + * MSB. As a result a single shift is enough to get the carry + */ + u = *tmpc >> ((mp_digit)((CHAR_BIT * sizeof(mp_digit)) - 1)); + + /* Clear carry from T[i] */ + *tmpc++ &= MP_MASK; + } + + /* now copy higher words if any, e.g. if A has more digits than B */ + for (; i < max; i++) { + /* T[i] = A[i] - U */ + *tmpc = *tmpa++ - u; + + /* U = carry bit of T[i] */ + u = *tmpc >> ((mp_digit)((CHAR_BIT * sizeof(mp_digit)) - 1)); + + /* Clear carry from T[i] */ + *tmpc++ &= MP_MASK; + } + + /* clear digits above used (since we may not have grown result above) */ + for (i = c->used; i < olduse; i++) { + *tmpc++ = 0; + } + } + + mp_clamp (c); + return MP_OKAY; +} + +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bn_s_mp_sub.c */ + +/* Start: bncore.c */ +#include "libtorrent/tommath_private.h" +#ifdef BNCORE_C +/* LibTomMath, multiple-precision integer library -- Tom St Denis + * + * LibTomMath is a library that provides multiple-precision + * integer arithmetic as well as number theoretic functionality. + * + * The library was designed directly after the MPI library by + * Michael Fromberger but has been written from scratch with + * additional optimizations in place. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tstdenis82@gmail.com, http://libtom.org + */ + +/* Known optimal configurations + + CPU /Compiler /MUL CUTOFF/SQR CUTOFF +------------------------------------------------------------- + Intel P4 Northwood /GCC v3.4.1 / 88/ 128/LTM 0.32 ;-) + AMD Athlon64 /GCC v3.4.4 / 80/ 120/LTM 0.35 + +*/ + +int KARATSUBA_MUL_CUTOFF = 80, /* Min. number of digits before Karatsuba multiplication is used. */ + KARATSUBA_SQR_CUTOFF = 120, /* Min. number of digits before Karatsuba squaring is used. */ + + TOOM_MUL_CUTOFF = 350, /* no optimal values of these are known yet so set em high */ + TOOM_SQR_CUTOFF = 400; +#endif + +/* $Source$ */ +/* $Revision$ */ +/* $Date$ */ + +/* End: bncore.c */ + + +/* EOF */ diff --git a/src/natpmp.cpp b/src/natpmp.cpp new file mode 100644 index 0000000..e107fa9 --- /dev/null +++ b/src/natpmp.cpp @@ -0,0 +1,709 @@ +/* + +Copyright (c) 2007-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#if defined TORRENT_OS2 +#include +#endif + +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/natpmp.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/enum_net.hpp" +#include "libtorrent/socket_io.hpp" +#include "libtorrent/io_service.hpp" +#include "libtorrent/aux_/time.hpp" +#include "libtorrent/aux_/escape_string.hpp" + +#include + +//#define NATPMP_LOG + +#ifdef NATPMP_LOG +#include +#endif + +#if defined TORRENT_ASIO_DEBUGGING +#include "libtorrent/debug.hpp" +#endif + +using namespace libtorrent; + +natpmp::natpmp(io_service& ios + , portmap_callback_t const& cb, log_callback_t const& lcb) + : m_callback(cb) + , m_log_callback(lcb) + , m_currently_mapping(-1) + , m_retry_count(0) + , m_socket(ios) + , m_send_timer(ios) + , m_refresh_timer(ios) + , m_next_refresh(-1) + , m_disabled(false) + , m_abort(false) +{ + // 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() +{ + mutex::scoped_lock l(m_mutex); + + error_code ec; + address gateway = get_default_gateway(m_socket.get_io_service(), ec); + if (ec) + { + char msg[200]; + snprintf(msg, sizeof(msg), "failed to find default route: %s" + , convert_from_native(ec.message()).c_str()); + log(msg, l); + disable(ec, l); + return; + } + + m_disabled = false; + + udp::endpoint nat_endpoint(gateway, 5351); + if (nat_endpoint == m_nat_endpoint) return; + m_nat_endpoint = nat_endpoint; + + char msg[200]; + snprintf(msg, sizeof(msg), "found router at: %s" + , print_address(m_nat_endpoint.address()).c_str()); + log(msg, l); + + m_socket.open(udp::v4(), ec); + if (ec) + { + disable(ec, l); + return; + } + m_socket.bind(udp::endpoint(address_v4::any(), 0), ec); + if (ec) + { + disable(ec, l); + return; + } + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("natpmp::on_reply"); +#endif + m_socket.async_receive_from(boost::asio::buffer(&m_response_buffer[0] + , sizeof(m_response_buffer)) + , m_remote, boost::bind(&natpmp::on_reply, self(), _1, _2)); + send_get_ip_address_request(l); + + for (std::vector::iterator i = m_mappings.begin() + , end(m_mappings.end()); i != end; ++i) + { + if (i->protocol != none + || i->action != mapping_t::action_none) + continue; + i->action = mapping_t::action_add; + update_mapping(i - m_mappings.begin(), l); + } +} + +void natpmp::send_get_ip_address_request(mutex::scoped_lock& l) +{ + using namespace libtorrent::detail; + + char buf[2]; + char* out = buf; + write_uint8(0, out); // NAT-PMP version + write_uint8(0, out); // public IP address request opcode + log("==> get public IP address", l); + + error_code ec; + m_socket.send_to(boost::asio::buffer(buf, sizeof(buf)), m_nat_endpoint, 0, ec); +} + +bool natpmp::get_mapping(int index, int& local_port, int& external_port, int& protocol) const +{ + mutex::scoped_lock l(m_mutex); + + TORRENT_ASSERT(index < int(m_mappings.size()) && index >= 0); + if (index >= int(m_mappings.size()) || index < 0) return false; + mapping_t const& m = m_mappings[index]; + if (m.protocol == none) return false; + local_port = m.local_port; + external_port = m.external_port; + protocol = m.protocol; + return true; +} + +void natpmp::log(char const* msg, mutex::scoped_lock& l) +{ + l.unlock(); + m_log_callback(msg); + l.lock(); +} + +void natpmp::disable(error_code const& ec, mutex::scoped_lock& l) +{ + m_disabled = true; + + for (std::vector::iterator i = m_mappings.begin() + , end(m_mappings.end()); i != end; ++i) + { + if (i->protocol == none) continue; + int const proto = i->protocol; + i->protocol = none; + int index = i - m_mappings.begin(); + l.unlock(); + m_callback(index, address(), 0, proto, ec); + l.lock(); + } + close_impl(l); +} + +void natpmp::delete_mapping(int index) +{ + mutex::scoped_lock l(m_mutex); + + TORRENT_ASSERT(index < int(m_mappings.size()) && index >= 0); + if (index >= int(m_mappings.size()) || index < 0) return; + mapping_t& m = m_mappings[index]; + + if (m.protocol == none) return; + if (!m.map_sent) + { + m.action = mapping_t::action_none; + m.protocol = none; + return; + } + + m.action = mapping_t::action_delete; + update_mapping(index, l); +} + +int natpmp::add_mapping(protocol_type p, int external_port, int local_port) +{ + mutex::scoped_lock l(m_mutex); + + if (m_disabled) return -1; + + std::vector::iterator i = std::find_if(m_mappings.begin() + , m_mappings.end(), boost::bind(&mapping_t::protocol, _1) == int(none)); + if (i == m_mappings.end()) + { + m_mappings.push_back(mapping_t()); + i = m_mappings.end() - 1; + } + i->protocol = p; + i->external_port = external_port; + i->local_port = local_port; + i->action = mapping_t::action_add; + + int mapping_index = i - m_mappings.begin(); + +#ifdef NATPMP_LOG + time_point now = aux::time_now(); + for (std::vector::iterator m = m_mappings.begin() + , end(m_mappings.end()); m != end; ++m) + { + std::cout << " ADD MAPPING: " << mapping_index << " [ " + "proto: " << (i->protocol == none ? "none" : i->protocol == tcp ? "tcp" : "udp") + << " port: " << i->external_port + << " local-port: " << i->local_port + << " action: " << (i->action == mapping_t::action_none ? "none" : i->action == mapping_t::action_add ? "add" : "delete") + << " ttl: " << total_seconds(i->expires - now) + << " ]" << std::endl; + } +#endif + + update_mapping(mapping_index, l); + return mapping_index; +} + +void natpmp::try_next_mapping(int i, mutex::scoped_lock& l) +{ +#ifdef NATPMP_LOG + time_point now = aux::time_now(); + for (std::vector::iterator m = m_mappings.begin() + , end(m_mappings.end()); m != end; ++m) + { + std::cout << " " << (m - m_mappings.begin()) << " [ " + "proto: " << (m->protocol == none ? "none" : m->protocol == tcp ? "tcp" : "udp") + << " port: " << m->external_port + << " local-port: " << m->local_port + << " action: " << (m->action == mapping_t::action_none ? "none" : m->action == mapping_t::action_add ? "add" : "delete") + << " ttl: " << total_seconds(m->expires - now) + << " ]" << std::endl; + } +#endif + if (i < int(m_mappings.size()) - 1) + { + update_mapping(i + 1, l); + return; + } + + std::vector::iterator m = std::find_if( + m_mappings.begin(), m_mappings.end() + , boost::bind(&mapping_t::action, _1) != int(mapping_t::action_none)); + + if (m == m_mappings.end()) + { + if (m_abort) + { + error_code ec; + m_send_timer.cancel(ec); + m_socket.close(ec); + } +#ifdef NATPMP_LOG + std::cout << " done" << (m_abort?" shutting down":"") << std::endl; +#endif + return; + } + +#ifdef NATPMP_LOG + std::cout << " updating " << (m - m_mappings.begin()) << std::endl; +#endif + + update_mapping(m - m_mappings.begin(), l); +} + +void natpmp::update_mapping(int i, mutex::scoped_lock& l) +{ + if (i == int(m_mappings.size())) + { + if (m_abort) + { + error_code ec; + m_send_timer.cancel(ec); + m_socket.close(ec); + } +#ifdef NATPMP_LOG + std::cout << " done" << (m_abort?" shutting down":"") << std::endl; +#endif + return; + } + + natpmp::mapping_t& m = m_mappings[i]; + if (m.action == mapping_t::action_none + || m.protocol == none) + { + try_next_mapping(i, l); + return; + } + + if (m_currently_mapping == -1) + { + // the socket is not currently in use + // send out a mapping request + m_retry_count = 0; + send_map_request(i, l); + } +} + +void natpmp::send_map_request(int i, mutex::scoped_lock& l) +{ + using namespace libtorrent::detail; + + TORRENT_ASSERT(m_currently_mapping == -1 + || m_currently_mapping == i); + m_currently_mapping = i; + mapping_t& m = m_mappings[i]; + TORRENT_ASSERT(m.action != mapping_t::action_none); + char buf[12]; + char* out = buf; + write_uint8(0, out); // NAT-PMP version + write_uint8(m.protocol, 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 + int ttl = m.action == mapping_t::action_add ? 3600 : 0; + write_uint32(ttl, out); // port mapping lifetime + + char msg[200]; + snprintf(msg, sizeof(msg), "==> port map [ mapping: %d action: %s" + " proto: %s local: %u external: %u ttl: %u ]" + , i, m.action == mapping_t::action_add ? "add" : "delete" + , m.protocol == udp ? "udp" : "tcp" + , m.local_port, m.external_port, ttl); + log(msg, l); + + error_code ec; + m_socket.send_to(boost::asio::buffer(buf, sizeof(buf)), m_nat_endpoint, 0, ec); + 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 = -1; + m.action = mapping_t::action_none; + try_next_mapping(i, l); + } + else + { +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("natpmp::resend_request"); +#endif + // linear back-off instead of exponential + ++m_retry_count; + m_send_timer.expires_from_now(milliseconds(250 * m_retry_count), ec); + m_send_timer.async_wait(boost::bind(&natpmp::resend_request, self(), i, _1)); + } +} + +void natpmp::resend_request(int i, error_code const& e) +{ +#if defined TORRENT_ASIO_DEBUGGING + complete_async("natpmp::resend_request"); +#endif + if (e) return; + mutex::scoped_lock l(m_mutex); + 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 = -1; + m_mappings[i].action = mapping_t::action_none; + // try again in two hours + m_mappings[i].expires = aux::time_now() + hours(2); + try_next_mapping(i, l); + return; + } + send_map_request(i, l); +} + +void natpmp::on_reply(error_code const& e + , std::size_t bytes_transferred) +{ + mutex::scoped_lock l(m_mutex); + +#if defined TORRENT_ASIO_DEBUGGING + complete_async("natpmp::on_reply"); +#endif + + using namespace libtorrent::detail; + if (e) + { + char msg[200]; + snprintf(msg, sizeof(msg), "error on receiving reply: %s" + , convert_from_native(e.message()).c_str()); + log(msg, l); + return; + } + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("natpmp::on_reply"); +#endif + // make a copy of the response packet buffer + // to avoid overwriting it in the next receive call + char msg_buf[sizeof(m_response_buffer)]; + memcpy(msg_buf, m_response_buffer, bytes_transferred); + + m_socket.async_receive_from(boost::asio::buffer(&m_response_buffer[0] + , sizeof(m_response_buffer)) + , m_remote, boost::bind(&natpmp::on_reply, self(), _1, _2)); + + // simulate packet loss +/* + if ((random() % 2) == 0) + { + log(" simulating drop", l); + return; + } +*/ + if (m_remote != m_nat_endpoint) + { + char msg[200]; + snprintf(msg, sizeof(msg), "received packet from wrong IP: %s" + , print_endpoint(m_remote).c_str()); + log(msg, l); + return; + } + + error_code ec; + m_send_timer.cancel(ec); + + if (bytes_transferred < 12) + { + char msg[200]; + snprintf(msg, sizeof(msg), "received packet of invalid size: %d", int(bytes_transferred)); + log(msg, l); + return; + } + + char* in = msg_buf; + int version = read_uint8(in); + int cmd = read_uint8(in); + int result = read_uint16(in); + int time = read_uint32(in); + + if (cmd == 128) + { + // public IP request response + m_external_ip = read_v4_address(in); + + char msg[200]; + snprintf(msg, sizeof(msg), "<== public IP address [ %s ]", print_address(m_external_ip).c_str()); + log(msg, l); + return; + + } + + if (bytes_transferred != 16) + { + char msg[200]; + snprintf(msg, sizeof(msg), "received packet of invalid size: %d", int(bytes_transferred)); + log(msg, l); + return; + } + + int private_port = read_uint16(in); + int public_port = read_uint16(in); + int lifetime = read_uint32(in); + + (void)time; // to remove warning + + int protocol = (cmd - 128 == 1)?udp:tcp; + + char msg[200]; + int num_chars = snprintf(msg, sizeof(msg), "<== port map [" + " protocol: %s local: %u external: %u ttl: %u ]" + , (cmd - 128 == 1 ? "udp" : "tcp") + , private_port, public_port, lifetime); + + if (version != 0) + { + snprintf(msg + num_chars, sizeof(msg) - num_chars, "unexpected version: %u" + , version); + log(msg, l); + } + + mapping_t* m = 0; + int index = -1; + for (std::vector::iterator 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; + m = &*i; + index = i - m_mappings.begin(); + break; + } + + if (m == 0) + { + snprintf(msg + num_chars, sizeof(msg) - num_chars, " not found in map table"); + log(msg, l); + return; + } + m->outstanding_request = false; + + log(msg, l); + + if (public_port == 0 || lifetime == 0) + { + // this means the mapping was + // successfully closed + m->protocol = none; + } + else + { + m->expires = aux::time_now() + seconds(int(lifetime * 0.7f)); + m->external_port = public_port; + } + + if (result != 0) + { + int errors[] = + { + errors::unsupported_protocol_version, + errors::natpmp_not_authorized, + errors::network_failure, + errors::no_resources, + errors::unsupported_opcode, + }; + int ev = errors::no_error; + if (result >= 1 && result <= 5) ev = errors[result - 1]; + + m->expires = aux::time_now() + hours(2); + int const proto = m->protocol; + l.unlock(); + m_callback(index, address(), 0, proto + , error_code(ev, get_libtorrent_category())); + l.lock(); + } + else if (m->action == mapping_t::action_add) + { + int const proto = m->protocol; + l.unlock(); + m_callback(index, m_external_ip, m->external_port, proto + , error_code(errors::no_error, get_libtorrent_category())); + l.lock(); + } + + if (m_abort) return; + + m_currently_mapping = -1; + m->action = mapping_t::action_none; + m_send_timer.cancel(ec); + update_expiration_timer(l); + try_next_mapping(index, l); +} + +void natpmp::update_expiration_timer(mutex::scoped_lock& l) +{ + if (m_abort) return; + + time_point now = aux::time_now() + milliseconds(100); +#ifdef NATPMP_LOG + std::cout << time_now_string() << " update_expiration_timer " << std::endl; + for (std::vector::iterator i = m_mappings.begin() + , end(m_mappings.end()); i != end; ++i) + { + std::cout << " " << (i - m_mappings.begin()) << " [ " + "proto: " << (i->protocol == none ? "none" : i->protocol == tcp ? "tcp" : "udp") + << " port: " << i->external_port + << " local-port: " << i->local_port + << " action: " << (i->action == mapping_t::action_none ? "none" : i->action == mapping_t::action_add ? "add" : "delete") + << " ttl: " << total_seconds(i->expires - now) + << " ]" << std::endl; + } +#endif + time_point min_expire = now + seconds(3600); + int min_index = -1; + for (std::vector::iterator i = m_mappings.begin() + , end(m_mappings.end()); i != end; ++i) + { + if (i->protocol == none + || i->action != mapping_t::action_none) continue; + int index = i - m_mappings.begin(); + if (i->expires < now) + { + char msg[200]; + snprintf(msg, sizeof(msg), "mapping %u expired", index); + log(msg, l); + i->action = mapping_t::action_add; + if (m_next_refresh == index) m_next_refresh = -1; + update_mapping(index, l); + } + 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 >= 0) + { +#ifdef NATPMP_LOG + std::cout << time_now_string() << " next expiration [" + " i: " << min_index + << " ttl: " << total_seconds(min_expire - aux::time_now()) + << " ]" << std::endl; +#endif + error_code ec; + if (m_next_refresh >= 0) m_refresh_timer.cancel(ec); + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("natpmp::mapping_expired"); +#endif + m_refresh_timer.expires_from_now(min_expire - now, ec); + m_refresh_timer.async_wait(boost::bind(&natpmp::mapping_expired, self(), _1, min_index)); + m_next_refresh = min_index; + } +} + +void natpmp::mapping_expired(error_code const& e, int i) +{ +#if defined TORRENT_ASIO_DEBUGGING + complete_async("natpmp::mapping_expired"); +#endif + if (e) return; + mutex::scoped_lock l(m_mutex); + char msg[200]; + snprintf(msg, sizeof(msg), "mapping %u expired", i); + log(msg, l); + m_mappings[i].action = mapping_t::action_add; + if (m_next_refresh == i) m_next_refresh = -1; + update_mapping(i, l); +} + +void natpmp::close() +{ + mutex::scoped_lock l(m_mutex); + close_impl(l); +} + +void natpmp::close_impl(mutex::scoped_lock& l) +{ + m_abort = true; + log("closing", l); +#ifdef NATPMP_LOG + std::cout << time_now_string() << " close" << std::endl; + time_point now = aux::time_now(); +#endif + if (m_disabled) return; + for (std::vector::iterator i = m_mappings.begin() + , end(m_mappings.end()); i != end; ++i) + { +#ifdef NATPMP_LOG + std::cout << " " << (i - m_mappings.begin()) << " [ " + "proto: " << (i->protocol == none ? "none" : i->protocol == tcp ? "tcp" : "udp") + << " port: " << i->external_port + << " local-port: " << i->local_port + << " action: " << (i->action == mapping_t::action_none ? "none" : i->action == mapping_t::action_add ? "add" : "delete") + << " ttl: " << total_seconds(i->expires - now) + << " ]" << std::endl; +#endif + if (i->protocol == none) continue; + i->action = mapping_t::action_delete; + } + error_code ec; + m_refresh_timer.cancel(ec); + m_currently_mapping = -1; + update_mapping(0, l); +} + diff --git a/src/packet_buffer.cpp b/src/packet_buffer.cpp new file mode 100644 index 0000000..bc073b3 --- /dev/null +++ b/src/packet_buffer.cpp @@ -0,0 +1,219 @@ +/* + +Copyright (c) 2010-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include // free and calloc +#include // for bad_alloc +#include "libtorrent/packet_buffer.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/invariant_check.hpp" + +namespace libtorrent { + + bool compare_less_wrap(boost::uint32_t lhs, boost::uint32_t rhs + , boost::uint32_t mask); + + packet_buffer_impl::packet_buffer_impl() + : m_storage(0) + , m_capacity(0) + , m_size(0) + , m_first(0) + , m_last(0) + {} + +#if TORRENT_USE_INVARIANT_CHECKS + void packet_buffer_impl::check_invariant() const + { + int count = 0; + for (int i = 0; i < int(m_capacity); ++i) + { + count += m_storage[i] ? 1 : 0; + } + TORRENT_ASSERT(count == int(m_size)); + } +#endif + + packet_buffer_impl::~packet_buffer_impl() + { + free(m_storage); + } + + void* packet_buffer_impl::insert(index_type idx, void* value) + { + INVARIANT_CHECK; + + TORRENT_ASSERT_VAL(idx <= 0xffff, idx); + // you're not allowed to insert NULLs! + TORRENT_ASSERT(value); + + if (value == 0) 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::size_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); + + void* old_value = m_storage[idx & (m_capacity - 1)]; + m_storage[idx & (m_capacity - 1)] = 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 == 0) ++m_size; + + TORRENT_ASSERT_VAL(m_first <= 0xffff, m_first); + return old_value; + } + + void* packet_buffer_impl::at(index_type idx) const + { + INVARIANT_CHECK; + if (idx >= m_first + m_capacity) + return 0; + + if (compare_less_wrap(idx, m_first, 0xffff)) + { + return 0; + } + + const int mask = (m_capacity - 1); + return m_storage[idx & mask]; + } + + void packet_buffer_impl::reserve(std::size_t size) + { + INVARIANT_CHECK; + TORRENT_ASSERT_VAL(size <= 0xffff, size); + std::size_t new_size = m_capacity == 0 ? 16 : m_capacity; + + while (new_size < size) + new_size <<= 1; + + void** new_storage = static_cast(malloc(sizeof(void*) * new_size)); +#ifndef BOOST_NO_EXCEPTIONS + if (new_storage == NULL) throw std::bad_alloc(); +#endif + + for (index_type i = 0; i < new_size; ++i) + new_storage[i] = 0; + + for (index_type i = m_first; i < (m_first + m_capacity); ++i) + new_storage[i & (new_size - 1)] = m_storage[i & (m_capacity - 1)]; + + free(m_storage); + + m_storage = new_storage; + m_capacity = new_size; + } + + void* packet_buffer_impl::remove(index_type idx) + { + INVARIANT_CHECK; + // TODO: use compare_less_wrap for this comparison as well + if (idx >= m_first + m_capacity) + return 0; + + if (compare_less_wrap(idx, m_first, 0xffff)) + return 0; + + const int mask = (m_capacity - 1); + void* old_value = m_storage[idx & mask]; + m_storage[idx & mask] = 0; + + if (old_value) + { + --m_size; + if (m_size == 0) m_last = m_first; + } + + if (idx == m_first && m_size != 0) + { + ++m_first; + for (boost::uint32_t 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 (boost::uint32_t 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..81ae622 --- /dev/null +++ b/src/parse_url.cpp @@ -0,0 +1,134 @@ +/* + +Copyright (c) 2008-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/parse_url.hpp" +#include + +namespace libtorrent +{ + + // returns protocol, auth, hostname, port, path + boost::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 + std::string::iterator start = url.begin(); + // remove white spaces in front of the url + while (start != url.end() && is_space(*start)) + ++start; + std::string::iterator end + = std::find(url.begin(), url.end(), ':'); + protocol.assign(start, end); + + if (end == url.end()) + { + ec = errors::unsupported_url_protocol; + goto exit; + } + ++end; + if (end == url.end() || *end != '/') + { + ec = errors::unsupported_url_protocol; + goto exit; + } + ++end; + if (end == url.end() || *end != '/') + { + ec = errors::unsupported_url_protocol; + goto exit; + } + ++end; + start = end; + + at = std::find(start, url.end(), '@'); + colon = std::find(start, url.end(), ':'); + end = std::find(start, url.end(), '/'); + + if (at != url.end() + && colon != url.end() + && colon < at + && at < end) + { + auth.assign(start, at); + start = at; + ++start; + } + + // this is for IPv6 addresses + if (start != url.end() && *start == '[') + { + port_pos = std::find(start, url.end(), ']'); + if (port_pos == url.end()) + { + ec = errors::expected_close_bracket_in_address; + goto exit; + } + // strip the brackets + hostname.assign(start + 1, port_pos); + port_pos = std::find(port_pos, url.end(), ':'); + } + else + { + port_pos = std::find(start, url.end(), ':'); + if (port_pos < end) hostname.assign(start, port_pos); + else hostname.assign(start, end); + } + + if (port_pos < end) + { + ++port_pos; + for (std::string::iterator i = port_pos; i < end; ++i) + { + if (is_digit(*i)) continue; + ec = errors::invalid_port; + goto exit; + } + port = std::atoi(std::string(port_pos, end).c_str()); + } + + start = end; +exit: + return boost::make_tuple(protocol, auth, hostname, port + , std::string(start, url.end())); + } + +} + diff --git a/src/part_file.cpp b/src/part_file.cpp new file mode 100644 index 0000000..bba8bb1 --- /dev/null +++ b/src/part_file.cpp @@ -0,0 +1,426 @@ +/* + +Copyright (c) 2012-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. + +*/ + + +/* + + 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 + +#ifdef TORRENT_USE_VALGRIND +#include +#endif + +namespace +{ + // round up to even kilobyte + int round_up(int n) + { return (n + 1023) & ~0x3ff; } +} + +namespace libtorrent +{ + part_file::part_file(std::string const& path, std::string const& name + , int num_pieces, int piece_size) + : m_path(path) + , m_name(name) + , m_num_allocated(0) + , m_max_pieces(num_pieces) + , m_piece_size(piece_size) + , m_header_size(round_up((2 + num_pieces) * 4)) + , m_dirty_metadata(false) + { + TORRENT_ASSERT(num_pieces > 0); + TORRENT_ASSERT(m_piece_size > 0); + + error_code ec; + std::string fn = combine_path(m_path, m_name); + m_file.open(fn, file::read_only, ec); + if (!ec) + { + // parse header + boost::scoped_array header(new boost::uint32_t[m_header_size]); + file::iovec_t b = {header.get(), size_t(m_header_size) }; + int n = m_file.readv(0, &b, 1, ec); + if (ec) return; + + // we don't have a full header. consider the file empty + if (n < m_header_size) return; + using namespace libtorrent::detail; + + char* ptr = reinterpret_cast(header.get()); + // we have a header. Parse it + int num_pieces_ = read_uint32(ptr); + int piece_size_ = 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 + std::vector free_slots; + free_slots.resize(num_pieces, true); + + for (int i = 0; i < num_pieces; ++i) + { + int slot = read_uint32(ptr); + if (slot == 0xffffffff) continue; + + // invalid part-file + TORRENT_ASSERT(slot < num_pieces); + if (slot >= num_pieces) continue; + + if (slot >= m_num_allocated) + m_num_allocated = slot + 1; + + free_slots[slot] = false; + m_piece_map[i] = slot; + } + + // now, populate the free_list with the "holes" + for (int i = 0; i < m_num_allocated; ++i) + { + if (free_slots[i]) m_free_slots.push_back(i); + } + + m_file.close(); + } + } + + part_file::~part_file() + { + error_code ec; + flush_metadata_impl(ec); + } + + int part_file::allocate_slot(int 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()); + int 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(file::iovec_t const* bufs, int num_bufs, int piece, int offset, error_code& ec) + { + TORRENT_ASSERT(offset >= 0); + mutex::scoped_lock l(m_mutex); + + open_file(file::read_write, ec); + if (ec) return -1; + + int slot = -1; + boost::unordered_map::iterator i = m_piece_map.find(piece); + if (i == m_piece_map.end()) + slot = allocate_slot(piece); + else + slot = i->second; + + l.unlock(); + + boost::int64_t slot_offset = boost::int64_t(m_header_size) + boost::int64_t(slot) * m_piece_size; + return m_file.writev(slot_offset + offset, bufs, num_bufs, ec); + } + + int part_file::readv(file::iovec_t const* bufs, int num_bufs + , int piece, int offset, error_code& ec) + { + TORRENT_ASSERT(offset >= 0); + mutex::scoped_lock l(m_mutex); + + boost::unordered_map::iterator 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; + } + + int slot = i->second; + + open_file(file::read_write, ec); + if (ec) return -1; + + l.unlock(); + + boost::int64_t slot_offset = boost::int64_t(m_header_size) + boost::int64_t(slot) * m_piece_size; + return m_file.readv(slot_offset + offset, bufs, num_bufs, ec); + } + + void part_file::open_file(int mode, error_code& ec) + { + if (m_file.is_open() + && ((m_file.open_mode() & file::rw_mask) == mode + || mode == file::read_only)) return; + + std::string fn = combine_path(m_path, m_name); + m_file.open(fn, mode, ec); + if (((mode & file::rw_mask) != file::read_only) + && ec == boost::system::errc::no_such_file_or_directory) + { + // this means the directory the file is in doesn't exist. + // so create it + ec.clear(); + create_directories(m_path, ec); + + if (ec) return; + m_file.open(fn, mode, ec); + } + } + + void part_file::free_piece(int piece) + { + mutex::scoped_lock l(m_mutex); + + boost::unordered_map::iterator 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) + { + mutex::scoped_lock l(m_mutex); + + flush_metadata_impl(ec); + if (ec) return; + + m_file.close(); + + 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::import_file(file& f, boost::int64_t offset + , boost::int64_t size, error_code& ec) + { + TORRENT_UNUSED(f); + TORRENT_UNUSED(offset); + TORRENT_UNUSED(size); + TORRENT_UNUSED(ec); + + // not implemented + ec.assign(boost::system::errc::operation_not_supported + , boost::system::generic_category()); + } + + void part_file::export_file(file& f, boost::int64_t offset, boost::int64_t size, error_code& ec) + { + mutex::scoped_lock l(m_mutex); + + int piece = offset / m_piece_size; + int const end = ((offset + size) + m_piece_size - 1) / m_piece_size; + + boost::scoped_array buf; + + boost::int64_t piece_offset = offset - boost::int64_t(piece) * m_piece_size; + boost::int64_t file_offset = 0; + for (; piece < end; ++piece) + { + boost::unordered_map::iterator i = m_piece_map.find(piece); + int const block_to_copy = (std::min)(m_piece_size - piece_offset, size); + if (i != m_piece_map.end()) + { + int const slot = i->second; + open_file(file::read_only, ec); + if (ec) return; + + if (!buf) buf.reset(new char[m_piece_size]); + + boost::int64_t const slot_offset = boost::int64_t(m_header_size) + + boost::int64_t(slot) * m_piece_size; + + // don't hold the lock during disk I/O + l.unlock(); + + file::iovec_t v = { buf.get(), size_t(block_to_copy) }; + v.iov_len = m_file.readv(slot_offset + piece_offset, &v, 1, ec); + TORRENT_ASSERT(!ec); + if (ec || v.iov_len == 0) return; + + boost::int64_t ret = f.writev(file_offset, &v, 1, ec); + TORRENT_ASSERT(ec || ret == v.iov_len); + if (ec || ret != v.iov_len) return; + + // 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. + boost::unordered_map::iterator 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) + { + mutex::scoped_lock 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()) + { + m_file.close(); + + // if we don't have any pieces left in the + // part file, remove it + std::string p = combine_path(m_path, m_name); + remove(p, ec); + + if (ec == boost::system::errc::no_such_file_or_directory) + ec.clear(); + return; + } + + open_file(file::read_write, ec); + if (ec) return; + + boost::scoped_array header(new boost::uint32_t[m_header_size]); + + using namespace libtorrent::detail; + + char* ptr = reinterpret_cast(header.get()); + + write_uint32(m_max_pieces, ptr); + write_uint32(m_piece_size, ptr); + + for (int piece = 0; piece < m_max_pieces; ++piece) + { + boost::unordered_map::iterator i = m_piece_map.find(piece); + int slot = 0xffffffff; + if (i != m_piece_map.end()) + slot = i->second; + write_uint32(slot, ptr); + } + memset(ptr, 0, m_header_size - (ptr - reinterpret_cast(header.get()))); + +#ifdef TORRENT_USE_VALGRIND + VALGRIND_CHECK_MEM_IS_DEFINED(header.get(), m_header_size); +#endif + file::iovec_t b = {header.get(), size_t(m_header_size) }; + m_file.writev(0, &b, 1, ec); + if (ec) return; + } +} + diff --git a/src/pe_crypto.cpp b/src/pe_crypto.cpp new file mode 100644 index 0000000..018b5b9 --- /dev/null +++ b/src/pe_crypto.cpp @@ -0,0 +1,463 @@ +/* + +Copyright (c) 2007-2016, Un Shyam, Arvid Norberg, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include + +extern "C" { +#include "libtorrent/tommath.h" +} + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/random.hpp" +#include "libtorrent/pe_crypto.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/assert.hpp" + +namespace libtorrent +{ + namespace + { + const unsigned char dh_prime[96] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, + 0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, + 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6, + 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, + 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, + 0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, + 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9, + 0xA6, 0x3A, 0x36, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x05, 0x63 + }; + } + + struct mp_bigint + { + mp_bigint() + { mp_init(&v); } + mp_int* operator&() { return &v; } + ~mp_bigint() { mp_clear(&v); } + private: + // non-copyable + mp_bigint(mp_bigint const&); + mp_bigint const& operator=(mp_bigint const&); + mp_int v; + }; + + // Set the prime P and the generator, generate local public key + dh_key_exchange::dh_key_exchange() + { + // create local key + for (int i = 0; i < int(sizeof(m_dh_local_secret)); ++i) + m_dh_local_secret[i] = random() & 0xff; + + mp_bigint prime; + mp_bigint secret; + mp_bigint key; + + // TODO 2: use exceptions for error reporting here + if (mp_read_unsigned_bin(&prime, dh_prime, sizeof(dh_prime))) + { + TORRENT_ASSERT(false); + return; + } + if (mp_read_unsigned_bin(&secret + , reinterpret_cast(m_dh_local_secret) + , sizeof(m_dh_local_secret))) + { + TORRENT_ASSERT(false); + return; + } + + // generator is 2 + mp_set_int(&key, 2); + // key = (2 ^ secret) % prime + if (mp_exptmod(&key, &secret, &prime, &key)) + { + TORRENT_ASSERT(false); + return; + } + + // key is now our local key + int const size = mp_unsigned_bin_size(&key); + TORRENT_ASSERT(size >= 0); + TORRENT_ASSERT(size <= sizeof(m_dh_local_key)); + if (size < 0 || size > sizeof(m_dh_local_key)) return; + std::memset(m_dh_local_key, 0, sizeof(m_dh_local_key) - size); + mp_to_unsigned_bin(&key + , reinterpret_cast(m_dh_local_key) + + sizeof(m_dh_local_key) - size); + } + + char const* dh_key_exchange::get_local_key() const + { + return m_dh_local_key; + } + + // compute shared secret given remote public key + int dh_key_exchange::compute_secret(char const* remote_pubkey) + { + TORRENT_ASSERT(remote_pubkey); + mp_bigint prime; + mp_bigint secret; + mp_bigint remote_key; + + // TODO 2: use exceptions for error reporting here + if (mp_read_unsigned_bin(&prime, dh_prime, sizeof(dh_prime))) + { + TORRENT_ASSERT(false); + return -1; + } + if (mp_read_unsigned_bin(&secret + , reinterpret_cast(m_dh_local_secret) + , sizeof(m_dh_local_secret))) + { + TORRENT_ASSERT(false); + return -1; + } + if (mp_read_unsigned_bin(&remote_key + , reinterpret_cast(remote_pubkey), 96)) + { + TORRENT_ASSERT(false); + return -1; + } + + if (mp_exptmod(&remote_key, &secret, &prime, &remote_key)) + { + TORRENT_ASSERT(false); + return -1; + } + + // remote_key is now the shared secret + int const size = mp_unsigned_bin_size(&remote_key); + TORRENT_ASSERT(size >= 0); + TORRENT_ASSERT(size <= sizeof(m_dh_shared_secret)); + if (size < 0 || size > sizeof(m_dh_shared_secret)) + { + return -1; + } + + std::memset(m_dh_shared_secret, 0, sizeof(m_dh_shared_secret) - size); + mp_to_unsigned_bin(&remote_key + , reinterpret_cast(m_dh_shared_secret) + + sizeof(m_dh_shared_secret) - size); + + // calculate the xor mask for the obfuscated hash + hasher h; + h.update("req3", 4); + h.update(m_dh_shared_secret, sizeof(m_dh_shared_secret)); + m_xor_mask = h.final(); + return 0; + } + + int encryption_handler::encrypt(std::vector& iovec) + { + TORRENT_ASSERT(!m_send_barriers.empty()); + TORRENT_ASSERT(m_send_barriers.front().enc_handler); + + int to_process = m_send_barriers.front().next; + + if (to_process != INT_MAX) + { + for (std::vector::iterator i = iovec.begin(); + to_process >= 0; ++i) + { + if (to_process == 0) + { + iovec.erase(i, iovec.end()); + break; + } + else if (to_process < boost::asio::buffer_size(*i)) + { + *i = boost::asio::mutable_buffer(boost::asio::buffer_cast(*i), to_process); + iovec.erase(++i, iovec.end()); + to_process = 0; + break; + } + to_process -= boost::asio::buffer_size(*i); + } + TORRENT_ASSERT(to_process == 0); + } + +#if defined TORRENT_DEBUG || defined TORRENT_RELEASE_ASSERTS + to_process = 0; + for (std::vector::iterator i = iovec.begin(); + i != iovec.end(); ++i) + to_process += boost::asio::buffer_size(*i); +#endif + + int next_barrier = 0; + if (iovec.empty() || (next_barrier = m_send_barriers.front().enc_handler->encrypt(iovec))) + { + if (m_send_barriers.front().next != INT_MAX) + { + if (m_send_barriers.size() == 1) + // transitioning back to plaintext + next_barrier = INT_MAX; + m_send_barriers.pop_front(); + } + +#if defined TORRENT_DEBUG || defined TORRENT_RELEASE_ASSERTS + if (next_barrier != INT_MAX) + { + int overhead = 0; + for (std::vector::iterator i = iovec.begin(); + i != iovec.end(); ++i) + overhead += boost::asio::buffer_size(*i); + TORRENT_ASSERT(overhead + to_process == next_barrier); + } +#endif + } + else + { + iovec.clear(); + } + return next_barrier; + } + + int encryption_handler::decrypt(crypto_receive_buffer& recv_buffer, std::size_t& bytes_transferred) + { + TORRENT_ASSERT(!is_recv_plaintext()); + int consume = 0; + if (recv_buffer.crypto_packet_finished()) + { + std::vector wr_buf; + recv_buffer.mutable_buffers(wr_buf, bytes_transferred); + int packet_size = 0; + int produce = bytes_transferred; + m_dec_handler->decrypt(wr_buf, consume, produce, packet_size); + TORRENT_ASSERT(packet_size || produce); + TORRENT_ASSERT(packet_size >= 0); + bytes_transferred = produce; + if (packet_size) + recv_buffer.crypto_cut(consume, packet_size); + } + else + bytes_transferred = 0; + return consume; + } + + bool encryption_handler::switch_send_crypto(boost::shared_ptr crypto + , int pending_encryption) + { + bool place_barrier = false; + if (!m_send_barriers.empty()) + { + std::list::iterator end = m_send_barriers.end(); --end; + for (std::list::iterator 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(boost::shared_ptr crypto + , crypto_receive_buffer& recv_buffer) + { + m_dec_handler = crypto; + int packet_size = 0; + if (crypto) + { + int consume = 0; + int produce = 0; + std::vector wr_buf; + crypto->decrypt(wr_buf, consume, produce, packet_size); + 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(unsigned char const* key, int len) + { + m_decrypt = true; + rc4_init(key, len, &m_rc4_incoming); + // Discard first 1024 bytes + char buf[1024]; + std::vector vec(1, boost::asio::mutable_buffer(buf, 1024)); + int consume = 0; + int produce = 0; + int packet_size = 0; + decrypt(vec, consume, produce, packet_size); + } + + void rc4_handler::set_outgoing_key(unsigned char const* key, int len) + { + m_encrypt = true; + rc4_init(key, len, &m_rc4_outgoing); + // Discard first 1024 bytes + char buf[1024]; + std::vector vec(1, boost::asio::mutable_buffer(buf, 1024)); + encrypt(vec); + } + + int rc4_handler::encrypt(std::vector& buf) + { + if (!m_encrypt) return 0; + if (buf.empty()) return 0; + + int bytes_processed = 0; + for (std::vector::iterator i = buf.begin(); + i != buf.end(); ++i) + { + unsigned char* pos = boost::asio::buffer_cast(*i); + int len = boost::asio::buffer_size(*i); + + TORRENT_ASSERT(len >= 0); + TORRENT_ASSERT(pos); + + bytes_processed += len; + rc4_encrypt(pos, len, &m_rc4_outgoing); + } + buf.clear(); + return bytes_processed; + } + + void rc4_handler::decrypt(std::vector& buf + , int& consume + , int& produce + , int& packet_size) + { + // these are out-parameters that are not set + TORRENT_UNUSED(consume); + TORRENT_UNUSED(packet_size); + + if (!m_decrypt) return; + + int bytes_processed = 0; + for (std::vector::iterator i = buf.begin(); + i != buf.end(); ++i) + { + unsigned char* pos = boost::asio::buffer_cast(*i); + int len = boost::asio::buffer_size(*i); + + TORRENT_ASSERT(len >= 0); + TORRENT_ASSERT(pos); + + bytes_processed += len; + rc4_encrypt(pos, len, &m_rc4_incoming); + } + buf.clear(); + produce = bytes_processed; + } + +} // namespace libtorrent + +// 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, unsigned long len, rc4 *state) +{ + size_t const key_size = sizeof(state->buf); + unsigned char key[key_size], tmp, *s; + int keylen, x, y, j; + + TORRENT_ASSERT(state != 0); + 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; + std::memcpy(key, s, key_size); + keylen = state->x; + + /* make RC4 perm and shuffle */ + for (x = 0; x < key_size; ++x) { + s[x] = x; + } + + for (j = x = y = 0; x < 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; +} + +unsigned long rc4_encrypt(unsigned char *out, unsigned long outlen, rc4 *state) +{ + unsigned char x, y, *s, tmp; + unsigned long n; + + TORRENT_ASSERT(out != 0); + TORRENT_ASSERT(state != 0); + + n = outlen; + x = state->x; + y = state->y; + s = state->buf; + 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; +} + +#endif // #if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + diff --git a/src/peer_class.cpp b/src/peer_class.cpp new file mode 100644 index 0000000..9a1dd9f --- /dev/null +++ b/src/peer_class.cpp @@ -0,0 +1,140 @@ +/* + +Copyright (c) 2011-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/peer_class.hpp" +#include "libtorrent/peer_connection.hpp" + +#ifdef TORRENT_USE_VALGRIND +#include +#endif + +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 const& label) + { + peer_class_t ret = 0; + if (!m_free_list.empty()) + { + ret = m_free_list.back(); + m_free_list.pop_back(); + } + else + { + ret = m_peer_classes.size(); + m_peer_classes.push_back(boost::shared_ptr()); + } + + TORRENT_ASSERT(m_peer_classes[ret].get() == 0); + m_peer_classes[ret] = boost::make_shared(label); + return ret; + } + + void peer_class_pool::decref(peer_class_t c) + { +#ifdef TORRENT_USE_VALGRIND + VALGRIND_CHECK_VALUE_IS_DEFINED(c); +#endif + TORRENT_ASSERT(c < m_peer_classes.size()); + TORRENT_ASSERT(m_peer_classes[c].get()); + + --m_peer_classes[c]->references; + if (m_peer_classes[c]->references) return; + m_peer_classes[c].reset(); + m_free_list.push_back(c); + } + + void peer_class_pool::incref(peer_class_t c) + { +#ifdef TORRENT_USE_VALGRIND + VALGRIND_CHECK_VALUE_IS_DEFINED(c); +#endif + TORRENT_ASSERT(c < m_peer_classes.size()); + TORRENT_ASSERT(m_peer_classes[c].get()); + + ++m_peer_classes[c]->references; + } + + peer_class* peer_class_pool::at(peer_class_t c) + { +#ifdef TORRENT_USE_VALGRIND + VALGRIND_CHECK_VALUE_IS_DEFINED(c); +#endif + if (c >= m_peer_classes.size()) return 0; + return m_peer_classes[c].get(); + } + + peer_class const* peer_class_pool::at(peer_class_t c) const + { + if (c >= m_peer_classes.size()) return 0; + return m_peer_classes[c].get(); + } + +} + diff --git a/src/peer_class_set.cpp b/src/peer_class_set.cpp new file mode 100644 index 0000000..65f7ca8 --- /dev/null +++ b/src/peer_class_set.cpp @@ -0,0 +1,75 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/peer_class_set.hpp" +#include "libtorrent/peer_class.hpp" +#include +#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 >= m_class.size() - 1) + { + TORRENT_ASSERT(false); + 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 c) + { + boost::array::iterator i = std::find(m_class.begin() + , m_class.begin() + m_size, c); + int idx = 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..229505f --- /dev/null +++ b/src/peer_connection.cpp @@ -0,0 +1,6888 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include + +#ifdef TORRENT_DEBUG +#include +#endif + +#ifdef TORRENT_USE_OPENSSL +#include +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#ifndef TORRENT_DISABLE_LOGGING +#include // for va_start, va_end +#include // for vsnprintf +#include "libtorrent/socket_io.hpp" +#endif + +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/identify_client.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/file.hpp" +#include "libtorrent/version.hpp" +#include "libtorrent/extensions.hpp" +#include "libtorrent/aux_/session_interface.hpp" +#include "libtorrent/peer_list.hpp" +#include "libtorrent/socket_type.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/broadcast_socket.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/peer_info.hpp" +#include "libtorrent/bt_peer_connection.hpp" +#include "libtorrent/network_thread_pool.hpp" +#include "libtorrent/error.hpp" +#include "libtorrent/alloca.hpp" +#include "libtorrent/disk_interface.hpp" +#include "libtorrent/bandwidth_manager.hpp" +#include "libtorrent/request_blocks.hpp" // for request_a_block +#include "libtorrent/performance_counters.hpp" // for counters +#include "libtorrent/alert_manager.hpp" // for alert_manageralert_manager +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/kademlia/node_id.hpp" +#include "libtorrent/close_reason.hpp" +#include "libtorrent/aux_/time.hpp" + +//#define TORRENT_CORRUPT_DATA + +using boost::shared_ptr; + +namespace libtorrent +{ + + enum + { + // the limits of the download queue size + min_request_queue = 2 + }; + + namespace { + + bool pending_block_in_buffer(pending_block const& pb) + { + return pb.send_buffer_offset != pending_block::not_in_buffer; + } + + } + +#if TORRENT_USE_ASSERTS + bool peer_connection::is_single_thread() const + { + boost::shared_ptr t = m_torrent.lock(); + if (!t) return true; + return t->is_single_thread(); + } +#endif + + // outbound connection + peer_connection::peer_connection(peer_connection_args const& pack) + : peer_connection_hot_members(pack.tor, *pack.ses, *pack.sett) + , m_socket(pack.s) + , m_peer_info(pack.peerinfo) + , m_counters(*pack.stats_counters) + , m_num_pieces(0) + , m_recv_buffer(*pack.allocator) + , m_max_out_request_queue(m_settings.get_int(settings_pack::max_out_request_queue)) + , m_remote(pack.endp) + , m_disk_thread(*pack.disk_thread) + , m_allocator(*pack.allocator) + , m_ios(*pack.ios) + , m_work(m_ios) + , m_last_piece(aux::time_now()) + , m_last_request(aux::time_now()) + , m_last_incoming_request(min_time()) + , m_last_unchoke(aux::time_now()) + , m_last_unchoked(aux::time_now()) + , m_last_choke(min_time()) + , m_last_receive(aux::time_now()) + , m_last_sent(aux::time_now()) + , m_last_sent_payload(aux::time_now()) + , m_requested(min_time()) + , m_remote_dl_update(aux::time_now()) + , m_connect(aux::time_now()) + , m_became_uninterested(aux::time_now()) + , m_became_uninteresting(aux::time_now()) + , m_downloaded_at_last_round(0) + , m_uploaded_at_last_round(0) + , m_uploaded_at_last_unchoke(0) + , m_downloaded_last_second(0) + , m_uploaded_last_second(0) + , m_outstanding_bytes(0) + , m_last_seen_complete(0) + , m_receiving_block(piece_block::invalid) + , m_extension_outstanding_bytes(0) + , m_queued_time_critical(0) + , m_reading_bytes(0) + , m_picker_options(0) + , m_num_invalid_requests(0) + , m_remote_pieces_dled(0) + , m_remote_dl_rate(0) + , m_outstanding_writing_bytes(0) + , m_download_rate_peak(0) + , m_upload_rate_peak(0) + , m_send_barrier(INT_MAX) + , m_desired_queue_size(4) + , m_prefer_contiguous_blocks(0) + , m_disk_read_failures(0) + , 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) + , m_share_mode(false) + , m_upload_only(false) + , m_bitfield_received(false) + , m_no_download(false) + , m_sent_suggests(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) +#if TORRENT_USE_ASSERTS + , m_in_constructor(true) + , m_disconnect_started(false) + , m_initialized(false) + , m_in_use(1337) + , m_received_in_piece(0) + , m_destructed(false) + , m_socket_is_writing(false) +#endif + { + m_counters.inc_stats_counter(counters::num_tcp_peers + m_socket->type() - 1); + + 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); + + m_superseed_piece[0] = -1; + m_superseed_piece[1] = -1; + boost::shared_ptr t = m_torrent.lock(); + // if t is NULL, we better not be connecting, since + // we can't decrement the connecting counter + TORRENT_ASSERT(t || !m_connecting); + if (m_connecting && t) t->inc_num_connecting(); + m_est_reciprocation_rate = m_settings.get_int(settings_pack::default_est_reciprocation_rate); + + 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 == 0 || pack.peerinfo->banned == false); +#ifndef TORRENT_NO_DEPRECATE +#ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES + std::fill(m_country, m_country + 2, 0); +#endif +#endif // TORRENT_NO_DEPRECATE +#ifndef TORRENT_DISABLE_LOGGING + error_code ec; + TORRENT_ASSERT(m_socket->remote_endpoint(ec) == m_remote || ec); + tcp::endpoint local_ep = m_socket->local_endpoint(ec); + + peer_log(m_outgoing ? peer_log_alert::outgoing : peer_log_alert::incoming + , m_outgoing ? "OUTGOING_CONNECTION" : "INCOMING_CONNECTION" + , "ep: %s type: %s seed: %d p: %p local: %s" + , print_endpoint(m_remote).c_str() + , m_socket->type_name() + , m_peer_info ? m_peer_info->seed : 0 + , static_cast(m_peer_info) + , print_endpoint(local_ep).c_str()); +#endif +#ifdef TORRENT_DEBUG + piece_failed = false; +#endif + std::fill(m_peer_id.begin(), m_peer_id.end(), 0); + } + + 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::increase_est_reciprocation_rate() + { + TORRENT_ASSERT(is_single_thread()); + m_est_reciprocation_rate += m_est_reciprocation_rate + * m_settings.get_int(settings_pack::increase_est_reciprocation_rate) / 100; + } + + void peer_connection::decrease_est_reciprocation_rate() + { + TORRENT_ASSERT(is_single_thread()); + m_est_reciprocation_rate -= m_est_reciprocation_rate + * m_settings.get_int(settings_pack::decrease_est_reciprocation_rate) / 100; + } + + int peer_connection::get_priority(int 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; + } + + boost::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 == 0 || m_peer_info->connection == this); + boost::shared_ptr t = m_torrent.lock(); + + if (!m_outgoing) + { + tcp::socket::non_blocking_io ioc(true); + error_code ec; + m_socket->io_control(ioc, ec); + if (ec) + { + disconnect(ec, op_iocontrol); + return; + } + m_remote = m_socket->remote_endpoint(ec); + if (ec) + { + disconnect(ec, op_getpeername); + return; + } + m_local = m_socket->local_endpoint(ec); + if (ec) + { + disconnect(ec, op_getname); + return; + } + if (m_remote.address().is_v4() && m_settings.get_int(settings_pack::peer_tos) != 0) + { + m_socket->set_option(type_of_service(m_settings.get_int(settings_pack::peer_tos)), ec); +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing, "SET_TOS", "tos: %d e: %s" + , m_settings.get_int(settings_pack::peer_tos), ec.message().c_str()); +#endif + } +#if TORRENT_USE_IPV6 && defined IPV6_TCLASS + else if (m_remote.address().is_v6() && m_settings.get_int(settings_pack::peer_tos) != 0) + { + m_socket->set_option(traffic_class(m_settings.get_int(settings_pack::peer_tos)), ec); + } +#endif + } + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "SET_PEER_CLASS", "a: %s" + , print_address(m_remote.address()).c_str()); +#endif + + m_ses.set_peer_classes(this, m_remote.address(), m_socket->type()); + +#ifndef TORRENT_DISABLE_LOGGING + for (int i = 0; i < num_classes(); ++i) + { + peer_log(peer_log_alert::info, "CLASS", "%s" + , m_ses.peer_classes().at(class_at(i))->label.c_str()); + } +#endif + + if (t && t->ready_for_connections()) + { + init(); + } + + // if this is an incoming connection, we're done here + if (!m_connecting) return; + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing, "OPEN", "protocol: %s" + , (m_remote.address().is_v4()?"IPv4":"IPv6")); +#endif + error_code ec; + m_socket->open(m_remote.protocol(), ec); + if (ec) + { + disconnect(ec, op_sock_open); + return; + } + + tcp::endpoint bound_ip = m_ses.bind_outgoing_socket(*m_socket + , m_remote.address(), ec); +#ifndef TORRENT_DISABLE_LOGGING + 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, op_sock_bind); + return; + } + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing, "ASYNC_CONNECT", "dst: %s" + , print_endpoint(m_remote).c_str()); +#endif +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("peer_connection::on_connection_complete"); +#endif + +#ifndef TORRENT_DISABLE_LOGGING + if (t) + t->debug_log("START connect [%p] (%d)", static_cast(this) + , int(t->num_peers())); +#endif + + m_socket->async_connect(m_remote + , boost::bind(&peer_connection::on_connection_complete, self(), _1)); + m_connect = aux::time_now(); + + sent_syn(m_remote.address().is_v6()); + + if (t && t->alerts().should_post()) + { + t->alerts().emplace_alert( + t->get_handle(), remote(), pid(), m_socket->type()); + } +#ifndef TORRENT_DISABLE_LOGGING + 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 + m_ios.post(boost::bind(&peer_connection::do_update_interest, self())); + } + 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; + + boost::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.size() == 0) + { +#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(); + int num_pieces = p.num_pieces(); + for (int j = 0; j != num_pieces; ++j) + { + if (m_have_piece[j] + && t->piece_priority(j) > 0 + && !p.has_piece_passed(j)) + { + interested = true; +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "UPDATE_INTEREST", "interesting, piece: %d", 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 + void peer_connection::peer_log(peer_log_alert::direction_t direction + , char const* event) const + { + 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 + { + TORRENT_ASSERT(is_single_thread()); + + if (!m_ses.alerts().should_post()) return; + + va_list v; + va_start(v, fmt); + + // TODO: it would be neat to be able to print this straight into the + // alert's stack allocator + char buf[512]; + vsnprintf(buf, sizeof(buf), fmt, v); + va_end(v); + + torrent_handle h; + boost::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, buf); + } +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + void peer_connection::add_extension(boost::shared_ptr ext) + { + TORRENT_ASSERT(is_single_thread()); + m_extensions.push_back(ext); + } + + peer_plugin const* peer_connection::find_plugin(char const* type) + { + TORRENT_ASSERT(is_single_thread()); + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + if (strcmp((*i)->type(), type) == 0) return (*i).get(); + } + return 0; + } +#endif + + void peer_connection::send_allowed_set() + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + boost::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + + if (t->super_seeding()) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ALLOWED", "skipping allowed set because of super seeding"); +#endif + return; + } + + 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; + + 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 (int i = 0; i < num_pieces; ++i) + { + // 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()); + } +#if TORRENT_USE_IPV6 + else + { + address_v6::bytes_type bytes = addr.to_v6().to_bytes(); + x.assign(reinterpret_cast(bytes.data()), bytes.size()); + } +#endif + x.append(t->torrent_file().info_hash().data(), 20); + + sha1_hash hash = hasher(x.c_str(), x.size()).final(); + int attempts = 0; + int loops = 0; + for (;;) + { + char const* p = hash.data(); + for (int i = 0; i < hash.size / sizeof(boost::uint32_t); ++i) + { + ++loops; + int const piece = detail::read_uint32(p) % 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.data(), 20).final(); + } + } + + void peer_connection::on_metadata_impl() + { + TORRENT_ASSERT(is_single_thread()); + boost::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(); + + // 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 + for (std::vector::iterator i = m_allowed_fast.begin(); + i != m_allowed_fast.end();) + { + if (*i < m_num_pieces) + { + ++i; + continue; + } + i = m_allowed_fast.erase(i); + } + + for (std::vector::iterator i = m_suggested_pieces.begin(); + i != m_suggested_pieces.end();) + { + if (*i < m_num_pieces) + { + ++i; + continue; + } + i = m_suggested_pieces.erase(i); + } + on_metadata(); + if (m_disconnecting) return; + } + + void peer_connection::init() + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + boost::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(); +#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 == int(m_have_piece.size())) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "INIT", "this is a seed p: %p" + , static_cast(m_peer_info)); +#endif + + TORRENT_ASSERT(m_have_piece.all_set()); + TORRENT_ASSERT(m_have_piece.count() == m_have_piece.size()); + TORRENT_ASSERT(m_have_piece.size() == t->torrent_file().num_pieces()); + + // if this is a web seed. we don't have a peer_info struct + t->set_seed(m_peer_info, true); + m_upload_only = true; + + t->peer_has_all(this); + +#if TORRENT_USE_INVARIANT_CHECKS + if (t && t->has_picker()) + t->picker().check_peer_invariant(m_have_piece, peer_info_struct()); +#endif + if (t->is_upload_only()) send_not_interested(); + else t->peer_is_interesting(*this); + return; + } + + // if we're a seed, we don't keep track of piece availability + if (t->has_picker()) + { + TORRENT_ASSERT(m_have_piece.size() == t->torrent_file().num_pieces()); + t->peer_has(m_have_piece, this); + bool interesting = false; + for (int i = 0; i < int(m_have_piece.size()); ++i) + { + if (m_have_piece[i]) + { + // if the peer has a piece and we don't, the peer is interesting + if (!t->have_piece(i) + && t->picker().piece_priority(i) != 0) + interesting = true; + } + } + if (interesting) t->peer_is_interesting(*this); + else send_not_interested(); + } + else + { + update_interest(); + } + } + + peer_connection::~peer_connection() + { + m_counters.inc_stats_counter(counters::num_tcp_peers + m_socket->type() - 1, -1); + +// INVARIANT_CHECK; + TORRENT_ASSERT(!m_in_constructor); + TORRENT_ASSERT(m_disconnecting); + TORRENT_ASSERT(m_disconnect_started); + 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 + boost::shared_ptr t = m_torrent.lock(); + // if t is NULL, 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 + TORRENT_ASSERT(!m_connecting); + if (m_connecting) + { + m_counters.inc_stats_counter(counters::num_peers_half_open, -1); + if (t) t->dec_num_connecting(); + 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()); + +#if TORRENT_USE_ASSERTS + if (m_peer_info) + TORRENT_ASSERT(m_peer_info->connection == 0); +#endif + } + + bool peer_connection::on_parole() const + { return peer_info_struct() && peer_info_struct()->on_parole; } + + int peer_connection::picker_options() const + { + TORRENT_ASSERT(is_single_thread()); + int ret = m_picker_options; + + boost::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + if (!t) return 0; + + if (t->num_time_critical_pieces() > 0) + { + ret |= piece_picker::time_critical_mode; + } + + if (t->is_sequential_download()) + { + ret |= piece_picker::sequential; + } + else if (t->num_have() < m_settings.get_int(settings_pack::initial_picker_threshold)) + { + // if we have fewer pieces than a certain threshold + // don't pick rare pieces, just pick random ones, + // and prioritize finishing them + ret |= piece_picker::prioritize_partials; + } + else + { + ret |= piece_picker::rarest_first; + } + + if (m_snubbed) + { + // snubbed peers should request + // the common pieces first, just to make + // it more likely for all snubbed peers to + // request blocks from the same piece + ret |= piece_picker::reverse; + } + + 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 = boost::uint16_t(m_ses.session_time()); + int rewind = m_settings.get_int(settings_pack::min_reconnect_time) + * m_settings.get_int(settings_pack::max_failcount); + if (peer_info_struct()->last_connected < rewind) peer_info_struct()->last_connected = 0; + else peer_info_struct()->last_connected -= rewind; + + if (peer_info_struct()->fast_reconnects < 15) + ++peer_info_struct()->fast_reconnects; + } + + void peer_connection::received_piece(int 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", index); +#endif + + // remove suggested pieces once we have them + std::vector::iterator 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 + boost::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); +#endif + } + + void peer_connection::announce_piece(int index) + { + TORRENT_ASSERT(is_single_thread()); + // dont announce during handshake + if (in_handshake()) return; + + if (has_piece(index)) + { + // optimization, don't send have messages + // to peers that already have the piece + if (!m_settings.get_bool(settings_pack::send_redundant_have)) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "HAVE", "piece: %d SUPRESSED", index); +#endif + return; + } + } + + if (disconnect_if_redundant()) return; + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "HAVE", "piece: %d", index); +#endif + write_have(index); +#if TORRENT_USE_ASSERTS + boost::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); +#endif + } + + bool peer_connection::has_piece(int i) const + { + TORRENT_ASSERT(is_single_thread()); + boost::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + TORRENT_ASSERT(t->valid_metadata()); + TORRENT_ASSERT(i >= 0); + TORRENT_ASSERT(i < t->torrent_file().num_pieces()); + 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 extra_bytes) const + { + TORRENT_ASSERT(is_single_thread()); + boost::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + + int rate = 0; + + // if we haven't received any data recently, the current download rate + // is not representative + if (aux::time_now() - m_last_piece > seconds(30) && m_download_rate_peak > 0) + { + rate = m_download_rate_peak; + } + else if (aux::time_now() - m_last_unchoked < seconds(5) + && m_statistics.total_payload_upload() < 2 * 0x4000) + { + // if we're have only been unchoked for a short period of time, + // we don't know what rate we can get from this peer. Instead of assuming + // the lowest possible rate, assume the average. + + int peers_with_requests = 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(boost::int64_t downloaded, boost::int64_t uploaded) + { + TORRENT_ASSERT(is_single_thread()); + m_statistics.add_stat(downloaded, uploaded); + } + + void peer_connection::received_bytes(int bytes_payload, int bytes_protocol) + { + TORRENT_ASSERT(is_single_thread()); + m_statistics.received_bytes(bytes_payload, bytes_protocol); + if (m_ignore_stats) return; + boost::shared_ptr t = m_torrent.lock(); + if (!t) return; + t->received_bytes(bytes_payload, bytes_protocol); + } + + void peer_connection::sent_bytes(int bytes_payload, int bytes_protocol) + { + TORRENT_ASSERT(is_single_thread()); + m_statistics.sent_bytes(bytes_payload, bytes_protocol); +#ifndef TORRENT_DISABLE_EXTENSIONS + if (bytes_payload) + { + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + (*i)->sent_payload(bytes_payload); + } + } +#endif + if (m_ignore_stats) return; + boost::shared_ptr t = m_torrent.lock(); + if (!t) return; + t->sent_bytes(bytes_payload, bytes_protocol); + } + + void peer_connection::trancieve_ip_packet(int bytes, bool ipv6) + { + TORRENT_ASSERT(is_single_thread()); + m_statistics.trancieve_ip_packet(bytes, ipv6); + if (m_ignore_stats) return; + boost::shared_ptr t = m_torrent.lock(); + if (!t) return; + t->trancieve_ip_packet(bytes, ipv6); + } + + void peer_connection::sent_syn(bool ipv6) + { + TORRENT_ASSERT(is_single_thread()); + m_statistics.sent_syn(ipv6); + if (m_ignore_stats) return; + boost::shared_ptr t = m_torrent.lock(); + if (!t) return; + t->sent_syn(ipv6); + } + + void peer_connection::received_synack(bool ipv6) + { + TORRENT_ASSERT(is_single_thread()); + m_statistics.received_synack(ipv6); + if (m_ignore_stats) return; + boost::shared_ptr t = m_torrent.lock(); + if (!t) return; + t->received_synack(ipv6); + } + + bitfield const& peer_connection::get_bitfield() const + { + TORRENT_ASSERT(is_single_thread()); + return m_have_piece; + } + + void peer_connection::received_valid_data(int 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 (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + TORRENT_TRY { + (*i)->on_piece_pass(index); + } TORRENT_CATCH(std::exception&) {} + } +#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(int index, bool single_peer) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + TORRENT_UNUSED(single_peer); + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + TORRENT_TRY { + (*i)->on_piece_failed(index); + } TORRENT_CATCH(std::exception&) {} + } +#else + TORRENT_UNUSED(index); +#endif + return true; + } + + // verifies a piece to see if it is valid (is within a valid range) + // and if it can correspond to a request generated by libtorrent. + bool peer_connection::verify_piece(const peer_request& p) const + { + TORRENT_ASSERT(is_single_thread()); + boost::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + + TORRENT_ASSERT(t->valid_metadata()); + torrent_info const& ti = t->torrent_file(); + + return p.piece >= 0 + && p.piece < ti.num_pieces() + && p.start >= 0 + && p.start < ti.piece_length() + && t->to_req(piece_block(p.piece, p.start / t->block_size())) == p; + } + + void peer_connection::attach_to_torrent(sha1_hash const& ih) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + +#ifndef TORRENT_DISABLE_LOGGING + m_connect_time = clock_type::now(); + peer_log(peer_log_alert::info, "ATTACH", "attached to torrent"); +#endif + + TORRENT_ASSERT(!m_disconnecting); + TORRENT_ASSERT(m_torrent.expired()); + boost::weak_ptr wpt = m_ses.find_torrent(ih); + boost::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) + peer_log(peer_log_alert::info, "ATTACH" + , "Delay loaded torrent: %s:", to_hex(ih.to_string()).c_str()); +#endif + } + + if (!t) + { + // we couldn't find the torrent! +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ATTACH" + , "couldn't find a torrent with the given info_hash: %s torrents:" + , to_hex(ih.to_string()).c_str()); +#endif + +#ifndef TORRENT_DISABLE_DHT + if (dht::verify_secret_id(ih)) + { + // this means the hash was generated from our generate_secret_id() + // as part of DHT traffic. The fact that we got an incoming + // connection on this info-hash, means the other end, making this + // connection fished it out of the DHT chatter. That's suspicious. + m_ses.ban_ip(m_remote.address()); + } +#endif + disconnect(errors::invalid_info_hash, op_bittorrent, 1); + return; + } + + if (t->is_paused() + && t->is_auto_managed() + && m_settings.get_bool(settings_pack::incoming_starts_queued_torrents) + && !t->is_aborted()) + { + t->resume(); + } + + if (t->is_paused() || t->is_aborted() || t->graceful_pause()) + { + // paused torrents will not accept + // incoming connections unless they are auto managed + // and inconing_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, op_bittorrent, 2); + return; + } + +#if TORRENT_USE_I2P + i2p_stream* i2ps = m_socket->get(); + if (!i2ps && t->torrent_file().is_i2p() + && !m_settings.get_bool(settings_pack::allow_i2p_mixed)) + { + // the torrent is an i2p torrent, the peer is a regular peer + // and we don't allow mixed mode. Disconnect the peer. +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ATTACH", "rejected regular connection to i2p torrent"); +#endif + disconnect(errors::peer_banned, op_bittorrent, 2); + return; + } +#endif // TORRENT_USE_I2P + + TORRENT_ASSERT(m_torrent.expired()); + + // check to make sure we don't have another connection with the same + // info_hash and peer_id. If we do. close this connection. + t->attach_peer(this); + if (m_disconnecting) return; + // it's important to assign the torrent after successfully attaching. + // if the peer disconnects while attaching, it's not a proper member + // of the torrent and peer_connection::disconnect() will fail if it + // think it is + m_torrent = t; + + if (m_exceeded_limit) + { + // find a peer in some torrent (presumably the one with most peers) + // and disconnect the lowest ranking peer + boost::weak_ptr torr = m_ses.find_disconnect_candidate_torrent(); + boost::shared_ptr other_t = torr.lock(); + + if (other_t) + { + if (other_t->num_peers() <= t->num_peers()) + { + disconnect(errors::too_many_connections, op_bittorrent); + return; + } + // find the lowest ranking peer and disconnect that + peer_connection* p = other_t->find_lowest_ranking_peer(); + p->disconnect(errors::too_many_connections, op_bittorrent); + peer_disconnected_other(); + } + else + { + disconnect(errors::too_many_connections, op_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()); + } + + boost::uint32_t peer_connection::peer_rank() const + { + TORRENT_ASSERT(is_single_thread()); + return m_peer_info == NULL ? 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 (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + if ((*i)->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()); + boost::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() == 0 || !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 (std::vector::const_iterator i = m_request_queue.begin() + , end(m_request_queue.end()); i != end; ++i) + { + p.abort_download(i->block, peer_info_struct()); + } + m_request_queue.clear(); + m_queued_time_critical = 0; + } + } + + namespace { + + bool match_request(peer_request const& r, piece_block const& b, int block_size) + { + if (int(b.piece_index) != r.piece) return false; + if (int(b.block_index) != r.start / block_size) return false; + if (r.start % block_size != 0) return false; + return true; + } + } + + // ----------------------------- + // -------- REJECT PIECE ------- + // ----------------------------- + + void peer_connection::incoming_reject_request(peer_request const& r) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + boost::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" + , r.piece, r.start, r.length); +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + if ((*i)->on_reject(r)) return; + } +#endif + + if (is_disconnecting()) return; + + std::vector::iterator dlq_iter = std::find_if( + m_download_queue.begin(), m_download_queue.end() + , boost::bind(match_request, boost::cref(r), boost::bind(&pending_block::block, _1) + , t->block_size())); + + if (dlq_iter != m_download_queue.end()) + { + pending_block b = *dlq_iter; + bool 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"); + } +#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. + std::vector::iterator 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 + { + std::vector::iterator 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(int index) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "SUGGEST_PIECE" + , "piece: %d", index); +#endif + boost::shared_ptr t = m_torrent.lock(); + if (!t) return; + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + if ((*i)->on_suggest(index)) return; + } +#endif + + if (is_disconnecting()) return; + if (index < 0) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "INVALID_SUGGEST_PIECE" + , "%d", index); +#endif + return; + } + + if (t->valid_metadata()) + { + if (index >= int(m_have_piece.size())) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "INVALID_SUGGEST" + , "%d s: %d", index, int(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 (int(m_suggested_pieces.size()) > 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" + , index, int(m_suggested_pieces.size())); +#endif + } + + // ----------------------------- + // ---------- UNCHOKE ---------- + // ----------------------------- + + void peer_connection::incoming_unchoke() + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + boost::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + +#ifndef TORRENT_DISABLE_LOGGING + m_unchoke_time = clock_type::now(); + t->debug_log("UNCHOKE [%p] (%d ms)", static_cast(this) + , int(total_milliseconds(m_unchoke_time - m_bitfield_time))); +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + if ((*i)->on_unchoke()) return; + } +#endif + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "UNCHOKE"); +#endif + if (m_peer_choked) + m_counters.inc_stats_counter(counters::num_peers_down_unchoked); + + m_peer_choked = false; + m_last_unchoked = aux::time_now(); + if (is_disconnecting()) return; + + if (is_interesting()) + { + if (request_a_block(*t, *this)) + m_counters.inc_stats_counter(counters::unchoke_piece_picks); + send_block_requests(); + } + } + + // ----------------------------- + // -------- INTERESTED --------- + // ----------------------------- + + void peer_connection::incoming_interested() + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + boost::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + if ((*i)->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 expempted 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. + + boost::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + + t->unchoke_peer(*this); + } +#ifndef TORRENT_DISABLE_LOGGING + else + { + peer_log(peer_log_alert::info, "UNCHOKE", "did not unchoke, the number of uploads (%d) " + "is more than or equal to the limit (%d)" + , m_ses.num_uploads(), 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 (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + if ((*i)->on_not_interested()) return; + } +#endif + + m_became_uninterested = aux::time_now(); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "NOT_INTERESTED"); +#endif + if (m_peer_interested) + m_counters.inc_stats_counter(counters::num_peers_up_interested, -1); + + m_peer_interested = false; + if (is_disconnecting()) return; + + boost::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; + } + + boost::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(int index) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + boost::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + if ((*i)->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(); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "HAVE", "piece: %d", index); +#endif + + if (is_disconnecting()) return; + + if (!t->valid_metadata() && index >= int(m_have_piece.size())) + { + if (index < 131072) + { + // 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(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 >= int(m_have_piece.size()) || index < 0) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ERROR", "have-metadata have_piece: %d size: %d" + , index, int(m_have_piece.size())); +#endif + disconnect(errors::invalid_have, op_bittorrent, 2); + return; + } + + if (t->super_seeding() && !m_settings.get_bool(settings_pack::strict_super_seeding)) + { + // if we're superseeding and the peer just told + // us that it completed the piece we're superseeding + // to it, change the superseeding 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)); + } + } + + if (m_have_piece[index]) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming, "HAVE" + , "got redundant HAVE message for index: %d", 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); + + // this will disregard all have messages we get within + // the first two seconds. Since some clients implements + // lazy bitfields, these will not be reliable to use + // for an estimated peer download rate. + if (!peer_info_struct() + || m_ses.session_time() - peer_info_struct()->last_connected > 2) + { + // update bytes downloaded since last timer + ++m_remote_pieces_dled; + } + + // it's important to not disconnect before we have + // updated the piece picker, otherwise we will incorrectly + // decrement the piece count without first incrementing it + if (is_seed()) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "SEED", "this is a seed. p: %p" + , static_cast(m_peer_info)); +#endif + + TORRENT_ASSERT(m_have_piece.all_set()); + TORRENT_ASSERT(m_have_piece.count() == m_have_piece.size()); + TORRENT_ASSERT(m_have_piece.size() == t->torrent_file().num_pieces()); + + t->seen_complete(); + t->set_seed(m_peer_info, true); + m_upload_only = true; + +#if TORRENT_USE_INVARIANT_CHECKS + if (t && t->has_picker()) + t->picker().check_peer_invariant(m_have_piece, peer_info_struct()); +#endif + } + + // it's important to update whether we're intersted 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_seed() + && !is_interesting() + && (!t->has_picker() || t->picker().piece_priority(index) != 0)) + t->peer_is_interesting(*this); + + disconnect_if_redundant(); + if (is_disconnecting()) return; + + // 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 (torrent::peer_iterator i = t->begin() + , end(t->end()); i != end; ++i) + { + peer_connection* p = *i; + 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())); + } + } + } + + // ----------------------------- + // -------- DONT HAVE ---------- + // ----------------------------- + + void peer_connection::incoming_dont_have(int index) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + boost::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + if ((*i)->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", index); +#endif + + // if we got an invalid message, abort + if (index >= int(m_have_piece.size()) || index < 0) + { + disconnect(errors::invalid_dont_have, op_bittorrent, 2); + 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", index); +#endif + return; + } + + bool was_seed = is_seed(); + m_have_piece.clear_bit(index); + TORRENT_ASSERT(m_num_pieces > 0); + --m_num_pieces; + + // only update the piece_picker if + // we have the metadata and if + // we're not a seed (in which case + // we won't have a piece picker) + if (!t->valid_metadata()) return; + + t->peer_lost(index, this); + + if (was_seed) + t->set_seed(m_peer_info, false); + } + + // ----------------------------- + // --------- BITFIELD ---------- + // ----------------------------- + + void peer_connection::incoming_bitfield(bitfield const& bits) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + boost::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + if ((*i)->on_bitfield(bits)) return; + } +#endif + + if (is_disconnecting()) return; + +#ifndef TORRENT_DISABLE_LOGGING + std::string bitfield_str; + bitfield_str.resize(bits.size()); + for (int i = 0; i < int(bits.size()); ++i) + bitfield_str[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 metedata, we cannot + // verify the bitfield size + if (t->valid_metadata() + && bits.size() != int(m_have_piece.size())) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "BITFIELD" + , "invalid size: %d expected %d", bits.size() + , int(m_have_piece.size())); +#endif + disconnect(errors::invalid_bitfield_size, op_bittorrent, 2); + 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; + +#ifndef TORRENT_DISABLE_LOGGING + m_bitfield_time = clock_type::now(); + t->debug_log("HANDSHAKE [%p] (%d ms)" + , static_cast(this) + , int(total_milliseconds(m_bitfield_time - m_connect_time))); +#endif + // 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 == int(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 == int(bits.size())); + +#if TORRENT_USE_INVARIANT_CHECKS + if (t && t->has_picker()) + t->picker().check_peer_invariant(m_have_piece, peer_info_struct()); +#endif + return; + } + + TORRENT_ASSERT(t->valid_metadata()); + + int num_pieces = bits.count(); + if (num_pieces == int(m_have_piece.size())) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "SEED", "this is a seed. p: %p" + , static_cast(m_peer_info)); +#endif + + t->set_seed(m_peer_info, true); + m_upload_only = true; + + m_have_piece.set_all(); + m_num_pieces = num_pieces; + t->peer_has_all(this); + + TORRENT_ASSERT(m_have_piece.all_set()); + TORRENT_ASSERT(m_have_piece.count() == m_have_piece.size()); + TORRENT_ASSERT(m_have_piece.size() == t->torrent_file().num_pieces()); + +#if TORRENT_USE_INVARIANT_CHECKS + if (t && t->has_picker()) + t->picker().check_peer_invariant(m_have_piece, peer_info_struct()); +#endif + + // this will cause us to send the INTERESTED message + if (!t->is_upload_only()) + t->peer_is_interesting(*this); + + disconnect_if_redundant(); + + return; + } + + // let the torrent know which pieces the peer has if we're a seed, we + // don't keep track of piece availability + t->peer_has(bits, this); + + m_have_piece = bits; + m_num_pieces = num_pieces; + + update_interest(); + } + + bool peer_connection::disconnect_if_redundant() + { + TORRENT_ASSERT(is_single_thread()); + if (m_disconnecting) return false; + if (m_need_interest_update) return false; + + // we cannot disconnect in a constructor + TORRENT_ASSERT(m_in_constructor == false); + if (!m_settings.get_bool(settings_pack::close_redundant_connections)) return false; + + boost::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; + + // don't close connections in share mode, we don't know if we need them + if (t->share_mode()) return false; + + if (m_upload_only && t->is_upload_only() + && can_disconnect(error_code(errors::upload_upload_connection, get_libtorrent_category()))) + { +#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, op_bittorrent); + return true; + } + + if (m_upload_only + && !m_interesting + && m_bitfield_received + && t->are_files_checked() + && can_disconnect(error_code(errors::uninteresting_upload_peer, get_libtorrent_category()))) + { +#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, op_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 (extension_list_t::const_iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + if (!(*i)->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; + + boost::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + + m_counters.inc_stats_counter(counters::piece_requests); + +#ifndef TORRENT_DISABLE_LOGGING + const bool valid_piece_index + = r.piece >= 0 && r.piece < int(t->torrent_file().num_pieces()); + + peer_log(peer_log_alert::incoming_message, "REQUEST" + , "piece: %d s: %x l: %x", r.piece, r.start, r.length); +#endif + + if (t->super_seeding() + && !super_seeded_piece(r.piece)) + { + m_counters.inc_stats_counter(counters::invalid_piece_requests); + ++m_num_invalid_requests; +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "INVALID_REQUEST", "piece not superseeded " + "i: %d t: %d n: %d h: %d ss1: %d ss2: %d" + , m_peer_interested + , valid_piece_index + ? int(t->torrent_file().piece_size(r.piece)) + : -1 + , t->torrent_file().num_pieces() + , valid_piece_index ? t->has_piece_passed(r.piece) : 0 + , m_superseed_piece[0] + , 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 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; + } + + // 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 (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + if ((*i)->on_request(r)) return; + } +#endif + if (is_disconnecting()) return; + + if (!t->valid_metadata()) + { + m_counters.inc_stats_counter(counters::invalid_piece_requests); + // if we don't have valid metadata yet, + // we shouldn't get a request +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "INVALID_REQUEST", "we don't have metadata yet"); + peer_log(peer_log_alert::outgoing_message, "REJECT_PIECE", "piece: %d s: %x l: %x no metadata" + , r.piece, r.start, r.length); +#endif + write_reject_request(r); + return; + } + + if (int(m_requests.size()) > m_settings.get_int(settings_pack::max_allowed_in_request_queue)) + { + m_counters.inc_stats_counter(counters::max_piece_requests); + // don't allow clients to abuse our + // memory consumption. + // ignore requests if the client + // is making too many of them. +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "INVALID_REQUEST", "incoming request queue full %d" + , int(m_requests.size())); + peer_log(peer_log_alert::outgoing_message, "REJECT_PIECE", "piece: %d s: %x l: %x too many requests" + , r.piece, r.start, r.length); +#endif + write_reject_request(r); + return; + } + + int fast_idx = -1; + std::vector::iterator fast_iter = std::find(m_accept_fast.begin() + , m_accept_fast.end(), r.piece); + if (fast_iter != m_accept_fast.end()) fast_idx = fast_iter - m_accept_fast.begin(); + + if (!m_peer_interested) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "INVALID_REQUEST", "peer is not interested " + " t: %d n: %d block_limit: %d" + , valid_piece_index + ? int(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()) + { + // msvc 12 appears to deduce the rvalue reference template + // incorrectly for bool temporaries. So, create a dummy instance + bool 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); + } + + // 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 < 0 + || r.piece >= t->torrent_file().num_pieces() + || (!t->has_piece_passed(r.piece) + && !t->is_predictive_piece(r.piece) + && !t->seed_mode()) + || r.start < 0 + || r.start >= t->torrent_file().piece_size(r.piece) + || r.length <= 0 + || r.length + r.start > t->torrent_file().piece_size(r.piece) + || r.length > t->block_size()) + { + m_counters.inc_stats_counter(counters::invalid_piece_requests); + +#ifndef TORRENT_DISABLE_LOGGING + 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 + ? int(t->torrent_file().piece_size(r.piece)) : -1 + , t->torrent_file().num_pieces() + , t->has_piece_passed(r.piece) + , t->block_size()); + + peer_log(peer_log_alert::outgoing_message, "REJECT_PIECE" + , "piece: %d s: %d l: %d invalid request" + , r.piece , r.start , r.length); +#endif + + write_reject_request(r); + ++m_num_invalid_requests; + + if (t->alerts().should_post()) + { + // msvc 12 appears to deduce the rvalue reference template + // incorrectly for bool temporaries. So, create a dummy instance + bool 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(error_code(errors::too_many_requests_when_choked + , get_libtorrent_category()))) + { + disconnect(errors::too_many_requests_when_choked, op_bittorrent, 2); + 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 + const int blocks_per_piece = static_cast( + (t->torrent_file().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(error_code(errors::too_many_requests_when_choked, get_libtorrent_category()))) + { + disconnect(errors::too_many_requests_when_choked, op_bittorrent, 2); + return; + } + + if (m_choked && fast_idx == -1) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "REJECTING REQUEST", "peer choked and piece not in allowed fast set"); + peer_log(peer_log_alert::outgoing_message, "REJECT_PIECE", "piece: %d s: %d l: %d peer choked" + , r.piece, r.start, r.length); +#endif + m_counters.inc_stats_counter(counters::choked_piece_requests); + write_reject_request(r); + + // allow peers to send request up to 2 seconds after getting choked, + // then disconnect them + if (aux::time_now() - seconds(2) > m_last_choke + && can_disconnect(error_code(errors::too_many_requests_when_choked, get_libtorrent_category()))) + { + disconnect(errors::too_many_requests_when_choked, op_bittorrent, 2); + 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 >= 0); + TORRENT_ASSERT(r.piece < t->torrent_file().num_pieces()); + + m_requests.push_back(r); + + if (t->alerts().should_post()) + { + t->alerts().emplace_alert(r, t->get_handle() + , m_remote, m_peer_id); + } + + m_last_incoming_request = aux::time_now(); + fill_send_buffer(); + } + } + + // reject all requests to this piece + void peer_connection::reject_piece(int index) + { + TORRENT_ASSERT(is_single_thread()); + for (std::vector::iterator 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 bytes) + { + TORRENT_ASSERT(is_single_thread()); + m_last_piece = aux::time_now(); + TORRENT_ASSERT(m_outstanding_bytes >= bytes); + m_outstanding_bytes -= bytes; + if (m_outstanding_bytes < 0) m_outstanding_bytes = 0; + boost::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 + buffer::const_interval recv_buffer = m_recv_buffer.get(); + int recv_pos = recv_buffer.end - recv_buffer.begin; + TORRENT_ASSERT(recv_pos >= 9); +#endif + + boost::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + + if (!verify_piece(r)) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "INVALID_PIECE", "piece: %d s: %d l: %d" + , r.piece, r.start, r.length); +#endif + disconnect(errors::invalid_piece, op_bittorrent, 2); + return; + } + + piece_block b(r.piece, r.start / t->block_size()); + m_receiving_block = b; + + bool in_req_queue = false; + for (std::vector::iterator i = m_download_queue.begin() + , end(m_download_queue.end()); i != end; ++i) + { + if (i->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 (std::vector::iterator 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, int(b.block_index), int(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 + { + check_postcondition(boost::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); + } + } + } + + shared_ptr t; + }; +#endif + + + // ----------------------------- + // ----------- PIECE ----------- + // ----------------------------- + + void peer_connection::incoming_piece(peer_request const& p, char const* data) + { + TORRENT_ASSERT(is_single_thread()); + bool exceeded = false; + char* buffer = m_allocator.allocate_disk_buffer(exceeded, self(), "receive buffer"); + + if (buffer == 0) + { + disconnect(errors::no_memory, op_alloc_recvbuf); + return; + } + + // 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 + // continously 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) == 0) + 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 + } + + disk_buffer_holder holder(m_allocator, buffer); + std::memcpy(buffer, data, p.length); + incoming_piece(p, holder); + } + + void peer_connection::incoming_piece(peer_request const& p, disk_buffer_holder& data) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + boost::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + + m_recv_buffer.assert_no_disk_buffer(); + + // 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 (m_remote.address().is_v4() + && (m_remote.address().to_v4().to_ulong() & 0xf) == 0) + { + data.get()[0] = ~data.get()[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 (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + if ((*i)->on_piece(p, data)) + { +#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 + peer_log(peer_log_alert::incoming_message, "PIECE", "piece: %d s: %x l: %x ds: %d qs: %d q: %d" + , 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, op_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, torrent::piece_seed); + return; + } + + time_point now = clock_type::now(); + + t->need_picker(); + + piece_picker& picker = t->picker(); + + piece_block block_finished(p.piece, p.start / t->block_size()); + TORRENT_ASSERT(verify_piece(p)); + + std::vector::iterator b + = std::find_if( + m_download_queue.begin() + , m_download_queue.end() + , 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, int(block_finished.block_index) + , int(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, torrent::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)) + { + torrent::wasted_reason_t reason; + if (b->timed_out) reason = torrent::piece_timed_out; + else if (b->not_wanted) reason = torrent::piece_cancelled; + else if (b->busy) reason = torrent::piece_end_game; + else reason = torrent::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(total_milliseconds(now - m_requested)); +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "REQUEST_TIME", "%d +- %d ms" + , m_request_time.mean(), m_request_time.avg_deviation()); +#endif + + // we completed an incoming block, and there are still outstanding + // requests. The next block we expect to receive now has another + // timeout period until we time out. So, reset the timer. + if (!m_download_queue.empty()) + m_requested = now; + + if (request_a_block(*t, *this)) + m_counters.inc_stats_counter(counters::incoming_redundant_piece_picks); + send_block_requests(); + return; + } + + // we received a request within the timeout, make sure this peer is + // not snubbed anymore + if (total_seconds(now - m_requested) + < request_timeout() + && m_snubbed) + { + m_snubbed = false; + if (t->alerts().should_post()) + { + t->alerts().emplace_alert(t->get_handle() + , m_remote, m_peer_id); + } + } + +#ifndef TORRENT_DISABLE_LOGGING + t->debug_log("PIECE [%p] (%d ms) (%d)", static_cast(this) + , int(total_milliseconds(clock_type::now() - m_unchoke_time)), t->num_have()); + + peer_log(peer_log_alert::info, "FILE_ASYNC_WRITE", "piece: %d s: %x l: %x" + , 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; + + if (!t->need_loaded()) + { + t->add_redundant_bytes(p.length, torrent::piece_unknown); + return; + } + t->inc_refcount("async_write"); + m_disk_thread.async_write(&t->storage(), p, data + , boost::bind(&peer_connection::on_disk_write_complete + , self(), _1, p, t)); + + boost::uint64_t write_queue_size = m_counters.inc_stats_counter( + counters::queued_write_bytes, p.length); + m_outstanding_writing_bytes += p.length; + + boost::uint64_t max_queue_size = m_settings.get_int( + settings_pack::max_queued_disk_bytes); + if (write_queue_size > max_queue_size + && write_queue_size - p.length < max_queue_size + && m_settings.get_int(settings_pack::cache_size) > 5 + && t->alerts().should_post()) + { + t->alerts().emplace_alert(t->get_handle() + , performance_alert::too_high_disk_queue_limit); + } + + m_request_time.add_sample(total_milliseconds(now - m_requested)); +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "REQUEST_TIME", "%d +- %d ms" + , m_request_time.mean(), m_request_time.avg_deviation()); +#endif + + // we completed an incoming block, and there are still outstanding + // requests. The next block we expect to receive now has another + // timeout period until we time out. So, reset the timer. + if (!m_download_queue.empty()) + m_requested = now; + + bool was_finished = picker.is_piece_finished(p.piece); + // did we request this block from any other peers? + bool multi = picker.num_peers(block_finished) > 1; +// fprintf(stderr, "peer_connection mark_as_writing peer: %p piece: %d block: %d\n" +// , peer_info_struct(), block_finished.piece_index, block_finished.block_index); + picker.mark_as_writing(block_finished, peer_info_struct()); + + TORRENT_ASSERT(picker.num_peers(block_finished) == 0); + // if we requested this block from other peers, cancel it now + if (multi) t->cancel_block(block_finished); + + if (m_settings.get_int(settings_pack::predictive_piece_announce)) + { + int piece = block_finished.piece_index; + piece_picker::downloading_piece st; + t->picker().piece_info(piece, st); + + int num_blocks = t->picker().blocks_in_piece(piece); + if (st.requested > 0 && st.writing + st.finished + st.requested == num_blocks) + { + std::vector d; + t->picker().get_downloaders(d, piece); + if (d.size() == 1) + { + // only make predictions if all remaining + // blocks are requested from the same peer + torrent_peer* peer = d[0]; + if (peer->connection) + { + // we have a connection. now, what is the current + // download rate from this peer, and how many blocks + // do we have left to download? + boost::int64_t rate = peer->connection->statistics().download_payload_rate(); + boost::int64_t bytes_left = boost::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, (bytes_left * 1000) / rate); + } + } + } + } + } + + 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. + boost::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, op_bittorrent); + } + + void peer_connection::on_disk_write_complete(disk_io_job const* j + , peer_request p, boost::shared_ptr t) + { + TORRENT_ASSERT(is_single_thread()); + torrent_ref_holder h(t.get(), "async_write"); + if (t) t->dec_refcount("async_write"); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "FILE_ASYNC_WRITE_COMPLETE", "ret: %d piece: %d s: %x l: %x e: %s" + , j->ret, p.piece, p.start, p.length, j->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; + } + + // flush send buffer at the end of + // this burst of disk events +// m_ses.cork_burst(this); + + INVARIANT_CHECK; + + if (!t) + { + disconnect(j->error.ec, op_file_write); + return; + } + + t->schedule_storage_tick(); + + // in case the outstanding bytes just dropped down + // to allow to receive more data + setup_receive(); + + piece_block block_finished(p.piece, p.start / t->block_size()); + + if (j->ret < 0) + { + // handle_disk_error may disconnect us + t->handle_disk_error(j, this); + return; + } + + TORRENT_ASSERT(j->ret == p.length); + + if (!t->has_picker()) return; + + piece_picker& picker = t->picker(); + + TORRENT_ASSERT(p.piece == j->piece); + TORRENT_ASSERT(p.start == j->d.io.offset); + TORRENT_ASSERT(picker.num_peers(block_finished) == 0); + + if (j->ret == -1 + && j->error.ec == boost::system::errc::operation_canceled) + { + picker.mark_as_canceled(block_finished, peer_info_struct()); + TORRENT_ASSERT(false); // how do we get here? + return; + } +// 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(), int(block_finished.block_index) + , int(block_finished.piece_index)); + } + + disconnect_if_redundant(); + + if (m_disconnecting) return; + +#if TORRENT_USE_ASSERTS + if (t->has_picker()) + { + const std::vector& q + = picker.get_download_queue(); + + for (std::vector::const_iterator + i = q.begin(), end(q.end()); i != end; ++i) + { + if (i->index != block_finished.piece_index) continue; + piece_picker::block_info* info = picker.blocks_for_piece(*i); + 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 (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + if ((*i)->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", r.piece, r.start, r.length); +#endif + + std::vector::iterator i + = std::find(m_requests.begin(), m_requests.end(), r); + + if (i != m_requests.end()) + { + m_counters.inc_stats_counter(counters::cancelled_piece_requests); + m_requests.erase(i); + + if (m_requests.empty()) + m_counters.inc_stats_counter(counters::num_peers_up_requests, -1); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "REJECT_PIECE", "piece: %d s: %x l: %x cancelled" + , r.piece , r.start , r.length); +#endif + write_reject_request(r); + } + else + { + // TODO: 2 since we throw away the queue entry once we issue + // the disk job, this may happen. Instead, we should keep the + // queue entry around, mark it as having been requested from + // disk and once the disk job comes back, discard it if it has + // been cancelled. Maybe even be able to cancel disk jobs? +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "INVALID_CANCEL", "got cancel not in the queue"); +#endif + } + } + + // ----------------------------- + // --------- DHT PORT ---------- + // ----------------------------- + + void peer_connection::incoming_dht_port(int 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(udp::endpoint( + m_remote.address(), listen_port)); +#else + TORRENT_UNUSED(listen_port); +#endif + } + + // ----------------------------- + // --------- HAVE ALL ---------- + // ----------------------------- + + void peer_connection::incoming_have_all() + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + boost::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 (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + if ((*i)->on_have_all()) return; + } +#endif + if (is_disconnecting()) return; + + if (m_bitfield_received) + t->peer_lost(m_have_piece, this); + + m_have_all = true; + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "SEED", "this is a seed p: %p" + , static_cast(m_peer_info)); +#endif + + t->set_seed(m_peer_info, true); + m_upload_only = true; + m_bitfield_received = true; + +#ifndef TORRENT_DISABLE_LOGGING + m_bitfield_time = clock_type::now(); + t->debug_log("HANDSHAKE [%p] (%d ms)" + , static_cast(this) + , int(total_milliseconds(m_bitfield_time - m_connect_time))); +#endif + + // 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 + + boost::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + if ((*i)->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; + +#ifndef TORRENT_DISABLE_LOGGING + m_bitfield_time = clock_type::now(); + t->debug_log("HANDSHAKE [%p] (%d ms)" + , static_cast(this) + , int(total_milliseconds(m_bitfield_time - m_connect_time))); +#endif + m_have_piece.clear_all(); + m_num_pieces = 0; + + // if the peer is ready to download stuff, it must have metadata + m_has_metadata = true; + + // we're never interested in a peer that doesn't have anything + send_not_interested(); + + TORRENT_ASSERT(!m_have_piece.empty() || !t->ready_for_connections()); + disconnect_if_redundant(); + } + + // ----------------------------- + // ------- ALLOWED FAST -------- + // ----------------------------- + + void peer_connection::incoming_allowed_fast(int index) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + boost::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + +#ifndef TORRENT_DISABLE_LOGGING + { + time_point now = clock_type::now(); + t->debug_log("ALLOW FAST [%p] (%d ms)" + , static_cast(this) + , int(total_milliseconds(now - m_connect_time))); + if (m_peer_choked) m_unchoke_time = now; + } + peer_log(peer_log_alert::incoming_message, "ALLOWED_FAST", "%d", index); +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + if ((*i)->on_allowed_fast(index)) return; + } +#endif + if (is_disconnecting()) return; + + if (index < 0) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "INVALID_ALLOWED_FAST" + , "%d", index); +#endif + return; + } + + if (t->valid_metadata()) + { + if (index >= int(m_have_piece.size())) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "INVALID_ALLOWED_FAST" + , "%d s: %d", index, int(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 (int(m_have_piece.size()) > index + && m_have_piece[index] + && !t->has_piece_passed(index) + && t->valid_metadata() + && t->has_picker() + && t->picker().piece_priority(index) > 0) + { + t->peer_is_interesting(*this); + } + } + + std::vector const& peer_connection::allowed_fast() + { + TORRENT_ASSERT(is_single_thread()); + boost::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; + boost::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()); + std::vector::iterator rit = std::find_if(m_request_queue.begin() + , m_request_queue.end(), has_block(block)); + if (rit == m_request_queue.end()) return false; +#if TORRENT_USE_ASSERTS + boost::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + TORRENT_ASSERT(t->has_picker()); + TORRENT_ASSERT(t->picker().is_requested(block)); +#endif + // ignore it if it's already time critical + if (rit - m_request_queue.begin() < m_queued_time_critical) return false; + pending_block b = *rit; + m_request_queue.erase(rit); + m_request_queue.insert(m_request_queue.begin() + m_queued_time_critical, b); + ++m_queued_time_critical; + return true; + } + + bool peer_connection::add_request(piece_block const& block, int flags) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + boost::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(int(block.piece_index) < t->torrent_file().num_pieces()); + TORRENT_ASSERT(int(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() + , 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" + , 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" + , block.piece_index, block.block_index); +#endif + return false; + } + + if ((flags & req_busy) && !(flags & req_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 + for (std::vector::const_iterator i = m_download_queue.begin() + , end(m_download_queue.end()); i != end; ++i) + { + if (i->busy) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "PIECE_PICKER" + , "not_picking: %d,%d already in download queue & busy" + , block.piece_index, block.block_index); +#endif + return false; + } + } + + for (std::vector::const_iterator i = m_request_queue.begin() + , end(m_request_queue.end()); i != end; ++i) + { + if (i->busy) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "PIECE_PICKER" + , "not_picking: %d,%d already in request queue & busy" + , 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" + , 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 & req_busy) ? true : false; + if (flags & req_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; + + boost::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 (std::vector::iterator i = temp_copy.begin() + , end(temp_copy.end()); i != end; ++i) + { + piece_block b = i->block; + + int block_offset = b.block_index * t->block_size(); + int 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" + , b.piece_index, block_offset, block_size, b.block_index); +#endif + write_cancel(r); + } + } + + void peer_connection::cancel_request(piece_block const& block, bool force) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + boost::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(int(block.piece_index) < t->torrent_file().num_pieces()); + TORRENT_ASSERT(int(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; + + std::vector::iterator it + = std::find_if(m_download_queue.begin(), m_download_queue.end(), has_block(block)); + if (it == m_download_queue.end()) + { + std::vector::iterator rit = std::find_if(m_request_queue.begin() + , m_request_queue.end(), 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 block_offset = block.block_index * t->block_size(); + int 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" + , 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 == NULL + || 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); + } + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "CHOKE"); +#endif + write_choke(); + m_counters.inc_stats_counter(counters::num_peers_up_unchoked_all, -1); + if (!ignore_unchoke_slots()) + m_counters.inc_stats_counter(counters::num_peers_up_unchoked, -1); + m_choked = true; + + m_last_choke = aux::time_now(); + m_num_invalid_requests = 0; + + // reject the requests we have in the queue + // except the allowed fast pieces + for (std::vector::iterator i = m_requests.begin(); + i != m_requests.end();) + { + if (std::find(m_accept_fast.begin(), m_accept_fast.end(), i->piece) + != m_accept_fast.end()) + { + ++i; + continue; + } + peer_request const& r = *i; + m_counters.inc_stats_counter(counters::choked_piece_requests); +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "REJECT_PIECE" + , "piece: %d s: %d l: %d choking" + , r.piece , r.start , r.length); +#endif + write_reject_request(r); + i = m_requests.erase(i); + + if (m_requests.empty()) + m_counters.inc_stats_counter(counters::num_peers_up_requests, -1); + } + return true; + } + + bool peer_connection::send_unchoke() + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + if (!m_choked) return false; + boost::shared_ptr t = m_torrent.lock(); + if (!t->ready_for_connections()) return false; + + if (!m_sent_suggests) + { + std::vector const& ret + = t->get_suggested_pieces(); + + for (std::vector::const_iterator i = ret.begin() + , end(ret.end()); i != end; ++i) + { + TORRENT_ASSERT(i->piece_index >= 0); + // this can happen if a piece fail to be + // flushed to disk for whatever reason + if (!t->has_piece_passed(i->piece_index)) continue; + send_suggest(i->piece_index); + } + + m_sent_suggests = true; + } + + m_last_unchoke = aux::time_now(); + write_unchoke(); + m_counters.inc_stats_counter(counters::num_peers_up_unchoked_all); + if (!ignore_unchoke_slots()) + m_counters.inc_stats_counter(counters::num_peers_up_unchoked); + m_choked = false; + + m_uploaded_at_last_unchoke = m_statistics.total_payload_upload(); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "UNCHOKE"); +#endif + return true; + } + + void peer_connection::send_interested() + { + TORRENT_ASSERT(is_single_thread()); + if (m_interesting) return; + boost::shared_ptr t = m_torrent.lock(); + if (!t->ready_for_connections()) return; + m_interesting = true; + m_counters.inc_stats_counter(counters::num_peers_down_interested); + write_interested(); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "INTERESTED"); +#endif + } + + void peer_connection::send_not_interested() + { + TORRENT_ASSERT(is_single_thread()); + // we cannot disconnect in a constructor, and + // this function may end up doing that + TORRENT_ASSERT(m_in_constructor == false); + + if (!m_interesting) + { + disconnect_if_redundant(); + return; + } + + boost::shared_ptr t = m_torrent.lock(); + if (!t->ready_for_connections()) return; + m_interesting = false; + m_slow_start = false; + m_counters.inc_stats_counter(counters::num_peers_down_interested, -1); + + disconnect_if_redundant(); + if (m_disconnecting) return; + + write_not_interested(); + + m_became_uninteresting = aux::time_now(); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "NOT_INTERESTED"); +#endif + } + + void peer_connection::send_suggest(int piece) + { + TORRENT_ASSERT(is_single_thread()); + if (m_connecting) return; + if (in_handshake()) return; + + // don't suggest a piece that the peer already has + // don't suggest anything to a peer that isn't interested + if (has_piece(piece) || !m_peer_interested) + return; + + // we cannot suggest a piece we don't have! +#if TORRENT_USE_ASSERTS + { + boost::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + TORRENT_ASSERT(t->has_piece_passed(piece)); + TORRENT_ASSERT(piece >= 0 && piece < t->torrent_file().num_pieces()); + } +#endif + + + if (m_sent_suggested_pieces.empty()) + { + boost::shared_ptr t = m_torrent.lock(); + m_sent_suggested_pieces.resize(t->torrent_file().num_pieces(), false); + } + + TORRENT_ASSERT(piece >= 0 && piece < m_sent_suggested_pieces.size()); + + if (m_sent_suggested_pieces[piece]) return; + m_sent_suggested_pieces.set_bit(piece); + + write_suggest(piece); + } + + void peer_connection::send_block_requests() + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + boost::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + + if (m_disconnecting) return; + + // TODO: 3 once peers are properly put in graceful pause mode, they can + // cancel all outstanding requests and this test can be removed. + if (t->graceful_pause()) return; + + // we can't download pieces in these states + if (t->state() == torrent_status::checking_files + || t->state() == torrent_status::checking_resume_data + || t->state() == torrent_status::downloading_metadata + || t->state() == torrent_status::allocating) + return; + + if (int(m_download_queue.size()) >= m_desired_queue_size + || t->upload_mode()) return; + + bool 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_finished(block.block) + || 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 block_size = (std::min)(t->torrent_file().piece_size( + block.block.piece_index) - block_offset, t->block_size()); + TORRENT_ASSERT(block_size > 0); + TORRENT_ASSERT(block_size <= t->block_size()); + + peer_request r; + r.piece = block.block.piece_index; + r.start = block_offset; + r.length = block_size; + + if (m_download_queue.empty()) + m_counters.inc_stats_counter(counters::num_peers_down_requests); + + TORRENT_ASSERT(verify_piece(t->to_req(block.block))); + block.send_buffer_offset = m_send_buffer.size(); + m_download_queue.push_back(block); + m_outstanding_bytes += block_size; +#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 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 (front.block.piece_index * blocks_per_piece + front.block.block_index + != block.block.piece_index * blocks_per_piece + block.block.block_index + 1) + break; + block = m_request_queue.front(); + m_request_queue.erase(m_request_queue.begin()); + TORRENT_ASSERT(verify_piece(t->to_req(block.block))); + + if (m_download_queue.empty()) + m_counters.inc_stats_counter(counters::num_peers_down_requests); + + block.send_buffer_offset = m_send_buffer.size(); + m_download_queue.push_back(block); + if (m_queued_time_critical) --m_queued_time_critical; + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "MERGING_REQUEST" + , "piece: %d block: %d" + , block.block.piece_index, block.block.block_index); +#endif + + block_offset = block.block.block_index * t->block_size(); + block_size = (std::min)(t->torrent_file().piece_size( + block.block.piece_index) - block_offset, t->block_size()); + TORRENT_ASSERT(block_size > 0); + TORRENT_ASSERT(block_size <= t->block_size()); + + r.length += block_size; + m_outstanding_bytes += block_size; +#if TORRENT_USE_INVARIANT_CHECKS + check_invariant(); +#endif + } + } + + // the verification will fail for coalesced blocks + TORRENT_ASSERT(verify_piece(r) || m_request_large_blocks); + +#ifndef TORRENT_DISABLE_EXTENSIONS + bool handled = false; + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + if ((handled = (*i)->write_request(r))) break; + } + if (is_disconnecting()) return; + if (!handled) +#endif + { + write_request(r); + m_last_request = aux::time_now(); + } + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "REQUEST" + , "piece: %d s: %x l: %x ds: %dB/s dqs: %d rqs: %d blk: %s" + , r.piece, r.start, r.length, statistics().download_rate() + , int(m_desired_queue_size), int(m_download_queue.size()) + , m_request_large_blocks?"large":"single"); +#endif + } + m_last_piece = aux::time_now(); + + if (!m_download_queue.empty() + && empty_download_queue) + { + // This means we just added a request to this connection that + // previously did not have a request. That's when we start the + // request timeout. + m_requested = aux::time_now(); +#ifndef TORRENT_DISABLE_LOGGING + t->debug_log("REQUEST [%p] (%d ms)", static_cast(this) + , int(total_milliseconds(clock_type::now() - m_unchoke_time))); +#endif + } + } + + void peer_connection::connect_failed(error_code const& e) + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(e); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "CONNECTION FAILED" + , "%s", print_endpoint(m_remote).c_str()); +#endif +#ifndef TORRENT_DISABLE_LOGGING + m_ses.session_log("CONNECTION FAILED: %s", print_endpoint(m_remote).c_str()); +#endif + + m_counters.inc_stats_counter(counters::connect_timeouts); + + boost::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) t->dec_num_connecting(); + 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 + torrent_peer* pi = peer_info_struct(); + fast_reconnect(true); + disconnect(e, op_connect, 0); + if (t && pi) t->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_rendezvous, remote(), 0); + } +#endif + + disconnect(e, op_connect, 1); + return; + } + + // 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 op, int error) + { + TORRENT_ASSERT(is_single_thread()); +#if TORRENT_USE_ASSERTS + m_disconnect_started = true; +#endif + + if (m_disconnecting) return; + + m_socket->set_close_reason(error_to_close_reason(ec)); + close_reason_t close_reason = close_reason_t(m_socket->get_close_reason()); +#ifndef TORRENT_DISABLE_LOGGING + if (close_reason != 0) + { + 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 + switch (error) + { + case 0: + peer_log(peer_log_alert::info, "CONNECTION_CLOSED", "op: %d error: %s" + , op, ec.message().c_str()); + break; + case 1: + peer_log(peer_log_alert::info, "CONNECTION_FAILED", "op: %d error: %s" + , op, ec.message().c_str()); + break; + case 2: + peer_log(peer_log_alert::info, "PEER_ERROR" ,"op: %d error: %s" + , op, ec.message().c_str()); + break; + } + + if (ec == error_code(boost::asio::error::eof + , boost::asio::error::get_misc_category()) + && !in_handshake() + && !is_connecting() + && aux::time_now() - connected_time() < seconds(15)) + { + peer_log(peer_log_alert::info, "SHORT_LIVED_DISCONNECT", ""); + } +#endif + + if ((m_channel_state[upload_channel] & peer_info::bw_network) == 0) + { + // make sure we free up all send buffers that are owned + // by the disk thread + m_send_buffer.clear(); + m_recv_buffer.free_disk_buffer(); + } + + // we cannot do this in a constructor + TORRENT_ASSERT(m_in_constructor == false); + if (error > 0) + { + 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 == 2) 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 == error_code(errors::upload_upload_connection) + || ec == error_code(errors::uninteresting_upload_peer) + || ec == error_code(errors::torrent_aborted) + || ec == error_code(errors::self_connection) + || ec == error_code(errors::torrent_paused)) + m_counters.inc_stats_counter(counters::uninteresting_peers); + + if (ec == error_code(errors::timed_out) + || ec == error::timed_out) + m_counters.inc_stats_counter(counters::transport_timeout_peers); + + if (ec == error_code(errors::timed_out_inactivity) + || ec == error_code(errors::timed_out_no_request) + || ec == error_code(errors::timed_out_no_interest)) + m_counters.inc_stats_counter(counters::timeout_peers); + + if (ec == error_code(errors::no_memory)) + m_counters.inc_stats_counter(counters::no_memory_peers); + + if (ec == error_code(errors::too_many_connections)) + m_counters.inc_stats_counter(counters::too_many_peers); + + if (ec == error_code(errors::timed_out_no_handshake)) + m_counters.inc_stats_counter(counters::connect_timeouts); + + if (error > 0) + { + 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) && !defined(TORRENT_DISABLE_EXTENSIONS) + if (type() == bittorrent_connection && op != op_connect) + { + bt_peer_connection* 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 + } + + boost::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; + } + + boost::shared_ptr t = m_torrent.lock(); + if (m_connecting) + { + m_counters.inc_stats_counter(counters::num_peers_half_open, -1); + if (t) t->dec_num_connecting(); + m_connecting = false; + } + + torrent_handle handle; + if (t) handle = t->get_handle(); + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + (*i)->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); + } + + if (t) + { + if (ec) + { + if ((error > 1 || ec.category() == get_socks_category()) + && t->alerts().should_post()) + { + t->alerts().emplace_alert(handle, remote() + , pid(), op, ec); + } + + if (error <= 1 && t->alerts().should_post()) + { + t->alerts().emplace_alert(handle + , remote(), pid(), op, m_socket->type(), ec, close_reason); + } + } + + // make sure we keep all the stats! + if (!m_ignore_stats) + { + // report any partially received payload as redundant + boost::optional pbp = downloading_piece_progress(); + if (pbp + && pbp->bytes_downloaded > 0 + && pbp->bytes_downloaded < pbp->full_block_bytes) + { + t->add_redundant_bytes(pbp->bytes_downloaded, torrent::piece_closing); + } + } + + if (t->has_picker()) + { + piece_picker& picker = t->picker(); + + 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(); + } + while (!m_request_queue.empty()) + { + pending_block& 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 + check_invariant(); +#endif + t->remove_peer(this); + } + else + { + TORRENT_ASSERT(m_download_queue.empty()); + TORRENT_ASSERT(m_request_queue.empty()); + } + +#if defined TORRENT_DEBUG && defined 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 + + m_disconnecting = true; + error_code e; + + async_shutdown(*m_socket, m_socket); + + m_ses.close_connection(this, ec); + } + + 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; + boost::shared_ptr t = m_torrent.lock(); + if (t && m_ses.ignore_unchoke_slots_set(*t)) return true; + return false; + } + + // defined in upnp.cpp + bool is_local(address const& a); + + bool peer_connection::on_local_network() const + { + TORRENT_ASSERT(is_single_thread()); + if (libtorrent::is_local(m_remote.address()) + || is_loopback(m_remote.address())) return true; + return false; + } + + 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 now = aux::time_now(); + + p.download_rate_peak = m_download_rate_peak; + p.upload_rate_peak = m_upload_rate_peak; + p.rtt = m_request_time.mean(); + p.down_speed = statistics().download_rate(); + p.up_speed = statistics().upload_rate(); + p.payload_down_speed = statistics().download_payload_rate(); + p.payload_up_speed = statistics().upload_payload_rate(); + p.pid = pid(); + p.ip = remote(); + p.pending_disk_bytes = m_outstanding_writing_bytes; + p.pending_disk_read_bytes = m_reading_bytes; + p.send_quota = m_quota[upload_channel]; + p.receive_quota = m_quota[download_channel]; + p.num_pieces = m_num_pieces; + if (m_download_queue.empty()) p.request_timeout = -1; + else p.request_timeout = int(total_seconds(m_requested - now) + + request_timeout()); + + p.download_queue_time = download_queue_time(); + p.queue_bytes = m_outstanding_bytes; + +#ifndef TORRENT_NO_DEPRECATE +#ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES + p.country[0] = m_country[0]; + p.country[1] = m_country[1]; +#else + std::fill(p.country, p.country + 2, 0); +#endif +#endif // TORRENT_NO_DEPRECATE + + p.total_download = statistics().total_payload_download(); + p.total_upload = statistics().total_payload_upload(); +#ifndef TORRENT_NO_DEPRECATE + 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 = int(desired_queue_size()); + p.upload_queue_length = int(upload_queue().size()); + p.timed_out_requests = 0; + p.busy_requests = 0; + for (std::vector::const_iterator i = m_download_queue.begin() + , end(m_download_queue.end()); i != end; ++i) + { + if (i->timed_out) ++p.timed_out_requests; + if (i->busy) ++p.busy_requests; + } + + if (boost::optional ret = downloading_piece_progress()) + { + 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 = -1; + p.downloading_block_index = -1; + p.downloading_progress = 0; + p.downloading_total = 0; + } + + p.pieces = get_bitfield(); + p.last_request = now - m_last_request; + p.last_active = now - (std::max)(m_last_sent, m_last_receive); + + // this will set the flags so that we can update them later + p.flags = 0; + get_specific_peer_info(p); + + p.flags |= is_seed() ? peer_info::seed : 0; + p.flags |= m_snubbed ? peer_info::snubbed : 0; + p.flags |= m_upload_only ? peer_info::upload_only : 0; + p.flags |= m_endgame_mode ? peer_info::endgame_mode : 0; + p.flags |= m_holepunch_mode ? peer_info::holepunched : 0; + if (peer_info_struct()) + { + torrent_peer* pi = peer_info_struct(); + TORRENT_ASSERT(pi->in_use); + p.source = pi->source; + p.failcount = pi->failcount; + p.num_hashfails = pi->hashfails; + p.flags |= pi->on_parole ? peer_info::on_parole : 0; + p.flags |= pi->optimistically_unchoked ? peer_info::optimistic_unchoke : 0; + } + else + { + p.source = 0; + p.failcount = 0; + p.num_hashfails = 0; + } + + p.remote_dl_rate = m_remote_dl_rate; + 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.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.size() == 0) + { + 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 = boost::uint64_t(p.pieces.count()) * 1000000 / p.pieces.size(); + } + + p.estimated_reciprocation_rate = m_est_reciprocation_rate; + + error_code ec; + p.local_endpoint = get_socket()->local_endpoint(ec); + } + + // allocates a disk buffer of size 'disk_buffer_size' and replaces the + // end of the current receive buffer with it. i.e. the receive pos + // must be <= packet_size - disk_buffer_size + // the disk buffer can be accessed through release_disk_receive_buffer() + // when it is queried, the responsibility to free it is transferred + // to the caller + bool peer_connection::allocate_disk_receive_buffer(int disk_buffer_size) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + m_recv_buffer.assert_no_disk_buffer(); + TORRENT_ASSERT(m_recv_buffer.pos() <= m_recv_buffer.packet_size() - disk_buffer_size); + TORRENT_ASSERT(disk_buffer_size <= 16 * 1024); + + if (disk_buffer_size == 0) return true; + + if (disk_buffer_size > 16 * 1024) + { + disconnect(errors::invalid_piece_size, op_bittorrent, 2); + return false; + } + + // first free the old buffer + m_recv_buffer.free_disk_buffer(); + // then allocate a new one + + bool exceeded = false; + m_recv_buffer.assign_disk_buffer( + m_allocator.allocate_disk_buffer(exceeded, self(), "receive buffer") + , disk_buffer_size); + + if (!m_recv_buffer.has_disk_buffer()) + { + disconnect(errors::no_memory, op_alloc_recvbuf); + return false; + } + + // to understand why m_outstanding_writing_bytes is here, see comment by + // the other call to allocate_disk_buffer() + if (exceeded && m_outstanding_writing_bytes > 0) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "DISK", "exceeded disk buffer watermark"); +#endif + if ((m_channel_state[download_channel] & peer_info::bw_disk) == 0) + m_counters.inc_stats_counter(counters::num_peers_down_disk); + m_channel_state[download_channel] |= peer_info::bw_disk; + } + + return true; + } + + void peer_connection::superseed_piece(int replace_piece, int new_piece) + { + TORRENT_ASSERT(is_single_thread()); + + if (is_connecting()) return; + if (in_handshake()) return; + + if (new_piece == -1) + { + if (m_superseed_piece[0] == -1) return; + m_superseed_piece[0] = -1; + m_superseed_piece[1] = -1; + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "SUPER_SEEDING", "ending"); +#endif + boost::shared_ptr t = m_torrent.lock(); + 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; + } + + assert(!has_piece(new_piece)); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "HAVE", "piece: %d (super seed)" + , new_piece); +#endif + write_have(new_piece); + + if (replace_piece >= 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; + } + + void peer_connection::max_out_request_queue(int s) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "MAX_OUT_QUEUE_SIZE", "%d -> %d" + , m_max_out_request_queue, s); +#endif + m_max_out_request_queue = s; + } + + int peer_connection::max_out_request_queue() const + { + return m_max_out_request_queue; + } + + void peer_connection::update_desired_queue_size() + { + TORRENT_ASSERT(is_single_thread()); + if (m_snubbed) + { + m_desired_queue_size = 1; + return; + } + +#ifndef TORRENT_DISABLE_LOGGING + int const previous_queue_size = m_desired_queue_size; +#endif + + int const download_rate = statistics().download_payload_rate(); + + // the desired download queue size + int const queue_time = m_settings.get_int(settings_pack::request_queue_time); + + // when we're in slow-start mode we increase the desired queue size every + // time we receive a piece, no need to adjust it here (other than + // enforcing the upper limit) + if (!m_slow_start) + { + // (if the latency is more than this, the download will stall) + // so, the queue size is queue_time * down_rate / 16 kiB + // (16 kB is the size of each request) + // the minimum number of requests is 2 and the maximum is 48 + // the block size doesn't have to be 16. So we first query the + // torrent for it + boost::shared_ptr t = m_torrent.lock(); + int const block_size = t->block_size(); + + TORRENT_ASSERT(block_size > 0); + + m_desired_queue_size = queue_time * download_rate / block_size; + } + + 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" + , m_desired_queue_size, m_max_out_request_queue + , download_rate, queue_time, int(m_snubbed), int(m_slow_start)); + } +#endif + } + + void peer_connection::second_tick(int tick_interval_ms) + { + TORRENT_ASSERT(is_single_thread()); + time_point now = aux::time_now(); + boost::shared_ptr me(self()); + + // the invariant check must be run before me is destructed + // in case the peer got disconnected + INVARIANT_CHECK; + + boost::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_connecting = false; + } + disconnect(errors::torrent_aborted, op_bittorrent); + return; + } + + if (m_endgame_mode + && m_interesting + && m_download_queue.empty() + && m_request_queue.empty() + && now - seconds(5) >= m_last_request) + { + // this happens when we're in strict end-game + // mode and the peer could not request any blocks + // because they were all taken but there were still + // unrequested blocks. Now, 5 seconds later, there + // might not be any unrequested blocks anymore, so + // we should try to pick another block to see + // if we can pick a busy one + m_last_request = now; + if (request_a_block(*t, *this)) + m_counters.inc_stats_counter(counters::end_game_piece_picks); + if (m_disconnecting) return; + send_block_requests(); + } + + if (t->super_seeding() + && t->ready_for_connections() + && !m_peer_interested + && m_became_uninterested + seconds(10) < now) + { + // maybe we need to try another piece, to see if the peer + // become interested in us then + superseed_piece(-1, t->get_piece_to_super_seed(m_have_piece)); + } + + on_tick(); + if (is_disconnecting()) return; + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + (*i)->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; + d = (std::min)(now - m_last_receive, now - m_last_sent); + + 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(error_code(errors::timed_out, get_libtorrent_category()))) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "CONNECT_FAILED", "waited %d seconds" + , int(total_seconds(d))); +#endif + connect_failed(errors::timed_out); + return; + } + } + + // if we can't read, it means we're blocked on the rate-limiter + // or the disk, not the peer itself. In this case, don't blame + // the peer and disconnect it + bool may_timeout = (m_channel_state[download_channel] & peer_info::bw_network) != 0; + + // TODO: 2 use a deadline_timer for timeouts. Don't rely on second_tick()! + // Hook this up to connect timeout as well. This would improve performance + // because of less work in second_tick(), and might let use remove ticking + // entirely eventually + if (may_timeout && d > seconds(timeout()) && !m_connecting && m_reading_bytes == 0 + && can_disconnect(error_code(errors::timed_out_inactivity, get_libtorrent_category()))) + { +#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, op_bittorrent); + return; + } + + // do not stall waiting for a handshake + int timeout = m_settings.get_int (settings_pack::handshake_timeout); +#if TORRENT_USE_I2P + timeout *= is_i2p(*m_socket) ? 4 : 1; +#endif + if (may_timeout + && !m_connecting + && in_handshake() + && d > seconds(timeout)) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "NO_HANDSHAKE", "waited %d seconds" + , int(total_seconds(d))); +#endif + disconnect(errors::timed_out_no_handshake, op_bittorrent); + return; + } + + // disconnect peers that we unchoked, but they didn't send a request in + // the last 60 seconds, and we haven't been working on servicing a request + // for more than 60 seconds. + // but only if we're a seed + d = now - (std::max)((std::max)(m_last_unchoke, m_last_incoming_request) + , m_last_sent_payload); + + if (may_timeout + && !m_connecting + && m_requests.empty() + && m_reading_bytes == 0 + && !m_choked + && m_peer_interested + && t && t->is_upload_only() + && d > seconds(60) + && can_disconnect(error_code(errors::timed_out_no_request, get_libtorrent_category()))) + { +#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, op_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 d1; + time_duration d2; + d1 = now - m_became_uninterested; + d2 = now - m_became_uninteresting; + time_duration time_limit = seconds( + m_settings.get_int(settings_pack::inactivity_timeout)); + + // don't bother disconnect peers we haven't been interested + // in (and that hasn't been interested in us) for a while + // unless we have used up all our connection slots + if (may_timeout + && !m_interesting + && !m_peer_interested + && d1 > time_limit + && d2 > time_limit + && (m_ses.num_connections() >= m_settings.get_int(settings_pack::connections_limit) + || (t && t->num_peers() >= t->max_connections())) + && can_disconnect(error_code(errors::timed_out_no_interest))) + { +#ifndef TORRENT_DISABLE_LOGGING + 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, op_bittorrent); + return; + } + + if (may_timeout + && !m_download_queue.empty() + && m_quota[download_channel] > 0 + && now > m_requested + seconds(request_timeout())) + { + snub_peer(); + } + + // if we haven't sent something in too long, send a keep-alive + keep_alive(); + + // if our download rate isn't increasing significantly anymore, end slow + // start. The 10kB is to have some slack here. + // we can't do this when we're choked, because we aren't sending any + // requests yet, so there hasn't been an opportunity to ramp up the + // connection yet. + if (m_slow_start + && !m_peer_choked + && m_downloaded_last_second > 0 + && m_downloaded_last_second + 5000 + >= m_statistics.last_payload_downloaded()) + { + m_slow_start = false; +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "SLOW_START", "exit slow start: " + "prev-dl: %d dl: %d" + , int(m_downloaded_last_second) + , int(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 piece_timeout = m_settings.get_int(settings_pack::piece_timeout); + + if (!m_download_queue.empty() + && m_quota[download_channel] > 0 + && now - m_last_piece > seconds(piece_timeout)) + { + // this peer isn't sending the pieces we've + // requested (this has been observed by BitComet) + // in this case we'll clear our download queue and + // re-request the blocks. +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "PIECE_REQUEST_TIMED_OUT" + , "%d time: %d to: %d" + , int(m_download_queue.size()), int(total_seconds(now - m_last_piece)) + , piece_timeout); +#endif + + snub_peer(); + } + + // update once every minute + if (now - m_remote_dl_update >= seconds(60)) + { + boost::int64_t piece_size = t->torrent_file().piece_length(); + + if (m_remote_dl_rate > 0) + m_remote_dl_rate = int((m_remote_dl_rate * 2 / 3) + + ((boost::int64_t(m_remote_pieces_dled) * piece_size / 3) / 60)); + else + m_remote_dl_rate = int(boost::int64_t(m_remote_pieces_dled) + * piece_size / 60); + + m_remote_pieces_dled = 0; + m_remote_dl_update = now; + } + + fill_send_buffer(); + } + + void peer_connection::snub_peer() + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + boost::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 = 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 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 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(), int(qe.block.block_index) + , int(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(); + } + + int peer_connection::preferred_caching() const + { + TORRENT_ASSERT(is_single_thread()); + int line_size = 0; + if (m_settings.get_bool(settings_pack::guided_read_cache)) + { + boost::shared_ptr t = m_torrent.lock(); + int upload_rate = m_statistics.upload_payload_rate(); + if (upload_rate == 0) upload_rate = 1; + + int num_uploads = m_ses.num_uploads(); + if (num_uploads == 0) num_uploads = 1; + + // assume half of the cache is write cache if we're downloading + // this torrent as well + int cache_size = m_settings.get_int(settings_pack::cache_size) / num_uploads; + if (!t->is_upload_only()) cache_size /= 2; + // cache_size is the amount of cache we have per peer. The + // cache line should not be greater than this + + line_size = cache_size; + } + return line_size; + } + + void peer_connection::fill_send_buffer() + { + TORRENT_ASSERT(is_single_thread()); +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + INVARIANT_CHECK; +#endif + + bool sent_a_piece = false; + boost::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(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 + peer_log(peer_log_alert::outgoing, "SEND_BUFFER_WATERMARK" + , "current watermark: %d max: %d min: %d factor: %d uploaded: %d B/s" + , buffer_size_watermark + , m_ses.settings().get_int(settings_pack::send_buffer_watermark) + , m_ses.settings().get_int(settings_pack::send_buffer_low_watermark) + , m_ses.settings().get_int(settings_pack::send_buffer_watermark_factor) + , int(m_uploaded_last_second)); +#endif + + // don't just pop the front element here, since in seed mode one request may + // be blocked because we have to verify the hash first, so keep going with the + // next request. However, only let each peer have one hash verification outstanding + // at any given time + for (int i = 0; i < m_requests.size() + && (send_buffer_size() + m_reading_bytes < buffer_size_watermark); ++i) + { + TORRENT_ASSERT(t->ready_for_connections()); + peer_request& r = m_requests[i]; + + TORRENT_ASSERT(r.piece >= 0); + TORRENT_ASSERT(r.piece < int(m_have_piece.size())); +// TORRENT_ASSERT(t->have_piece(r.piece)); + TORRENT_ASSERT(r.start + r.length <= t->torrent_file().piece_size(r.piece)); + TORRENT_ASSERT(r.length > 0 && r.start >= 0); + + if (t->is_deleted()) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "REJECT_PIECE" + , "piece: %d s: %x l: %x torrent deleted" + , r.piece , r.start , r.length); +#endif + write_reject_request(r); + continue; + } + + if (t->seed_mode() && !t->verified_piece(r.piece)) + { + // 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", r.piece); +#endif + // this means we're in seed mode and we haven't yet + // verified this piece (r.piece) + if (!t->need_loaded()) return; + t->inc_refcount("async_seed_hash"); + m_disk_thread.async_hash(&t->storage(), r.piece, 0 + , boost::bind(&peer_connection::on_seed_mode_hashed, self(), _1) + , this); + t->verifying(r.piece); + continue; + } + + // in seed mode, we might end up accepting a request + // which it later turns out we cannot serve, if we ended + // up not having that piece + if (!t->has_piece_passed(r.piece)) + { + // 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; +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "REJECT_PIECE" + , "piece: %d s: %x l: %x piece not passed hash check" + , 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", r.piece, r.start, r.length); +#endif + m_reading_bytes += r.length; + sent_a_piece = true; + + // the callback function may be called immediately, instead of being posted + if (!t->need_loaded()) return; + + TORRENT_ASSERT(t->valid_metadata()); + TORRENT_ASSERT(r.piece >= 0); + TORRENT_ASSERT(r.piece < t->torrent_file().num_pieces()); + + t->inc_refcount("async_read"); + m_disk_thread.async_read(&t->storage(), r + , boost::bind(&peer_connection::on_disk_read_complete + , self(), _1, r, clock_type::now()), this); + } + m_last_sent_payload = clock_type::now(); + m_requests.erase(m_requests.begin() + i); + + if (m_requests.empty()) + m_counters.inc_stats_counter(counters::num_peers_up_requests, -1); + + --i; + } + + if (t->share_mode() && sent_a_piece) + t->recalc_share_mode(); + } + + // this is called when a previously unchecked piece has been + // checked, while in seed-mode + void peer_connection::on_seed_mode_hashed(disk_io_job const* j) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + boost::shared_ptr t = m_torrent.lock(); + torrent_ref_holder h(t.get(), "async_seed_hash"); + if (t) t->dec_refcount("async_seed_hash"); + + TORRENT_ASSERT(m_outstanding_piece_verification > 0); + --m_outstanding_piece_verification; + + if (!t || t->is_aborted()) return; + + if (j->error) + { + t->handle_disk_error(j, this); + t->leave_seed_mode(false); + return; + } + + // we're using the piece hashes here, we need the torrent to be loaded + if (!t->need_loaded()) return; + + if (!m_settings.get_bool(settings_pack::disable_hash_checks) + && sha1_hash(j->d.piece_hash) != t->torrent_file().hash_for_piece(j->piece)) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "SEED_MODE_FILE_HASH" + , "piece: %d failed", j->piece); +#endif + + t->leave_seed_mode(false); + } + else + { + TORRENT_ASSERT(t->verifying_piece(j->piece)); + if (t->seed_mode()) t->verified(j->piece); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "SEED_MODE_FILE_HASH" + , "piece: %d passed", j->piece); +#endif + if (t) + { + if (t->seed_mode() && t->all_verified()) + t->leave_seed_mode(true); + } + } + + // try to service the requests again, now that the piece + // has been verified + fill_send_buffer(); + } + + void peer_connection::on_disk_read_complete(disk_io_job const* j + , peer_request r, time_point issue_time) + { + TORRENT_ASSERT(is_single_thread()); + // return value: + // 0: success, piece passed hash check + // -1: disk failure + + int disk_rtt = int(total_microseconds(clock_type::now() - issue_time)); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "FILE_ASYNC_READ_COMPLETE" + , "ret: %d piece: %d s: %x l: %x b: %p c: %s e: %s rtt: %d us" + , j->ret, r.piece, r.start, r.length + , static_cast(j->buffer.disk_block) + , (j->flags & disk_io_job::cache_hit ? "cache hit" : "cache miss") + , j->error.ec.message().c_str(), disk_rtt); +#endif + + m_reading_bytes -= r.length; + + boost::shared_ptr t = m_torrent.lock(); + torrent_ref_holder h(t.get(), "async_read"); + if (t) t->dec_refcount("async_read"); + + if (j->ret < 0) + { + if (!t) + { + disconnect(j->error.ec, op_file_read); + return; + } + + TORRENT_ASSERT(j->buffer.disk_block == 0); + write_dont_have(r.piece); + write_reject_request(r); + if (t->alerts().should_post()) + t->alerts().emplace_alert(j->error.ec + , t->resolve_filename(j->error.file) + , j->error.operation_str(), t->get_handle()); + + ++m_disk_read_failures; + if (m_disk_read_failures > 100) disconnect(j->error.ec, op_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; + + TORRENT_ASSERT(j->ret == r.length); + + // even if we're disconnecting, we need to free this block + // otherwise the disk thread will hang, waiting for the network + // thread to be done with it + disk_buffer_holder buffer(m_allocator, *j); + + if (m_disconnecting) return; + + // flush send buffer at the end of + // this burst of disk events +// m_ses.cork_burst(this); + + if (!t) + { + disconnect(j->error.ec, op_file_read); + return; + } + + if (j->ret != r.length) + { + // handle_disk_error may disconnect us + t->handle_disk_error(j, this); + return; + } + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message + , "PIECE", "piece: %d s: %x l: %x", 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 + && (j->flags & disk_io_job::cache_hit) == 0) + { + t->add_suggest_piece(r.piece); + } + write_piece(r, buffer); + } + + void peer_connection::assign_bandwidth(int channel, int amount) + { + TORRENT_ASSERT(is_single_thread()); +#ifndef TORRENT_DISABLE_LOGGING + peer_log(channel == upload_channel + ? peer_log_alert::outgoing : peer_log_alert::incoming + , "ASSIGN_BANDWIDHT", "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 channel) + { + TORRENT_ASSERT(is_single_thread()); + shared_ptr t = m_torrent.lock(); + + const int tick_interval = (std::max)(1, m_settings.get_int(settings_pack::tick_interval)); + + if (channel == download_channel) + { + return (std::max)((std::max)(m_outstanding_bytes + , m_recv_buffer.packet_bytes_remaining()) + 30 + , int(boost::int64_t(m_statistics.download_rate()) * 2 + / (1000 / tick_interval))); + } + else + { + return (std::max)((std::max)(m_reading_bytes + , m_send_buffer.size()) + , int((boost::int64_t(m_statistics.upload_rate()) * 2 + * tick_interval) / 1000)); + } + } + + int peer_connection::request_bandwidth(int 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; + + 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 priority = get_priority(channel); + + int max_channels = num_classes() + (t ? t->num_classes() : 0) + 2; + bandwidth_channel** channels = TORRENT_ALLOCA(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 + c, max_channels - c); + if (t) + { + c += m_ses.copy_pertinent_channels(*t, channel + , channels + c, max_channels - c); + } + +#ifdef TORRENT_DEBUG + // 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) == 0); + + bandwidth_manager* manager = m_ses.get_bandwidth_manager(channel); + + int ret = manager->request_bandwidth(self() + , bytes, priority, channels, c); + + if (ret == 0) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log( + channel == download_channel ? peer_log_alert::incoming + : peer_log_alert::outgoing, + "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::uncork_socket() + { + TORRENT_ASSERT(is_single_thread()); + if (!m_corked) return; + m_corked = false; + setup_send(); + } + + void peer_connection::setup_send() + { + TORRENT_ASSERT(is_single_thread()); + if (m_disconnecting) return; + + // we may want to request more quota at this point + request_bandwidth(upload_channel); + + if (m_channel_state[upload_channel] & peer_info::bw_network) return; + + if (m_send_barrier == 0) + { + std::vector vec; + m_send_buffer.build_mutable_iovec(m_send_buffer.size(), vec); + int next_barrier = hit_send_barrier(vec); + for (std::vector::reverse_iterator i = vec.rbegin(); + i != vec.rend(); ++i) + { + m_send_buffer.prepend_buffer(boost::asio::buffer_cast(*i) + , boost::asio::buffer_size(*i) + , boost::asio::buffer_size(*i) + , &nop + , NULL); + } + set_send_barrier(next_barrier); + } + + if ((m_quota[upload_channel] == 0 || m_send_barrier == 0) + && !m_send_buffer.empty() + && !m_connecting) + { + return; + } + + int 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) == 0) + 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) + { + boost::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 (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] + , int(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] + , int(m_send_buffer.size()), m_connecting?"yes":"no" + , m_disconnecting?"yes":"no", m_reading_bytes); + } +#endif + return; + } + + // send the actual buffer + int amount_to_send = m_send_buffer.size(); + if (amount_to_send > quota_left) + amount_to_send = quota_left; + if (amount_to_send > m_send_barrier) + amount_to_send = m_send_barrier; + + TORRENT_ASSERT(amount_to_send > 0); + + if (m_corked) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing, "CORKED_WRITE", "bytes: %d" + , amount_to_send); +#endif + return; + } + + TORRENT_ASSERT((m_channel_state[upload_channel] & peer_info::bw_network) == 0); +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing, "ASYNC_WRITE", "bytes: %d", amount_to_send); +#endif + std::vector const& vec = m_send_buffer.build_iovec(amount_to_send); +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("peer_connection::on_send_data"); +#endif + +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(!m_socket_is_writing); + m_socket_is_writing = true; +#endif + + // uTP sockets aren't thread safe... + if (is_utp(*m_socket)) + { + m_socket->async_write_some(vec, make_write_handler(boost::bind( + &peer_connection::on_send_data, self(), _1, _2))); + } + else + { + socket_job j; + j.type = socket_job::write_job; + j.vec = &vec; + j.peer = self(); + m_ses.post_socket_job(j); + } + + m_channel_state[upload_channel] |= peer_info::bw_network; + } + + void peer_connection::on_disk() + { + TORRENT_ASSERT(is_single_thread()); + if ((m_channel_state[download_channel] & peer_info::bw_disk) == 0) return; + boost::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; + + // we may want to request more quota at this point + request_bandwidth(download_channel); + + 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 + 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; + } + error_code ec; + + try_read(read_async, ec); + } + + size_t peer_connection::try_read(sync_t s, error_code& ec) + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(m_connected); + + if (m_quota[download_channel] == 0) + { + ec = boost::asio::error::would_block; + return 0; + } + + if (!can_read()) + { + ec = boost::asio::error::would_block; + return 0; + } + + int max_receive = m_recv_buffer.max_receive(); + + boost::array vec; + int num_bufs = 0; + // only apply the contiguous receive buffer when we don't have any + // outstanding requests. When we're likely to receive pieces, we'll + // save more time from avoiding copying data from the socket + if ((m_settings.get_bool(settings_pack::contiguous_recv_buffer) + || m_download_queue.empty()) && !m_recv_buffer.has_disk_buffer()) + { + if (s == read_sync) + { + ec = boost::asio::error::would_block; + return 0; + } + + TORRENT_ASSERT((m_channel_state[download_channel] & peer_info::bw_network) == 0); + m_channel_state[download_channel] |= peer_info::bw_network; +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming, "ASYNC_READ"); +#endif + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("peer_connection::on_receive_data_nb"); +#endif + m_socket->async_read_some(null_buffers(), make_read_handler( + boost::bind(&peer_connection::on_receive_data_nb, self(), _1, _2))); + return 0; + } + + TORRENT_ASSERT(max_receive >= 0); + + int quota_left = m_quota[download_channel]; + if (max_receive > quota_left) + max_receive = quota_left; + + if (max_receive == 0) + { + ec = boost::asio::error::would_block; + return 0; + } + + num_bufs = m_recv_buffer.reserve(vec, max_receive); + + if (s == read_async) + { + TORRENT_ASSERT((m_channel_state[download_channel] & peer_info::bw_network) == 0); + 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 + + // utp sockets aren't thread safe... +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("peer_connection::on_receive_data"); +#endif + if (is_utp(*m_socket)) + { + if (num_bufs == 1) + { + TORRENT_ASSERT(boost::asio::buffer_size(vec[0]) > 0); + m_socket->async_read_some( + boost::asio::mutable_buffers_1(vec[0]), make_read_handler( + boost::bind(&peer_connection::on_receive_data, self(), _1, _2))); + } + else + { + TORRENT_ASSERT(boost::asio::buffer_size(vec[0]) + + boost::asio::buffer_size(vec[1])> 0); + m_socket->async_read_some( + vec, make_read_handler( + boost::bind(&peer_connection::on_receive_data, self(), _1, _2))); + } + } + else + { + socket_job j; + j.type = socket_job::read_job; + j.peer = self(); + if (num_bufs == 1) + { + TORRENT_ASSERT(boost::asio::buffer_size(vec[0]) > 0); + j.recv_buf = boost::asio::buffer_cast(vec[0]); + j.buf_size = boost::asio::buffer_size(vec[0]); + } + else + { + TORRENT_ASSERT(boost::asio::buffer_size(vec[0]) + + boost::asio::buffer_size(vec[1])> 0); + j.read_vec = vec; + } + m_ses.post_socket_job(j); + } + return 0; + } + + size_t ret = 0; + if (num_bufs == 1) + { + ret = m_socket->read_some(boost::asio::mutable_buffers_1(vec[0]), ec); + } + else + { + ret = m_socket->read_some(vec, ec); + } + // this is weird. You would imagine read_some() would do this + if (ret == 0 && !ec) ec = boost::asio::error::eof; + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming, "SYNC_READ", "max: %d ret: %d e: %s" + , max_receive, int(ret), ec ? ec.message().c_str() : ""); +#endif + return ret; + } + + void peer_connection::append_send_buffer(char* buffer, int size + , chained_buffer::free_buffer_fun destructor, void* userdata + , block_cache_reference ref) + { + TORRENT_ASSERT(is_single_thread()); + m_send_buffer.append_buffer(buffer, size, size, destructor + , userdata, ref); + } + + void peer_connection::append_const_send_buffer(char const* buffer, int size + , chained_buffer::free_buffer_fun destructor, void* userdata + , block_cache_reference ref) + { + TORRENT_ASSERT(is_single_thread()); + m_send_buffer.append_buffer(const_cast(buffer), size, size, destructor + , userdata, ref); + } + + boost::optional + 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 boost::optional(); + } + + namespace { + void session_free_buffer(char* buffer, void* userdata, block_cache_reference) + { + aux::session_interface* ses = static_cast(userdata); + ses->free_buffer(buffer); + } + } + + void peer_connection::send_buffer(char const* buf, int size, int flags) + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_UNUSED(flags); + + int free_space = m_send_buffer.space_in_last_buffer(); + if (free_space > size) free_space = size; + if (free_space > 0) + { + char* dst = m_send_buffer.append(buf, free_space); + + // this should always succeed, because we checked how much space + // there was up-front + TORRENT_UNUSED(dst); + TORRENT_ASSERT(dst != 0); + size -= free_space; + buf += free_space; + } + if (size <= 0) return; + + int i = 0; + while (size > 0) + { + char* chain_buf = m_ses.allocate_buffer(); + if (chain_buf == 0) + { + disconnect(errors::no_memory, op_alloc_sndbuf); + return; + } + + const int alloc_buf_size = m_ses.send_buffer_size(); + int buf_size = (std::min)(alloc_buf_size, size); + memcpy(chain_buf, buf, buf_size); + buf += buf_size; + size -= buf_size; + m_send_buffer.append_buffer(chain_buf, alloc_buf_size, buf_size + , &session_free_buffer, &m_ses); + ++i; + } + setup_send(); + } + + template + struct set_to_zero + { + set_to_zero(T& v, bool cond): m_val(v), m_cond(cond) {} + void fire() { if (!m_cond) return; m_cond = false; m_val = 0; } + ~set_to_zero() { if (m_cond) m_val = 0; } + private: + T& m_val; + bool m_cond; + }; + + void peer_connection::on_receive_data_nb(const error_code& error + , std::size_t bytes_transferred) + { + TORRENT_ASSERT(is_single_thread()); +#if defined TORRENT_ASIO_DEBUGGING + complete_async("peer_connection::on_receive_data_nb"); +#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); + + // nb is short for null_buffers. In this mode we don't actually + // allocate a receive buffer up-front, but get notified when + // we can read from the socket, and then determine how much there + // is to read. + + if (error) + { + TORRENT_ASSERT_VAL(error.value() != 0, error.value()); +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ERROR" + , "in peer_connection::on_receive_data_nb error: (%s:%d) %s" + , error.category().name(), error.value() + , error.message().c_str()); +#endif + on_receive(error, bytes_transferred); + disconnect(error, op_sock_read); + return; + } + + error_code ec; + std::size_t buffer_size = m_socket->available(ec); + if (ec) + { + disconnect(ec, op_available); + return; + } + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming, "READ_AVAILABLE" + , "bytes: %d", int(buffer_size)); +#endif + + // at this point the ioctl told us the socket doesn't have any + // pending bytes. This probably means some error happened. + // in order to find out though, we need to initiate a read + // operation + if (buffer_size == 0) + { + // try to read one byte. The socket is non-blocking anyway + // so worst case, we'll fail with EWOULDBLOCK + buffer_size = 1; + } + else + { + if (buffer_size > m_quota[download_channel]) + { + request_bandwidth(download_channel, buffer_size); + buffer_size = m_quota[download_channel]; + } + // we're already waiting to get some more + // quota from the bandwidth manager + if (buffer_size == 0) + { + // 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; + return; + } + } + + if (buffer_size > 2097152) buffer_size = 2097152; + + boost::asio::mutable_buffer buffer = m_recv_buffer.reserve(buffer_size); + TORRENT_ASSERT(m_recv_buffer.normalized()); + + // utp sockets aren't thread safe... + if (is_utp(*m_socket)) + { + bytes_transferred = m_socket->read_some(boost::asio::mutable_buffers_1(buffer), ec); + + if (ec) + { + if (ec == boost::asio::error::try_again + || ec == boost::asio::error::would_block) + { + // 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(); + return; + } + disconnect(ec, op_sock_read); + return; + } + } + else + { +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("peer_connection::on_receive_data"); +#endif + socket_job j; + j.type = socket_job::read_job; + j.recv_buf = boost::asio::buffer_cast(buffer); + j.buf_size = boost::asio::buffer_size(buffer); + j.peer = self(); + m_ses.post_socket_job(j); + return; + } + + receive_data_impl(error, bytes_transferred, 0); + } + + // -------------------------- + // RECEIVE DATA + // -------------------------- + + // nb is true if this callback is due to a null_buffers() + // invocation of async_read_some(). In that case, we need + // to disregard bytes_transferred. + // at all exit points of this function, one of the following MUST hold: + // 1. the socket is disconnecting + // 2. m_channel_state[download_channel] & peer_info::bw_network == 0 + + void peer_connection::on_receive_data(const error_code& error + , std::size_t bytes_transferred) + { + TORRENT_ASSERT(is_single_thread()); +#if defined TORRENT_ASIO_DEBUGGING + complete_async("peer_connection::on_receive_data"); +#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); + + receive_data_impl(error, bytes_transferred, 10); + } + + void peer_connection::receive_data_impl(const error_code& error + , std::size_t bytes_transferred, int read_loops) + { + TORRENT_ASSERT(is_single_thread()); +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming, "ON_RECEIVE_DATA" + , "bytes: %d error: (%s:%d) %s" + , int(bytes_transferred), error.category().name(), error.value() + , error.message().c_str()); +#endif + + // 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 + boost::shared_ptr me(self()); + + // flush the send buffer at the end of this function + cork _c(*this); + + INVARIANT_CHECK; + + int bytes_in_loop = bytes_transferred; + + if (error) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ERROR" + , "in peer_connection::on_receive_data_impl error: %s" + , error.message().c_str()); +#endif + trancieve_ip_packet(bytes_in_loop, m_remote.address().is_v6()); + on_receive(error, bytes_transferred); + disconnect(error, op_sock_read); + return; + } + + TORRENT_ASSERT(bytes_transferred > 0); + + m_counters.inc_stats_counter(counters::on_read_counter); + m_ses.received_buffer(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; + + int num_loops = 0; + do + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming, "READ" + , "%d bytes", int(bytes_transferred)); +#endif + // correct the dl quota usage, if not all of the buffer was actually read + TORRENT_ASSERT(int(bytes_transferred) <= m_quota[download_channel]); + m_quota[download_channel] -= bytes_transferred; + + if (m_disconnecting) + { + trancieve_ip_packet(bytes_in_loop, m_remote.address().is_v6()); + return; + } + + TORRENT_ASSERT(bytes_transferred > 0); + m_recv_buffer.received(bytes_transferred); + + int bytes = bytes_transferred; + int sub_transferred = 0; + do { +// TODO: The stats checks can not be honored when authenticated encryption is in use +// because we may have encrypted data which we cannot authenticate yet +#if 0 + boost::int64_t cur_payload_dl = m_statistics.last_payload_downloaded(); + boost::int64_t cur_protocol_dl = m_statistics.last_protocol_downloaded(); +#endif + sub_transferred = m_recv_buffer.advance_pos(bytes); + on_receive(error, sub_transferred); + bytes -= sub_transferred; + TORRENT_ASSERT(sub_transferred > 0); + +#if 0 + TORRENT_ASSERT(m_statistics.last_payload_downloaded() - cur_payload_dl >= 0); + TORRENT_ASSERT(m_statistics.last_protocol_downloaded() - cur_protocol_dl >= 0); + boost::int64_t stats_diff = m_statistics.last_payload_downloaded() - cur_payload_dl + + m_statistics.last_protocol_downloaded() - cur_protocol_dl; + TORRENT_ASSERT(stats_diff == int(sub_transferred)); +#endif + if (m_disconnecting) return; + + } while (bytes > 0 && sub_transferred > 0); + + m_recv_buffer.normalize(); + + TORRENT_ASSERT(m_recv_buffer.pos_at_end()); + TORRENT_ASSERT(m_recv_buffer.packet_size() > 0); + + if (m_peer_choked) + { + m_recv_buffer.clamp_size(); + } + + if (num_loops > read_loops) break; + + error_code ec; + bytes_transferred = try_read(read_sync, ec); + TORRENT_ASSERT(bytes_transferred > 0 || ec); + if (ec == boost::asio::error::would_block || ec == boost::asio::error::try_again) break; + if (ec) + { + trancieve_ip_packet(bytes_in_loop, m_remote.address().is_v6()); + disconnect(ec, op_sock_read); + return; + } + bytes_in_loop += bytes_transferred; + ++num_loops; + } + while (bytes_transferred > 0); + + m_last_receive = aux::time_now(); + + if (is_seed()) + { + boost::shared_ptr t = m_torrent.lock(); + if (t) t->seen_complete(); + } + + trancieve_ip_packet(bytes_in_loop, m_remote.address().is_v6()); + + // 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; + + boost::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()); +#if defined TORRENT_ASIO_DEBUGGING + complete_async("peer_connection::on_connection_complete"); +#endif + +#if !defined TORRENT_DISABLE_LOGGING || defined TORRENT_USE_OPENSSL + time_point completed = clock_type::now(); +#endif + + INVARIANT_CHECK; + +#ifndef TORRENT_DISABLE_LOGGING + { + boost::shared_ptr t = m_torrent.lock(); + if (t) t->debug_log("END connect [%p]", static_cast(this)); + m_connect_time = completed; + } +#endif + +#ifdef TORRENT_USE_OPENSSL + // add this RTT to the PRNG seed, to add more unpredictability + boost::uint64_t now = total_microseconds(completed - m_connect); + // assume 12 bits of entropy (i.e. about 8 milliseconds) + RAND_add(&now, 8, 1.5); +#endif + + // if t is NULL, we better not be connecting, since + // we can't decrement the connecting counter + boost::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_connecting = false; + } + + if (m_disconnecting) return; + + if (e) + { + connect_failed(e); + return; + } + + TORRENT_ASSERT(!m_connected); + m_connected = true; + m_counters.inc_stats_counter(counters::num_peers_connected); + + if (m_disconnecting) return; + m_last_receive = aux::time_now(); + + error_code ec; + m_local = m_socket->local_endpoint(ec); + if (ec) + { + disconnect(ec, op_getname); + return; + } + + // if there are outgoing interfaces specified, verify this + // peer is correctly bound to on 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, op_get_interface); + return; + } + disconnect(error_code( + boost::system::errc::no_such_device, generic_category()) + , op_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(m_remote.address().is_v6()); + + TORRENT_ASSERT(m_socket); +#ifndef TORRENT_DISABLE_LOGGING + 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 + tcp::socket::non_blocking_io ioc(true); +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "SET_NON_BLOCKING"); +#endif + m_socket->io_control(ioc, ec); + if (ec) + { + disconnect(ec, op_iocontrol); + return; + } + + if (m_remote == m_socket->local_endpoint(ec)) + { + // if the remote endpoint is the same as the local endpoint, we're connected + // to ourselves + if (m_peer_info && t) t->ban_peer(m_peer_info); + disconnect(errors::self_connection, op_bittorrent, 1); + return; + } + + if (m_remote.address().is_v4() && m_settings.get_int(settings_pack::peer_tos) != 0) + { + error_code err; + m_socket->set_option(type_of_service(m_settings.get_int(settings_pack::peer_tos)), err); +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing, "SET_TOS", "tos: %d e: %s" + , m_settings.get_int(settings_pack::peer_tos), err.message().c_str()); +#endif + } +#if TORRENT_USE_IPV6 && defined IPV6_TCLASS + else if (m_remote.address().is_v6() && m_settings.get_int(settings_pack::peer_tos) != 0) + { + error_code err; + m_socket->set_option(traffic_class(m_settings.get_int(settings_pack::peer_tos)), err); +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing, "SET_TOS", "tos: %d e: %s" + , m_settings.get_int(settings_pack::peer_tos), err.message().c_str()); +#endif + } +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + (*i)->on_connected(); + } +#endif + + on_connected(); + setup_send(); + setup_receive(); + } + + // -------------------------- + // SEND DATA + // -------------------------- + + void peer_connection::on_send_data(error_code const& error + , std::size_t bytes_transferred) + { + TORRENT_ASSERT(is_single_thread()); + m_counters.inc_stats_counter(counters::on_write_counter); + m_ses.sent_buffer(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 + peer_log(peer_log_alert::info, "ON_SEND_DATA", "bytes: %d error: %s" + , int(bytes_transferred), error.message().c_str()); +#endif + + INVARIANT_CHECK; + +#if defined TORRENT_ASIO_DEBUGGING + complete_async("peer_connection::on_send_data"); +#endif + // keep ourselves alive in until this function exits in + // case we disconnect + boost::shared_ptr me(self()); + + TORRENT_ASSERT(m_channel_state[upload_channel] & peer_info::bw_network); + + m_send_buffer.pop_front(bytes_transferred); + + time_point now = clock_type::now(); + + for (std::vector::iterator i = m_download_queue.begin() + , end(m_download_queue.end()); i != end; ++i) + { + if (i->send_buffer_offset == pending_block::not_in_buffer) continue; + boost::int32_t offset = i->send_buffer_offset; + offset -= bytes_transferred; + if (offset < 0) + i->send_buffer_offset = pending_block::not_in_buffer; + else + i->send_buffer_offset = offset; + } + + m_channel_state[upload_channel] &= ~peer_info::bw_network; + + TORRENT_ASSERT(int(bytes_transferred) <= m_quota[upload_channel]); + m_quota[upload_channel] -= bytes_transferred; + + trancieve_ip_packet(bytes_transferred, m_remote.address().is_v6()); + + if (m_send_barrier != INT_MAX) + m_send_barrier -= 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 + peer_log(peer_log_alert::info, "ERROR" + , "%s in peer_connection::on_send_data", error.message().c_str()); +#endif + disconnect(error, op_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(); + m_recv_buffer.free_disk_buffer(); + return; + } + + TORRENT_ASSERT(!m_connecting); + TORRENT_ASSERT(bytes_transferred > 0); + + m_last_sent = now; + +#if TORRENT_USE_ASSERTS + boost::int64_t cur_payload_ul = m_statistics.last_payload_uploaded(); + boost::int64_t 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); + boost::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)); + } + } + + boost::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); + } + else if (!m_in_constructor) + { + TORRENT_ASSERT(m_ses.has_peer(this)); + } + + 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 block_size = t->block_size(); + piece_block last_block(ti.num_pieces()-1 + , (ti.piece_size(ti.num_pieces()-1) + block_size - 1) / block_size); + 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) + { + fprintf(stderr, "m_outstanding_bytes = %d\noutstanding_bytes = %d\n" + , m_outstanding_bytes, outstanding_bytes); + } + + TORRENT_ASSERT(m_outstanding_bytes == outstanding_bytes); + } + + std::set unique; + std::transform(m_download_queue.begin(), m_download_queue.end() + , std::inserter(unique, unique.begin()), boost::bind(&pending_block::block, _1)); + std::transform(m_request_queue.begin(), m_request_queue.end() + , std::inserter(unique, unique.begin()), boost::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 == 0); + + if (m_peer_info->optimistically_unchoked) + TORRENT_ASSERT(!is_choked()); + } + + TORRENT_ASSERT(m_have_piece.count() == m_num_pieces); + + if (!t) + { +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + // since this connection doesn't have a torrent reference + // no torrent should have a reference to this connection either + TORRENT_ASSERT(!m_ses.any_torrent_has_peer(this)); +#endif + return; + } + + if (t->ready_for_connections() && m_initialized) + TORRENT_ASSERT(t->torrent_file().num_pieces() == int(m_have_piece.size())); + + // in share mode we don't close redundant connections + if (m_settings.get_bool(settings_pack::close_redundant_connections) + && !t->share_mode()) + { + bool const ok_to_disconnect = + can_disconnect(error_code(errors::upload_upload_connection)) + || can_disconnect(error_code(errors::uninteresting_upload_peer)) + || can_disconnect(error_code(errors::too_many_requests_when_choked)) + || can_disconnect(error_code(errors::timed_out_no_interest)) + || can_disconnect(error_code(errors::timed_out_no_request)) + || can_disconnect(error_code(errors::timed_out_inactivity)); + + // make sure upload only peers are disconnected + if (t->is_upload_only() + && m_upload_only + && !m_need_interest_update + && t->valid_metadata() + && has_metadata() + && ok_to_disconnect) + TORRENT_ASSERT(m_disconnect_started || t->graceful_pause() || t->has_error()); + + if (m_upload_only + && !m_interesting + && !m_need_interest_update + && m_bitfield_received + && t->are_files_checked() + && t->valid_metadata() + && has_metadata() + && ok_to_disconnect) + TORRENT_ASSERT(m_disconnect_started); + } + + if (!m_disconnect_started && m_initialized + && m_settings.get_bool(settings_pack::close_redundant_connections)) + { + // none of this matters if we're disconnecting anyway + if (t->is_upload_only() && !m_need_interest_update) + TORRENT_ASSERT(!m_interesting || t->graceful_pause() || t->has_error()); + if (is_seed()) + TORRENT_ASSERT(m_upload_only); + } + +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + if (t->has_picker()) + { + std::map num_requests; + for (torrent::const_peer_iterator i = t->begin(); i != t->end(); ++i) + { + // make sure this peer is not a dangling pointer + TORRENT_ASSERT(m_ses.has_peer(*i)); + peer_connection const& p = *(*i); + for (std::vector::const_iterator j = p.request_queue().begin() + , end(p.request_queue().end()); j != end; ++j) + { + ++num_requests[j->block].num_peers; + ++num_requests[j->block].num_peers_with_timeouts; + ++num_requests[j->block].num_peers_with_nowant; + ++num_requests[j->block].num_not_requested; +// num_requests[j->block].peers.push_back(&p); + } + for (std::vector::const_iterator j = p.download_queue().begin() + , end(p.download_queue().end()); j != end; ++j) + { + if (!j->not_wanted && !j->timed_out) ++num_requests[j->block].num_peers; + if (j->timed_out) ++num_requests[j->block].num_peers_with_timeouts; + if (j->not_wanted) ++num_requests[j->block].num_peers_with_nowant; +// num_requests[j->block].peers.push_back(&p); + } + } + for (std::map::iterator j = num_requests.begin() + , end(num_requests.end()); j != end; ++j) + { + piece_block b = j->first; + peer_count_t const& pc = j->second; + int count = pc.num_peers; + int count_with_timeouts = pc.num_peers_with_timeouts; + int count_with_nowant = pc.num_peers_with_nowant; + (void)count_with_timeouts; + (void)count_with_nowant; + int picker_count = t->picker().num_peers(b); + if (!t->picker().is_downloaded(b)) + TORRENT_ASSERT(picker_count == count); + } + } +#endif +/* + if (t->has_picker() && !t->is_aborted()) + { + for (std::vector::const_iterator i = m_download_queue.begin() + , end(m_download_queue.end()); i != end; ++i) + { + pending_block const& pb = *i; + if (pb.timed_out || pb.not_wanted) continue; + TORRENT_ASSERT(t->picker().get_block_state(pb.block) != piece_picker::block_info::state_none); + TORRENT_ASSERT(complete); + } + } +*/ +// extremely expensive invariant check +/* + if (!t->is_seed()) + { + piece_picker& p = t->picker(); + const std::vector& dlq = p.get_download_queue(); + const int blocks_per_piece = static_cast( + t->torrent_file().piece_length() / t->block_size()); + + for (std::vector::const_iterator i = + dlq.begin(); i != dlq.end(); ++i) + { + for (int j = 0; j < blocks_per_piece; ++j) + { + if (std::find(m_request_queue.begin(), m_request_queue.end() + , piece_block(i->index, j)) != m_request_queue.end() + || + std::find(m_download_queue.begin(), m_download_queue.end() + , piece_block(i->index, j)) != m_download_queue.end()) + { + TORRENT_ASSERT(i->info[j].peer == m_remote); + } + else + { + TORRENT_ASSERT(i->info[j].peer != m_remote || i->info[j].finished); + } + } + } + } +*/ + } +#endif + + void peer_connection::set_holepunch_mode() + { + m_holepunch_mode = true; +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "HOLEPUNCH_MODE", "[ on ]"); +#endif + } + + void peer_connection::keep_alive() + { + TORRENT_ASSERT(is_single_thread()); +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + INVARIANT_CHECK; +#endif + + time_duration d; + d = aux::time_now() - m_last_sent; + if (total_seconds(d) < timeout() / 2) return; + + if (m_connecting) return; + if (in_handshake()) return; + + // if the last send has not completed yet, do not send a keep + // alive + if (m_channel_state[upload_channel] & peer_info::bw_network) return; + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "KEEPALIVE"); +#endif + + m_last_sent = aux::time_now(); + 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. + boost::shared_ptr t = m_torrent.lock(); + return m_num_pieces == int(m_have_piece.size()) + && m_num_pieces > 0 && t && t->valid_metadata(); + } + + 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; + } + + void peer_connection::set_upload_only(bool u) + { + TORRENT_ASSERT(is_single_thread()); + // if the peer is a seed, don't allow setting + // upload_only to false + if (m_upload_only || is_seed()) return; + + m_upload_only = u; + boost::shared_ptr t = associated_torrent().lock(); + t->set_seed(m_peer_info, u); + disconnect_if_redundant(); + } + +} diff --git a/src/peer_connection_handle.cpp b/src/peer_connection_handle.cpp new file mode 100644 index 0000000..9137f5f --- /dev/null +++ b/src/peer_connection_handle.cpp @@ -0,0 +1,341 @@ +/* + +Copyright (c) 2015-2016, Arvid Norberg, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/peer_connection_handle.hpp" +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/bt_peer_connection.hpp" + +#ifndef TORRENT_DISABLE_LOGGING +#include // for va_start, va_end +#endif + +namespace libtorrent +{ + +int peer_connection_handle::type() const +{ + boost::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->type(); +} + +void peer_connection_handle::add_extension(boost::shared_ptr ext) +{ +#ifndef TORRENT_DISABLE_EXTENSIONS + boost::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + pc->add_extension(ext); +#else + TORRENT_UNUSED(ext); +#endif +} + +peer_plugin const* peer_connection_handle::find_plugin(char const* type) +{ +#ifndef TORRENT_DISABLE_EXTENSIONS + boost::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->find_plugin(type); +#else + TORRENT_UNUSED(type); + return NULL; +#endif +} + +bool peer_connection_handle::is_seed() const +{ + boost::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->is_seed(); +} + +bool peer_connection_handle::upload_only() const +{ + boost::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->upload_only(); +} + +peer_id const& peer_connection_handle::pid() const +{ + boost::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->pid(); +} + +bool peer_connection_handle::has_piece(int i) const +{ + boost::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->has_piece(i); +} + +bool peer_connection_handle::is_interesting() const +{ + boost::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->is_interesting(); +} + +bool peer_connection_handle::is_choked() const +{ + boost::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->is_choked(); +} + +bool peer_connection_handle::is_peer_interested() const +{ + boost::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->is_peer_interested(); +} + +bool peer_connection_handle::has_peer_choked() const +{ + boost::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->has_peer_choked(); +} + +void peer_connection_handle::choke_this_peer() +{ + boost::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + pc->choke_this_peer(); +} + +void peer_connection_handle::maybe_unchoke_this_peer() +{ + boost::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + pc->maybe_unchoke_this_peer(); +} + +void peer_connection_handle::get_peer_info(peer_info& p) const +{ + boost::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + pc->get_peer_info(p); +} + +torrent_handle peer_connection_handle::associated_torrent() const +{ + boost::shared_ptr pc = native_handle(); + if (!pc) return torrent_handle(); + boost::shared_ptr t = pc->associated_torrent().lock(); + if (!t) return torrent_handle(); + return t->get_handle(); +} + +tcp::endpoint const& peer_connection_handle::remote() const +{ + boost::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->remote(); +} + +tcp::endpoint peer_connection_handle::local_endpoint() const +{ + boost::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->local_endpoint(); +} + +void peer_connection_handle::disconnect(error_code const& ec, operation_t op, int error) +{ + boost::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + pc->disconnect(ec, op, error); +} + +bool peer_connection_handle::is_disconnecting() const +{ + boost::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->is_disconnecting(); +} + +bool peer_connection_handle::is_connecting() const +{ + boost::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->is_connecting(); +} + +bool peer_connection_handle::is_outgoing() const +{ + boost::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->is_outgoing(); +} + +bool peer_connection_handle::on_local_network() const +{ + boost::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->on_local_network(); +} + +bool peer_connection_handle::ignore_unchoke_slots() const +{ + boost::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->ignore_unchoke_slots(); +} + +bool peer_connection_handle::failed() const +{ + boost::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->failed(); +} + +#ifndef TORRENT_DISABLE_LOGGING + +TORRENT_FORMAT(4,5) +void peer_connection_handle::peer_log(peer_log_alert::direction_t direction + , char const* event, char const* fmt, ...) const +{ + boost::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" +#endif + pc->peer_log(direction, event, fmt, v); +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + va_end(v); +} + +#endif // TORRENT_DISABLE_LOGGING + +bool peer_connection_handle::can_disconnect(error_code const& ec) const +{ + boost::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->can_disconnect(ec); +} + +bool peer_connection_handle::has_metadata() const +{ + boost::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->has_metadata(); +} + +bool peer_connection_handle::in_handshake() const +{ + boost::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->in_handshake(); +} + +void peer_connection_handle::send_buffer(char const* begin, int size, int flags) +{ + boost::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + pc->send_buffer(begin, size, flags); +} + +time_t peer_connection_handle::last_seen_complete() const +{ + boost::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->last_seen_complete(); +} + +time_point peer_connection_handle::time_of_last_unchoke() const +{ + boost::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->time_of_last_unchoke(); +} + +bool bt_peer_connection_handle::packet_finished() const +{ + boost::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->packet_finished(); +} + +bool bt_peer_connection_handle::support_extensions() const +{ + boost::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) && !defined(TORRENT_DISABLE_EXTENSIONS) + boost::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->supports_encryption(); +#else + return false; +#endif +} + +void bt_peer_connection_handle::switch_send_crypto(boost::shared_ptr crypto) +{ +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + boost::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + pc->switch_send_crypto(crypto); +#else + TORRENT_UNUSED(crypto); +#endif +} + +void bt_peer_connection_handle::switch_recv_crypto(boost::shared_ptr crypto) +{ +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + boost::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + pc->switch_recv_crypto(crypto); +#else + TORRENT_UNUSED(crypto); +#endif +} + +boost::shared_ptr bt_peer_connection_handle::native_handle() const +{ + return boost::static_pointer_cast( + peer_connection_handle::native_handle()); +} + +} // namespace libtorrent diff --git a/src/peer_list.cpp b/src/peer_list.cpp new file mode 100644 index 0000000..12adb44 --- /dev/null +++ b/src/peer_list.cpp @@ -0,0 +1,1399 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/web_peer_connection.hpp" +#include "libtorrent/peer_list.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/socket_type.hpp" +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/aux_/session_interface.hpp" +#include "libtorrent/piece_picker.hpp" +#include "libtorrent/broadcast_socket.hpp" +#include "libtorrent/peer_info.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/extensions.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/torrent_peer_allocator.hpp" + +#ifdef TORRENT_DEBUG +#include "libtorrent/bt_peer_connection.hpp" +#endif + +#if defined TORRENT_DEBUG || defined TORRENT_RELEASE_ASSERTS +#include "libtorrent/socket_io.hpp" // for print_endpoint +#endif + +#ifndef TORRENT_DISABLE_LOGGING +#include "libtorrent/socket_io.hpp" // for print_endpoint +#include "libtorrent/ip_voter.hpp" // for external_ip +#endif + +namespace +{ + using namespace libtorrent; + + struct match_peer_endpoint + { + match_peer_endpoint(tcp::endpoint const& ep) + : m_ep(ep) + {} + + bool operator()(torrent_peer const* p) const + { + TORRENT_ASSERT(p->in_use); + return p->address() == m_ep.address() && p->port == m_ep.port(); + } + + tcp::endpoint const& m_ep; + }; + +#ifndef TORRENT_DISABLE_INVARIANT_CHECKS + struct match_peer_connection + { + match_peer_connection(peer_connection_interface const& c) : m_conn(c) {} + + bool operator()(torrent_peer const* p) const + { + TORRENT_ASSERT(p->in_use); + return p->connection == &m_conn; + } + + peer_connection_interface const& m_conn; + }; +#endif + +#if TORRENT_USE_ASSERTS + struct match_peer_connection_or_endpoint + { + match_peer_connection_or_endpoint(peer_connection_interface const& c) : m_conn(c) {} + + bool operator()(torrent_peer const* p) const + { + TORRENT_ASSERT(p->in_use); + return p->connection == &m_conn + || (p->ip() == m_conn.remote() + && p->connectable); + } + + peer_connection_interface const& m_conn; + }; +#endif + +} + +namespace libtorrent +{ + peer_list::peer_list() + : m_locked_peer(NULL) + , m_num_seeds(0) + , m_finished(0) + , m_round_robin(0) + , m_num_connect_candidates(0) + , m_max_failcount(3) + { + thread_started(); + } + + void peer_list::set_max_failcount(torrent_state* state) + { + if (state->max_failcount == m_max_failcount) return; + + recalculate_connect_candidates(state); + } + + // disconnects and removes all peers that are now filtered fills in 'erased' + // with torrent_peer pointers that were removed from the peer list. Any + // references to these peers must be cleared immediately after this call + // returns. For instance, in the piece picker. + void peer_list::apply_ip_filter(ip_filter const& filter + , torrent_state* state, std::vector
    & banned) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + for (iterator i = m_peers.begin(); i != m_peers.end();) + { + if ((filter.access((*i)->address()) & ip_filter::blocked) == 0) + { + ++i; + continue; + } + if (*i == m_locked_peer) + { + ++i; + continue; + } + + int current = 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 + , op_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 == 0 + || (*i)->connection->peer_info_struct() == 0); + } + + erase_peer(i, state); + i = m_peers.begin() + current; + } + } + + void peer_list::clear_peer_prio() + { + for (peers_t::iterator i = m_peers.begin() + , end(m_peers.end()); i != end; ++i) + (*i)->peer_rank = 0; + } + + // disconnects and removes all peers that are now filtered + // fills in 'erased' with torrent_peer pointers that were removed + // from the peer list. Any references to these peers must be cleared + // immediately after this call returns. For instance, in the piece picker. + void peer_list::apply_port_filter(port_filter const& filter + , torrent_state* state, std::vector
    & banned) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + for (iterator i = m_peers.begin(); i != m_peers.end();) + { + if ((filter.access((*i)->port) & port_filter::blocked) == 0) + { + ++i; + continue; + } + if (*i == m_locked_peer) + { + ++i; + continue; + } + + int current = 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 = m_peers.size(); + peer_connection_interface* p = (*i)->connection; + + banned.push_back(p->remote().address()); + + p->disconnect(errors::banned_by_port_filter, op_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 == 0 + || (*i)->connection->peer_info_struct() == 0); + } + + 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); + + std::pair range = find_peers(p->address()); + iterator iter = std::find_if(range.first, range.second, match_peer_endpoint(p->ip())); + if (iter == range.second) return; + erase_peer(iter, state); + } + + // any peer that is erased from m_peers will be + // erased through this function. This way we can make + // sure that any references to the peer are removed + // as well, such as in the piece picker. + void peer_list::erase_peer(iterator i, torrent_state* state) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + TORRENT_ASSERT(i != m_peers.end()); + TORRENT_ASSERT(m_locked_peer != *i); + + state->erased.push_back(*i); + if ((*i)->seed) + { + TORRENT_ASSERT(m_num_seeds > 0); + --m_num_seeds; + } + if (is_connect_candidate(**i)) + update_connect_candidates(-1); + TORRENT_ASSERT(m_num_connect_candidates < int(m_peers.size())); + if (m_round_robin > i - m_peers.begin()) --m_round_robin; + if (m_round_robin >= int(m_peers.size())) m_round_robin = 0; + + // if this peer is in the connect candidate + // cache, erase it from there as well + std::vector::iterator ci = std::find(m_candidate_cache.begin(), m_candidate_cache.end(), *i); + if (ci != m_candidate_cache.end()) m_candidate_cache.erase(ci); + +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT((*i)->in_use); + (*i)->in_use = false; +#endif + + state->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.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.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 == 0; + } + + void peer_list::erase_peers(torrent_state* state, int 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 (m_finished != state->is_finished) + recalculate_connect_candidates(state); + + int round_robin = random() % m_peers.size(); + + 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 current = round_robin; + + if (is_erase_candidate(pe) + && (erase_candidate == -1 + || !compare_peer_erase(*m_peers[erase_candidate], pe))) + { + if (should_erase_immediately(pe)) + { + if (erase_candidate > current) --erase_candidate; + if (force_erase_candidate > current) --force_erase_candidate; + TORRENT_ASSERT(current >= 0 && current < int(m_peers.size())); + erase_peer(m_peers.begin() + current, state); + continue; + } + else + { + erase_candidate = current; + } + } + if (is_force_erase_candidate(pe) + && (force_erase_candidate == -1 + || !compare_peer_erase(*m_peers[force_erase_candidate], pe))) + { + force_erase_candidate = current; + } + + ++round_robin; + } + + if (erase_candidate > -1) + { + TORRENT_ASSERT(erase_candidate >= 0 && erase_candidate < int(m_peers.size())); + erase_peer(m_peers.begin() + erase_candidate, state); + } + else if ((flags & force_erase) && force_erase_candidate > -1) + { + TORRENT_ASSERT(force_erase_candidate >= 0 && force_erase_candidate < int(m_peers.size())); + erase_peer(m_peers.begin() + force_erase_candidate, state); + } + } + + // returns true if the peer was actually banned + bool peer_list::ban_peer(torrent_peer* p) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + TORRENT_ASSERT(p->in_use); + + if (is_connect_candidate(*p)) + update_connect_candidates(-1); + + p->banned = true; + TORRENT_ASSERT(!is_connect_candidate(*p)); + return true; + } + + void peer_list::set_connection(torrent_peer* p, peer_connection_interface* c) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + TORRENT_ASSERT(p->in_use); + TORRENT_ASSERT(c); + + const bool was_conn_cand = is_connect_candidate(*p); + p->connection = c; + if (was_conn_cand) update_connect_candidates(-1); + } + + void peer_list::inc_failcount(torrent_peer* p) + { + // failcount is a 5 bit value + if (p->failcount == 31) return; + + const bool 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 f) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + TORRENT_ASSERT(p->in_use); + const bool was_conn_cand = is_connect_candidate(*p); + p->failcount = 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 (m_finished != state->is_finished) + recalculate_connect_candidates(state); + + external_ip const& external = *state->ip; + int external_port = state->port; + + if (m_round_robin >= int(m_peers.size())) m_round_robin = 0; + + int max_peerlist_size = state->max_peerlist_size; + + // TODO: 2 it would be nice if there was a way to iterate over these + // torrent_peer objects in the order they are allocated in the pool + // instead. It would probably be more efficient + for (int iterations = (std::min)(int(m_peers.size()), 300); + iterations > 0; --iterations) + { + ++state->loop_counter; + + if (m_round_robin >= int(m_peers.size())) m_round_robin = 0; + + torrent_peer& pe = *m_peers[m_round_robin]; + TORRENT_ASSERT(pe.in_use); + int current = m_round_robin; + + // if the number of peers is growing large + // we need to start weeding. + + if (int(m_peers.size()) >= max_peerlist_size * 0.95 + && max_peerlist_size > 0) + { + if (is_erase_candidate(pe) + && (erase_candidate == -1 + || !compare_peer_erase(*m_peers[erase_candidate], pe))) + { + if (should_erase_immediately(pe)) + { + if (erase_candidate > current) --erase_candidate; + erase_peer(m_peers.begin() + current, state); + continue; + } + else + { + erase_candidate = current; + } + } + } + + ++m_round_robin; + + if (!is_connect_candidate(pe)) continue; + + if (pe.last_connected + && session_time - pe.last_connected < + (int(pe.failcount) + 1) * state->min_reconnect_time) + continue; + + // compare peer returns true if lhs is better than rhs. In this + // case, it returns true if the current candidate is better than + // pe, which is the peer m_round_robin points to. If it is, just + // keep looking. + if (peers.size() == candidate_count + && compare_peer(peers.back(), &pe, external, external_port)) continue; + + if (peers.size() >= candidate_count) + peers.resize(candidate_count - 1); + + // insert this candidate sorted into peers + std::vector::iterator i = std::lower_bound(peers.begin(), peers.end() + , &pe, boost::bind(&peer_list::compare_peer, this, _1, _2, boost::cref(external), external_port)); + + peers.insert(i, &pe); + } + + if (erase_candidate > -1) + { + erase_peer(m_peers.begin() + erase_candidate, state); + } + } + + bool peer_list::new_connection(peer_connection_interface& c, int session_time + , torrent_state* state) + { + TORRENT_ASSERT(is_single_thread()); +// TORRENT_ASSERT(!c.is_outgoing()); + + INVARIANT_CHECK; + + TORRENT_ASSERT(!state->is_paused); + + iterator iter; + torrent_peer* i = 0; + + bool found = false; + if (state->allow_multiple_connections_per_ip) + { + tcp::endpoint remote = c.remote(); + std::pair range = find_peers(remote.address()); + iter = std::find_if(range.first, range.second, match_peer_endpoint(remote)); + + 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 + 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, op_bittorrent); + return false; + } + + if (i->connection != 0) + { + bool self_connection = + i->connection->remote() == c.local_endpoint() + || i->connection->local_endpoint() == c.remote(); + + if (self_connection) + { + c.disconnect(errors::self_connection, op_bittorrent, 1); + TORRENT_ASSERT(i->connection->peer_info_struct() == i); + i->connection->disconnect(errors::self_connection, op_bittorrent, 1); + TORRENT_ASSERT(i->connection == 0); + 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, op_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 outgoing1 = c.is_outgoing(); + + // for this, we compare our endpoints (IP and port) + // and whoever has the lower IP,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 our_port = outgoing1 ? i->connection->local_endpoint().port() : c.local_endpoint().port(); + int other_port= outgoing1 ? c.remote().port() : i->connection->remote().port(); + + if (our_port < other_port) + { +#ifndef TORRENT_DISABLE_LOGGING + c.peer_log(peer_log_alert::info, "DUPLICATE_PEER_RESOLUTION" + , "\"%d\" < \"%d\"", our_port, other_port); + i->connection->peer_log(peer_log_alert::info, "DUPLICATE_PEER_RESOLUTION" + , "\"%d\" < \"%d\"", our_port, other_port); +#endif + + // we should keep our outgoing connection + if (!outgoing1) + { + c.disconnect(errors::duplicate_peer_id, op_bittorrent); + return false; + } + TORRENT_ASSERT(m_locked_peer == NULL); + m_locked_peer = i; + i->connection->disconnect(errors::duplicate_peer_id, op_bittorrent); + m_locked_peer = NULL; + } + else + { +#ifndef TORRENT_DISABLE_LOGGING + c.peer_log(peer_log_alert::info, "DUPLICATE_PEER_RESOLUTION" + , "\"%d\" >= \"%d\"", our_port, other_port); + i->connection->peer_log(peer_log_alert::info, "DUPLICATE_PEER_RESOLUTION" + , "\"%d\" >= \"%d\"", our_port, other_port); +#endif + // they should keep their outgoing connection + if (outgoing1) + { + c.disconnect(errors::duplicate_peer_id, op_bittorrent); + return false; + } + TORRENT_ASSERT(m_locked_peer == NULL); + m_locked_peer = i; + i->connection->disconnect(errors::duplicate_peer_id, op_bittorrent); + m_locked_peer = NULL; + } + } + } + + 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, op_bittorrent); + return false; + } + // restore it + iter = std::lower_bound( + m_peers.begin(), m_peers.end() + , c.remote().address(), peer_address_compare() + ); + } + +#if TORRENT_USE_IPV6 + bool is_v6 = c.remote().address().is_v6(); +#else + bool is_v6 = false; +#endif + torrent_peer* p = state->peer_allocator->allocate_peer_entry( + is_v6 ? torrent_peer_allocator_interface::ipv6_peer_type + : torrent_peer_allocator_interface::ipv4_peer_type); + if (p == 0) return false; + +#if TORRENT_USE_IPV6 + if (is_v6) + new (p) ipv6_peer(c.remote(), false, 0); + else +#endif + new (p) ipv4_peer(c.remote(), false, 0); + +#if TORRENT_USE_ASSERTS + p->in_use = true; +#endif + + iter = m_peers.insert(iter, p); + + if (m_round_robin >= iter - m_peers.begin()) ++m_round_robin; + + i = *iter; + + i->source = peer_info::incoming; + } + + TORRENT_ASSERT(i); + c.set_peer_info(i); + TORRENT_ASSERT(i->connection == 0); + c.add_stat(boost::int64_t(i->prev_amount_download) << 10, boost::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 = 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 port, torrent_peer* p, int src, torrent_state* state) + { + TORRENT_ASSERT(p != 0); + 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) + { + tcp::endpoint remote(p->address(), port); + std::pair range = find_peers(remote.address()); + iterator i = std::find_if(range.first, range.second + , match_peer_endpoint(remote)); + if (i != range.second) + { + torrent_peer& pp = **i; + TORRENT_ASSERT(pp.in_use); + if (pp.connection) + { + bool 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 |= 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 == NULL); + m_locked_peer = p; + p->connection->disconnect(errors::duplicate_peer_id, op_bittorrent); + m_locked_peer = NULL; + 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 was_conn_cand = is_connect_candidate(*p); + p->port = port; + p->source |= 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 + for (const_iterator i = m_peers.begin() + , end(m_peers.end()); i != end; ++i) + { + if (*i == p) return true; + } + return false; + } + + void peer_list::set_seed(torrent_peer* p, bool s) + { + TORRENT_ASSERT(is_single_thread()); + if (p == 0) return; + TORRENT_ASSERT(p->in_use); + if (p->seed == s) return; + bool 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, int flags + , torrent_state* state) + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(p); + TORRENT_ASSERT(p->in_use); + + int max_peerlist_size = state->max_peerlist_size; + + if (max_peerlist_size + && int(m_peers.size()) >= max_peerlist_size) + { + if (p->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) && !defined(TORRENT_DISABLE_EXTENSIONS) + if (flags & flag_encryption) p->pe_support = true; +#endif + if (flags & flag_seed) + { + p->seed = true; + TORRENT_ASSERT(m_num_seeds < int(m_peers.size())); + ++m_num_seeds; + } + if (flags & flag_utp) + p->supports_utp = true; + if (flags & flag_holepunch) + p->supports_holepunch = true; + if (is_connect_candidate(*p)) + update_connect_candidates(1); + + return true; + } + + void peer_list::update_peer(torrent_peer* p, int src, int flags + , tcp::endpoint const& remote, char const* /* destination*/) + { + TORRENT_ASSERT(is_single_thread()); + bool 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 |= src; + + // if this peer has failed before, decrease the + // counter to allow it another try, since somebody + // else is appearantly 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 & flag_seed) && !p->connection) + { + if (!p->seed) + { + TORRENT_ASSERT(m_num_seeds < int(m_peers.size())); + ++m_num_seeds; + } + p->seed = true; + } + if (flags & flag_utp) + p->supports_utp = true; + if (flags & flag_holepunch) + p->supports_holepunch = true; + + if (was_conn_cand != is_connect_candidate(*p)) + { + update_connect_candidates(was_conn_cand ? -1 : 1); + } + } + + void peer_list::update_connect_candidates(int delta) + { + TORRENT_ASSERT(is_single_thread()); + if (delta == 0) return; + m_num_connect_candidates += delta; + if (delta < 0) + { + TORRENT_ASSERT(m_num_connect_candidates >= 0); + if (m_num_connect_candidates < 0) m_num_connect_candidates = 0; + } + } + +#if TORRENT_USE_I2P + torrent_peer* peer_list::add_i2p_peer(char const* destination, int src, char flags, torrent_state* state) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + bool found = false; + iterator iter = std::lower_bound( + m_peers.begin(), m_peers.end() + , destination, peer_address_compare() + ); + + if (iter != m_peers.end() && strcmp((*iter)->dest(), destination) == 0) + found = true; + + torrent_peer* p = 0; + + if (!found) + { + // we don't have any info about this peer. + // add a new entry + p = state->peer_allocator->allocate_peer_entry(torrent_peer_allocator_interface::i2p_peer_type); + if (p == NULL) return NULL; + new (p) i2p_peer(destination, true, src); + +#if TORRENT_USE_ASSERTS + p->in_use = true; +#endif + + if (!insert_peer(p, iter, flags, state)) + { +#if TORRENT_USE_ASSERTS + p->in_use = false; +#endif + + state->peer_allocator->free_peer_entry(p); + return 0; + } + } + else + { + p = *iter; + update_peer(p, src, flags, tcp::endpoint(), destination); + } + return p; + } +#endif // TORRENT_USE_I2P + + // if this returns non-NULL, the torrent need to post status update + torrent_peer* peer_list::add_peer(tcp::endpoint const& remote, int src, char flags + , torrent_state* state) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + // just ignore the obviously invalid entries + if (remote.address() == address() || remote.port() == 0) + return 0; + +#if TORRENT_USE_IPV6 + // 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 0; +#endif + + iterator iter; + torrent_peer* p = 0; + + bool found = false; + if (state->allow_multiple_connections_per_ip) + { + std::pair range = find_peers(remote.address()); + iter = std::find_if(range.first, range.second, match_peer_endpoint(remote)); + 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 + +#if TORRENT_USE_IPV6 + bool is_v6 = remote.address().is_v6(); +#else + bool is_v6 = false; +#endif + p = state->peer_allocator->allocate_peer_entry( + is_v6 ? torrent_peer_allocator_interface::ipv6_peer_type + : torrent_peer_allocator_interface::ipv4_peer_type); + if (p == NULL) return NULL; + +#if TORRENT_USE_IPV6 + if (is_v6) + new (p) ipv6_peer(remote, true, src); + else +#endif + new (p) ipv4_peer(remote, true, src); + +#if TORRENT_USE_ASSERTS + p->in_use = true; +#endif + + if (!insert_peer(p, iter, flags, state)) + { +#if TORRENT_USE_ASSERTS + p->in_use = false; +#endif + state->peer_allocator->free_peer_entry(p); + return 0; + } + state->first_time_seen = true; + } + else + { + p = *iter; + TORRENT_ASSERT(p->in_use); + update_peer(p, src, flags, remote, 0); + 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 (m_finished != state->is_finished) + recalculate_connect_candidates(state); + + // clear out any peers from the cache that no longer + // are connection candidates + for (std::vector::iterator 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 NULL; + } + + 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(m_finished == state->is_finished); + + TORRENT_ASSERT(is_connect_candidate(*p)); + return p; + } + + // this is called whenever a peer connection is closed + void peer_list::connection_closed(const peer_connection_interface& c + , int session_time, torrent_state* state) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + torrent_peer* p = c.peer_info_struct(); + + // if we couldn't find the connection in our list, just ignore it. + if (p == 0) return; + + TORRENT_ASSERT(p->in_use); + +#ifndef TORRENT_DISABLE_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::find_if( + m_peers.begin() + , m_peers.end() + , match_peer_connection(c)) + != m_peers.end()); +#endif + + TORRENT_ASSERT(p->connection == &c); + TORRENT_ASSERT(!is_connect_candidate(*p)); + + p->connection = 0; + 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 = 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; + + for (const_iterator i = m_peers.begin(); + i != m_peers.end(); ++i) + { + m_num_connect_candidates += is_connect_candidate(**i); + } + +#if TORRENT_USE_INVARIANT_CHECKS + // the invariant is not likely to be upheld at the entry of this function + // but it is likely to have been restored by the end of it + check_invariant(); +#endif + } + +#if TORRENT_USE_ASSERTS + bool peer_list::has_connection(const peer_connection_interface* c) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + TORRENT_ASSERT(c); + + iterator iter = std::lower_bound( + m_peers.begin(), m_peers.end() + , c->remote().address(), peer_address_compare()); + + if (iter != m_peers.end() && (*iter)->address() == c->remote().address()) + return true; + + return std::find_if( + m_peers.begin() + , m_peers.end() + , match_peer_connection_or_endpoint(*c)) != m_peers.end(); + } +#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 total_connections = 0; + int nonempty_connections = 0; + 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; + ++total_connections; + 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() == 0 + || p.connection->peer_info_struct() == &p); + ++nonempty_connections; + } + + TORRENT_ASSERT(m_num_connect_candidates == connect_candidates); +#endif // TORRENT_EXPENSIVE_INVARIANT_CHECKS + + } +#endif // TORRENT_DEBUG + + // this returns true if lhs is a better erase candidate than rhs + bool peer_list::compare_peer_erase(torrent_peer const& lhs, torrent_peer const& rhs) const + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(lhs.connection == 0); + TORRENT_ASSERT(rhs.connection == 0); + + // primarily, prefer getting rid of peers we've already tried and failed + if (lhs.failcount != rhs.failcount) + return lhs.failcount > rhs.failcount; + + bool lhs_resume_data_source = lhs.source == peer_info::resume_data; + bool rhs_resume_data_source = rhs.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 lhs_resume_data_source > rhs_resume_data_source; + + if (lhs.connectable != rhs.connectable) + return lhs.connectable < rhs.connectable; + + return lhs.trust_points < rhs.trust_points; + } + + // this returns true if lhs is a better connect candidate than rhs + bool peer_list::compare_peer(torrent_peer const* lhs, torrent_peer const* rhs + , external_ip const& external, int external_port) const + { + TORRENT_ASSERT(is_single_thread()); + // prefer peers with lower failcount + if (lhs->failcount != rhs->failcount) + return lhs->failcount < rhs->failcount; + + // Local peers should always be tried first + bool lhs_local = is_local(lhs->address()); + bool rhs_local = is_local(rhs->address()); + if (lhs_local != rhs_local) return lhs_local > rhs_local; + + if (lhs->last_connected != rhs->last_connected) + return lhs->last_connected < rhs->last_connected; + + int lhs_rank = source_rank(lhs->source); + int rhs_rank = source_rank(rhs->source); + if (lhs_rank != rhs_rank) return lhs_rank > rhs_rank; + + boost::uint32_t lhs_peer_rank = lhs->rank(external, external_port); + boost::uint32_t rhs_peer_rank = rhs->rank(external, external_port); + if (lhs_peer_rank > rhs_peer_rank) return true; + return false; + } +} + diff --git a/src/performance_counters.cpp b/src/performance_counters.cpp new file mode 100644 index 0000000..cc737d7 --- /dev/null +++ b/src/performance_counters.cpp @@ -0,0 +1,167 @@ +/* + +Copyright (c) 2013-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/performance_counters.hpp" +#include "libtorrent/assert.hpp" +#include // for memset + +#ifdef TORRENT_USE_VALGRIND +#include +#endif + +namespace libtorrent { + + + counters::counters() + { +#if BOOST_ATOMIC_LLONG_LOCK_FREE == 2 + for (int i = 0; i < sizeof(m_stats_counter) + / sizeof(m_stats_counter[0]); ++i) + m_stats_counter[i].store(0, boost::memory_order_relaxed); +#else + memset(m_stats_counter, 0, sizeof(m_stats_counter)); +#endif + } + + counters::counters(counters const& c) + { +#if BOOST_ATOMIC_LLONG_LOCK_FREE == 2 + for (int i = 0; i < sizeof(m_stats_counter) + / sizeof(m_stats_counter[0]); ++i) + m_stats_counter[i].store( + c.m_stats_counter[i].load(boost::memory_order_relaxed) + , boost::memory_order_relaxed); +#else + mutex::scoped_lock l(c.m_mutex); + memcpy(m_stats_counter, c.m_stats_counter, sizeof(m_stats_counter)); +#endif + } + + counters& counters::operator=(counters const& c) + { +#if BOOST_ATOMIC_LLONG_LOCK_FREE == 2 + for (int i = 0; i < sizeof(m_stats_counter) + / sizeof(m_stats_counter[0]); ++i) + m_stats_counter[i].store( + c.m_stats_counter[i].load(boost::memory_order_relaxed) + , boost::memory_order_relaxed); +#else + mutex::scoped_lock l(m_mutex); + mutex::scoped_lock l2(c.m_mutex); + memcpy(m_stats_counter, c.m_stats_counter, sizeof(m_stats_counter)); +#endif + return *this; + } + + boost::int64_t counters::operator[](int i) const + { + TORRENT_ASSERT(i >= 0); + TORRENT_ASSERT(i < num_counters); +#ifdef TORRENT_USE_VALGRIND + VALGRIND_CHECK_VALUE_IS_DEFINED(m_stats_counter[i]); +#endif + +#if BOOST_ATOMIC_LLONG_LOCK_FREE == 2 + return m_stats_counter[i].load(boost::memory_order_relaxed); +#else + mutex::scoped_lock l(m_mutex); + return m_stats_counter[i]; +#endif + } + + // the argument specifies which counter to + // increment or decrement + boost::int64_t counters::inc_stats_counter(int c, boost::int64_t value) + { + // 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); + +#if BOOST_ATOMIC_LLONG_LOCK_FREE == 2 + boost::int64_t pv = m_stats_counter[c].fetch_add(value, boost::memory_order_relaxed); + TORRENT_ASSERT(pv + value >= 0); + return pv + value; +#else + mutex::scoped_lock l(m_mutex); + TORRENT_ASSERT(m_stats_counter[c] + value >= 0); + return m_stats_counter[c] += value; +#endif + } + + // ratio is a vaue between 0 and 100 representing the percentage the value + // is blended in at. + void counters::blend_stats_counter(int c, boost::int64_t value, int ratio) + { + TORRENT_ASSERT(c >= num_stats_counters); + TORRENT_ASSERT(c < num_counters); + TORRENT_ASSERT(ratio >= 0); + TORRENT_ASSERT(ratio <= 100); + +#if BOOST_ATOMIC_LLONG_LOCK_FREE == 2 + boost::int64_t current = m_stats_counter[c].load(boost::memory_order_relaxed); + boost::int64_t new_value = (current * (100-ratio) + value * ratio) / 100; + + while (!m_stats_counter[c].compare_exchange_weak(current, new_value + , boost::memory_order_relaxed)) + { + new_value = (current * (100-ratio) + value * ratio) / 100; + } +#else + mutex::scoped_lock l(m_mutex); + boost::int64_t current = m_stats_counter[c]; + m_stats_counter[c] = (current * (100-ratio) + value * ratio) / 100; +#endif + } + + void counters::set_value(int c, boost::int64_t value) + { + TORRENT_ASSERT(c >= 0); + TORRENT_ASSERT(c < num_counters); + +#if BOOST_ATOMIC_LLONG_LOCK_FREE == 2 + m_stats_counter[c].store(value); +#else + mutex::scoped_lock 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..ac75969 --- /dev/null +++ b/src/piece_picker.cpp @@ -0,0 +1,3778 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include +#include + +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/piece_picker.hpp" +#include "libtorrent/bitfield.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/alloca.hpp" +#include "libtorrent/performance_counters.hpp" // for counters +#include "libtorrent/alert_types.hpp" // for picker_log_alert + +#if TORRENT_USE_ASSERTS +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/torrent_peer.hpp" +#endif + +#ifdef TORRENT_USE_VALGRIND +#include +#endif + +#include "libtorrent/invariant_check.hpp" + +#define TORRENT_PIECE_PICKER_INVARIANT_CHECK INVARIANT_CHECK +//#define TORRENT_NO_EXPENSIVE_INVARIANT_CHECK +//#define TORRENT_PIECE_PICKER_INVARIANT_CHECK + +// this is really only useful for debugging unit tests +//#define TORRENT_PICKER_LOG + +namespace libtorrent +{ + + const piece_block piece_block::invalid((std::numeric_limits::max)(), (std::numeric_limits::max)()); + + piece_picker::piece_picker() + : m_seeds(0) + , m_num_passed(0) + , m_priority_boundries(1, int(m_pieces.size())) + , m_blocks_per_piece(0) + , m_blocks_in_last_piece(0) + , m_num_filtered(0) + , m_num_have_filtered(0) + , m_cursor(0) + , m_reverse_cursor(0) + , m_num_have(0) + , m_num_pad_files(0) + , m_dirty(false) + { +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "new piece_picker" << std::endl; +#endif +#if TORRENT_USE_INVARIANT_CHECKS + check_invariant(); +#endif + } + + void piece_picker::init(int blocks_per_piece, int blocks_in_last_piece, int total_num_pieces) + { + TORRENT_ASSERT(blocks_per_piece > 0); + TORRENT_ASSERT(total_num_pieces > 0); + +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "piece_picker::init()" << std::endl; +#endif + // allocate the piece_map to cover all pieces + // and make them invalid (as if we don't have a single piece) + m_piece_map.resize(total_num_pieces, piece_pos(0, 0)); + m_reverse_cursor = int(m_piece_map.size()); + m_cursor = 0; + + for (int i = 0; i < piece_pos::num_download_categories; ++i) + m_downloads[i].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_num_passed = 0; + m_dirty = true; + for (std::vector::iterator i = m_piece_map.begin() + , end(m_piece_map.end()); i != end; ++i) + { + i->peer_count = 0; + i->download_state = piece_pos::piece_open; + i->index = 0; +#ifdef TORRENT_DEBUG_REFCOUNTS + i->have_peers.clear(); +#endif + } + + for (std::vector::iterator i = m_piece_map.begin() + m_cursor + , end(m_piece_map.end()); i != end && (i->have() || i->filtered()); + ++i, ++m_cursor); + for (std::vector::reverse_iterator i = m_piece_map.rend() + - m_reverse_cursor; m_reverse_cursor > 0 && (i->have() || i->filtered()); + ++i, --m_reverse_cursor); + + // the piece index is stored in 20 bits, which limits the allowed + // number of pieces somewhat + TORRENT_ASSERT(m_piece_map.size() < piece_pos::we_have_index); + + m_blocks_per_piece = blocks_per_piece; + m_blocks_in_last_piece = blocks_in_last_piece; + if (m_blocks_in_last_piece == 0) m_blocks_in_last_piece = blocks_per_piece; + + TORRENT_ASSERT(m_blocks_in_last_piece <= m_blocks_per_piece); + } + + void piece_picker::piece_info(int index, piece_picker::downloading_piece& st) const + { +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + TORRENT_PIECE_PICKER_INVARIANT_CHECK; +#endif + + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < int(m_piece_map.size())); + + int state = m_piece_map[index].download_queue(); + if (state != piece_pos::piece_open) + { + std::vector::const_iterator 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 = blocks_in_piece(index); + return; + } + st.finished = 0; + } + + piece_picker::piece_stats_t piece_picker::piece_stats(int index) const + { + TORRENT_ASSERT(index >= 0 && index < int(m_piece_map.size())); + piece_pos const& pp = m_piece_map[index]; + piece_stats_t ret = { + pp.peer_count + m_seeds, + pp.priority(this), + pp.have(), + pp.downloading() + }; + return ret; + } + + piece_picker::dlpiece_iter piece_picker::add_download_piece(int piece) + { + TORRENT_ASSERT(piece >= 0); + TORRENT_ASSERT(piece < int(m_piece_map.size())); +#if TORRENT_USE_INVARIANT_CHECKS + check_piece_state(); +#endif + + int block_index; + + if (m_free_block_infos.empty()) + { + // we need to allocate more space in m_block_info + block_index = m_block_info.size() / m_blocks_per_piece; + TORRENT_ASSERT((m_block_info.size() % m_blocks_per_piece) == 0); + m_block_info.resize(m_block_info.size() + m_blocks_per_piece); + } + else + { + // there is already free space in m_block_info, grab one range + block_index = m_free_block_infos.back(); + m_free_block_infos.pop_back(); + } + + // always insert into bucket 0 (piece_downloading) + downloading_piece ret; + ret.index = piece; + int download_state = piece_pos::piece_downloading; + std::vector::iterator 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 = block_index; + TORRENT_ASSERT(int(ret.info_idx) * m_blocks_per_piece + + m_blocks_per_piece <= int(m_block_info.size())); + +#ifdef TORRENT_USE_VALGRIND + VALGRIND_CHECK_VALUE_IS_DEFINED(piece); + VALGRIND_CHECK_VALUE_IS_DEFINED(block_index); +#endif + block_info* info = blocks_for_piece(ret); + for (int i = 0; i < m_blocks_per_piece; ++i) + { + info[i].num_peers = 0; + info[i].state = block_info::state_none; + info[i].peer = 0; +#ifdef TORRENT_USE_VALGRIND + VALGRIND_CHECK_VALUE_IS_DEFINED(info[i].peer); +#endif +#if TORRENT_USE_ASSERTS + info[i].piece_index = piece; + info[i].peers.clear(); +#endif + } +#ifdef TORRENT_USE_VALGRIND + VALGRIND_CHECK_VALUE_IS_DEFINED(ret.info_idx); + VALGRIND_CHECK_VALUE_IS_DEFINED(ret.index); +#endif + downloading_iter = m_downloads[download_state].insert(downloading_iter, ret); + +#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 + + int 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 = 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].download_state = piece_pos::piece_open; + m_downloads[download_state].erase(i); + + TORRENT_ASSERT(prev_size == 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 (int k = 0; k < piece_pos::num_download_categories; ++k) + ret.insert(ret.end(), m_downloads[k].begin(), m_downloads[k].end()); + return ret; + } + + int piece_picker::get_download_queue_size() const + { + int ret = 0; + for (int k = 0; k < piece_pos::num_download_categories; ++k) + ret += m_downloads[k].size(); + return ret; + } + + void piece_picker::get_download_queue_sizes(int* partial + , int* full, int* finished, int* zero_prio) const + { + *partial = m_downloads[piece_pos::piece_downloading].size(); + *full = m_downloads[piece_pos::piece_full].size(); + *finished = m_downloads[piece_pos::piece_finished].size(); + *zero_prio = m_downloads[piece_pos::piece_zero_prio].size(); + } + + piece_picker::block_info* piece_picker::blocks_for_piece( + downloading_piece const& dp) + { + int idx = int(dp.info_idx) * m_blocks_per_piece; + TORRENT_ASSERT(idx + m_blocks_per_piece <= m_block_info.size()); + return &m_block_info[idx]; + } + + piece_picker::block_info const* piece_picker::blocks_for_piece( + downloading_piece const& dp) const + { + return const_cast(this)->blocks_for_piece(dp); + } + +#if TORRENT_USE_INVARIANT_CHECKS + + void piece_picker::check_piece_state() const + { +#ifndef TORRENT_DISABLE_INVARIANT_CHECKS + for (int k = 0; k < piece_pos::num_download_categories; ++k) + { + if (!m_downloads[k].empty()) + { + for (std::vector::const_iterator 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.finished + dp.writing >= next.finished + next.writing); + TORRENT_ASSERT(dp.index < next.index); + TORRENT_ASSERT(int(dp.info_idx) * m_blocks_per_piece + + m_blocks_per_piece <= int(m_block_info.size())); + block_info const* info = blocks_for_piece(dp); + for (int j = 0; j < m_blocks_per_piece; ++j) + { + if (info[j].peer) + { + torrent_peer* p = info[j].peer; + TORRENT_ASSERT(p->in_use); + TORRENT_ASSERT(p->connection == NULL + || static_cast(p->connection)->m_in_use); + } + } + } + } + } +#endif + } + + void piece_picker::verify_pick(std::vector const& picked + , bitfield const& bits) const + { + TORRENT_ASSERT(bits.size() == m_piece_map.size()); + for (std::vector::const_iterator i = picked.begin() + , end(picked.end()); i != end; ++i) + { + TORRENT_ASSERT(i->piece_index >= 0); + TORRENT_ASSERT(i->piece_index < bits.size()); + TORRENT_ASSERT(bits[i->piece_index]); + TORRENT_ASSERT(!m_piece_map[i->piece_index].have()); + TORRENT_ASSERT(!m_piece_map[i->piece_index].filtered()); + } + } + + void piece_picker::verify_priority(int range_start, int range_end, int prio) const + { + TORRENT_ASSERT(range_start <= range_end); + TORRENT_ASSERT(range_end <= int(m_pieces.size())); + for (std::vector::const_iterator i = m_pieces.begin() + range_start + , end(m_pieces.begin() + range_end); i != end; ++i) + { + int index = *i; + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < int(m_piece_map.size())); + int p = m_piece_map[index].priority(this); + TORRENT_ASSERT(p == prio); + } + } + +#if defined TORRENT_PICKER_LOG + void piece_picker::print_pieces() const + { + int limit = 20; + std::cerr << "[" << this << "] "; + if (m_dirty) + { + std::cerr << " === dirty ===" << std::endl; + return; + } + + for (std::vector::const_iterator i = m_priority_boundries.begin() + , end(m_priority_boundries.end()); i != end; ++i) + { + std::cerr << *i << " "; + } + std::cerr << std::endl; + int index = 0; + std::cerr << "[" << this << "] "; + std::vector::const_iterator j = m_priority_boundries.begin(); + for (std::vector::const_iterator i = m_pieces.begin() + , end(m_pieces.end()); i != end; ++i, ++index) + { + if (limit == 0) + { + std::cerr << " ..."; + break; + } + if (*i == -1) break; + while (j != m_priority_boundries.end() && *j <= index) + { + std::cerr << "| "; + ++j; + } + std::cerr << *i << "(" << m_piece_map[*i].index << ") "; + --limit; + } + std::cerr << std::endl; + } +#endif // TORRENT_PICKER_LOG +#endif // TORRENT_USE_INVARIANT_CHECKS + +#if TORRENT_USE_INVARIANT_CHECKS + void piece_picker::check_peer_invariant(bitfield const& have + , torrent_peer const* p) const + { +#ifdef TORRENT_DEBUG_REFCOUNTS + int num_pieces = have.size(); + for (int i = 0; i < num_pieces; ++i) + { + int h = have[i]; + TORRENT_ASSERT(m_piece_map[i].have_peers.count(p) == h); + } +#else + TORRENT_UNUSED(have); + TORRENT_UNUSED(p); +#endif + } + + void piece_picker::check_invariant(torrent const* t) const + { +#ifndef TORRENT_DEBUG_REFCOUNTS +#ifdef TORRENT_OPTIMIZE_MEMORY_USAGE + TORRENT_ASSERT(sizeof(piece_pos) == 4); +#else + TORRENT_ASSERT(sizeof(piece_pos) == 8); +#endif +#endif + TORRENT_ASSERT(m_num_have >= 0); + TORRENT_ASSERT(m_num_have_filtered >= 0); + TORRENT_ASSERT(m_num_filtered >= 0); + TORRENT_ASSERT(m_seeds >= 0); + + for (int k = 0; k < piece_pos::num_download_categories; ++k) + { + if (!m_downloads[k].empty()) + { + for (std::vector::const_iterator 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.finished + dp.writing >= next.finished + next.writing); + TORRENT_ASSERT(dp.index < next.index); + TORRENT_ASSERT(int(dp.info_idx) * m_blocks_per_piece + + m_blocks_per_piece <= int(m_block_info.size())); +#if TORRENT_USE_ASSERTS + block_info const* info = blocks_for_piece(dp); + for (int j = 0; j < m_blocks_per_piece; ++j) + { + if (!info[j].peer) continue; + torrent_peer* p = info[j].peer; + TORRENT_ASSERT(p->in_use); + TORRENT_ASSERT(p->connection == NULL + || static_cast(p->connection)->m_in_use); + } +#endif + } + } + } + + if (t != 0) + TORRENT_ASSERT(int(m_piece_map.size()) == t->torrent_file().num_pieces()); + + for (int j = 0; j < piece_pos::num_download_categories; ++j) + { + for (std::vector::const_iterator i = m_downloads[j].begin() + , end(m_downloads[j].end()); i != end; ++i) + { + TORRENT_ASSERT(m_piece_map[i->index].download_queue() == j); + const int num_blocks = blocks_in_piece(i->index); + int num_requested = 0; + int num_finished = 0; + int num_writing = 0; + int num_open = 0; + block_info const* info = blocks_for_piece(*i); + for (int k = 0; k < num_blocks; ++k) + { + TORRENT_ASSERT(info[k].piece_index == i->index); + TORRENT_ASSERT(info[k].peer == 0 + || info[k].peer->in_use); + + if (info[k].state == block_info::state_finished) + { + ++num_finished; + TORRENT_ASSERT(info[k].num_peers == 0); + } + else if (info[k].state == block_info::state_requested) + { + ++num_requested; + TORRENT_ASSERT(info[k].num_peers > 0); + } + else if (info[k].state == block_info::state_writing) + { + ++num_writing; + TORRENT_ASSERT(info[k].num_peers == 0); + } + else if (info[k].state == block_info::state_none) + { + ++num_open; + TORRENT_ASSERT(info[k].num_peers == 0); + } + } + + switch(j) + { + case piece_pos::piece_downloading: + TORRENT_ASSERT(!m_piece_map[i->index].filtered()); + TORRENT_ASSERT(num_open > 0); + break; + case piece_pos::piece_full: + TORRENT_ASSERT(!m_piece_map[i->index].filtered()); + TORRENT_ASSERT(num_open == 0); + // if requested == 0, the piece should be in the finished state + TORRENT_ASSERT(num_requested > 0); + break; + case piece_pos::piece_finished: + TORRENT_ASSERT(!m_piece_map[i->index].filtered()); + TORRENT_ASSERT(num_open == 0); + TORRENT_ASSERT(num_requested == 0); + TORRENT_ASSERT(num_finished + num_writing == num_blocks); + break; + case piece_pos::piece_zero_prio: + TORRENT_ASSERT(m_piece_map[i->index].filtered()); + break; + } + + TORRENT_ASSERT(num_requested == i->requested); + TORRENT_ASSERT(num_writing == i->writing); + TORRENT_ASSERT(num_finished == i->finished); + + if (m_piece_map[i->index].download_queue() == piece_pos::piece_full + || m_piece_map[i->index].download_queue() == piece_pos::piece_finished) + TORRENT_ASSERT(num_finished + num_writing + num_requested == num_blocks); + } + } + int num_pieces = int(m_piece_map.size()); + TORRENT_ASSERT(m_cursor >= 0); + TORRENT_ASSERT(m_cursor <= num_pieces); + TORRENT_ASSERT(m_reverse_cursor <= num_pieces); + TORRENT_ASSERT(m_reverse_cursor >= 0); + TORRENT_ASSERT(m_reverse_cursor > m_cursor + || (m_cursor == num_pieces && m_reverse_cursor == 0)); + + if (!m_dirty) + { + TORRENT_ASSERT(!m_priority_boundries.empty()); + int prio = 0; + int start = 0; + for (std::vector::const_iterator i = m_priority_boundries.begin() + , end(m_priority_boundries.end()); i != end; ++i) + { + verify_priority(start, *i, prio); + ++prio; + start = *i; + } + TORRENT_ASSERT(m_priority_boundries.back() == int(m_pieces.size())); + } + +#ifdef TORRENT_NO_EXPENSIVE_INVARIANT_CHECK + return; +#endif + + { + int index = 0; + for (std::vector::const_iterator i = m_piece_map.begin() + , end(m_piece_map.end()); i != end && (i->have() || i->filtered()); + ++i, ++index); + TORRENT_ASSERT(m_cursor == index); + index = num_pieces; + if (num_pieces > 0) + { + for (std::vector::reverse_iterator i = m_piece_map.rend() + - index; index > 0 && (i->have() || i->filtered()); ++i, --index); + TORRENT_ASSERT(index == num_pieces + || m_piece_map[index].have() + || m_piece_map[index].filtered()); + TORRENT_ASSERT(m_reverse_cursor == index); + } + else + { + TORRENT_ASSERT(m_reverse_cursor == 0); + } + } + + int num_filtered = 0; + int num_have_filtered = 0; + int num_have = 0; + for (std::vector::const_iterator i = m_piece_map.begin(); + i != m_piece_map.end(); ++i) + { + int index = static_cast(i - m_piece_map.begin()); + piece_pos const& p = *i; + + if (p.filtered()) + { + if (p.index != piece_pos::we_have_index) + ++num_filtered; + else + ++num_have_filtered; + } + +#ifdef TORRENT_DEBUG_REFCOUNTS + TORRENT_ASSERT(p.have_peers.size() == p.peer_count + m_seeds); +#endif + if (p.index == piece_pos::we_have_index) + ++num_have; + +#if 0 + if (t != 0) + { + int actual_peer_count = 0; + for (torrent::const_peer_iterator peer = t->begin(); + peer != t->end(); ++peer) + { + if (peer->second->has_piece(index)) actual_peer_count++; + } + + TORRENT_ASSERT((int)i->peer_count == actual_peer_count); +/* + int num_downloaders = 0; + for (std::vector::const_iterator peer = t->begin(); + peer != t->end(); + ++peer) + { + const std::vector& queue = (*peer)->download_queue(); + if (std::find_if(queue.begin(), queue.end(), has_index(index)) == queue.end()) continue; + + ++num_downloaders; + } + + if (i->downloading()) + { + TORRENT_ASSERT(num_downloaders == 1); + } + else + { + TORRENT_ASSERT(num_downloaders == 0); + } +*/ + } +#endif + + if (p.index == piece_pos::we_have_index) + { + TORRENT_ASSERT(t == 0 || t->have_piece(index)); + TORRENT_ASSERT(p.downloading() == false); + } + + if (t != 0) + TORRENT_ASSERT(!t->have_piece(index)); + + int prio = p.priority(this); +#if TORRENT_USE_ASSERTS + 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)); + } +#endif + + if (!m_dirty) + { + TORRENT_ASSERT(prio < int(m_priority_boundries.size()) + || m_dirty); + if (prio >= 0) + { + TORRENT_ASSERT(p.index < m_pieces.size()); + TORRENT_ASSERT(m_pieces[p.index] == index); + } + 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::find(m_pieces.begin(), m_pieces.end(), index) + == m_pieces.end()); + } + } + + int count_downloading = std::count_if( + m_downloads[piece_pos::piece_downloading].begin() + , m_downloads[piece_pos::piece_downloading].end() + , has_index(index)); + + int count_full = std::count_if( + m_downloads[piece_pos::piece_full].begin() + , m_downloads[piece_pos::piece_full].end() + , has_index(index)); + + int count_finished = std::count_if( + m_downloads[piece_pos::piece_finished].begin() + , m_downloads[piece_pos::piece_finished].end() + , has_index(index)); + + int count_zero = std::count_if( + m_downloads[piece_pos::piece_zero_prio].begin() + , m_downloads[piece_pos::piece_zero_prio].end() + , has_index(index)); + + TORRENT_ASSERT(i->download_queue() == piece_pos::piece_open + || count_zero + count_downloading + count_full + + count_finished == 1); + + switch(i->download_queue()) + { + case piece_pos::piece_open: + TORRENT_ASSERT(count_downloading + + count_full + count_finished + count_zero == 0); + break; + case piece_pos::piece_downloading: + TORRENT_ASSERT(count_downloading == 1); + break; + case piece_pos::piece_full: + TORRENT_ASSERT(count_full == 1); + break; + case piece_pos::piece_finished: + TORRENT_ASSERT(count_finished == 1); + break; + case piece_pos::piece_zero_prio: + TORRENT_ASSERT(count_zero == 1); + break; + }; + } + TORRENT_ASSERT(num_have == m_num_have); + TORRENT_ASSERT(num_filtered == m_num_filtered); + TORRENT_ASSERT(num_have_filtered == m_num_have_filtered); + + if (!m_dirty) + { + for (std::vector::const_iterator i = m_pieces.begin() + , end(m_pieces.end()); i != end; ++i) + { + TORRENT_ASSERT(m_piece_map[*i].priority(this) >= 0); + } + } + } +#endif + + std::pair piece_picker::distributed_copies() const + { + TORRENT_ASSERT(m_seeds >= 0); + const int num_pieces = m_piece_map.size(); + + if (num_pieces == 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 == num_pieces); + return std::make_pair(min_availability + m_seeds, fraction_part * 1000 / num_pieces); + } + + void piece_picker::priority_range(int prio, int* start, int* end) + { + TORRENT_ASSERT(prio >= 0); + TORRENT_ASSERT(prio < int(m_priority_boundries.size()) + || m_dirty); + if (prio == 0) *start = 0; + else *start = m_priority_boundries[prio - 1]; + *end = m_priority_boundries[prio]; + TORRENT_ASSERT(*start <= *end); + } + + void piece_picker::add(int index) + { + TORRENT_ASSERT(!m_dirty); + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < int(m_piece_map.size())); + piece_pos& 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_boundries.size()) <= priority) + m_priority_boundries.resize(priority + 1, m_pieces.size()); + + TORRENT_ASSERT(int(m_priority_boundries.size()) >= priority); + + int range_start, range_end; + priority_range(priority, &range_start, &range_end); + int new_index; + if (range_end == range_start) new_index = range_start; + else new_index = random() % (range_end - range_start + 1) + range_start; + +#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(); +#endif + m_pieces.push_back(-1); + + for (;;) + { + TORRENT_ASSERT(new_index < int(m_pieces.size())); + int temp = m_pieces[new_index]; + m_pieces[new_index] = index; + m_piece_map[index].index = new_index; + index = temp; + do + { + temp = m_priority_boundries[priority]++; + ++priority; + } while (temp == new_index && priority < int(m_priority_boundries.size())); + new_index = temp; +#ifdef TORRENT_PICKER_LOG + print_pieces(); + std::cerr << "[" << this << "] " << " index: " << index + << " prio: " << priority + << " new_index: " << new_index + << std::endl; +#endif + if (priority >= int(m_priority_boundries.size())) break; + TORRENT_ASSERT(temp >= 0); + } + if (index != -1) + { + TORRENT_ASSERT(new_index == int(m_pieces.size() - 1)); + m_pieces[new_index] = index; + m_piece_map[index].index = new_index; + +#ifdef TORRENT_PICKER_LOG + print_pieces(); +#endif + } + } + + void piece_picker::remove(int priority, int elem_index) + { + TORRENT_ASSERT(!m_dirty); + TORRENT_ASSERT(priority >= 0); + TORRENT_ASSERT(elem_index < int(m_pieces.size())); + TORRENT_ASSERT(elem_index >= 0); + +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "remove " << m_pieces[elem_index] << " (" << priority << ")" << std::endl; +#endif + int next_index = elem_index; + TORRENT_ASSERT(m_piece_map[m_pieces[elem_index]].priority(this) == -1); + for (;;) + { +#ifdef TORRENT_PICKER_LOG + print_pieces(); +#endif + TORRENT_ASSERT(elem_index < int(m_pieces.size())); + int temp; + do + { + temp = --m_priority_boundries[priority]; + ++priority; + } while (next_index == temp && priority < int(m_priority_boundries.size())); + if (next_index == temp) break; + next_index = temp; + + int 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 < int(m_pieces.size() - 1)); + elem_index = next_index; + + if (priority == int(m_priority_boundries.size())) + break; + } + m_pieces.pop_back(); + TORRENT_ASSERT(next_index == int(m_pieces.size())); +#ifdef TORRENT_PICKER_LOG + print_pieces(); +#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, int elem_index) + { + TORRENT_ASSERT(!m_dirty); + TORRENT_ASSERT(elem_index >= 0); + TORRENT_ASSERT(elem_index < int(m_piece_map.size())); + TORRENT_ASSERT(priority >= 0); + TORRENT_ASSERT(int(m_priority_boundries.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_boundries[priority] >= elem_index); + TORRENT_ASSERT(priority == 0 || m_priority_boundries[priority-1] <= elem_index); + TORRENT_ASSERT(priority + 1 == m_priority_boundries.size() || m_priority_boundries[priority+1] > elem_index); + + int index = m_pieces[elem_index]; + // update the piece_map + piece_pos& p = m_piece_map[index]; + TORRENT_ASSERT(int(p.index) == elem_index || p.have()); + + int new_priority = p.priority(this); + + if (new_priority == priority) return; + + if (new_priority == -1) + { + remove(priority, elem_index); + return; + } + + if (int(m_priority_boundries.size()) <= new_priority) + m_priority_boundries.resize(new_priority + 1, m_pieces.size()); + +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "update " << index << " (" << priority << "->" << new_priority << ")" << std::endl; +#endif + if (priority > new_priority) + { + int new_index; + int temp = index; + for (;;) + { +#ifdef TORRENT_PICKER_LOG + print_pieces(); +#endif + TORRENT_ASSERT(priority > 0); + --priority; + new_index = m_priority_boundries[priority]++; + TORRENT_ASSERT(new_index >= 0); + TORRENT_ASSERT(new_index < int(m_pieces.size())); + 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 < int(m_pieces.size())); + } + elem_index = new_index; + if (priority == new_priority) break; + } +#ifdef TORRENT_PICKER_LOG + print_pieces(); +#endif + m_pieces[elem_index] = index; + m_piece_map[index].index = elem_index; + TORRENT_ASSERT(elem_index < int(m_pieces.size())); +#ifdef TORRENT_PICKER_LOG + print_pieces(); +#endif + shuffle(priority, elem_index); +#ifdef TORRENT_PICKER_LOG + print_pieces(); +#endif + TORRENT_ASSERT(m_piece_map[index].priority(this) == priority); + } + else + { + int new_index; + int temp = index; + for (;;) + { +#ifdef TORRENT_PICKER_LOG + print_pieces(); +#endif + TORRENT_ASSERT(priority >= 0); + TORRENT_ASSERT(priority < int(m_priority_boundries.size())); + new_index = --m_priority_boundries[priority]; + TORRENT_ASSERT(new_index >= 0); + TORRENT_ASSERT(new_index < int(m_pieces.size())); + 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 < int(m_pieces.size())); + } + elem_index = new_index; + ++priority; + if (priority == new_priority) break; + } +#ifdef TORRENT_PICKER_LOG + print_pieces(); +#endif + m_pieces[elem_index] = index; + m_piece_map[index].index = elem_index; + TORRENT_ASSERT(elem_index < int(m_pieces.size())); +#ifdef TORRENT_PICKER_LOG + print_pieces(); +#endif + shuffle(priority, elem_index); +#ifdef TORRENT_PICKER_LOG + print_pieces(); +#endif + TORRENT_ASSERT(m_piece_map[index].priority(this) == priority); + } + } + + void piece_picker::shuffle(int priority, int 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 >= 0); + TORRENT_ASSERT(elem_index < int(m_pieces.size())); + TORRENT_ASSERT(m_piece_map[m_pieces[elem_index]].priority(this) == priority); + + int range_start, range_end; + priority_range(priority, &range_start, &range_end); + TORRENT_ASSERT(range_start < range_end); + int other_index = random() % (range_end - range_start) + range_start; + + 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]]; + + int temp = p1.index; + p1.index = p2.index; + p2.index = temp; + std::swap(m_pieces[other_index], m_pieces[elem_index]); + } + + void piece_picker::restore_piece(int index) + { + TORRENT_PIECE_PICKER_INVARIANT_CHECK; + +#if TORRENT_USE_INVARIANT_CHECKS + check_piece_state(); +#endif + +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "restore_piece(" << index << ")" << std::endl; +#endif + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < int(m_piece_map.size())); + + int download_state = m_piece_map[index].download_queue(); + TORRENT_ASSERT(download_state != piece_pos::piece_open); + if (download_state == piece_pos::piece_open) return; + + std::vector::iterator i = find_dl_piece(download_state, index); + + TORRENT_ASSERT(i != m_downloads[download_state].end()); + TORRENT_ASSERT(int(i->info_idx) * m_blocks_per_piece + + m_blocks_per_piece <= int(m_block_info.size())); + + i->locked = false; + + piece_pos& p = m_piece_map[index]; + int prev_priority = p.priority(this); + erase_download_piece(i); + int 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 + TORRENT_PIECE_PICKER_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 + TORRENT_PIECE_PICKER_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 (std::vector::iterator i = m_piece_map.begin() + , end(m_piece_map.end()); i != end; ++i) + { +#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(int index, const torrent_peer* peer) + { +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + TORRENT_PIECE_PICKER_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() + { + TORRENT_PIECE_PICKER_INVARIANT_CHECK; + + TORRENT_ASSERT(m_seeds > 0); + --m_seeds; + + for (std::vector::iterator i = m_piece_map.begin() + , end(m_piece_map.end()); i != end; ++i) + { + ++i->peer_count; + } + + m_dirty = true; + } + + void piece_picker::dec_refcount(int index, const torrent_peer* peer) + { +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + TORRENT_PIECE_PICKER_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 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(bitfield const& bitmask, const torrent_peer* peer) + { +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + TORRENT_PIECE_PICKER_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() == m_piece_map.size()) + { + inc_refcount_all(peer); + return; + } + + const int 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 + int* incremented = TORRENT_ALLOCA(int, size); + int num_inc = 0; + + 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. Othewise 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 + int index = 0; + for (bitfield::const_iterator 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) + { + int 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; + } + } + + int index = 0; + bool updated = false; + for (bitfield::const_iterator 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(bitfield const& bitmask, const torrent_peer* peer) + { +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + TORRENT_PIECE_PICKER_INVARIANT_CHECK; +#endif + TORRENT_ASSERT(bitmask.size() <= 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() == m_piece_map.size()) + { + dec_refcount_all(peer); + return; + } + + const int 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 + int* decremented = TORRENT_ALLOCA(int, size); + int num_dec = 0; + + 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. Othewise 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 + int index = 0; + for (bitfield::const_iterator 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) + { + int 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; + } + } + + int index = 0; + bool updated = false; + for (bitfield::const_iterator 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_boundries.empty()) m_priority_boundries.resize(1, 0); +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "update_pieces" << std::endl; +#endif + std::fill(m_priority_boundries.begin(), m_priority_boundries.end(), 0); + for (std::vector::iterator i = m_piece_map.begin() + , end(m_piece_map.end()); i != end; ++i) + { + int prio = i->priority(this); + if (prio == -1) continue; + if (prio >= int(m_priority_boundries.size())) + m_priority_boundries.resize(prio + 1, 0); + i->index = m_priority_boundries[prio]; + ++m_priority_boundries[prio]; + } + +#ifdef TORRENT_PICKER_LOG + print_pieces(); +#endif + + int index = 0; + for (std::vector::iterator i = m_priority_boundries.begin() + , end(m_priority_boundries.end()); i != end; ++i) + { + *i += index; + index = *i; + } + m_pieces.resize(index, 0); + +#ifdef TORRENT_PICKER_LOG + print_pieces(); +#endif + + index = 0; + for (std::vector::iterator i = m_piece_map.begin() + , end(m_piece_map.end()); i != end; ++i, ++index) + { + piece_pos& p = *i; + int prio = p.priority(this); + if (prio == -1) continue; + int new_index = (prio == 0 ? 0 : m_priority_boundries[prio - 1]) + p.index; + m_pieces[new_index] = index; + } + + int start = 0; + for (std::vector::iterator i = m_priority_boundries.begin() + , end(m_priority_boundries.end()); i != end; ++i) + { + if (start == *i) continue; + std::random_shuffle(&m_pieces[0] + start, &m_pieces[0] + *i, randint); + start = *i; + } + + index = 0; + for (std::vector::const_iterator i = m_pieces.begin() + , end(m_pieces.end()); i != end; ++i, ++index) + { + TORRENT_ASSERT(*i >= 0 && *i < int(m_piece_map.size())); + m_piece_map[*i].index = index; + } + + m_dirty = false; +#ifdef TORRENT_PICKER_LOG + print_pieces(); +#endif + } + + void piece_picker::piece_passed(int index) + { + piece_pos& p = m_piece_map[index]; + int 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; + + std::vector::iterator 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(int index) + { + TORRENT_PIECE_PICKER_INVARIANT_CHECK; + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < int(m_piece_map.size())); + + 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 + int download_state = p.download_queue(); + if (download_state == piece_pos::piece_open) return; + + std::vector::iterator 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_num_filtered; + --m_num_have_filtered; + } + else + { + // update cursors + if (index < m_cursor) + m_cursor = index; + if (index >= m_reverse_cursor) + m_reverse_cursor = index + 1; + if (m_reverse_cursor == m_cursor) + { + m_reverse_cursor = 0; + m_cursor = num_pieces(); + } + } + + --m_num_have; + p.set_not_have(); + + if (m_dirty) return; + if (p.priority(this) >= 0) add(index); + } + + // this is used to indicate that we succesfully 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(int index) + { +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + TORRENT_PIECE_PICKER_INVARIANT_CHECK; +#endif + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < int(m_piece_map.size())); + +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "piece_picker::we_have(" + << index << ")" << std::endl; +#endif + piece_pos& p = m_piece_map[index]; + int info_index = p.index; + int priority = p.priority(this); + TORRENT_ASSERT(priority < int(m_priority_boundries.size()) || m_dirty); + + if (p.have()) return; + + int state = p.download_queue(); + if (state != piece_pos::piece_open) + { + std::vector::iterator i + = find_dl_piece(state, index); + TORRENT_ASSERT(i != m_downloads[state].end()); + // decrement num_passed here to compensate + // for the unconditional increment further down + if (i->passed_hash_check) --m_num_passed; + erase_download_piece(i); + } + + if (p.filtered()) + { + --m_num_filtered; + ++m_num_have_filtered; + } + ++m_num_have; + ++m_num_passed; + p.set_have(); + if (m_cursor == m_reverse_cursor - 1 && + m_cursor == index) + { + m_cursor = int(m_piece_map.size()); + m_reverse_cursor = 0; + TORRENT_ASSERT(num_pieces() > 0); + } + else if (m_cursor == index) + { + ++m_cursor; + for (std::vector::const_iterator i = m_piece_map.begin() + m_cursor + , end(m_piece_map.end()); i != end && (i->have() || i->filtered()); + ++i, ++m_cursor); + } + else if (m_reverse_cursor - 1 == index) + { + --m_reverse_cursor; + TORRENT_ASSERT(m_piece_map[m_reverse_cursor].have() + || m_piece_map[m_reverse_cursor].filtered()); + for (std::vector::const_iterator i = m_piece_map.begin() + + m_reverse_cursor - 1; m_reverse_cursor > 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 == num_pieces() && m_reverse_cursor == 0)); + if (priority == -1) return; + if (m_dirty) return; + remove(priority, info_index); + TORRENT_ASSERT(p.priority(this) == -1); + } + + bool piece_picker::set_piece_priority(int index, int new_piece_priority) + { +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + TORRENT_PIECE_PICKER_INVARIANT_CHECK; +#endif + +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "set_piece_priority(" << index + << ", " << new_piece_priority << ")" << std::endl; +#endif + + TORRENT_ASSERT(new_piece_priority >= 0); + TORRENT_ASSERT(new_piece_priority < priority_levels); + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < int(m_piece_map.size())); + + piece_pos& p = m_piece_map[index]; + + // if the priority isn't changed, don't do anything + if (new_piece_priority == int(p.piece_priority)) return false; + + int prev_priority = p.priority(this); + TORRENT_ASSERT(m_dirty || prev_priority < int(m_priority_boundries.size())); + + bool ret = false; + if (new_piece_priority == piece_pos::filter_priority + && p.piece_priority != piece_pos::filter_priority) + { + // the piece just got filtered + if (p.have()) + { + ++m_num_have_filtered; + } + else + { + ++m_num_filtered; + + // update m_cursor + if (m_cursor == m_reverse_cursor - 1 && m_cursor == index) + { + m_cursor = int(m_piece_map.size()); + m_reverse_cursor = 0; + } + else if (m_cursor == index) + { + ++m_cursor; + while (m_cursor < int(m_piece_map.size()) + && (m_piece_map[m_cursor].have() + || m_piece_map[m_cursor].filtered())) + ++m_cursor; + } + else if (m_reverse_cursor == index + 1) + { + --m_reverse_cursor; + while (m_reverse_cursor > 0 + && (m_piece_map[m_reverse_cursor-1].have() + || m_piece_map[m_reverse_cursor-1].filtered())) + --m_reverse_cursor; + } + } + ret = true; + } + else if (new_piece_priority != piece_pos::filter_priority + && p.piece_priority == piece_pos::filter_priority) + { + // the piece just got unfiltered + if (p.have()) + { + --m_num_have_filtered; + } + else + { + --m_num_filtered; + // update cursors + if (index < m_cursor) + m_cursor = index; + if (index >= m_reverse_cursor) + m_reverse_cursor = index + 1; + if (m_reverse_cursor == m_cursor) + { + m_reverse_cursor = 0; + m_cursor = num_pieces(); + } + } + ret = true; + } + TORRENT_ASSERT(m_num_filtered >= 0); + TORRENT_ASSERT(m_num_have_filtered >= 0); + + p.piece_priority = new_piece_priority; + int 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()) + { + std::vector::iterator i = find_dl_piece( + p.download_queue(), index); + if (i != m_downloads[p.download_queue()].end()) + update_piece_state(i); + } + + return ret; + } + + int piece_picker::piece_priority(int index) const + { + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < int(m_piece_map.size())); + + return m_piece_map[index].piece_priority; + } + + void piece_picker::piece_priorities(std::vector& pieces) const + { + pieces.resize(m_piece_map.size()); + std::vector::iterator j = pieces.begin(); + for (std::vector::const_iterator i = m_piece_map.begin(), + end(m_piece_map.end()); i != end; ++i, ++j) + { + *j = i->piece_priority; + } + } + + // ============ start deprecation ============== + + void piece_picker::filtered_pieces(std::vector& mask) const + { + mask.resize(m_piece_map.size()); + std::vector::iterator j = mask.begin(); + for (std::vector::const_iterator i = m_piece_map.begin(), + end(m_piece_map.end()); i != end; ++i, ++j) + { + *j = i->filtered(); + } + } + + // ============ end deprecation ============== + + namespace + { + int append_blocks(std::vector& dst, std::vector& src + , int const num_blocks) + { + if (src.empty()) return num_blocks; + int const to_copy = (std::min)(int(src.size()), num_blocks); + + dst.insert(dst.end(), src.begin(), src.begin() + to_copy); + src.erase(src.begin(), src.begin() + to_copy); + return num_blocks - to_copy; + } + } + + // lower availability comes first. This is a less-than comparison, it returns + // true if lhs has lower availability than rhs + bool piece_picker::partial_compare_rarest_first(downloading_piece const* lhs + , downloading_piece const* rhs) const + { + int lhs_availability = m_piece_map[lhs->index].peer_count; + int rhs_availability = m_piece_map[rhs->index].peer_count; + if (lhs_availability != rhs_availability) + return lhs_availability < rhs_availability; + + // if the availability is the same, prefer the piece that's closest to + // being complete. + int lhs_blocks_left = m_blocks_per_piece - lhs->finished - lhs->writing + - lhs->requested; + TORRENT_ASSERT(lhs_blocks_left > 0); + int rhs_blocks_left = m_blocks_per_piece - rhs->finished - rhs->writing + - rhs->requested; + TORRENT_ASSERT(rhs_blocks_left > 0); + return lhs_blocks_left < rhs_blocks_left; + } + + // pieces describes which pieces the peer we're requesting from has. + // interesting_blocks is an out parameter, and will be filled with (up to) + // num_blocks of interesting blocks that the peer has. + // prefer_contiguous_blocks can be set if this peer should download whole + // pieces rather than trying to download blocks from the same piece as other + // peers. the peer argument is the torrent_peer of the peer we're + // picking pieces from. This is used when downloading whole pieces, to only + // pick from the same piece the same peer is downloading from. + + // options are: + // * rarest_first + // pick the rarest pieces first + // * reverse + // reverse the piece picking. Pick the most common + // pieces first or the last pieces (if picking sequential) + // * sequential + // download pieces in-order + // * on_parole + // the peer is on parole, only pick whole pieces which + // has only been downloaded and requested from the same + // peer + // * prioritize_partials + // pick blocks from downloading pieces first + + // only one of rarest_first or sequential can be set + + // the return value is a combination of picker_log_alert::picker_flags_t, + // indicating which path throught the picker we took to arrive at the + // returned block picks. + boost::uint32_t piece_picker::pick_pieces(bitfield const& pieces + , std::vector& interesting_blocks, int num_blocks + , int prefer_contiguous_blocks, torrent_peer* peer + , int options, std::vector const& suggested_pieces + , int num_peers + , counters& pc + ) const + { + TORRENT_ASSERT(peer == 0 || peer->in_use); + boost::uint32_t ret = 0; + + // 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 + // deduct pad files because they case partial pieces which are OK + // 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()) + - m_num_pad_files; + if (num_partials > num_peers * 3 / 2 + || num_partials * m_blocks_per_piece > 2048) + { + // if we have too many partial pieces, prioritize completing + // them. In order for this to have an affect, also disable + // prefer whole pieces (otherwise partial pieces would be de-prioritized) + options |= prioritize_partials; + prefer_contiguous_blocks = 0; + + ret |= picker_log_alert::partial_ratio; + } + + if (prefer_contiguous_blocks) ret |= picker_log_alert::prefer_contiguous; + + // only one of rarest_first and sequential can be set. + TORRENT_ASSERT(((options & rarest_first) ? 1 : 0) + + ((options & sequential) ? 1 : 0) <= 1); +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + TORRENT_PIECE_PICKER_INVARIANT_CHECK; +#endif + TORRENT_ASSERT(num_blocks > 0); + TORRENT_ASSERT(pieces.size() == m_piece_map.size()); + + TORRENT_ASSERT(!m_priority_boundries.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; + const std::vector empty_vector; + + // When prefer_contiguous_blocks is set (usually set when downloading from + // fast peers) the partial pieces will not be prioritized, but actually + // ignored as long as possible. All blocks found in downloading + // pieces are regarded as backup blocks + + if (options & prioritize_partials) + { + // first, allocate a small array on the stack of all the partial + // pieces (downloading_piece). We'll then sort this list by + // availability or by some other condition. The list of partial pieces + // in m_downloads is ordered by piece index, this is to have O(log n) + // lookups when finding a downloading_piece for a specific piece index. + // this is important and needs to stay sorted that way, that's why + // we're copying it here + downloading_piece const** ordered_partials = TORRENT_ALLOCA( + 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 (std::vector::const_iterator i + = m_downloads[piece_pos::piece_downloading].begin() + , end(m_downloads[piece_pos::piece_downloading].end()); i != end; ++i) + { + pc.inc_stats_counter(counters::piece_picker_partial_loops); + + // in time critical mode, only pick high priority pieces + if ((options & time_critical_mode) + && piece_priority(i->index) != priority_levels - 1) + continue; + + if (!is_piece_free(i->index, pieces)) continue; + + TORRENT_ASSERT(m_piece_map[i->index].download_queue() + == piece_pos::piece_downloading); + + ordered_partials[num_ordered_partials++] = &*i; + } + + // 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, ordered_partials + num_ordered_partials + , boost::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 (std::vector::const_iterator i = suggested_pieces.begin(); + i != suggested_pieces.end(); ++i) + { + // in time critical mode, only pick high priority pieces + if ((options & time_critical_mode) + && piece_priority(*i) != priority_levels - 1) + continue; + + pc.inc_stats_counter(counters::piece_picker_suggest_loops); + if (!is_piece_free(*i, pieces)) continue; + + ret |= picker_log_alert::suggested_pieces; + + num_blocks = add_blocks(*i, pieces + , interesting_blocks, backup_blocks + , backup_blocks2, num_blocks + , prefer_contiguous_blocks, peer, empty_vector + , options); + if (num_blocks <= 0) return ret; + } + } + + if (options & sequential) + { + if (m_dirty) update_pieces(); + TORRENT_ASSERT(!m_dirty); + + for (std::vector::const_iterator i = m_pieces.begin(); + i != m_pieces.end() && piece_priority(*i) == priority_levels - 1; ++i) + { + if (!is_piece_free(*i, pieces)) continue; + + ret |= picker_log_alert::prio_sequential_pieces; + + num_blocks = add_blocks(*i, pieces + , interesting_blocks, backup_blocks + , backup_blocks2, num_blocks + , prefer_contiguous_blocks, peer, suggested_pieces + , options); + if (num_blocks <= 0) return ret; + } + + // in time critical mode, only pick high priority pieces + if ((options & time_critical_mode) == 0) + { + if (options & reverse) + { + for (int i = m_reverse_cursor - 1; i >= m_cursor; --i) + { + pc.inc_stats_counter(counters::piece_picker_sequential_loops); + if (!is_piece_free(i, pieces)) continue; + // we've already added high priority pieces + if (piece_priority(i) == priority_levels - 1) continue; + + ret |= picker_log_alert::reverse_sequential; + + num_blocks = add_blocks(i, pieces + , interesting_blocks, backup_blocks + , backup_blocks2, num_blocks + , prefer_contiguous_blocks, peer, suggested_pieces + , options); + if (num_blocks <= 0) return ret; + } + } + else + { + for (int i = m_cursor; i < m_reverse_cursor; ++i) + { + pc.inc_stats_counter(counters::piece_picker_sequential_loops); + if (!is_piece_free(i, pieces)) continue; + // we've already added high priority pieces + if (piece_priority(i) == priority_levels - 1) continue; + + ret |= picker_log_alert::sequential_pieces; + + num_blocks = add_blocks(i, pieces + , interesting_blocks, backup_blocks + , backup_blocks2, num_blocks + , prefer_contiguous_blocks, peer, suggested_pieces + , options); + if (num_blocks <= 0) return ret; + } + } + } + } + else if (options & rarest_first) + { + if (m_dirty) update_pieces(); + TORRENT_ASSERT(!m_dirty); + + // in time critical mode, we're only allowed to pick high priority + // pieces. This is why reverse mode is disabled when we're in + // time-critical mode, because all high priority pieces are at the + // front of the list + if ((options & reverse) && (options & time_critical_mode) == 0) + { + for (int i = m_priority_boundries.size() - 1; i >= 0; --i) + { + int start = (i == 0) ? 0 : m_priority_boundries[i - 1]; + int end = m_priority_boundries[i]; + for (int p = end - 1; p >= start; --p) + { + pc.inc_stats_counter(counters::piece_picker_reverse_rare_loops); + + if (!is_piece_free(m_pieces[p], pieces)) continue; + + ret |= picker_log_alert::reverse_rarest_first; + + num_blocks = add_blocks(m_pieces[p], pieces + , interesting_blocks, backup_blocks + , backup_blocks2, num_blocks + , prefer_contiguous_blocks, peer, suggested_pieces + , options); + if (num_blocks <= 0) return ret; + } + } + } + else + { + for (std::vector::const_iterator i = m_pieces.begin(); + i != m_pieces.end(); ++i) + { + pc.inc_stats_counter(counters::piece_picker_rare_loops); + + // in time critical mode, only pick high priority pieces + // it's safe to break here because in this mode we + // pick pieces in priority order. Once we hit a lower priority + // piece, we won't encounter any more high priority ones + if ((options & time_critical_mode) + && piece_priority(*i) != priority_levels - 1) + break; + + if (!is_piece_free(*i, pieces)) continue; + + ret |= picker_log_alert::rarest_first; + + num_blocks = add_blocks(*i, pieces + , interesting_blocks, backup_blocks + , backup_blocks2, num_blocks + , prefer_contiguous_blocks, peer, suggested_pieces + , options); + if (num_blocks <= 0) return ret; + } + } + } + else if (options & time_critical_mode) + { + // if we're in time-critical mode, we are only allowed to pick + // high priority pieces. + for (std::vector::const_iterator i = m_pieces.begin(); + i != m_pieces.end() && piece_priority(*i) == priority_levels - 1; ++i) + { + if (!is_piece_free(*i, pieces)) continue; + + ret |= picker_log_alert::time_critical; + + num_blocks = add_blocks(*i, pieces + , interesting_blocks, backup_blocks + , backup_blocks2, num_blocks + , prefer_contiguous_blocks, peer, suggested_pieces + , options); + if (num_blocks <= 0) return ret; + } + } + else + { + // we're not using rarest first (only for the first + // bucket, since that's where the currently downloading + // pieces are) + int start_piece = random() % m_piece_map.size(); + + int piece = start_piece; + while (num_blocks > 0) + { + // skip pieces we can't pick, and suggested pieces + // since we've already picked those + while (!is_piece_free(piece, pieces) + || std::find(suggested_pieces.begin() + , suggested_pieces.end(), piece) + != suggested_pieces.end()) + { + pc.inc_stats_counter(counters::piece_picker_rand_start_loops); + ++piece; + if (piece == int(m_piece_map.size())) piece = 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); + + int start, end; + boost::tie(start, end) = expand_piece(piece + , prefer_contiguous_blocks, pieces, options); + TORRENT_ASSERT(end - start > 0); + for (int k = start; k < end; ++k) + { + TORRENT_ASSERT(m_piece_map[k].downloading() == false); + TORRENT_ASSERT(m_piece_map[k].priority(this) >= 0); + const int num_blocks_in_piece = blocks_in_piece(k); + + ret |= picker_log_alert::random_pieces; + + for (int j = 0; j < num_blocks_in_piece; ++j) + { + pc.inc_stats_counter(counters::piece_picker_rand_loops); + TORRENT_ASSERT(is_piece_free(k, pieces)); + interesting_blocks.push_back(piece_block(k, j)); + --num_blocks; + --prefer_contiguous_blocks; + if (prefer_contiguous_blocks <= 0 + && num_blocks <= 0) break; + } + } + piece = end; + } + else + { + ret |= picker_log_alert::random_pieces; + + num_blocks = add_blocks(piece, pieces + , interesting_blocks, backup_blocks + , backup_blocks2, num_blocks + , prefer_contiguous_blocks, peer, empty_vector + , options); + ++piece; + } + + if (piece == int(m_piece_map.size())) piece = 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; + + downloading_piece const** partials + = TORRENT_ALLOCA(downloading_piece const*, partials_size); + int c = 0; + +#if TORRENT_USE_ASSERTS && !defined TORRENT_DISABLE_INVARIANT_CHECKS + // if we get here, we're about to pick a busy block. First, make sure + // we really exhausted the available blocks + for (std::vector::const_iterator i + = m_downloads[piece_pos::piece_downloading].begin() + , end(m_downloads[piece_pos::piece_downloading].end()); i != end; ++i) + { + downloading_piece const& dp = *i; + + if ((options & time_critical_mode) + && piece_priority(dp.index) != priority_levels - 1) + continue; + + // we either don't have this piece, or we've already requested from it + if (!pieces[dp.index]) continue; + + // if we already have the piece, obviously we should not have + // since this is a partial piece in the piece_downloading state, we + // should not already have it + TORRENT_ASSERT(!m_piece_map[dp.index].have()); + + // if it was filtered, it would be in the prio_zero queue + TORRENT_ASSERT(!m_piece_map[dp.index].filtered()); + + // we're not allowed to pick from locked pieces + if (dp.locked) continue; + + bool found = false; + for (std::vector::const_iterator j + = interesting_blocks.begin(), end2(interesting_blocks.end()); + j != end2; ++j) + { + if (j->piece_index != dp.index) continue; + found = true; + break; + } + + // we expect to find this piece in our interesting_blocks list + TORRENT_ASSERT(found); + } +#endif + + for (std::vector::const_iterator i + = m_downloads[piece_pos::piece_full].begin() + , end(m_downloads[piece_pos::piece_full].end()); + i != end; ++i) + { + if (c == partials_size) break; + + downloading_piece const& dp = *i; + 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) > 0); + + if ((options & time_critical_mode) + && piece_priority(dp.index) != priority_levels - 1) + continue; + + partials[c++] = &dp; + } + + partials_size = c; + while (partials_size > 0) + { + pc.inc_stats_counter(counters::piece_picker_busy_loops); + int piece = random() % partials_size; + downloading_piece const* dp = partials[piece]; + TORRENT_ASSERT(pieces[dp->index]); + TORRENT_ASSERT(piece_priority(dp->index) > 0); + // fill in with blocks requested from other peers + // as backups + const int num_blocks_in_piece = blocks_in_piece(dp->index); + TORRENT_ASSERT(dp->requested > 0); + block_info const* binfo = blocks_for_piece(*dp); + for (int j = 0; j < num_blocks_in_piece; ++j) + { + block_info const& info = binfo[j]; + TORRENT_ASSERT(info.peer == 0 + || static_cast(info.peer)->in_use); + TORRENT_ASSERT(info.piece_index == dp->index); + if (info.state != block_info::state_requested + || info.peer == peer) + continue; + temp.push_back(piece_block(dp->index, j)); + } + // are we done? + if (!temp.empty()) + { + ret |= picker_log_alert::end_game; + + interesting_blocks.push_back(temp[random() % temp.size()]); + --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 defined TORRENT_DEBUG && !defined TORRENT_DISABLE_INVARIANT_CHECKS +// make sure that we at this point have added requests to all unrequested blocks +// in all downloading pieces + + for (std::vector::const_iterator i + = m_downloads[piece_pos::piece_downloading].begin() + , end(m_downloads[piece_pos::piece_downloading].end()); i != end; ++i) + { + if (!pieces[i->index]) continue; + if (piece_priority(i->index) == 0) continue; + if (i->locked) continue; + + if ((options & time_critical_mode) + && piece_priority(i->index) != priority_levels - 1) + continue; + + const int num_blocks_in_piece = blocks_in_piece(i->index); + block_info const* binfo = blocks_for_piece(*i); + for (int j = 0; j < num_blocks_in_piece; ++j) + { + block_info const& info = binfo[j]; + TORRENT_ASSERT(info.piece_index == i->index); + if (info.state != block_info::state_none) continue; + std::vector::iterator k = std::find( + interesting_blocks.begin(), interesting_blocks.end() + , piece_block(i->index, j)); + if (k != interesting_blocks.end()) continue; + + fprintf(stderr, "interesting blocks:\n"); + for (k = interesting_blocks.begin(); k != interesting_blocks.end(); ++k) + fprintf(stderr, "(%d, %d)", k->piece_index, k->block_index); + fprintf(stderr, "\nnum_blocks: %d\n", num_blocks); + + for (std::vector::const_iterator l + = m_downloads[piece_pos::piece_downloading].begin() + , end2(m_downloads[piece_pos::piece_downloading].end()); + l != end2; ++l) + { + block_info const* binfo2 = blocks_for_piece(*l); + fprintf(stderr, "%d : ", l->index); + const int cnt = blocks_in_piece(l->index); + for (int m = 0; m < cnt; ++m) + fprintf(stderr, "%d", binfo2[m].state); + fprintf(stderr, "\n"); + } + + TORRENT_ASSERT(false); + } + } + + if (interesting_blocks.empty()) + { + for (int i = 0; i < num_pieces(); ++i) + { + if (!pieces[i]) continue; + if (m_piece_map[i].priority(this) <= 0) continue; + if (have_piece(i)) continue; + + int 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 // TORRENT_DEBUG && !defined TORRENT_DISABLE_INVARIANT_CHECKS + return ret; + } + + // have piece means that the piece passed hash check + // AND has been successfully written to disk + bool piece_picker::have_piece(int index) const + { + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < int(m_piece_map.size())); + piece_pos const& p = m_piece_map[index]; + return p.index == piece_pos::we_have_index; + } + + int piece_picker::blocks_in_piece(int index) const + { + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < int(m_piece_map.size()) || m_piece_map.empty()); + if (index + 1 == int(m_piece_map.size())) + return m_blocks_in_last_piece; + else + return m_blocks_per_piece; + } + + bool piece_picker::is_piece_free(int piece, bitfield const& bitmask) const + { + TORRENT_ASSERT(piece >= 0 && piece < int(m_piece_map.size())); + return bitmask[piece] + && !m_piece_map[piece].have() + && !m_piece_map[piece].filtered(); + } + + bool piece_picker::can_pick(int piece, bitfield const& bitmask) const + { + TORRENT_ASSERT(piece >= 0 && piece < int(m_piece_map.size())); + 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 (std::vector::iterator i = m_block_info.begin() + , end(m_block_info.end()); i != end; ++i) + { + TORRENT_ASSERT(i->peer == 0 || static_cast(i->peer)->in_use); + } + } +#endif + + void piece_picker::clear_peer(torrent_peer* peer) + { + for (std::vector::iterator i = m_block_info.begin() + , end(m_block_info.end()); i != end; ++i) + { + if (i->peer == peer) i->peer = 0; + } + } + + // 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 + boost::tuple piece_picker::requested_from( + piece_picker::downloading_piece const& p + , int 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; + block_info const* binfo = blocks_for_piece(p); + for (int j = 0; j < num_blocks_in_piece; ++j) + { + piece_picker::block_info const& info = binfo[j]; + TORRENT_ASSERT(info.peer == 0 || static_cast(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 = j - contiguous_blocks; + } + contiguous_blocks = 0; + if (info.peer != peer) + { + exclusive = false; + if (info.state == piece_picker::block_info::state_requested + && info.peer != 0) + { + exclusive_active = false; + } + } + } + if (contiguous_blocks > max_contiguous) + { + max_contiguous = contiguous_blocks; + first_block = num_blocks_in_piece - contiguous_blocks; + } + return boost::make_tuple(exclusive, exclusive_active, max_contiguous + , first_block); + } + + int piece_picker::add_blocks(int piece + , bitfield const& pieces + , std::vector& interesting_blocks + , std::vector& backup_blocks + , std::vector& backup_blocks2 + , int num_blocks, int prefer_contiguous_blocks + , torrent_peer* peer, std::vector const& ignore + , int options) const + { + TORRENT_ASSERT(piece >= 0); + TORRENT_ASSERT(piece < int(m_piece_map.size())); + TORRENT_ASSERT(is_piece_free(piece, pieces)); + +// std::cout << "add_blocks(" << piece << ")" << std::endl; +// std::cout << " num_blocks " << num_blocks << std::endl; + + // ignore pieces found in the ignore list + if (std::find(ignore.begin(), ignore.end(), piece) != ignore.end()) return num_blocks; + + if (m_piece_map[piece].download_queue() != piece_pos::piece_open + && m_piece_map[piece].download_queue() != piece_pos::piece_downloading) + return num_blocks; + + TORRENT_ASSERT(m_piece_map[piece].priority(this) >= 0); + int state = m_piece_map[piece].download_queue(); + 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; + + std::vector::const_iterator i = find_dl_piece( + piece_pos::piece_downloading, piece); + TORRENT_ASSERT(i != m_downloads[state].end()); + +// std::cout << "add_blocks_downloading(" << piece << ")" << std::endl; + + return add_blocks_downloading(*i, pieces + , interesting_blocks, backup_blocks, backup_blocks2 + , num_blocks, prefer_contiguous_blocks, peer, options); + } + + int num_blocks_in_piece = blocks_in_piece(piece); + + // pick a new piece + if (prefer_contiguous_blocks == 0) + { + if (num_blocks_in_piece > num_blocks) + num_blocks_in_piece = num_blocks; + TORRENT_ASSERT(is_piece_free(piece, pieces)); + for (int j = 0; j < num_blocks_in_piece; ++j) + interesting_blocks.push_back(piece_block(piece, j)); + num_blocks -= num_blocks_in_piece; + } + else + { + int start, end; + boost::tie(start, end) = expand_piece(piece, prefer_contiguous_blocks + , pieces, options); + for (int k = start; k < end; ++k) + { + TORRENT_ASSERT(m_piece_map[k].priority(this) > 0); + num_blocks_in_piece = blocks_in_piece(k); + TORRENT_ASSERT(is_piece_free(k, pieces)); + for (int j = 0; j < num_blocks_in_piece; ++j) + { + interesting_blocks.push_back(piece_block(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 + , 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, int 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; + boost::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; + + block_info 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) == 0) + { + 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 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.push_back(piece_block(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 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.push_back(piece_block(dp.index, block_idx)); + // we have found a block that's free to download + --num_blocks; + // if we prefer contiguous blocks, continue picking from this + // piece even though we have num_blocks + if (prefer_contiguous_blocks > 0) + { + --prefer_contiguous_blocks; + continue; + } + if (num_blocks <= 0) return 0; + } + + if (num_blocks <= 0) return 0; + if (options & on_parole) return num_blocks; + + if (int(backup_blocks.size()) >= num_blocks) return num_blocks; + +#if TORRENT_USE_INVARIANT_CHECKS + verify_pick(backup_blocks, pieces); +#endif + return num_blocks; + } + + std::pair piece_picker::expand_piece(int piece, int contiguous_blocks + , bitfield const& have, int options) const + { + if (contiguous_blocks == 0) return std::make_pair(piece, piece + 1); + + // round to even pieces and expand in order to get the number of + // contiguous pieces we want + int whole_pieces = (contiguous_blocks + m_blocks_per_piece - 1) + / m_blocks_per_piece; + + int start = piece; + int lower_limit; + + if (options & align_expanded_pieces) + { + lower_limit = piece - (piece % whole_pieces); + } + else + { + lower_limit = piece - whole_pieces + 1; + if (lower_limit < 0) lower_limit = 0; + } + + while (start - 1 >= lower_limit + && can_pick(start - 1, have)) + --start; + + TORRENT_ASSERT(start >= 0); + int end = piece + 1; + int upper_limit ; + if (options & align_expanded_pieces) + { + upper_limit = lower_limit + whole_pieces; + } + else + { + upper_limit = start + whole_pieces; + } + if (upper_limit > int(m_piece_map.size())) upper_limit = int(m_piece_map.size()); + while (end < upper_limit + && can_pick(end, have)) + ++end; + return std::make_pair(start, end); + } + + bool piece_picker::is_piece_finished(int index) const + { + TORRENT_ASSERT(index < int(m_piece_map.size())); + TORRENT_ASSERT(index >= 0); + + piece_pos const& p = m_piece_map[index]; + if (p.index == piece_pos::we_have_index) return true; + + int state = p.download_queue(); + if (state == piece_pos::piece_open) + { + for (int i = 0; i < piece_pos::num_download_categories; ++i) + TORRENT_ASSERT(find_dl_piece(i, index) == m_downloads[i].end()); + return false; + } + std::vector::const_iterator i = find_dl_piece(state, index); + TORRENT_ASSERT(i != m_downloads[state].end()); + TORRENT_ASSERT(int(i->finished) <= m_blocks_per_piece); + int 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_ASSERTS && !defined TORRENT_DISABLE_INVARIANT_CHECKS + block_info const* info = blocks_for_piece(*i); + for (int k = 0; k < max_blocks; ++k) + { + TORRENT_ASSERT(info[k].piece_index == index); + TORRENT_ASSERT(info[k].state == block_info::state_finished + || info[k].state == block_info::state_writing); + } +#endif + + return true; + } + + bool piece_picker::has_piece_passed(int index) const + { + TORRENT_ASSERT(index < int(m_piece_map.size())); + TORRENT_ASSERT(index >= 0); + + piece_pos const& p = m_piece_map[index]; + if (p.index == piece_pos::we_have_index) return true; + + int state = p.download_queue(); + if (state == piece_pos::piece_open) + { + for (int i = 0; i < piece_pos::num_download_categories; ++i) + TORRENT_ASSERT(find_dl_piece(i, index) == m_downloads[i].end()); + return false; + } + std::vector::const_iterator i = find_dl_piece(state, index); + TORRENT_ASSERT(i != m_downloads[state].end()); + return i->passed_hash_check; + } + + std::vector::iterator piece_picker::find_dl_piece( + int queue, int index) + { + TORRENT_ASSERT(queue >= 0 && queue < piece_pos::num_download_categories); + downloading_piece cmp; + cmp.index = index; + std::vector::iterator 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( + int queue, int 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 num_blocks = blocks_in_piece(dp->index); + piece_pos& p = m_piece_map[dp->index]; + int current_state = p.download_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 + int new_state = 0; + if (p.filtered()) + { + new_state = piece_pos::piece_zero_prio; + } + else if (dp->requested + dp->finished + dp->writing == 0) + { + new_state = piece_pos::piece_open; + } + else if (dp->requested + dp->finished + dp->writing < num_blocks) + { + new_state = p.reverse() + ? piece_pos::piece_downloading_reverse + : piece_pos::piece_downloading; + } + else if (dp->requested > 0) + { + TORRENT_ASSERT(dp->requested + dp->finished + dp->writing == num_blocks); + new_state = p.reverse() + ? piece_pos::piece_full_reverse + : piece_pos::piece_full; + } + else + { + TORRENT_ASSERT(dp->finished + dp->writing == num_blocks); + new_state = piece_pos::piece_finished; + } + +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << " new_state: " << new_state << " current_state: " << current_state << std::endl; +#endif + if (new_state == current_state) return dp; + if (new_state == piece_pos::piece_open) return dp; + + // assert that the iterator that was passed-in in fact lives in + // the correct list + TORRENT_ASSERT(find_dl_piece(p.download_queue(), dp->index) == dp); + + // remove the downloading_piece from the list corresponding + // to the old state + downloading_piece dp_info = *dp; + m_downloads[p.download_queue()].erase(dp); + + int prio = p.priority(this); + p.download_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; + std::vector::iterator 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; + } +/* + int piece_picker::get_block_state(piece_block 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.size()); + + // if we have the piece, the block state is considered finished + if (m_piece_map[block.piece_index].index == piece_pos::we_have_index) + return block_info::state_finished; + + int state = m_piece_map[block.piece_index].download_queue(); + if (state == piece_pos::piece_open) return block_info::state_none; + std::vector::const_iterator i = find_dl_piece(state + , block.piece_index); + + TORRENT_ASSERT(i != m_downloads[state].end()); + + block_info const* info = blocks_for_piece(*i); + TORRENT_ASSERT(info[block.block_index].piece_index == block.piece_index); + return info[block.block_index].state; + } +*/ + bool piece_picker::is_requested(piece_block block) const + { +#ifdef TORRENT_USE_VALGRIND + VALGRIND_CHECK_VALUE_IS_DEFINED(block); +#endif + 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.size()); + + int state = m_piece_map[block.piece_index].download_queue(); + if (state == piece_pos::piece_open) return false; + std::vector::const_iterator i = find_dl_piece(state + , block.piece_index); + + TORRENT_ASSERT(i != m_downloads[state].end()); + + block_info 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 block) const + { +#ifdef TORRENT_USE_VALGRIND + VALGRIND_CHECK_VALUE_IS_DEFINED(block); +#endif + 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.size()); + + if (m_piece_map[block.piece_index].index == piece_pos::we_have_index) return true; + int state = m_piece_map[block.piece_index].download_queue(); + if (state == piece_pos::piece_open) return false; + std::vector::const_iterator i = find_dl_piece(state + , block.piece_index); + TORRENT_ASSERT(i != m_downloads[state].end()); + + block_info 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 block) const + { +#ifdef TORRENT_USE_VALGRIND + VALGRIND_CHECK_VALUE_IS_DEFINED(block); +#endif + 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.size()); + + piece_pos const& p = m_piece_map[block.piece_index]; + if (p.index == piece_pos::we_have_index) return true; + if (p.download_queue() == piece_pos::piece_open) return false; + std::vector::const_iterator i = find_dl_piece(p.download_queue() + , block.piece_index); + TORRENT_ASSERT(i != m_downloads[p.download_queue()].end()); + + block_info 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; + } + + // options may be 0 or piece_picker::reverse + bool piece_picker::mark_as_downloading(piece_block block + , torrent_peer* peer, int options) + { +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "mark_as_downloading( {" + << block.piece_index << ", " << block.block_index << "} )" << std::endl; +#endif + + TORRENT_ASSERT(peer == 0 || 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.size()); + TORRENT_ASSERT(int(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 + TORRENT_PIECE_PICKER_INVARIANT_CHECK; +#endif + int prio = p.priority(this); + TORRENT_ASSERT(prio < int(m_priority_boundries.size()) + || m_dirty); + + p.download_state = (options & reverse) + ? piece_pos::piece_downloading_reverse + : piece_pos::piece_downloading; + + if (prio >= 0 && !m_dirty) update(prio, p.index); + + dlpiece_iter dp = add_download_piece(block.piece_index); + block_info* binfo = blocks_for_piece(*dp); + block_info& info = binfo[block.block_index]; + TORRENT_ASSERT(info.piece_index == block.piece_index); + 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 + TORRENT_PIECE_PICKER_INVARIANT_CHECK; +#endif + std::vector::iterator i = find_dl_piece(p.download_queue() + , block.piece_index); + TORRENT_ASSERT(i != m_downloads[p.download_queue()].end()); + block_info* binfo = 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) == 0 && 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 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.size()); + TORRENT_ASSERT(int(block.block_index) < blocks_in_piece(block.piece_index)); + + piece_pos const& p = m_piece_map[block.piece_index]; + if (!p.downloading()) return 0; + + std::vector::const_iterator i = find_dl_piece(p.download_queue() + , block.piece_index); + TORRENT_ASSERT(i != m_downloads[p.download_queue()].end()); + + block_info 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(std::vector& avail) const + { + TORRENT_ASSERT(m_seeds >= 0); + TORRENT_PIECE_PICKER_INVARIANT_CHECK; + + avail.resize(m_piece_map.size()); + std::vector::iterator j = avail.begin(); + for (std::vector::const_iterator 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(int piece) const + { + TORRENT_ASSERT(piece >= 0 && piece < int(m_piece_map.size())); + return m_piece_map[piece].peer_count + m_seeds; + } + + bool piece_picker::mark_as_writing(piece_block block, torrent_peer* peer) + { +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + TORRENT_PIECE_PICKER_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 == 0 || 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.size()); + TORRENT_ASSERT(int(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 prio = p.priority(this); + TORRENT_ASSERT(prio < int(m_priority_boundries.size()) + || m_dirty); + p.download_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); + + dlpiece_iter dp = add_download_piece(block.piece_index); + block_info* binfo = 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); + 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 + { + std::vector::iterator i = find_dl_piece(p.download_queue() + , block.piece_index); + TORRENT_ASSERT(i != m_downloads[p.download_queue()].end()); + block_info* binfo = blocks_for_piece(*i); + block_info& info = binfo[block.block_index]; + + TORRENT_ASSERT(&info >= &m_block_info[0]); + TORRENT_ASSERT(&info < &m_block_info[0] + m_block_info.size()); + TORRENT_ASSERT(info.piece_index == block.piece_index); + + info.peer = peer; + if (info.state == block_info::state_requested) --i->requested; + if (info.state == block_info::state_writing + || info.state == block_info::state_finished) + return false; + + ++i->writing; + info.state = block_info::state_writing; + TORRENT_ASSERT(info.piece_index == block.piece_index); + + // all other requests for this block should have been + // cancelled now + info.num_peers = 0; +#if TORRENT_USE_ASSERTS + info.peers.clear(); +#endif + + update_piece_state(i); + } + return true; + } + + // calling this function prevents this piece from being picked + // by the piece picker until the pieces is restored. This allow + // the disk thread to synchronize and flush any failed state + // (used for disk write failures and piece hash failures). + void piece_picker::lock_piece(int piece) + { + TORRENT_PIECE_PICKER_INVARIANT_CHECK; + +#if TORRENT_USE_INVARIANT_CHECKS + check_piece_state(); +#endif + +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "lock_piece(" << piece << ")" << std::endl; +#endif + + int state = m_piece_map[piece].download_queue(); + if (state == piece_pos::piece_open) return; + std::vector::iterator i = find_dl_piece(state, piece); + if (i == m_downloads[state].end()) return; + + TORRENT_ASSERT(i->passed_hash_check == false); + if (i->passed_hash_check) + { + // it's not clear why this would happen, + // but it seems reasonable to not break the + // accounting over it. + i->passed_hash_check = false; + TORRENT_ASSERT(m_num_passed > 0); + --m_num_passed; + } + + // prevent this piece from being picked until it's restored + i->locked = true; + } + + // TODO: 2 it would be nice if this could be folded into lock_piece() + // the main distinction is that this also maintains the m_num_passed + // counter and the passed_hash_check member + // Is there ever a case where we call write filed without also locking + // the piece? Perhaps write_failed() should imply locking it. + void piece_picker::write_failed(piece_block block) + { + TORRENT_PIECE_PICKER_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 + + int state = m_piece_map[block.piece_index].download_queue(); + if (state == piece_pos::piece_open) return; + std::vector::iterator i = find_dl_piece(state, block.piece_index); + if (i == m_downloads[state].end()) return; + + block_info* binfo = 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 = 0; + info.state = block_info::state_none; + if (i->passed_hash_check) + { + // the hash was good, but we failed to write + // some of the blocks to disk, which means we + // can't consider the piece complete + i->passed_hash_check = false; + TORRENT_ASSERT(m_num_passed > 0); + --m_num_passed; + } + + // prevent this hash job from actually completing + // this piece, by setting the failure state. + // the piece is unlocked in the call to restore_piece() + i->locked = true; + + i = update_piece_state(i); + + if (i->finished + i->writing + i->requested == 0) + { + piece_pos& p = m_piece_map[block.piece_index]; + int prev_priority = p.priority(this); + erase_download_piece(i); + int 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(const piece_block 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.piece_index >= 0); + TORRENT_ASSERT(block.block_index >= 0); + TORRENT_ASSERT(block.piece_index < m_piece_map.size()); + TORRENT_ASSERT(int(block.block_index) < blocks_in_piece(block.piece_index)); + + piece_pos& p = m_piece_map[block.piece_index]; + + if (p.download_queue() == piece_pos::piece_open) return; + + std::vector::iterator i = find_dl_piece(p.download_queue() + , block.piece_index); + + TORRENT_ASSERT(i != m_downloads[p.download_queue()].end()); + block_info* binfo = 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 == 0); + if (info.state == block_info::state_writing) + { + --i->writing; + info.state = block_info::state_none; + // i may be invalid after this call + i = update_piece_state(i); + + if (i->finished + i->writing + i->requested == 0) + { + int prev_priority = p.priority(this); + erase_download_piece(i); + int 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 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 == 0 || static_cast(peer)->in_use); + TORRENT_ASSERT(block.piece_index >= 0); + TORRENT_ASSERT(block.block_index >= 0); + TORRENT_ASSERT(block.piece_index < m_piece_map.size()); + TORRENT_ASSERT(int(block.block_index) < blocks_in_piece(block.piece_index)); + + 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 + TORRENT_PIECE_PICKER_INVARIANT_CHECK; +#endif + + int prio = p.priority(this); + TORRENT_ASSERT(prio < int(m_priority_boundries.size()) + || m_dirty); + p.download_state = piece_pos::piece_downloading; + if (prio >= 0 && !m_dirty) update(prio, p.index); + + dlpiece_iter dp = add_download_piece(block.piece_index); + block_info* binfo = 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); + 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 + TORRENT_PIECE_PICKER_INVARIANT_CHECK; +#endif + + std::vector::iterator i = find_dl_piece(p.download_queue() + , block.piece_index); + TORRENT_ASSERT(i != m_downloads[p.download_queue()].end()); + block_info* binfo = 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 NULL. If so, preserve the previous peer + // pointer, instead of forgetting who we downloaded this block from + if (info.state != block_info::state_writing || peer != 0) + info.peer = peer; + + ++i->finished; + if (info.state == block_info::state_writing) + { + TORRENT_ASSERT(i->writing > 0); + --i->writing; + info.state = block_info::state_finished; + } + else + { + TORRENT_ASSERT(info.state == block_info::state_none); + info.state = block_info::state_finished; + } + + i = update_piece_state(i); + + if (i->finished < blocks_in_piece(i->index)) + return; + + if (i->passed_hash_check) + we_have(i->index); + } + +#if TORRENT_USE_INVARIANT_CHECKS + check_piece_state(); +#endif + + } + +/* + void piece_picker::mark_as_checking(int index) + { + int state = m_piece_map[index].download_queue(); + if (state == piece_pos::piece_open) return; + std::vector::iterator i = find_dl_piece(state, index); + if (i == m_downloads[state].end()) return; + TORRENT_ASSERT(i->outstanding_hash_check == false); + i->outstanding_hash_check = true; + } + + void piece_picker::mark_as_done_checking(int index) + { + int state = m_piece_map[index].download_queue(); + if (state == piece_pos::piece_open) return; + std::vector::iterator i = find_dl_piece(state, index); + if (i == m_downloads[state].end()) return; + i->outstanding_hash_check = false; + } +*/ + + void piece_picker::get_downloaders(std::vector& d, int index) const + { + TORRENT_ASSERT(index >= 0 && index <= int(m_piece_map.size())); + + d.clear(); + int state = m_piece_map[index].download_queue(); + const int num_blocks = blocks_in_piece(index); + d.reserve(num_blocks); + + if (state == piece_pos::piece_open) + { + for (int i = 0; i < num_blocks; ++i) d.push_back(NULL); + return; + } + + std::vector::const_iterator i + = find_dl_piece(state, index); + TORRENT_ASSERT(i != m_downloads[state].end()); + block_info const* binfo = blocks_for_piece(*i); + for (int j = 0; j != num_blocks; ++j) + { + TORRENT_ASSERT(binfo[j].peer == 0 + || binfo[j].peer->in_use); + d.push_back(binfo[j].peer); + } + } + + torrent_peer* piece_picker::get_downloader(piece_block block) const + { + int state = m_piece_map[block.piece_index].download_queue(); + if (state == piece_pos::piece_open) return 0; + + std::vector::const_iterator i = find_dl_piece(state + , block.piece_index); + + TORRENT_ASSERT(block.block_index != piece_block::invalid.block_index); + block_info 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 NULL; + + torrent_peer* peer = binfo[block.block_index].peer; + TORRENT_ASSERT(peer == 0 || 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 block, torrent_peer* peer) + { +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + TORRENT_PIECE_PICKER_INVARIANT_CHECK; +#endif + +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "abort_download( {" << block.piece_index << ", " << block.block_index << "} )" << std::endl; +#endif + TORRENT_ASSERT(peer == 0 || 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.size()); + TORRENT_ASSERT(int(block.block_index) < blocks_in_piece(block.piece_index)); + + int state = m_piece_map[block.piece_index].download_queue(); + if (state == piece_pos::piece_open) return; + + std::vector::iterator i = find_dl_piece(state + , block.piece_index); + TORRENT_ASSERT(i != m_downloads[state].end()); + + block_info* binfo = blocks_for_piece(*i); + block_info& info = binfo[block.block_index]; + TORRENT_ASSERT(info.peer == 0 || 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& p = m_piece_map[block.piece_index]; + int 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 = 0; + TORRENT_ASSERT(info.peers.size() == info.num_peers); + + TORRENT_ASSERT(int(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 = 0; + + // clear this block as being downloaded + info.state = block_info::state_none; + TORRENT_ASSERT(i->requested > 0); + --i->requested; + + // if there are no other blocks in this piece + // that's being downloaded, remove it from the list + if (i->requested + i->finished + i->writing == 0) + { + TORRENT_ASSERT(prev_prio < int(m_priority_boundries.size()) + || m_dirty); + erase_download_piece(i); + int 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); + } + + int piece_picker::unverified_blocks() const + { + int counter = 0; + for (int k = 0; k < piece_pos::num_download_categories; ++k) + { + for (std::vector::const_iterator i = m_downloads[k].begin(); + i != m_downloads[k].end(); ++i) + { + counter += int(i->finished); + } + } + return counter; + } + +} + diff --git a/src/platform_util.cpp b/src/platform_util.cpp new file mode 100644 index 0000000..5e22cec --- /dev/null +++ b/src/platform_util.cpp @@ -0,0 +1,149 @@ +/* + +Copyright (c) 2012-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/platform_util.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include + +#if TORRENT_USE_RLIMIT + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wlong-long" +#endif // __GNUC__ + +#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; + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif // __GNUC__ + +#endif // TORRENT_USE_RLIMIT + +#ifdef TORRENT_BSD +#include +#include +#endif + +#if defined TORRENT_WINDOWS +#include +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent +{ + + int max_open_files() + { +#if defined TORRENT_BUILD_SIMULATOR + return 256; +#elif TORRENT_USE_RLIMIT + + struct rlimit rl; + if (getrlimit(RLIMIT_NOFILE, &rl) == 0) + { + if (rl.rlim_cur == rlim_infinity) + return (std::numeric_limits::max)(); + + return rl.rlim_cur; + } + 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 + } + + boost::uint64_t total_physical_ram() + { +#if defined TORRENT_BUILD_SIMULATOR + return boost::uint64_t(4) * 1024 * 1024 * 1024; +#else + // figure out how much physical RAM there is in + // this machine. This is used for automatically + // sizing the disk cache size when it's set to + // automatic. + boost::uint64_t ret = 0; + +#ifdef TORRENT_BSD +#ifdef HW_MEMSIZE + int mib[2] = { CTL_HW, HW_MEMSIZE }; +#else + // not entirely sure this sysctl supports 64 + // bit return values, but it's probably better + // than not building + int mib[2] = { CTL_HW, HW_PHYSMEM }; +#endif + size_t len = sizeof(ret); + if (sysctl(mib, 2, &ret, &len, NULL, 0) != 0) + ret = 0; +#elif defined TORRENT_WINDOWS + MEMORYSTATUSEX ms; + ms.dwLength = sizeof(MEMORYSTATUSEX); + if (GlobalMemoryStatusEx(&ms)) + ret = ms.ullTotalPhys; + else + ret = 0; +#elif defined TORRENT_LINUX + ret = sysconf(_SC_PHYS_PAGES); + ret *= sysconf(_SC_PAGESIZE); +#elif defined TORRENT_AMIGA + ret = AvailMem(MEMF_PUBLIC); +#endif + +#if TORRENT_USE_RLIMIT + if (ret > 0) + { + struct rlimit r; + if (getrlimit(rlimit_as, &r) == 0 && r.rlim_cur != rlim_infinity) + { + if (ret > r.rlim_cur) + ret = r.rlim_cur; + } + } +#endif + return ret; +#endif // TORRENT_BUILD_SIMULATOR + } +} + diff --git a/src/proxy_base.cpp b/src/proxy_base.cpp new file mode 100644 index 0000000..36c1580 --- /dev/null +++ b/src/proxy_base.cpp @@ -0,0 +1,55 @@ +/* + +Copyright (c) 2012-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/proxy_base.hpp" + +namespace libtorrent +{ + + proxy_base::proxy_base(io_service& io_service) + : m_sock(io_service) + , m_port(0) + , m_resolver(io_service) + {} + + proxy_base::~proxy_base() {} + + bool proxy_base::handle_error(error_code const& e, boost::shared_ptr const& h) + { + if (!e) return false; + (*h)(e); + error_code ec; + close(ec); + return true; + } +} + diff --git a/src/proxy_settings.cpp b/src/proxy_settings.cpp new file mode 100644 index 0000000..807896d --- /dev/null +++ b/src/proxy_settings.cpp @@ -0,0 +1,78 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/aux_/proxy_settings.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/aux_/session_settings.hpp" + +namespace libtorrent { +namespace aux { + +proxy_settings::proxy_settings() + : type(0) + , port(0) + , proxy_hostnames(true) + , proxy_peer_connections(true) + , proxy_tracker_connections(true) +{} + +proxy_settings::proxy_settings(settings_pack const& sett) +{ + hostname = sett.get_str(settings_pack::proxy_hostname); + username = sett.get_str(settings_pack::proxy_username); + password = sett.get_str(settings_pack::proxy_password); + type = sett.get_int(settings_pack::proxy_type); + port = sett.get_int(settings_pack::proxy_port); + proxy_hostnames = sett.get_bool(settings_pack::proxy_hostnames); + proxy_peer_connections = sett.get_bool( + settings_pack::proxy_peer_connections); + proxy_tracker_connections = sett.get_bool( + settings_pack::proxy_tracker_connections); +} + +proxy_settings::proxy_settings(aux::session_settings const& sett) +{ + hostname = sett.get_str(settings_pack::proxy_hostname); + username = sett.get_str(settings_pack::proxy_username); + password = sett.get_str(settings_pack::proxy_password); + type = sett.get_int(settings_pack::proxy_type); + port = sett.get_int(settings_pack::proxy_port); + proxy_hostnames = sett.get_bool(settings_pack::proxy_hostnames); + proxy_peer_connections = sett.get_bool( + settings_pack::proxy_peer_connections); + proxy_tracker_connections = sett.get_bool( + settings_pack::proxy_tracker_connections); +} + +} // namespace aux +} // namespace libtorrent + diff --git a/src/puff.cpp b/src/puff.cpp new file mode 100644 index 0000000..b6d86be --- /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 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 NULL */ +#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() */ + 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) + 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 != NULL) { + 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; + while (1) { + 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) + 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 != NULL) { + 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 != NULL) { + 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 */ + int len; /* last length to repeat */ + + symbol = decode(s, &lencode); + if (symbol < 0) + return symbol; /* invalid symbol */ + if (symbol < 16) /* length in 0..15 */ + lengths[index++] = symbol; + else { /* repeat instruction */ + len = 0; /* 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..484d71f --- /dev/null +++ b/src/random.cpp @@ -0,0 +1,103 @@ +/* + +Copyright (c) 2011-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/assert.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#if !TORRENT_THREADSAFE_STATIC +#include "libtorrent/thread.hpp" +#endif + +namespace libtorrent +{ + using boost::random::random_device; + using boost::random::mt19937; + using boost::random::uniform_int_distribution; + +#ifdef TORRENT_BUILD_SIMULATOR + + boost::uint32_t random() + { + // make sure random numbers are deterministic. Seed with a fixed number + static mt19937 random_engine(4040); + return uniform_int_distribution(0, UINT_MAX)(random_engine); + } + +#else + +#if !TORRENT_THREADSAFE_STATIC + // because local statics are not atomic pre c++11 + // do it manually, probably at a higher cost + namespace + { + static mutex random_device_mutex; + static random_device* dev = NULL; + static mt19937* rnd = NULL; + } +#endif + + boost::uint32_t random() + { +#if TORRENT_THREADSAFE_STATIC + static random_device dev; + static mt19937 random_engine(dev()); + return uniform_int_distribution(0, UINT_MAX)(random_engine); +#else + mutex::scoped_lock l(random_device_mutex); + + if (dev == NULL) + { + dev = new random_device(); + rnd = new mt19937((*dev)()); + } + return uniform_int_distribution(0, UINT_MAX)(*rnd); +#endif + } + +#endif // TORRENT_BUILD_SIMULATOR + + boost::uint32_t randint(int i) + { + return random() % i; + } + +} + diff --git a/src/receive_buffer.cpp b/src/receive_buffer.cpp new file mode 100644 index 0000000..d5e86a6 --- /dev/null +++ b/src/receive_buffer.cpp @@ -0,0 +1,433 @@ +/* + +Copyright (c) 2014-2016, Arvid Norberg, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include + +namespace libtorrent { + + namespace { + int round_up8(int v) + { + return ((v & 7) == 0) ? v : v + (8 - (v & 7)); + } + } + +int receive_buffer::max_receive() +{ + int max = packet_bytes_remaining(); + if (m_recv_pos >= m_soft_packet_size) m_soft_packet_size = 0; + if (m_soft_packet_size && max > m_soft_packet_size - m_recv_pos) + max = m_soft_packet_size - m_recv_pos; + return max; +} + +boost::asio::mutable_buffer receive_buffer::reserve(int size) +{ + TORRENT_ASSERT(size > 0); + TORRENT_ASSERT(!m_disk_recv_buffer); + // this is unintuitive, but we used to use m_recv_pos in this function when + // we should have used m_recv_end. perhaps they always happen to be equal + TORRENT_ASSERT(m_recv_pos == m_recv_end); + + m_recv_buffer.resize(m_recv_end + size); + return boost::asio::buffer(&m_recv_buffer[0] + m_recv_end, size); +} + +int receive_buffer::reserve(boost::array& vec, int size) +{ + TORRENT_ASSERT(size > 0); + TORRENT_ASSERT(m_recv_pos >= 0); + TORRENT_ASSERT(m_packet_size > 0); + + // normalize() must be called before receiving more data + TORRENT_ASSERT(m_recv_start == 0); + + // this is unintuitive, but we used to use m_recv_pos in this function when + // we should have used m_recv_end. perhaps they always happen to be equal + TORRENT_ASSERT(m_recv_pos == m_recv_end); + + int num_bufs; + int const regular_buf_size = regular_buffer_size(); + + if (int(m_recv_buffer.size()) < regular_buf_size) + m_recv_buffer.resize(round_up8(regular_buf_size)); + + if (!m_disk_recv_buffer || regular_buf_size >= m_recv_end + size) + { + // only receive into regular buffer + TORRENT_ASSERT(m_recv_end + size <= int(m_recv_buffer.size())); + vec[0] = boost::asio::buffer(&m_recv_buffer[0] + m_recv_end, size); + TORRENT_ASSERT(boost::asio::buffer_size(vec[0]) > 0); + num_bufs = 1; + } + else if (m_recv_end >= regular_buf_size) + { + // only receive into disk buffer + TORRENT_ASSERT(m_recv_end - regular_buf_size >= 0); + TORRENT_ASSERT(m_recv_end - regular_buf_size + size <= m_disk_recv_buffer_size); + vec[0] = boost::asio::buffer(m_disk_recv_buffer.get() + m_recv_end - regular_buf_size, size); + TORRENT_ASSERT(boost::asio::buffer_size(vec[0]) > 0); + num_bufs = 1; + } + else + { + // receive into both regular and disk buffer + TORRENT_ASSERT(size + m_recv_end > regular_buf_size); + TORRENT_ASSERT(m_recv_end < regular_buf_size); + TORRENT_ASSERT(size - regular_buf_size + + m_recv_end <= m_disk_recv_buffer_size); + + vec[0] = boost::asio::buffer(&m_recv_buffer[0] + m_recv_end + , regular_buf_size - m_recv_end); + vec[1] = boost::asio::buffer(m_disk_recv_buffer.get() + , size - regular_buf_size + m_recv_end); + TORRENT_ASSERT(boost::asio::buffer_size(vec[0]) + + boost::asio::buffer_size(vec[1])> 0); + num_bufs = 2; + } + + return num_bufs; +} + +int receive_buffer::advance_pos(int bytes) +{ + int const packet_size = m_soft_packet_size ? m_soft_packet_size : m_packet_size; + int const limit = packet_size > m_recv_pos ? packet_size - m_recv_pos : packet_size; + int const sub_transferred = (std::min)(bytes, limit); + m_recv_pos += sub_transferred; + if (m_recv_pos >= m_soft_packet_size) m_soft_packet_size = 0; + return sub_transferred; +} + +void receive_buffer::clamp_size() +{ + if (m_recv_pos == 0 + && (m_recv_buffer.capacity() - m_packet_size) > 128) + { + // round up to an even 8 bytes since that's the RC4 blocksize + buffer(round_up8(m_packet_size)).swap(m_recv_buffer); + } +} + +// 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 size, int packet_size, int offset) +{ + 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 + , m_recv_end - m_recv_start - size - offset); + + m_recv_pos -= size; + m_recv_end -= size; + +#ifdef TORRENT_DEBUG + std::fill(m_recv_buffer.begin() + m_recv_end, m_recv_buffer.end(), 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; +} + +buffer::const_interval receive_buffer::get() const +{ + if (m_recv_buffer.empty()) + { + TORRENT_ASSERT(m_recv_pos == 0); + return buffer::interval(0,0); + } + + int rcv_pos = (std::min)(m_recv_pos, int(m_recv_buffer.size()) - m_recv_start); + return buffer::const_interval(&m_recv_buffer[0] + m_recv_start + , &m_recv_buffer[0] + m_recv_start + rcv_pos); +} + +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) +buffer::interval receive_buffer::mutable_buffer() +{ + if (m_recv_buffer.empty()) + { + TORRENT_ASSERT(m_recv_pos == 0); + return buffer::interval(0,0); + } + TORRENT_ASSERT(!m_disk_recv_buffer); + TORRENT_ASSERT(m_disk_recv_buffer_size == 0); + int rcv_pos = (std::min)(m_recv_pos, int(m_recv_buffer.size())); + return buffer::interval(&m_recv_buffer[0] + m_recv_start + , &m_recv_buffer[0] + m_recv_start + rcv_pos); +} + +// TODO: 2 should this take a boost::array<..., 2> instead? it could return the +// number of buffers added, just like reserve. +void receive_buffer::mutable_buffers(std::vector& vec, int const bytes) +{ + namespace asio = boost::asio; + + // 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) + + int const last_recv_pos = m_recv_pos - bytes; + TORRENT_ASSERT(bytes <= m_recv_pos); + + // the number of bytes in the current packet that are being received into a + // regular receive buffer (as opposed to a disk cache buffer) + int const regular_buf_size = regular_buffer_size(); + + TORRENT_ASSERT(regular_buf_size >= 0); + if (!m_disk_recv_buffer || regular_buf_size >= m_recv_pos) + { + // we just received into a regular disk buffer + vec.push_back(asio::mutable_buffer(&m_recv_buffer[0] + m_recv_start + + last_recv_pos, bytes)); + } + else if (last_recv_pos >= regular_buf_size) + { + // we only received into a disk buffer + vec.push_back(asio::mutable_buffer(m_disk_recv_buffer.get() + + last_recv_pos - regular_buf_size, bytes)); + } + else + { + // we received into a regular and a disk buffer + TORRENT_ASSERT(last_recv_pos < regular_buf_size); + TORRENT_ASSERT(m_recv_pos > regular_buf_size); + vec.push_back(asio::mutable_buffer(&m_recv_buffer[0] + m_recv_start + last_recv_pos + , regular_buf_size - last_recv_pos)); + vec.push_back(asio::mutable_buffer(m_disk_recv_buffer.get() + , m_recv_pos - regular_buf_size)); + } + +#if TORRENT_USE_ASSERTS + int vec_bytes = 0; + for (std::vector::iterator i = vec.begin(); + i != vec.end(); ++i) + vec_bytes += boost::asio::buffer_size(*i); + TORRENT_ASSERT(vec_bytes == bytes); +#endif +} +#endif + +void receive_buffer::assign_disk_buffer(char* buffer, int size) +{ + TORRENT_ASSERT(m_packet_size > 0); + assert_no_disk_buffer(); + m_disk_recv_buffer.reset(buffer); + if (m_disk_recv_buffer) + m_disk_recv_buffer_size = size; +} + +char* receive_buffer::release_disk_buffer() +{ + if (!m_disk_recv_buffer) return 0; + + TORRENT_ASSERT(m_disk_recv_buffer_size <= m_recv_end); + TORRENT_ASSERT(m_recv_start <= m_recv_end - m_disk_recv_buffer_size); + m_recv_end -= m_disk_recv_buffer_size; + m_disk_recv_buffer_size = 0; + return m_disk_recv_buffer.release(); +} + +// 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 receive_buffer::normalize() +{ + TORRENT_ASSERT(m_recv_end >= m_recv_start); + if (m_recv_start == 0) return; + + if (m_recv_end > m_recv_start) + std::memmove(&m_recv_buffer[0], &m_recv_buffer[0] + m_recv_start, m_recv_end - m_recv_start); + + m_recv_end -= m_recv_start; + m_recv_start = 0; + +#ifdef TORRENT_DEBUG + std::fill(m_recv_buffer.begin() + m_recv_end, m_recv_buffer.end(), 0xcc); +#endif +} + +void receive_buffer::reset(int packet_size) +{ + TORRENT_ASSERT(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) && !defined(TORRENT_DISABLE_EXTENSIONS) +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()); + TORRENT_ASSERT(!m_connection_buffer.has_disk_buffer()); + + 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); + } +} + +void crypto_receive_buffer::set_soft_packet_size(int size) +{ + if (m_recv_pos == INT_MAX) + m_connection_buffer.set_soft_packet_size(size); + else + m_soft_packet_size = size; +} + +int crypto_receive_buffer::advance_pos(int bytes) +{ + if (m_recv_pos == INT_MAX) return bytes; + + int packet_size = m_soft_packet_size ? m_soft_packet_size : m_packet_size; + int limit = packet_size > m_recv_pos ? packet_size - m_recv_pos : packet_size; + int sub_transferred = (std::min)(bytes, limit); + m_recv_pos += sub_transferred; + m_connection_buffer.cut(0, m_connection_buffer.packet_size() + sub_transferred); + if (m_recv_pos >= m_soft_packet_size) m_soft_packet_size = 0; + return sub_transferred; +} + +buffer::const_interval crypto_receive_buffer::get() const +{ + buffer::const_interval recv_buffer = m_connection_buffer.get(); + if (m_recv_pos < m_connection_buffer.pos()) + recv_buffer.end = recv_buffer.begin + m_recv_pos; + return recv_buffer; +} + +void crypto_receive_buffer::mutable_buffers( + std::vector& vec + , std::size_t bytes_transfered) +{ + int pending_decryption = bytes_transfered; + if (m_recv_pos != INT_MAX) + { + pending_decryption = m_connection_buffer.packet_size() - m_recv_pos; + } + m_connection_buffer.mutable_buffers(vec, pending_decryption); +} +#endif // TORRENT_DISABLE_ENCRYPTION + +} // namespace libtorrent diff --git a/src/request_blocks.cpp b/src/request_blocks.cpp new file mode 100644 index 0000000..fbd8f18 --- /dev/null +++ b/src/request_blocks.cpp @@ -0,0 +1,303 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/bitfield.hpp" +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/socket_type.hpp" +#include "libtorrent/peer_info.hpp" // for peer_info flags +#include "libtorrent/performance_counters.hpp" // for counters +#include "libtorrent/request_blocks.hpp" +#include "libtorrent/alert_manager.hpp" + +#include + +namespace libtorrent +{ + int source_rank(int 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() != 0 || c.type() != peer_connection::bittorrent_connection); + + bool time_critical_mode = t.num_time_critical_pieces() > 0; + + int desired_queue_size = c.desired_queue_size(); + + // in time critical mode, only have 1 outstanding request at a time + // via normal requests + if (time_critical_mode) + desired_queue_size = (std::min)(1, desired_queue_size); + + int num_requests = desired_queue_size + - int(c.download_queue().size()) + - int(c.request_queue().size()); + +#ifndef TORRENT_DISABLE_LOGGING + c.peer_log(peer_log_alert::info, "PIECE_PICKER" + , "dlq: %d rqq: %d target: %d req: %d engame: %d" + , int(c.download_queue().size()), int(c.request_queue().size()) + , c.desired_queue_size(), num_requests, c.endgame()); +#endif + TORRENT_ASSERT(c.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) + { + int blocks_per_piece = t.torrent_file().piece_length() / t.block_size(); + prefer_contiguous_blocks = c.statistics().download_payload_rate() + * t.settings().get_int(settings_pack::whole_pieces_threshold) + > t.torrent_file().piece_length() ? blocks_per_piece : 0; + } + + // 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. +#ifdef TORRENT_DEBUG + 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(); + bitfield const* bits = &c.get_bitfield(); + 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 + std::vector const& allowed_fast = c.allowed_fast(); + + // build a bitmask with only the allowed pieces in it + fast_mask.resize(c.get_bitfield().size(), false); + for (std::vector::const_iterator i = allowed_fast.begin() + , end(allowed_fast.end()); i != end; ++i) + 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. + boost::uint32_t 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[0], int(interesting_pieces.size())); + } + 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 dont_pick_busy_blocks + = ((ses.settings().get_bool(settings_pack::strict_end_game_mode) + && p.get_download_queue_size() < p.num_want_left()) + || dq.size() + rq.size() > 0) + && !time_critical_mode; + + // this is filled with an interesting piece + // that some other peer is currently downloading + piece_block busy_block = piece_block::invalid; + + for (std::vector::iterator i = interesting_pieces.begin(); + i != interesting_pieces.end(); ++i) + { + if (prefer_contiguous_blocks == 0 && num_requests <= 0) break; + + if (time_critical_mode && p.piece_priority(i->piece_index) != 7) + { + // assume the subsequent pieces are not prio 7 and + // be done + break; + } + + int num_block_requests = p.num_peers(*i); + 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(*i) > 0); + busy_block = *i; + continue; + } + + TORRENT_ASSERT(p.num_peers(*i) == 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(), has_block(*i)) != dq.end() + || std::find_if(rq.begin(), rq.end(), has_block(*i)) != rq.end()) + { +#ifdef TORRENT_DEBUG + std::vector::const_iterator j + = std::find_if(dq.begin(), dq.end(), has_block(*i)); + 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" + , i->piece_index, i->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(*i, 0)) continue; + TORRENT_ASSERT(p.num_peers(*i) == 1); + TORRENT_ASSERT(p.is_requested(*i)); + 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; + } + +#ifdef TORRENT_DEBUG + 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::req_busy); + return true; + } + +} + diff --git a/src/resolve_links.cpp b/src/resolve_links.cpp new file mode 100644 index 0000000..510998e --- /dev/null +++ b/src/resolve_links.cpp @@ -0,0 +1,141 @@ +/* + +Copyright (c) 2014-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/resolve_links.hpp" + +#include "libtorrent/torrent_info.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent +{ + +#ifndef TORRENT_DISABLE_MUTABLE_TORRENTS +resolve_links::resolve_links(boost::shared_ptr ti) + : m_torrent_file(ti) +{ + TORRENT_ASSERT(ti); + + int piece_size = ti->piece_length(); + + file_storage const& fs = ti->files(); + m_file_sizes.reserve(fs.num_files()); + for (int i = 0; i < fs.num_files(); ++i) + { + // don't match pad-files, and don't match files that aren't aligned to + // ieces. Files are matched by comparing piece hashes, so pieces must + // be aligned and the same size + if (fs.pad_file_at(i)) continue; + if ((fs.file_offset(i) % piece_size) != 0) continue; + + m_file_sizes.insert(std::make_pair(fs.file_size(i), i)); + } + + m_links.resize(m_torrent_file->num_files()); +} + +void resolve_links::match(boost::shared_ptr const& ti + , std::string const& save_path) +{ + if (!ti) return; + + // only torrents with the same + if (ti->piece_length() != m_torrent_file->piece_length()) return; + + int piece_size = ti->piece_length(); + + file_storage const& fs = ti->files(); + m_file_sizes.reserve(fs.num_files()); + for (int i = 0; i < fs.num_files(); ++i) + { + // 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; + + boost::int64_t file_size = fs.file_size(i); + + typedef boost::unordered_multimap::iterator iterator; + iterator iter = m_file_sizes.find(file_size); + + // we don't have a file whose size matches, look at the next one + if (iter == m_file_sizes.end()) continue; + + TORRENT_ASSERT(iter->second < m_torrent_file->files().num_files()); + TORRENT_ASSERT(iter->second >= 0); + + // if we already have found a duplicate for this file, no need + // to keep looking + if (m_links[iter->second].ti) continue; + + // files are aligned and have the same size, now start comparing + // piece hashes, to see if the files are identical + + // the pieces of the incoming file + int their_piece = fs.map_file(i, 0, 0).piece; + // the pieces of "this" file (from m_torrent_file) + int our_piece = m_torrent_file->files().map_file( + iter->second, 0, 0).piece; + + int num_pieces = (file_size + piece_size - 1) / piece_size; + + bool match = true; + for (int p = 0; p < num_pieces; ++p, ++their_piece, ++our_piece) + { + if (m_torrent_file->hash_for_piece(our_piece) + != ti->hash_for_piece(their_piece)) + { + match = false; + break; + } + } + if (!match) continue; + + m_links[iter->second].ti = ti; + m_links[iter->second].save_path = save_path; + m_links[iter->second].file_idx = i; + + // since we have a duplicate for this file, we may as well remove + // it from the file-size map, so we won't find it again. + m_file_sizes.erase(iter); + } + +} +#endif // TORRENT_DISABLE_MUTABLE_TORRENTS + +} // namespace libtorrent + diff --git a/src/resolver.cpp b/src/resolver.cpp new file mode 100644 index 0000000..a493386 --- /dev/null +++ b/src/resolver.cpp @@ -0,0 +1,141 @@ +/* + +Copyright (c) 2013-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/resolver.hpp" +#include +#include "libtorrent/debug.hpp" +#include "libtorrent/aux_/time.hpp" + +namespace libtorrent +{ + resolver::resolver(io_service& ios) + : m_ios(ios) + , m_resolver(ios) + , m_critical_resolver(ios) + , m_max_size(700) + , m_timeout(seconds(1200)) + {} + + void resolver::on_lookup(error_code const& ec, tcp::resolver::iterator i + , resolver_interface::callback_t h, std::string hostname) + { +#if defined TORRENT_ASIO_DEBUGGING + complete_async("resolver::on_lookup"); +#endif + if (ec) + { + std::vector
    empty; + h(ec, empty); + return; + } + + dns_cache_entry& ce = m_cache[hostname]; + time_point now = aux::time_now(); + ce.last_seen = now; + ce.addresses.clear(); + while (i != tcp::resolver::iterator()) + { + ce.addresses.push_back(i->endpoint().address()); + ++i; + } + + h(ec, ce.addresses); + + // if m_cache grows too big, weed out the + // oldest entries + if (m_cache.size() > m_max_size) + { + cache_t::iterator oldest = m_cache.begin(); + for (cache_t::iterator 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, int flags + , resolver_interface::callback_t const& h) + { + cache_t::iterator i = m_cache.find(host); + if (i != m_cache.end()) + { + // keep cache entries valid for m_timeout seconds + if ((flags & resolver_interface::prefer_cache) + || i->second.last_seen + m_timeout >= aux::time_now()) + { + error_code ec; + m_ios.post(boost::bind(h, ec, i->second.addresses)); + return; + } + } + + // 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 ip = address::from_string(host.c_str(), ec); + if (!ec) + { + std::vector
    addresses; + addresses.push_back(ip); + m_ios.post(boost::bind(h, ec, addresses)); + return; + } + + // the port is ignored + tcp::resolver::query q(host, "80"); + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("resolver::on_lookup"); +#endif + if (flags & resolver_interface::abort_on_shutdown) + { + m_resolver.async_resolve(q, boost::bind(&resolver::on_lookup, this, _1, _2 + , h, host)); + } + else + { + m_critical_resolver.async_resolve(q, boost::bind(&resolver::on_lookup, this, _1, _2 + , h, host)); + } + } + + void resolver::abort() + { + m_resolver.cancel(); + } +} + diff --git a/src/rss.cpp b/src/rss.cpp new file mode 100644 index 0000000..9f90938 --- /dev/null +++ b/src/rss.cpp @@ -0,0 +1,706 @@ +/* + +Copyright (c) 2010-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/rss.hpp" +#include "libtorrent/xml_parse.hpp" +#include "libtorrent/http_parser.hpp" +#include "libtorrent/http_connection.hpp" +#include "libtorrent/aux_/session_impl.hpp" +#include "libtorrent/aux_/session_call.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/alert_types.hpp" // for rss_alert + +#include +#include +#include +#include + +#ifndef TORRENT_NO_DEPRECATE + +namespace libtorrent { + +feed_item::feed_item(): size(-1) {} +feed_item::~feed_item() {} + +struct feed_state +{ + feed_state(feed& r) + : in_item(false) + , num_items(0) + , type(none) + , ret(r) + {} + + bool in_item; + int num_items; + std::string current_tag; + enum feed_type + { + none, atom, rss2 + } type; + feed_item current_item; + feed& ret; + + bool is_item(char const* tag) const + { + switch (type) + { + case atom: return string_equal_no_case(tag, "entry"); + case rss2: return string_equal_no_case(tag, "item"); + case none: return false; + } + return false; + } + + bool is_title(char const* tag) const + { + switch (type) + { + case atom: + case rss2: return string_equal_no_case(tag, "title"); + case none: return false; + } + return false; + } + + bool is_url(char const* tag) const + { + switch (type) + { + case atom: + case rss2: return string_equal_no_case(tag, "link"); + case none: return false; + } + return false; + } + + bool is_desc(char const* tag) const + { + switch (type) + { + case atom: return string_equal_no_case(tag, "summary"); + case rss2: return string_equal_no_case(tag, "description") + || string_equal_no_case(tag, "media:text"); + case none: return false; + } + return false; + } + + bool is_uuid(char const* tag) const + { + switch (type) + { + case atom: return string_equal_no_case(tag, "id"); + case rss2: return string_equal_no_case(tag, "guid"); + case none: return false; + } + return false; + } + + bool is_comment(char const* tag) const + { + switch (type) + { + case atom: return false; + case rss2: return string_equal_no_case(tag, "comments"); + case none: return false; + } + return false; + } + + bool is_category(char const* tag) const + { + switch (type) + { + case atom: return false; + case rss2: return string_equal_no_case(tag, "category"); + case none: return false; + } + return false; + } + + bool is_size(char const* tag) const + { + return string_equal_no_case(tag, "size") + || string_equal_no_case(tag, "contentlength"); + } + + bool is_hash(char const* tag) const + { + return string_equal_no_case(tag, "hash") + || string_equal_no_case(tag, "media:hash"); + } + + bool is_ttl(char const* tag) const + { + return string_equal_no_case(tag, "ttl"); + } +}; + +void parse_feed(feed_state& f, int token, char const* name, int name_len + , char const* val, int val_len) +{ + switch (token) + { + case xml_parse_error: + f.ret.m_error = errors::parse_failed; + return; + case xml_start_tag: + case xml_empty_tag: + { + f.current_tag.assign(name, name_len); + if (f.type == feed_state::none) + { + if (string_equal_no_case(f.current_tag.c_str(), "feed")) + f.type = feed_state::atom; + else if (string_equal_no_case(f.current_tag.c_str(), "rss")) + f.type = feed_state::rss2; + } + if (f.is_item(f.current_tag.c_str())) f.in_item = true; + return; + } + case xml_attribute: + { + if (!f.in_item) return; + std::string str(name, name_len); + if (f.is_url(f.current_tag.c_str()) + && f.type == feed_state::atom) + { + // atom feeds have items like this: + // + if (string_equal_no_case(str.c_str(), "href")) + f.current_item.url.assign(val, val_len); + else if (string_equal_no_case(str.c_str(), "length")) + f.current_item.size = strtoll(val, 0, 10); + } + else if (f.type == feed_state::rss2 + && string_equal_no_case(f.current_tag.c_str(), "enclosure")) + { + // rss feeds have items like this: + // + if (string_equal_no_case(str.c_str(), "url")) + f.current_item.url.assign(val, val_len); + else if (string_equal_no_case(str.c_str(), "length")) + f.current_item.size = strtoll(val, 0, 10); + } + else if (f.type == feed_state::rss2 + && string_equal_no_case(f.current_tag.c_str(), "media:content")) + { + // rss feeds sometimes have items like this: + // + if (string_equal_no_case(str.c_str(), "url")) + f.current_item.url.assign(val, val_len); + else if (string_equal_no_case(str.c_str(), "filesize")) + f.current_item.size = strtoll(val, 0, 10); + } + return; + } + case xml_end_tag: + { + if (f.in_item && f.is_item(std::string(name, name_len).c_str())) + { + f.in_item = false; + if (!f.current_item.title.empty() + && !f.current_item.url.empty()) + { + f.ret.add_item(f.current_item); + ++f.num_items; + } + f.current_item = feed_item(); + } + f.current_tag = ""; + return; + } + case xml_string: + { + if (!f.in_item) + { + if (f.is_title(f.current_tag.c_str())) + f.ret.m_title.assign(name, name_len); + else if (f.is_desc(f.current_tag.c_str())) + f.ret.m_description.assign(name, name_len); + else if (f.is_ttl(f.current_tag.c_str())) + { + int tmp = atoi(name); + if (tmp > 0) f.ret.m_ttl = tmp; + } + return; + } + if (f.is_title(f.current_tag.c_str())) + f.current_item.title.assign(name, name_len); + else if (f.is_desc(f.current_tag.c_str())) + f.current_item.description.assign(name, name_len); + else if (f.is_uuid(f.current_tag.c_str())) + f.current_item.uuid.assign(name, name_len); + else if (f.is_url(f.current_tag.c_str()) && f.type != feed_state::atom) + f.current_item.url.assign(name, name_len); + else if (f.is_comment(f.current_tag.c_str())) + f.current_item.comment.assign(name, name_len); + else if (f.is_category(f.current_tag.c_str())) + f.current_item.category.assign(name, name_len); + else if (f.is_size(f.current_tag.c_str())) + f.current_item.size = strtoll(name, 0, 10); + else if (f.is_hash(f.current_tag.c_str()) && name_len == 40) + { + if (!from_hex(name, 40, f.current_item.info_hash.data())) + { + // hex parsing failed + f.current_item.info_hash.clear(); + } + } + return; + } + case xml_declaration_tag: return; + case xml_comment: return; + } +} + +torrent_handle add_feed_item(session& s, feed_item const& fi + , add_torrent_params const& tp, error_code& ec) +{ + add_torrent_params p = tp; + p.url = fi.url; + p.uuid = fi.uuid; + // #error figure out how to get the feed url in here +// p.source_feed_url = ???; + p.ti.reset(); + p.info_hash.clear(); + p.name = fi.title.c_str(); + return s.add_torrent(p, ec); +} + +#ifndef BOOST_NO_EXCEPTIONS +torrent_handle add_feed_item(session& s, feed_item const& fi + , add_torrent_params const& tp) +{ + error_code ec; + torrent_handle ret = add_feed_item(s, fi, tp, ec); + if (ec) throw libtorrent_exception(ec); + return ret; +} +#endif + +boost::shared_ptr new_feed(aux::session_impl& ses, feed_settings const& sett) +{ + return boost::shared_ptr(new feed(ses, sett)); +} + +feed::feed(aux::session_impl& ses, feed_settings const& sett) + : m_last_attempt(0) + , m_last_update(0) + , m_ttl(-1) + , m_failures(0) + , m_updating(false) + , m_settings(sett) + , m_ses(ses) +{ +} + +void feed::set_settings(feed_settings const& s) +{ + m_settings = s; +} + +void feed::get_settings(feed_settings* s) const +{ + *s = m_settings; +} + +feed_handle feed::my_handle() +{ + return feed_handle(boost::weak_ptr(shared_from_this())); +} + +void feed::on_feed(error_code const& ec + , http_parser const& parser, char const* data, int size) +{ + // enabling this assert makes the unit test a lot more difficult +// TORRENT_ASSERT(m_updating); + m_updating = false; + + // rss_alert is deprecated, and so is all of this code. +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + + if (ec && ec != boost::asio::error::eof) + { + ++m_failures; + m_error = ec; + if (m_ses.alerts().should_post()) + { + m_ses.alerts().emplace_alert(my_handle(), m_settings.url + , rss_alert::state_error, m_error); + } + return; + } + + if (parser.status_code() != 200) + { + ++m_failures; + m_error = error_code(parser.status_code(), get_http_category()); + if (m_ses.alerts().should_post()) + { + m_ses.alerts().emplace_alert(my_handle(), m_settings.url + , rss_alert::state_error, m_error); + } + return; + } + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + + m_failures = 0; + + feed_state s(*this); + xml_parse(data, data + size, boost::bind(&parse_feed, boost::ref(s) + , _1, _2, _3, _4, _5)); + + time_t now = time(NULL); + + // keep history of the typical feed size times 5 + int max_history = (std::max)(s.num_items * 5, 100); + + // this is not very efficient, but that's probably OK for now + while (int(m_added.size()) > max_history) + { + // loop over all elements and find the one with the lowest timestamp + // i.e. it was added the longest ago, then remove it + std::map::iterator i = std::min_element( + m_added.begin(), m_added.end() + , boost::bind(&std::pair::second, _1) + < boost::bind(&std::pair::second, _2)); + m_added.erase(i); + } + + m_last_update = now; + + // rss_alert is deprecated, and so is all of this code. +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + + // report that we successfully updated the feed + if (m_ses.alerts().should_post()) + { + m_ses.alerts().emplace_alert(my_handle(), m_settings.url + , rss_alert::state_updated, error_code()); + } + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + + // update m_ses.m_next_rss_update timestamps + // now that we have updated our timestamp + m_ses.update_rss_feeds(); +} + +void feed::load_state(bdecode_node const& rd) +{ + m_title = rd.dict_find_string_value("m_title"); + m_description = rd.dict_find_string_value("m_description"); + m_last_attempt = rd.dict_find_int_value("m_last_attempt"); + m_last_update = rd.dict_find_int_value("m_last_update"); + + bdecode_node e = rd.dict_find_list("items"); + if (e) + { + m_items.reserve(e.list_size()); + for (int i = 0; i < e.list_size(); ++i) + { + bdecode_node entry = e.list_at(i); + if (entry.type() != bdecode_node::dict_t) continue; + + m_items.push_back(feed_item()); + feed_item& item = m_items.back(); + item.url = entry.dict_find_string_value("url"); + item.uuid = entry.dict_find_string_value("uuid"); + item.title = entry.dict_find_string_value("title"); + item.description = entry.dict_find_string_value("description"); + item.comment = entry.dict_find_string_value("comment"); + item.category = entry.dict_find_string_value("category"); + item.size = entry.dict_find_int_value("size"); + + // don't load duplicates + if (m_urls.find(item.url) != m_urls.end()) + { + m_items.pop_back(); + continue; + } + m_urls.insert(item.url); + } + } + + m_settings.url = rd.dict_find_string_value("url"); + m_settings.auto_download = rd.dict_find_int_value("auto_download"); + m_settings.auto_map_handles = rd.dict_find_int_value("auto_map_handles"); + m_settings.default_ttl = rd.dict_find_int_value("default_ttl"); + + e = rd.dict_find_dict("add_params"); + if (e) + { + m_settings.add_args.save_path = e.dict_find_string_value("save_path"); + m_settings.add_args.flags = e.dict_find_int_value("flags"); + } + + e = rd.dict_find_list("history"); + if (e) + { + for (int i = 0; i < e.list_size(); ++i) + { + if (e.list_at(i).type() != bdecode_node::list_t) continue; + + bdecode_node item = e.list_at(i); + + if (item.list_size() != 2 + || item.list_at(0).type() != bdecode_node::string_t + || item.list_at(1).type() != bdecode_node::int_t) + continue; + + m_added.insert(std::pair( + item.list_at(0).string_value() + , item.list_at(1).int_value())); + } + } +} + +void feed::save_state(entry& rd) const +{ + // feed properties + rd["m_title"] = m_title; + rd["m_description"] = m_description; + rd["m_last_attempt"] = m_last_attempt; + rd["m_last_update"] = m_last_update; + + // items + entry::list_type& items = rd["items"].list(); + for (std::vector::const_iterator i = m_items.begin() + , end(m_items.end()); i != end; ++i) + { + items.push_back(entry()); + entry& item = items.back(); + item["url"] = i->url; + item["uuid"] = i->uuid; + item["title"] = i->title; + item["description"] = i->description; + item["comment"] = i->comment; + item["category"] = i->category; + item["size"] = i->size; + } + + // settings + feed_settings sett_def; +#define TORRENT_WRITE_SETTING(name) \ + if (m_settings.name != sett_def.name) rd[#name] = m_settings.name + + TORRENT_WRITE_SETTING(url); + TORRENT_WRITE_SETTING(auto_download); + TORRENT_WRITE_SETTING(auto_map_handles); + TORRENT_WRITE_SETTING(default_ttl); + +#undef TORRENT_WRITE_SETTING + + entry& add = rd["add_params"]; + add_torrent_params add_def; +#define TORRENT_WRITE_SETTING(name) \ + if (m_settings.add_args.name != add_def.name) add[#name] = m_settings.add_args.name; + + TORRENT_WRITE_SETTING(save_path); + TORRENT_WRITE_SETTING(flags); + +#undef TORRENT_WRITE_SETTING + + entry::list_type& history = rd["history"].list(); + for (std::map::const_iterator i = m_added.begin() + , end(m_added.end()); i != end; ++i) + { + history.push_back(entry()); + entry::list_type& item = history.back().list(); + item.push_back(entry(i->first)); + item.push_back(entry(i->second)); + } +} + +void feed::add_item(feed_item const& item) +{ + // don't add duplicates + if (m_urls.find(item.url) != m_urls.end()) + return; + + m_urls.insert(item.url); + m_items.push_back(item); + + feed_item& i = m_items.back(); + + if (m_settings.auto_map_handles) + i.handle = torrent_handle(m_ses.find_torrent(i.uuid.empty() ? i.url : i.uuid)); + + if (m_ses.alerts().should_post()) + m_ses.alerts().emplace_alert(my_handle(), i); + + if (m_settings.auto_download) + { + if (!m_settings.auto_map_handles) + i.handle = torrent_handle(m_ses.find_torrent(i.uuid.empty() ? i.url : i.uuid)); + + // if we're already downloading this torrent + // move along to the next one + if (i.handle.is_valid()) return; + + // has this already been added? + if (m_added.find(i.url) != m_added.end()) return; + + // this means we should add this torrent to the session + add_torrent_params p = m_settings.add_args; + p.url = i.url; + p.uuid = i.uuid; + p.source_feed_url = m_settings.url; + p.ti.reset(); + p.info_hash.clear(); + p.name = i.title.c_str(); + + error_code e; + m_ses.add_torrent(p, e); + time_t now = time(NULL); + m_added.insert(make_pair(i.url, now)); + } +} + +// returns the number of seconds until trying again +int feed::update_feed() +{ + if (m_updating) return 60; + + m_last_attempt = time(0); + m_last_update = 0; + + // rss_alert is deprecated, and so is all of this code. +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + + if (m_ses.alerts().should_post()) + { + m_ses.alerts().emplace_alert(my_handle(), m_settings.url + , rss_alert::state_updating, error_code()); + } + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + + boost::shared_ptr feed( + new http_connection(m_ses.get_io_service() + , m_ses.get_resolver() + , boost::bind(&feed::on_feed, shared_from_this() + , _1, _2, _3, _4))); + + m_updating = true; + feed->get(m_settings.url, seconds(30), 0, 0, 5 + , m_ses.settings().get_str(settings_pack::user_agent)); + + return 60 + m_failures * m_failures * 60; +} + +void feed::get_feed_status(feed_status* ret) const +{ + ret->items = m_items; + ret->last_update = m_last_update; + ret->updating = m_updating; + ret->url = m_settings.url; + ret->title = m_title; + ret->description = m_description; + ret->error = m_error; + ret->ttl = m_ttl == -1 ? m_settings.default_ttl : m_ttl; + ret->next_update = next_update(time(0)); +} + +int feed::next_update(time_t now) const +{ + if (m_last_update == 0) return int(m_last_attempt + 60 * 5 - now); + int ttl = m_ttl == -1 ? m_settings.default_ttl : m_ttl; + TORRENT_ASSERT((m_last_update + ttl * 60) - now < INT_MAX); + return int((m_last_update + ttl * 60) - now); +} + +#define TORRENT_ASYNC_CALL(x) \ + boost::shared_ptr f = m_feed_ptr.lock(); \ + if (!f) return; \ + aux::session_impl& ses = f->session(); \ + ses.get_io_service().post(boost::bind(&feed:: x, f)) + +#define TORRENT_ASYNC_CALL1(x, a1) \ + boost::shared_ptr f = m_feed_ptr.lock(); \ + if (!f) return; \ + aux::session_impl& ses = f->session(); \ + ses.get_io_service().post(boost::bind(&feed:: x, f, a1)) + +#define TORRENT_SYNC_CALL1(x, a1) \ + boost::shared_ptr f = m_feed_ptr.lock(); \ + if (f) aux::sync_call_handle(f, boost::bind(&feed:: x, f, a1)); + +feed_handle::feed_handle(boost::weak_ptr const& p) + : m_feed_ptr(p) {} + +void feed_handle::update_feed() +{ + TORRENT_ASYNC_CALL(update_feed); +} + +feed_status feed_handle::get_feed_status() const +{ + feed_status ret; + TORRENT_SYNC_CALL1(get_feed_status, &ret); + return ret; +} + +void feed_handle::set_settings(feed_settings const& s) +{ + TORRENT_ASYNC_CALL1(set_settings, s); +} + +feed_settings feed_handle::settings() const +{ + feed_settings ret; + TORRENT_SYNC_CALL1(get_settings, &ret); + return ret; +} + +} + +#endif // TORRENT_NO_DEPRECATE + diff --git a/src/session.cpp b/src/session.cpp new file mode 100644 index 0000000..125f4f3 --- /dev/null +++ b/src/session.cpp @@ -0,0 +1,438 @@ +/* + +Copyright (c) 2006-2016, Arvid Norberg, Magnus Jonsson +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#ifdef TORRENT_PROFILE_CALLS +#include +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/extensions/ut_pex.hpp" +#include "libtorrent/extensions/ut_metadata.hpp" +#include "libtorrent/extensions/smart_ban.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/session_handle.hpp" +#include "libtorrent/fingerprint.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/file.hpp" +#include "libtorrent/bt_peer_connection.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/aux_/session_impl.hpp" +#include "libtorrent/aux_/session_call.hpp" +#include "libtorrent/kademlia/dht_tracker.hpp" +#include "libtorrent/natpmp.hpp" +#include "libtorrent/upnp.hpp" +#include "libtorrent/magnet_uri.hpp" +#include "libtorrent/lazy_entry.hpp" + +using boost::shared_ptr; +using boost::weak_ptr; +using libtorrent::aux::session_impl; + +namespace libtorrent +{ + TORRENT_EXPORT void min_memory_usage(settings_pack& set) + { + // 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); + + 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::network_threads, 0); + 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); + + // be extra nice on the hard drive when running + // on embedded devices. This might slow down + // torrent checking + set.set_int(settings_pack::file_checks_delay_per_block, 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); + + // don't keep track of all upnp devices, keep + // the device list small + set.set_bool(settings_pack::upnp_ignore_nonrouters, true); + + // never keep more than one 16kB block in + // the send buffer + set.set_int(settings_pack::send_buffer_watermark, 9); + + // don't use any disk cache + set.set_int(settings_pack::cache_size, 0); + set.set_int(settings_pack::cache_buffer_chunk_size, 1); + set.set_bool(settings_pack::use_read_cache, false); + set.set_bool(settings_pack::use_disk_read_ahead, false); + + set.set_bool(settings_pack::close_redundant_connections, true); + + set.set_int(settings_pack::max_peerlist_size, 500); + set.set_int(settings_pack::max_paused_peerlist_size, 50); + + // udp trackers are cheaper to talk to + set.set_bool(settings_pack::prefer_udp_trackers, true); + + set.set_int(settings_pack::max_rejects, 10); + + set.set_int(settings_pack::recv_socket_buffer_size, 16 * 1024); + set.set_int(settings_pack::send_socket_buffer_size, 16 * 1024); + + // use less memory when reading and writing + // whole pieces + set.set_bool(settings_pack::coalesce_reads, false); + set.set_bool(settings_pack::coalesce_writes, false); + } + + TORRENT_EXPORT void 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); + + // 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 many peers + set.set_int(settings_pack::unchoke_slots_limit, 2000); + + // we need more DHT capacity to ping more peers + // candidates before trying to connect + set.set_int(settings_pack::dht_upload_rate_limit, 20000); + + // use 1 GB of cache + set.set_int(settings_pack::cache_size, 32768 * 2); + set.set_bool(settings_pack::use_read_cache, true); + set.set_int(settings_pack::cache_buffer_chunk_size, 0); + set.set_int(settings_pack::read_cache_line_size, 32); + set.set_int(settings_pack::write_cache_line_size, 256); + set.set_bool(settings_pack::low_prio_disk, false); + // 30 seconds expiration to save cache + // space for active pieces + set.set_int(settings_pack::cache_expiry, 30); + + // in case the OS we're running on doesn't support + // readv/writev, allocate contiguous buffers for + // reads and writes + // disable, since it uses a lot more RAM and a significant + // amount of CPU to copy it around + set.set_bool(settings_pack::coalesce_reads, false); + set.set_bool(settings_pack::coalesce_writes, false); + + // the max number of bytes pending write before we throttle + // download rate + set.set_int(settings_pack::max_queued_disk_bytes, 7 * 1024 * 1024); + + // prevent fast pieces to interfere with suggested pieces + // since we unchoke everyone, we don't need fast pieces anyway + set.set_int(settings_pack::allowed_fast_set_size, 0); + + // suggest pieces in the read cache for higher cache hit rate + set.set_int(settings_pack::suggest_mode, settings_pack::suggest_read_cache); + + set.set_bool(settings_pack::close_redundant_connections, true); + + set.set_int(settings_pack::max_rejects, 10); + + set.set_int(settings_pack::recv_socket_buffer_size, 1024 * 1024); + set.set_int(settings_pack::send_socket_buffer_size, 1024 * 1024); + + // 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, 2000); + 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); + + // the number of threads to use to call async_write_some + // and read_some on peer sockets + // this doesn't work. See comment in settings_pack.cpp + set.set_int(settings_pack::network_threads, 0); + + // number of disk threads for low level file operations + set.set_int(settings_pack::aio_threads, 8); + + // keep 5 MiB outstanding when checking hashes + // of a resumed file + set.set_int(settings_pack::checking_mem_usage, 320); + + // the disk cache performs better with the pool allocator + set.set_bool(settings_pack::use_disk_cache_pool, true); + } + +#ifndef TORRENT_NO_DEPRECATE + // this function returns a session_settings object + // which will optimize libtorrent for minimum memory + // usage, with no consideration of performance. + TORRENT_EXPORT session_settings min_memory_usage() + { + aux::session_settings def; + initialize_default_settings(def); + settings_pack pack; + min_memory_usage(pack); + apply_pack(&pack, def, 0); + session_settings ret; + load_struct_from_settings(def, ret); + return ret; + } + + TORRENT_EXPORT session_settings high_performance_seed() + { + aux::session_settings def; + initialize_default_settings(def); + settings_pack pack; + high_performance_seed(pack); + apply_pack(&pack, def, 0); + session_settings ret; + load_struct_from_settings(def, ret); + return ret; + } +#endif + +#define TORRENT_ASYNC_CALL(x) \ + m_impl->get_io_service().dispatch(boost::bind(&session_impl:: x, m_impl.get())) + +#ifndef TORRENT_CFG +#error TORRENT_CFG is not defined! +#endif + + // this is a dummy function that's exported and named based + // on the configuration. The session.hpp file will reference + // it and if the library and the client are built with different + // configurations this will give a link error + void TORRENT_EXPORT TORRENT_CFG() {} + +#if defined _MSC_VER && defined TORRENT_DEBUG + static void straight_to_debugger(unsigned int, _EXCEPTION_POINTERS*) + { throw; } +#endif + + void session::start(int flags, settings_pack const& pack, io_service* ios) + { +#if defined _MSC_VER && defined TORRENT_DEBUG + // workaround for microsoft's + // hardware exceptions that makes + // it hard to debug stuff + ::_set_se_translator(straight_to_debugger); +#endif + + bool const internal_executor = ios == NULL; + + if (internal_executor) + { + // the user did not provide an executor, we have to use our own + m_io_service = boost::make_shared(); + ios = m_io_service.get(); + } + + m_impl = boost::make_shared(boost::ref(*ios)); + *static_cast(this) = session_handle(m_impl.get()); + +#ifndef TORRENT_DISABLE_EXTENSIONS + if (flags & add_default_plugins) + { + add_extension(create_ut_pex_plugin); + add_extension(create_ut_metadata_plugin); + add_extension(create_smart_ban_plugin); + } +#else + TORRENT_UNUSED(flags); +#endif + + m_impl->start_session(pack); + + if (internal_executor) + { + // start a thread for the message pump + m_thread = boost::make_shared(boost::bind(&io_service::run + , m_io_service.get())); + } + } + + session::~session() + { + aux::dump_call_profile(); + + TORRENT_ASSERT(m_impl); + TORRENT_ASYNC_CALL(abort); + +#if defined TORRENT_ASIO_DEBUGGING + int counter = 0; + while (log_async()) + { +#if defined TORRENT_WINDOWS || defined TORRENT_CYGWIN + Sleep(1000); +#elif defined TORRENT_BEOS + snooze_until(system_time() + 1000000, B_SYSTEM_TIMEBASE); +#else + usleep(1000000); +#endif + ++counter; + printf("\x1b[2J\x1b[0;0H\x1b[33m==== Waiting to shut down: %d ==== \x1b[0m\n\n" + , counter); + } + async_dec_threads(); + + fprintf(stderr, "\n\nEXPECTS NO MORE ASYNC OPS\n\n\n"); +#endif + + if (m_thread && m_thread.unique()) + 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(boost::function()); + return session_proxy(m_io_service, m_thread, m_impl); + } + +#ifndef TORRENT_NO_DEPRECATE + session_settings::session_settings(std::string const& user_agent_) + { + aux::session_settings def; + initialize_default_settings(def); + def.set_str(settings_pack::user_agent, user_agent_); + load_struct_from_settings(def, *this); + } + + session_settings::~session_settings() {} +#endif // TORRENT_NO_DEPRECATE + + session_proxy::~session_proxy() + { + if (m_thread && m_thread.unique()) + m_thread->join(); + } +} + diff --git a/src/session_call.cpp b/src/session_call.cpp new file mode 100644 index 0000000..e6bf859 --- /dev/null +++ b/src/session_call.cpp @@ -0,0 +1,100 @@ +/* + +Copyright (c) 2014-2016, Arvid Norberg, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/aux_/session_call.hpp" + +namespace libtorrent { namespace aux { + +#ifdef TORRENT_PROFILE_CALLS +static mutex g_calls_mutex; +static boost::unordered_map g_blocking_calls; +#endif + +void blocking_call() +{ +#ifdef TORRENT_PROFILE_CALLS + char stack[2048]; + print_backtrace(stack, sizeof(stack), 20); + mutex::scoped_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; + + mutex::scoped_lock l(g_calls_mutex); + for (boost::unordered_map::const_iterator i = g_blocking_calls.begin() + , end(g_blocking_calls.end()); i != end; ++i) + { + profile[i->second] = i->first; + } + for (std::map::const_reverse_iterator i = profile.rbegin() + , end(profile.rend()); i != end; ++i) + { + fprintf(out, "\n\n%d\n%s\n", i->first, i->second.c_str()); + } + fclose(out); +#endif +} + +void fun_wrap(bool& done, condition_variable& e, mutex& m, boost::function f) +{ + f(); + mutex::scoped_lock l(m); + done = true; + e.notify_all(); +} + +void torrent_wait(bool& done, aux::session_impl& ses) +{ + blocking_call(); + mutex::scoped_lock l(ses.mut); + while (!done) { ses.cond.wait(l); }; +} + +void sync_call(aux::session_impl& ses, boost::function f) +{ + bool done = false; + ses.get_io_service().dispatch(boost::bind(&fun_wrap + , boost::ref(done) + , boost::ref(ses.cond) + , boost::ref(ses.mut) + , f)); + torrent_wait(done, ses); +} + +} } // namespace aux namespace libtorrent diff --git a/src/session_handle.cpp b/src/session_handle.cpp new file mode 100644 index 0000000..08f437f --- /dev/null +++ b/src/session_handle.cpp @@ -0,0 +1,1028 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/session_handle.hpp" +#include "libtorrent/aux_/session_impl.hpp" +#include "libtorrent/aux_/session_call.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/lazy_entry.hpp" + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-macros" +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-macros" +#endif + +#define TORRENT_ASYNC_CALL(x) \ + m_impl->get_io_service().dispatch(boost::bind(&session_impl:: x, m_impl)) + +#define TORRENT_ASYNC_CALL1(x, a1) \ + m_impl->get_io_service().dispatch(boost::bind(&session_impl:: x, m_impl, a1)) + +#define TORRENT_ASYNC_CALL2(x, a1, a2) \ + m_impl->get_io_service().dispatch(boost::bind(&session_impl:: x, m_impl, a1, a2)) + +#define TORRENT_ASYNC_CALL3(x, a1, a2, a3) \ + m_impl->get_io_service().dispatch(boost::bind(&session_impl:: x, m_impl, a1, a2, a3)) + +#define TORRENT_SYNC_CALL(x) \ + aux::sync_call(*m_impl, boost::function(boost::bind(&session_impl:: x, m_impl))) + +#define TORRENT_SYNC_CALL1(x, a1) \ + aux::sync_call(*m_impl, boost::function(boost::bind(&session_impl:: x, m_impl, a1))) + +#define TORRENT_SYNC_CALL2(x, a1, a2) \ + aux::sync_call(*m_impl, boost::function(boost::bind(&session_impl:: x, m_impl, a1, a2))) + +#define TORRENT_SYNC_CALL3(x, a1, a2, a3) \ + aux::sync_call(*m_impl, boost::function(boost::bind(&session_impl:: x, m_impl, a1, a2, a3))) + +#define TORRENT_SYNC_CALL4(x, a1, a2, a3, a4) \ + aux::sync_call(*m_impl, boost::function(boost::bind(&session_impl:: x, m_impl, a1, a2, a3, a4))) + +#define TORRENT_SYNC_CALL_RET(type, x) \ + aux::sync_call_ret(*m_impl, boost::function(boost::bind(&session_impl:: x, m_impl))) + +#define TORRENT_SYNC_CALL_RET1(type, x, a1) \ + aux::sync_call_ret(*m_impl, boost::function(boost::bind(&session_impl:: x, m_impl, a1))) + +#define TORRENT_SYNC_CALL_RET2(type, x, a1, a2) \ + aux::sync_call_ret(*m_impl, boost::function(boost::bind(&session_impl:: x, m_impl, a1, a2))) + +#define TORRENT_SYNC_CALL_RET3(type, x, a1, a2, a3) \ + aux::sync_call_ret(*m_impl, boost::function(boost::bind(&session_impl:: x, m_impl, a1, a2, a3))) + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +using libtorrent::aux::session_impl; + +namespace libtorrent +{ + void session_handle::save_state(entry& e, boost::uint32_t flags) const + { + TORRENT_SYNC_CALL2(save_state, &e, flags); + } + + void session_handle::load_state(bdecode_node const& e + , boost::uint32_t const flags) + { + // this needs to be synchronized since the lifespan + // of e is tied to the caller + TORRENT_SYNC_CALL2(load_state, &e, flags); + } + + void session_handle::get_torrent_status(std::vector* ret + , boost::function const& pred + , boost::uint32_t flags) const + { + TORRENT_SYNC_CALL3(get_torrent_status, ret, boost::ref(pred), flags); + } + + void session_handle::refresh_torrent_status(std::vector* ret + , boost::uint32_t flags) const + { + TORRENT_SYNC_CALL2(refresh_torrent_status, ret, flags); + } + + void session_handle::post_torrent_updates(boost::uint32_t flags) + { + TORRENT_ASYNC_CALL1(post_torrent_updates, flags); + } + + void session_handle::post_session_stats() + { + TORRENT_ASYNC_CALL(post_session_stats); + } + + void session_handle::post_dht_stats() + { + TORRENT_ASYNC_CALL(post_dht_stats); + } + + io_service& session_handle::get_io_service() + { + return m_impl->get_io_service(); + } + + torrent_handle session_handle::find_torrent(sha1_hash const& info_hash) const + { + return TORRENT_SYNC_CALL_RET1(torrent_handle, find_torrent_handle, info_hash); + } + + std::vector session_handle::get_torrents() const + { + return TORRENT_SYNC_CALL_RET(std::vector, get_torrents); + } + + #ifndef BOOST_NO_EXCEPTIONS + torrent_handle session_handle::add_torrent(add_torrent_params const& params) + { + error_code ec; + torrent_handle r = TORRENT_SYNC_CALL_RET2(torrent_handle, add_torrent, params, boost::ref(ec)); + if (ec) throw libtorrent_exception(ec); + return r; + } +#endif + + torrent_handle session_handle::add_torrent(add_torrent_params const& params, error_code& ec) + { + ec.clear(); + return TORRENT_SYNC_CALL_RET2(torrent_handle, add_torrent, params, boost::ref(ec)); + } + + void session_handle::async_add_torrent(add_torrent_params const& params) + { + add_torrent_params* p = new add_torrent_params(params); +#ifndef TORRENT_NO_DEPRECATE + if (params.tracker_url) + { + p->trackers.push_back(params.tracker_url); + p->tracker_url = NULL; + } +#endif + TORRENT_ASYNC_CALL1(async_add_torrent, p); + } + +#ifndef BOOST_NO_EXCEPTIONS +#ifndef TORRENT_NO_DEPRECATE + // if the torrent already exists, this will throw duplicate_torrent + torrent_handle session_handle::add_torrent( + torrent_info const& ti + , std::string const& save_path + , entry const& resume_data + , storage_mode_t storage_mode + , bool paused + , storage_constructor_type sc) + { + boost::shared_ptr tip(boost::make_shared(ti)); + add_torrent_params p(sc); + p.ti = tip; + 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; + p.paused = paused; + return add_torrent(p); + } + + torrent_handle session_handle::add_torrent( + char const* tracker_url + , sha1_hash const& info_hash + , char const* name + , std::string const& save_path + , entry const& resume_data + , storage_mode_t storage_mode + , bool paused + , storage_constructor_type sc + , void* userdata) + { + add_torrent_params p(sc); + p.tracker_url = tracker_url; + p.info_hash = info_hash; + p.save_path = save_path; + p.storage_mode = storage_mode; + p.paused = 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_NO_DEPRECATE +#endif // BOOST_NO_EXCEPTIONS + + void session_handle::pause() + { + TORRENT_ASYNC_CALL(pause); + } + + void session_handle::resume() + { + TORRENT_ASYNC_CALL(resume); + } + + bool session_handle::is_paused() const + { + return TORRENT_SYNC_CALL_RET(bool, is_paused); + } + + void session_handle::set_load_function(user_load_function_t fun) + { + TORRENT_ASYNC_CALL1(set_load_function, fun); + } + +#ifndef TORRENT_NO_DEPRECATE + session_status session_handle::status() const + { + return TORRENT_SYNC_CALL_RET(session_status, status); + } + + void session_handle::get_cache_info(sha1_hash const& ih + , std::vector& ret) const + { + cache_status st; + get_cache_info(&st, find_torrent(ih)); + ret.swap(st.pieces); + } + + cache_status session_handle::get_cache_status() const + { + cache_status st; + get_cache_info(&st); + return st; + } +#endif + + void session_handle::get_cache_info(cache_status* ret + , torrent_handle h, int flags) const + { + piece_manager* st = 0; + boost::shared_ptr t = h.m_torrent.lock(); + if (t) + { + if (t->has_storage()) + st = &t->storage(); + else + flags = session::disk_cache_no_pieces; + } + m_impl->disk_thread().get_cache_info(ret, flags & session::disk_cache_no_pieces, st); + } + +#ifndef TORRENT_NO_DEPRECATE + feed_handle session_handle::add_feed(feed_settings const& feed) + { + // if you have auto-download enabled, you must specify a download directory! + TORRENT_ASSERT_PRECOND(!feed.auto_download || !feed.add_args.save_path.empty()); + return TORRENT_SYNC_CALL_RET1(feed_handle, add_feed, feed); + } + + void session_handle::remove_feed(feed_handle h) + { + TORRENT_ASYNC_CALL1(remove_feed, h); + } + + void session_handle::get_feeds(std::vector& f) const + { + f.clear(); + TORRENT_SYNC_CALL1(get_feeds, &f); + } + + void session_handle::start_dht() + { + settings_pack p; + p.set_bool(settings_pack::enable_dht, true); + apply_settings(p); + } + + void session_handle::stop_dht() + { + settings_pack p; + p.set_bool(settings_pack::enable_dht, false); + apply_settings(p); + } +#endif // TORRENT_NO_DEPRECATE + + void session_handle::set_dht_settings(dht_settings const& settings) + { +#ifndef TORRENT_DISABLE_DHT + TORRENT_ASYNC_CALL1(set_dht_settings, settings); +#else + TORRENT_UNUSED(settings); +#endif + } + + dht_settings session_handle::get_dht_settings() const + { +#ifndef TORRENT_DISABLE_DHT + return TORRENT_SYNC_CALL_RET(dht_settings, get_dht_settings); +#else + return dht_settings(); +#endif + } + + bool session_handle::is_dht_running() const + { +#ifndef TORRENT_DISABLE_DHT + return TORRENT_SYNC_CALL_RET(bool, is_dht_running); +#else + return false; +#endif + } + + void session_handle::set_dht_storage(dht::dht_storage_constructor_type sc) + { +#ifndef TORRENT_DISABLE_DHT + TORRENT_ASYNC_CALL1(set_dht_storage, sc); +#else + TORRENT_UNUSED(sc); +#endif + } + + void session_handle::add_dht_node(std::pair const& node) + { +#ifndef TORRENT_DISABLE_DHT + TORRENT_ASYNC_CALL1(add_dht_node_name, node); +#else + TORRENT_UNUSED(node); +#endif + } + + void session_handle::add_dht_router(std::pair const& node) + { +#ifndef TORRENT_DISABLE_DHT + TORRENT_ASYNC_CALL1(add_dht_router, node); +#else + TORRENT_UNUSED(node); +#endif + } + + void session_handle::dht_get_item(sha1_hash const& target) + { +#ifndef TORRENT_DISABLE_DHT + TORRENT_ASYNC_CALL1(dht_get_immutable_item, target); +#else + TORRENT_UNUSED(target); +#endif + } + + void session_handle::dht_get_item(boost::array key + , std::string salt) + { +#ifndef TORRENT_DISABLE_DHT + TORRENT_ASYNC_CALL2(dht_get_mutable_item, key, salt); +#else + TORRENT_UNUSED(key); + TORRENT_UNUSED(salt); +#endif + } + + sha1_hash session_handle::dht_put_item(entry data) + { + std::vector buf; + bencode(std::back_inserter(buf), data); + sha1_hash ret = hasher(&buf[0], buf.size()).final(); + +#ifndef TORRENT_DISABLE_DHT + TORRENT_ASYNC_CALL2(dht_put_immutable_item, data, ret); +#endif + return ret; + } + + void session_handle::dht_put_item(boost::array key + , boost::function& + , boost::uint64_t&, std::string const&)> cb + , std::string salt) + { +#ifndef TORRENT_DISABLE_DHT + TORRENT_ASYNC_CALL3(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 + TORRENT_ASYNC_CALL1(dht_get_peers, info_hash); +#else + TORRENT_UNUSED(info_hash); +#endif + } + + void session_handle::dht_announce(sha1_hash const& info_hash, int port, int flags) + { +#ifndef TORRENT_DISABLE_DHT + TORRENT_ASYNC_CALL3(dht_announce, info_hash, port, flags); +#else + TORRENT_UNUSED(info_hash); + TORRENT_UNUSED(port); + TORRENT_UNUSED(flags); +#endif + } + + void session_handle::dht_direct_request(udp::endpoint ep, entry const& e, void* userdata) + { +#ifndef TORRENT_DISABLE_DHT + TORRENT_ASYNC_CALL3(dht_direct_request, ep, e, userdata); +#else + TORRENT_UNUSED(ep); + TORRENT_UNUSED(e); + TORRENT_UNUSED(userdata); +#endif + } + +#ifndef TORRENT_NO_DEPRECATE + entry session_handle::dht_state() const + { +#ifndef TORRENT_DISABLE_DHT + return TORRENT_SYNC_CALL_RET(entry, dht_state); +#else + return entry(); +#endif + } + + void session_handle::start_dht(entry const& startup_state) + { +#ifndef TORRENT_DISABLE_DHT + TORRENT_ASYNC_CALL1(start_dht_deprecated, startup_state); +#else + TORRENT_UNUSED(startup_state); +#endif + } +#endif // TORRENT_NO_DEPRECATE + + void session_handle::add_extension(boost::function(torrent_handle const&, void*)> ext) + { +#ifndef TORRENT_DISABLE_EXTENSIONS + TORRENT_ASYNC_CALL1(add_extension, ext); +#else + TORRENT_UNUSED(ext); +#endif + } + + void session_handle::add_extension(boost::shared_ptr ext) + { +#ifndef TORRENT_DISABLE_EXTENSIONS + TORRENT_ASYNC_CALL1(add_ses_extension, ext); +#else + TORRENT_UNUSED(ext); +#endif + } + +#ifndef TORRENT_NO_DEPRECATE + void session_handle::load_asnum_db(char const*) {} + void session_handle::load_country_db(char const*) {} + + int session_handle::as_for_ip(address const&) + { return 0; } + +#if TORRENT_USE_WSTRING + void session_handle::load_asnum_db(wchar_t const*) {} + void session_handle::load_country_db(wchar_t const*) {} +#endif // TORRENT_USE_WSTRING + + void session_handle::load_state(entry const& ses_state + , boost::uint32_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 defined TORRENT_DEBUG || defined TORRENT_RELEASE_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) throw libtorrent_exception(ec); +#endif + TORRENT_SYNC_CALL2(load_state, &e, flags); + } + + entry session_handle::state() const + { + entry ret; + TORRENT_SYNC_CALL2(save_state, &ret, 0xffffffff); + return ret; + } + + void session_handle::load_state(lazy_entry const& ses_state + , boost::uint32_t const flags) + { + if (ses_state.type() == lazy_entry::none_t) return; + std::pair buf = ses_state.data_section(); + bdecode_node e; + error_code ec; +#if defined TORRENT_DEBUG || defined TORRENT_RELEASE_ASSERTS || !defined BOOST_NO_EXCEPTIONS + int ret = +#endif + bdecode(buf.first, buf.first + buf.second, e, ec); + + TORRENT_ASSERT(ret == 0); +#ifndef BOOST_NO_EXCEPTIONS + if (ret != 0) throw libtorrent_exception(ec); +#endif + TORRENT_SYNC_CALL2(load_state, &e, flags); + } +#endif // TORRENT_NO_DEPRECATE + + void session_handle::set_ip_filter(ip_filter const& f) + { + boost::shared_ptr copy = boost::make_shared(f); + TORRENT_ASYNC_CALL1(set_ip_filter, copy); + } + + ip_filter session_handle::get_ip_filter() const + { + return TORRENT_SYNC_CALL_RET(ip_filter, get_ip_filter); + } + + void session_handle::set_port_filter(port_filter const& f) + { + TORRENT_ASYNC_CALL1(set_port_filter, f); + } + +#ifndef TORRENT_NO_DEPRECATE + 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(p); + } +#endif + + peer_id session_handle::id() const + { + return TORRENT_SYNC_CALL_RET(peer_id, get_peer_id); + } + + void session_handle::set_key(int key) + { + TORRENT_ASYNC_CALL1(set_key, key); + } + + unsigned short session_handle::listen_port() const + { + return TORRENT_SYNC_CALL_RET(unsigned short, listen_port); + } + + unsigned short session_handle::ssl_listen_port() const + { + return TORRENT_SYNC_CALL_RET(unsigned short, ssl_listen_port); + } + + bool session_handle::is_listening() const + { + return TORRENT_SYNC_CALL_RET(bool, is_listening); + } + + void session_handle::set_peer_class_filter(ip_filter const& f) + { + TORRENT_ASYNC_CALL1(set_peer_class_filter, f); + } + + void session_handle::set_peer_class_type_filter(peer_class_type_filter const& f) + { + TORRENT_ASYNC_CALL1(set_peer_class_type_filter, f); + } + + int session_handle::create_peer_class(char const* name) + { + return TORRENT_SYNC_CALL_RET1(int, create_peer_class, name); + } + + void session_handle::delete_peer_class(int cid) + { + TORRENT_ASYNC_CALL1(delete_peer_class, cid); + } + + peer_class_info session_handle::get_peer_class(int cid) + { + return TORRENT_SYNC_CALL_RET1(peer_class_info, get_peer_class, cid); + } + + void session_handle::set_peer_class(int cid, peer_class_info const& pci) + { + TORRENT_ASYNC_CALL2(set_peer_class, cid, pci); + } + +#ifndef TORRENT_NO_DEPRECATE + void session_handle::use_interfaces(char const* interfaces) + { + settings_pack pack; + pack.set_str(settings_pack::outgoing_interfaces, interfaces); + apply_settings(pack); + } + + 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 == NULL || strlen(net_interface) == 0) + net_interface = "0.0.0.0"; + + interfaces_str = print_endpoint(tcp::endpoint(address::from_string(net_interface, ec), 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(p); + } +#endif + + void session_handle::remove_torrent(const torrent_handle& h, int options) + { + if (!h.is_valid()) +#ifdef BOOST_NO_EXCEPTIONS + return; +#else + throw_invalid_handle(); +#endif + TORRENT_ASYNC_CALL2(remove_torrent, h, options); + } + +#ifndef TORRENT_NO_DEPRECATE + void session_handle::set_settings(session_settings const& s) + { + TORRENT_ASYNC_CALL1(set_settings, s); + } + + session_settings session_handle::settings() const + { + return TORRENT_SYNC_CALL_RET(session_settings, deprecated_settings); + } + + void session_handle::set_pe_settings(pe_settings const& r) + { + settings_pack pack; + pack.set_bool(settings_pack::prefer_rc4, r.prefer_rc4); + pack.set_int(settings_pack::out_enc_policy, r.out_enc_policy); + pack.set_int(settings_pack::in_enc_policy, r.in_enc_policy); + pack.set_int(settings_pack::allowed_enc_level, r.allowed_enc_level); + + apply_settings(pack); + } + + 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 = sett.get_int(settings_pack::out_enc_policy); + r.in_enc_policy = sett.get_int(settings_pack::in_enc_policy); + r.allowed_enc_level = 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); + + boost::shared_ptr copy = boost::make_shared(s); + TORRENT_ASYNC_CALL1(apply_settings_pack, copy); + } + + settings_pack session_handle::get_settings() const + { + return TORRENT_SYNC_CALL_RET(settings_pack, get_settings); + } + +#ifndef TORRENT_NO_DEPRECATE + 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 = sett.get_int(settings_pack::i2p_port); + return ret; + } + + void session_handle::set_proxy(proxy_settings const& s) + { + settings_pack pack; + pack.set_str(settings_pack::proxy_hostname, s.hostname); + pack.set_str(settings_pack::proxy_username, s.username); + pack.set_str(settings_pack::proxy_password, s.password); + pack.set_int(settings_pack::proxy_type, s.type); + pack.set_int(settings_pack::proxy_port, s.port); + pack.set_bool(settings_pack::proxy_hostnames,s.proxy_hostnames); + pack.set_bool(settings_pack::proxy_peer_connections, s.proxy_peer_connections); + + apply_settings(pack); + } + + proxy_settings session_handle::proxy() const + { + settings_pack sett = get_settings(); + return proxy_settings(sett); + } + + int session_handle::num_uploads() const + { + return TORRENT_SYNC_CALL_RET(int, num_uploads); + } + + int session_handle::num_connections() const + { + return TORRENT_SYNC_CALL_RET(int, 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& s) + { + set_proxy(s); + } + + void session_handle::set_tracker_proxy(proxy_settings const& s) + { + set_proxy(s); + } + + 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 + { + return proxy(); + } + + void session_handle::set_dht_proxy(proxy_settings const& s) + { + set_proxy(s); + } + + proxy_settings session_handle::dht_proxy() const + { + return proxy(); + } + + int session_handle::upload_rate_limit() const + { + return TORRENT_SYNC_CALL_RET(int, upload_rate_limit); + } + + int session_handle::download_rate_limit() const + { + return TORRENT_SYNC_CALL_RET(int, download_rate_limit); + } + + int session_handle::local_upload_rate_limit() const + { + return TORRENT_SYNC_CALL_RET(int, local_upload_rate_limit); + } + + int session_handle::local_download_rate_limit() const + { + return TORRENT_SYNC_CALL_RET(int, 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) + { + TORRENT_ASYNC_CALL1(set_local_upload_rate_limit, bytes_per_second); + } + + void session_handle::set_local_download_rate_limit(int bytes_per_second) + { + TORRENT_ASYNC_CALL1(set_local_download_rate_limit, bytes_per_second); + } + + void session_handle::set_upload_rate_limit(int bytes_per_second) + { + TORRENT_ASYNC_CALL1(set_upload_rate_limit, bytes_per_second); + } + + void session_handle::set_download_rate_limit(int bytes_per_second) + { + TORRENT_ASYNC_CALL1(set_download_rate_limit, bytes_per_second); + } + + void session_handle::set_max_connections(int limit) + { + TORRENT_ASYNC_CALL1(set_max_connections, limit); + } + + void session_handle::set_max_uploads(int limit) + { + TORRENT_ASYNC_CALL1(set_max_uploads, limit); + } + + void session_handle::set_max_half_open_connections(int) {} + + int session_handle::max_uploads() const + { + return TORRENT_SYNC_CALL_RET(int, max_uploads); + } + + int session_handle::max_connections() const + { + return TORRENT_SYNC_CALL_RET(int, max_connections); + } + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + + std::auto_ptr session_handle::pop_alert() + { + alert const* a = m_impl->pop_alert(); + if (a == NULL) return std::auto_ptr(); + return a->clone(); + } + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + + void session_handle::pop_alerts(std::deque* alerts) + { + m_impl->pop_alerts(alerts); + } +#endif // TORRENT_NO_DEPRECATE + + // the alerts are const, they may not be deleted by the client + void session_handle::pop_alerts(std::vector* alerts) + { + m_impl->pop_alerts(alerts); + } + + alert* session_handle::wait_for_alert(time_duration max_wait) + { + return m_impl->wait_for_alert(max_wait); + } + + void session_handle::set_alert_notify(boost::function const& fun) + { + m_impl->alerts().set_notify_function(fun); + } + +#ifndef TORRENT_NO_DEPRECATE + void session_handle::set_severity_level(alert::severity_t s) + { + int m = 0; + switch (s) + { + case alert::debug: m = alert::all_categories; break; + case alert::info: m = alert::all_categories & ~(alert::debug_notification + | alert::progress_notification | alert::dht_notification); break; + case alert::warning: m = alert::all_categories & ~(alert::debug_notification + | alert::status_notification | alert::progress_notification + | alert::dht_notification); break; + case alert::critical: m = alert::error_notification | alert::storage_notification; break; + case alert::fatal: m = alert::error_notification; break; + case alert::none: m = 0; break; + } + + settings_pack p; + p.set_int(settings_pack::alert_mask, m); + apply_settings(p); + } + + size_t session_handle::set_alert_queue_size_limit(size_t queue_size_limit_) + { + return TORRENT_SYNC_CALL_RET1(size_t, set_alert_queue_size_limit, queue_size_limit_); + } + + void session_handle::set_alert_mask(boost::uint32_t m) + { + settings_pack p; + p.set_int(settings_pack::alert_mask, m); + apply_settings(p); + } + + boost::uint32_t session_handle::get_alert_mask() const + { + return get_settings().get_int(settings_pack::alert_mask); + } + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + + void session_handle::set_alert_dispatch(boost::function)> const& fun) + { + m_impl->alerts().set_dispatch_function(fun); + } + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + + void session_handle::start_lsd() + { + settings_pack p; + p.set_bool(settings_pack::enable_lsd, true); + apply_settings(p); + } + + void session_handle::stop_lsd() + { + settings_pack p; + p.set_bool(settings_pack::enable_lsd, false); + apply_settings(p); + } + + void session_handle::start_upnp() + { + settings_pack p; + p.set_bool(settings_pack::enable_upnp, true); + apply_settings(p); + } + + void session_handle::stop_upnp() + { + settings_pack p; + p.set_bool(settings_pack::enable_upnp, false); + apply_settings(p); + } + + void session_handle::start_natpmp() + { + settings_pack p; + p.set_bool(settings_pack::enable_natpmp, true); + apply_settings(p); + } + + void session_handle::stop_natpmp() + { + settings_pack p; + p.set_bool(settings_pack::enable_natpmp, false); + apply_settings(p); + } +#endif // TORRENT_NO_DEPRECATE + + int session_handle::add_port_mapping(session::protocol_type t, int external_port, int local_port) + { + return TORRENT_SYNC_CALL_RET3(int, add_port_mapping, int(t), external_port, local_port); + } + + void session_handle::delete_port_mapping(int handle) + { + TORRENT_ASYNC_CALL1(delete_port_mapping, handle); + } + +} // namespace libtorrent diff --git a/src/session_impl.cpp b/src/session_impl.cpp new file mode 100644 index 0000000..49c40fd --- /dev/null +++ b/src/session_impl.cpp @@ -0,0 +1,7330 @@ +/* + +Copyright (c) 2006-2016, Arvid Norberg, Magnus Jonsson +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include + +#if defined TORRENT_DEBUG && !defined TORRENT_DISABLE_INVARIANT_CHECKS +#if TORRENT_HAS_BOOST_UNORDERED +#include +#else +#include +#endif +#endif // TORRENT_DEBUG && !TORRENT_DISABLE_INVARIANT_CHECKS + +#include +#include +#include +#include +#include + +#ifdef TORRENT_USE_VALGRIND +#include +#endif + +#if TORRENT_USE_RLIMIT + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wlong-long" +#endif // __GNUC__ + +#include + +// capture this here where warnings are disabled (the macro generates warnings) +const rlim_t rlim_infinity = RLIM_INFINITY; + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif // __GNUC__ + +#endif // TORRENT_USE_RLIMIT + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/aux_/openssl.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/tracker_manager.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/fingerprint.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/file.hpp" +#include "libtorrent/bt_peer_connection.hpp" +#include "libtorrent/peer_connection_handle.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/session_stats.hpp" +#include "libtorrent/aux_/session_impl.hpp" +#ifndef TORRENT_DISABLE_DHT +#include "libtorrent/kademlia/dht_tracker.hpp" +#endif +#include "libtorrent/enum_net.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/utf8.hpp" +#include "libtorrent/upnp.hpp" +#include "libtorrent/natpmp.hpp" +#include "libtorrent/lsd.hpp" +#include "libtorrent/instantiate_connection.hpp" +#include "libtorrent/peer_info.hpp" +#include "libtorrent/build_config.hpp" +#include "libtorrent/extensions.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" + +#ifndef TORRENT_DISABLE_LOGGING + +#include "libtorrent/socket_io.hpp" + +// for logging stat layout +#include "libtorrent/stat.hpp" + +// 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_GCRYPT + +extern "C" { +GCRY_THREAD_OPTION_PTHREAD_IMPL; +} + +namespace +{ + // libgcrypt requires this to initialize the library + struct gcrypt_setup + { + gcrypt_setup() + { + gcry_check_version(0); + gcry_error_t e = gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread); + if (e != 0) fprintf(stderr, "libcrypt ERROR: %s\n", gcry_strerror(e)); + e = gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); + if (e != 0) fprintf(stderr, "initialization finished error: %s\n", gcry_strerror(e)); + } + } gcrypt_global_constructor; +} + +#endif // TORRENT_USE_GCRYPT + +#ifdef TORRENT_USE_OPENSSL + +#include +#include + +namespace +{ + // openssl requires this to clean up internal + // structures it allocates + struct openssl_cleanup + { + ~openssl_cleanup() { CRYPTO_cleanup_all_ex_data(); } + } openssl_global_destructor; +} + +#endif // TORRENT_USE_OPENSSL + +#ifdef TORRENT_WINDOWS +// for ERROR_SEM_TIMEOUT +#include +#endif + +using boost::shared_ptr; +using boost::weak_ptr; +using libtorrent::aux::session_impl; + +#ifdef BOOST_NO_EXCEPTIONS +namespace boost { + void throw_exception(std::exception const& e) { ::abort(); } +} +#endif + +namespace libtorrent { + +#if defined TORRENT_ASIO_DEBUGGING + std::map _async_ops; + std::deque _wakeups; + int _async_ops_nthreads = 0; + mutex _async_ops_mutex; +#endif + +socket_job::~socket_job() {} + +void network_thread_pool::process_job(socket_job const& j, bool post) +{ + TORRENT_UNUSED(post); + if (j.type == socket_job::write_job) + { + TORRENT_ASSERT(j.peer->m_socket_is_writing); + j.peer->get_socket()->async_write_some( + *j.vec, j.peer->make_write_handler(boost::bind( + &peer_connection::on_send_data, j.peer, _1, _2))); + } + else + { + if (j.recv_buf) + { + j.peer->get_socket()->async_read_some(boost::asio::buffer(j.recv_buf, j.buf_size) + , j.peer->make_read_handler(boost::bind( + &peer_connection::on_receive_data, j.peer, _1, _2))); + } + else + { + j.peer->get_socket()->async_read_some(j.read_vec + , j.peer->make_read_handler(boost::bind( + &peer_connection::on_receive_data, j.peer, _1, _2))); + } + } +} + +namespace aux { + + 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 + boost::uint32_t lfilter = 1 << m_local_peer_class; + boost::uint32_t gfilter = 1 << m_global_class; + + struct class_mapping + { + char const* first; + char const* last; + boost::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.16.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}, + }; + +#if TORRENT_USE_IPV6 + static const class_mapping v6_classes[] = + { + // everything + {"::0", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", gfilter}, + // link-local + {"fe80::", "febf::ffff:ffff:ffff:ffff:ffff:ffff:ffff", lfilter}, + // loop-back + {"::1", "::1", lfilter}, + }; +#endif + + 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 = address_v4::from_string(p[i].first, ec); + address_v4 end = address_v4::from_string(p[i].last, ec); + if (ec) continue; + m_peer_class_filter.add_rule(begin, end, p[i].filter); + } +#if TORRENT_USE_IPV6 + 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 = address_v6::from_string(p[i].first, ec); + address_v6 end = address_v6::from_string(p[i].last, ec); + if (ec) continue; + m_peer_class_filter.add_rule(begin, end, p[i].filter); + } +#endif + } + +#if defined TORRENT_USE_OPENSSL && BOOST_VERSION >= 104700 && OPENSSL_VERSION_NUMBER >= 0x90812f + namespace { + // when running bittorrent over SSL, the SNI (server name indication) + // extension is used to know which torrent the incoming connection is + // trying to connect to. The 40 first bytes in the name is expected to + // be the hex encoded info-hash + int servername_callback(SSL* s, int* ad, void* arg) + { + TORRENT_UNUSED(ad); + + session_impl* ses = reinterpret_cast(arg); + const char* servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name); + + if (!servername || strlen(servername) < 40) + return SSL_TLSEXT_ERR_ALERT_FATAL; + + sha1_hash info_hash; + bool valid = from_hex(servername, 40, info_hash.data()); + + // the server name is not a valid hex-encoded info-hash + if (!valid) + return SSL_TLSEXT_ERR_ALERT_FATAL; + + // see if there is a torrent with this info-hash + boost::shared_ptr t = ses->find_torrent(info_hash).lock(); + + // if there isn't, fail + if (!t) return SSL_TLSEXT_ERR_ALERT_FATAL; + + // if the torrent we found isn't an SSL torrent, also fail. + if (!t->is_ssl_torrent()) return SSL_TLSEXT_ERR_ALERT_FATAL; + + // if the torrent doesn't have an SSL context and should not allow + // incoming SSL connections + if (!t->ssl_ctx()) return SSL_TLSEXT_ERR_ALERT_FATAL; + + // use this torrent's certificate + SSL_CTX *torrent_context = t->ssl_ctx()->native_handle(); + + SSL_set_SSL_CTX(s, torrent_context); + SSL_set_verify(s, SSL_CTX_get_verify_mode(torrent_context), SSL_CTX_get_verify_callback(torrent_context)); + + return SSL_TLSEXT_ERR_OK; + } + } // anonymous namesoace +#endif + + session_impl::session_impl(io_service& ios) + : +#ifndef TORRENT_NO_DEPRECATE + m_next_rss_update(min_time()) + , +#endif +#ifndef TORRENT_DISABLE_POOL_ALLOCATOR + m_send_buffers(send_buffer_size()) + , +#endif + m_io_service(ios) +#ifdef TORRENT_USE_OPENSSL + , m_ssl_ctx(m_io_service, boost::asio::ssl::context::sslv23) +#endif + , m_alerts(m_settings.get_int(settings_pack::alert_queue_size), alert::all_categories) +#ifndef TORRENT_NO_DEPRECATE + , m_alert_pointer_pos(0) +#endif + , m_disk_thread(m_io_service, m_stats_counters + , static_cast(this)) + , m_download_rate(peer_connection::download_channel) + , m_upload_rate(peer_connection::upload_channel) + , m_global_class(0) + , m_tcp_peer_class(0) + , m_local_peer_class(0) + , m_tracker_manager(m_udp_socket, m_stats_counters, m_host_resolver + , m_settings +#if !defined TORRENT_DISABLE_LOGGING || TORRENT_USE_ASSERTS + , *this +#endif + ) + , m_num_save_resume(0) + , m_work(io_service::work(m_io_service)) + , m_max_queue_pos(-1) + , m_key(0) +#if TORRENT_USE_I2P + , m_i2p_conn(m_io_service) +#endif + , m_socks_listen_port(0) + , m_interface_index(0) + , m_unchoke_time_scaler(0) + , m_auto_manage_time_scaler(0) + , m_optimistic_unchoke_time_scaler(0) + , m_disconnect_time_scaler(90) + , m_auto_scrape_time_scaler(180) +#ifndef TORRENT_NO_DEPRECATE + , m_next_explicit_cache_torrent(0) + , m_cache_rotation_timer(0) +#endif + , m_next_suggest_torrent(0) + , m_suggest_timer(0) + , m_peak_up_rate(0) + , m_peak_down_rate(0) + , 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) + , m_next_port(0) +#ifndef TORRENT_DISABLE_DHT + , m_dht_storage_constructor(dht::dht_default_storage_constructor) + , m_dht_announce_timer(m_io_service) + , m_dht_interval_update_torrents(0) + , m_outstanding_router_lookups(0) +#endif + , m_external_udp_port(0) + , m_udp_socket(m_io_service) + , m_utp_socket_manager(m_settings, m_udp_socket, m_stats_counters, NULL + , boost::bind(&session_impl::incoming_connection, this, _1)) +#ifdef TORRENT_USE_OPENSSL + , m_ssl_udp_socket(m_io_service) + , m_ssl_utp_socket_manager(m_settings, m_ssl_udp_socket, m_stats_counters + , &m_ssl_ctx + , boost::bind(&session_impl::on_incoming_utp_ssl, this, _1)) +#endif + , m_boost_connections(0) + , m_timer(m_io_service) + , m_lsd_announce_timer(m_io_service) + , m_host_resolver(m_io_service) + , m_next_downloading_connect_torrent(0) + , m_next_finished_connect_torrent(0) + , m_download_connect_attempts(0) + , m_next_scrape_torrent(0) + , m_tick_residual(0) +#ifndef TORRENT_DISABLE_EXTENSIONS + , m_session_extension_features(0) +#endif + , m_deferred_submit_disk_jobs(false) + , m_pending_auto_manage(false) + , m_need_auto_manage(false) + , m_abort(false) + , m_paused(false) + { +#if TORRENT_USE_ASSERTS + m_posting_torrent_updates = false; +#endif + m_udp_socket.set_rate_limit(m_settings.get_int(settings_pack::dht_upload_rate_limit)); + + m_udp_socket.subscribe(&m_utp_socket_manager); + m_udp_socket.subscribe(this); + m_udp_socket.subscribe(&m_tracker_manager); + +#ifdef TORRENT_USE_OPENSSL + m_ssl_udp_socket.subscribe(&m_ssl_utp_socket_manager); + m_ssl_udp_socket.subscribe(this); +#endif + + error_code ec; + m_listen_interface = tcp::endpoint(address_v4::any(), 0); + TORRENT_ASSERT_VAL(!ec, ec); + + update_time_now(); + } + + // This function is called by the creating thread, not in the message loop's + // / io_service thread. + // TODO: 2 is there a reason not to move all of this into init()? and just + // post it to the io_service? + void session_impl::start_session(settings_pack const& pack) + { + if (pack.has_val(settings_pack::alert_mask)) + { + m_alerts.set_alert_mask(pack.get_int(settings_pack::alert_mask)); + } + +#ifndef TORRENT_DISABLE_LOGGING + session_log("start session"); +#endif + + error_code ec; +#ifdef TORRENT_USE_OPENSSL + m_ssl_ctx.set_verify_mode(boost::asio::ssl::context::verify_none, ec); +#if BOOST_VERSION >= 104700 +#if OPENSSL_VERSION_NUMBER >= 0x90812f + aux::openssl_set_tlsext_servername_callback(m_ssl_ctx.native_handle() + , servername_callback); + aux::openssl_set_tlsext_servername_arg(m_ssl_ctx.native_handle(), this); +#endif // OPENSSL_VERSION_NUMBER +#endif // BOOST_VERSION +#endif + +#ifndef TORRENT_DISABLE_DHT + m_next_dht_torrent = m_torrents.begin(); +#endif + m_next_lsd_torrent = m_torrents.begin(); + m_tcp_mapping[0] = -1; + m_tcp_mapping[1] = -1; + m_udp_mapping[0] = -1; + m_udp_mapping[1] = -1; +#ifdef TORRENT_USE_OPENSSL + m_ssl_tcp_mapping[0] = -1; + m_ssl_tcp_mapping[1] = -1; + m_ssl_udp_mapping[0] = -1; + m_ssl_udp_mapping[1] = -1; +#endif + + 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); + + // TODO: there's no rule here to make uTP connections not have the global or + // local rate limits apply to it. This used to be the default. + +#ifndef TORRENT_DISABLE_LOGGING + + session_log("config: %s version: %s revision: %s" + , TORRENT_CFG_STRING + , LIBTORRENT_VERSION + , LIBTORRENT_REVISION); + +#endif // TORRENT_DISABLE_LOGGING + + // ---- auto-cap max connections ---- + int 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 + session_log(" max connections: %d", m_settings.get_int(settings_pack::connections_limit)); + session_log(" max files: %d", max_files); + + session_log(" generated peer ID: %s", m_peer_id.to_string().c_str()); +#endif + + boost::shared_ptr copy = boost::make_shared(pack); + m_io_service.post(boost::bind(&session_impl::init, this, copy)); + } + + void session_impl::init(boost::shared_ptr pack) + { + // this is a debug facility + // see single_threaded in debug.hpp + thread_started(); + + TORRENT_ASSERT(is_single_thread()); + +#ifndef TORRENT_DISABLE_LOGGING + if (m_alerts.should_post()) + { + session_log(" *** session thread init"); + + // this specific output is parsed by tools/parse_session_stats.py + // if this is changed, that parser should also be changed + std::string stats_header = "session stats header: "; + std::vector stats = session_stats_metrics(); + std::sort(stats.begin(), stats.end() + , boost::bind(&stats_metric::value_index, _1) + < boost::bind(&stats_metric::value_index, _2)); + for (int i = 0; i < stats.size(); ++i) + { + if (i > 0) stats_header += ", "; + stats_header += stats[i].name; + } + m_alerts.emplace_alert(stats_header.c_str()); + } +#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 + error_code ec; + m_io_service.post(boost::bind(&session_impl::on_tick, this, ec)); + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("session_impl::on_lsd_announce"); +#endif + int 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_from_now(seconds(delay), ec); + m_lsd_announce_timer.async_wait( + boost::bind(&session_impl::on_lsd_announce, this, _1)); + TORRENT_ASSERT(!ec); + +#ifndef TORRENT_DISABLE_DHT + update_dht_announce_interval(); +#endif + +#ifndef TORRENT_DISABLE_LOGGING + session_log(" done starting session"); +#endif + + apply_settings_pack(pack); + + // call update_* after settings set initialized + +#ifndef TORRENT_NO_DEPRECATE + update_local_download_rate(); + update_local_upload_rate(); +#endif + update_download_rate(); + update_upload_rate(); + update_connections_limit(); + update_unchoke_limit(); + update_disk_threads(); + update_network_threads(); + update_upnp(); + update_natpmp(); + update_lsd(); + update_dht(); + update_peer_fingerprint(); + update_dht_bootstrap_nodes(); + + if (m_listen_sockets.empty()) + { + update_listen_interfaces(); + open_listen_port(); + } + } + + void session_impl::async_resolve(std::string const& host, int flags + , session_interface::callback_t const& h) + { + m_host_resolver.async_resolve(host, flags, h); + } + + void session_impl::queue_async_resume_data(boost::shared_ptr const& t) + { + INVARIANT_CHECK; + + int loaded_limit = m_settings.get_int(settings_pack::active_loaded_limit); + + if (m_num_save_resume + m_alerts.num_queued_resume() >= loaded_limit + && m_user_load_torrent + && loaded_limit > 0) + { + TORRENT_ASSERT(t); + // do loaded torrents first, otherwise they'll just be + // evicted and have to be loaded again + if (t->is_loaded()) + m_save_resume_queue.push_front(t); + else + m_save_resume_queue.push_back(t); + return; + } + + if (t->do_async_save_resume_data()) + ++m_num_save_resume; + } + + // this is called whenever a save_resume_data comes back + // from the disk thread + void session_impl::done_async_resume() + { + TORRENT_ASSERT(m_num_save_resume > 0); + --m_num_save_resume; + } + + // this is called when one or all save resume alerts are + // popped off the alert queue + void session_impl::async_resume_dispatched() + { + INVARIANT_CHECK; + + int num_queued_resume = m_alerts.num_queued_resume(); + + int loaded_limit = m_settings.get_int(settings_pack::active_loaded_limit); + while (!m_save_resume_queue.empty() + && (m_num_save_resume + num_queued_resume < loaded_limit + || loaded_limit == 0)) + { + boost::shared_ptr t = m_save_resume_queue.front(); + m_save_resume_queue.erase(m_save_resume_queue.begin()); + if (t->do_async_save_resume_data()) + ++m_num_save_resume; + } + } + + void session_impl::save_state(entry* eptr, boost::uint32_t flags) const + { + TORRENT_ASSERT(is_single_thread()); + + entry& e = *eptr; + // make it a dict + e.dict(); + + if (flags & session::save_settings) + { + entry::dictionary_type& sett = e["settings"].dict(); + save_settings_to_dict(m_settings, sett); + } + +#ifndef TORRENT_DISABLE_DHT + if (flags & session::save_dht_settings) + { + entry::dictionary_type& dht_sett = e["dht"].dict(); + + dht_sett["max_peers_reply"] = m_dht_settings.max_peers_reply; + dht_sett["search_branching"] = m_dht_settings.search_branching; + dht_sett["max_fail_count"] = m_dht_settings.max_fail_count; + dht_sett["max_torrents"] = m_dht_settings.max_torrents; + dht_sett["max_dht_items"] = m_dht_settings.max_dht_items; + dht_sett["max_peers"] = m_dht_settings.max_peers; + dht_sett["max_torrent_search_reply"] = m_dht_settings.max_torrent_search_reply; + dht_sett["restrict_routing_ips"] = m_dht_settings.restrict_routing_ips; + dht_sett["restrict_search_ips"] = m_dht_settings.restrict_search_ips; + dht_sett["extended_routing_table"] = m_dht_settings.extended_routing_table; + dht_sett["aggressive_lookups"] = m_dht_settings.aggressive_lookups; + dht_sett["privacy_lookups"] = m_dht_settings.privacy_lookups; + dht_sett["enforce_node_id"] = m_dht_settings.enforce_node_id; + dht_sett["ignore_dark_internet"] = m_dht_settings.ignore_dark_internet; + dht_sett["block_timeout"] = m_dht_settings.block_timeout; + dht_sett["block_ratelimit"] = m_dht_settings.block_ratelimit; + dht_sett["read_only"] = m_dht_settings.read_only; + dht_sett["item_lifetime"] = m_dht_settings.item_lifetime; + } + + if (m_dht && (flags & session::save_dht_state)) + { + e["dht state"] = m_dht->state(); + } +#endif + +#ifndef TORRENT_NO_DEPRECATE + if (flags & session::save_feeds) + { + entry::list_type& feeds = e["feeds"].list(); + for (std::vector >::const_iterator i = + m_feeds.begin(), end(m_feeds.end()); i != end; ++i) + { + feeds.push_back(entry()); + (*i)->save_state(feeds.back()); + } + } +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (ses_extension_list_t::const_iterator i = m_ses_extensions.begin() + , end(m_ses_extensions.end()); i != end; ++i) + { + TORRENT_TRY { + (*i)->save_state(*eptr); + } TORRENT_CATCH(std::exception&) {} + } +#endif + } + + proxy_settings session_impl::proxy() const + { + return proxy_settings(m_settings); + } + + void session_impl::load_state(bdecode_node const* e + , boost::uint32_t const flags = 0xffffffff) + { + 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; + // load from the old settings names + if (flags & session::save_dht_settings) + { + settings = e->dict_find_dict("dht"); + if (settings) + { + bdecode_node val; + val = settings.dict_find_int("max_peers_reply"); + if (val) m_dht_settings.max_peers_reply = val.int_value(); + val = settings.dict_find_int("search_branching"); + if (val) m_dht_settings.search_branching = val.int_value(); + val = settings.dict_find_int("max_fail_count"); + if (val) m_dht_settings.max_fail_count = val.int_value(); + val = settings.dict_find_int("max_torrents"); + if (val) m_dht_settings.max_torrents = val.int_value(); + val = settings.dict_find_int("max_dht_items"); + if (val) m_dht_settings.max_dht_items = val.int_value(); + val = settings.dict_find_int("max_peers"); + if (val) m_dht_settings.max_peers = val.int_value(); + val = settings.dict_find_int("max_torrent_search_reply"); + if (val) m_dht_settings.max_torrent_search_reply = val.int_value(); + val = settings.dict_find_int("restrict_routing_ips"); + if (val) m_dht_settings.restrict_routing_ips = val.int_value(); + val = settings.dict_find_int("restrict_search_ips"); + if (val) m_dht_settings.restrict_search_ips = val.int_value(); + val = settings.dict_find_int("extended_routing_table"); + if (val) m_dht_settings.extended_routing_table = val.int_value(); + val = settings.dict_find_int("aggressive_lookups"); + if (val) m_dht_settings.aggressive_lookups = val.int_value(); + val = settings.dict_find_int("privacy_lookups"); + if (val) m_dht_settings.privacy_lookups = val.int_value(); + val = settings.dict_find_int("enforce_node_id"); + if (val) m_dht_settings.enforce_node_id = val.int_value(); + val = settings.dict_find_int("ignore_dark_internet"); + if (val) m_dht_settings.ignore_dark_internet = val.int_value(); + val = settings.dict_find_int("block_timeout"); + if (val) m_dht_settings.block_timeout = val.int_value(); + val = settings.dict_find_int("block_ratelimit"); + if (val) m_dht_settings.block_ratelimit = val.int_value(); + val = settings.dict_find_int("read_only"); + if (val) m_dht_settings.read_only = val.int_value(); + val = settings.dict_find_int("item_lifetime"); + if (val) m_dht_settings.item_lifetime = val.int_value(); + } + } + + if (flags & session::save_dht_state) + { + settings = e->dict_find_dict("dht state"); + if (settings) + { + m_dht_state = settings; + need_update_dht = true; + } + } +#endif + +#ifndef TORRENT_NO_DEPRECATE + bool need_update_proxy = false; + if (flags & session::save_proxy) + { + settings = e->dict_find_dict("proxy"); + if (settings) + { + bdecode_node val; + val = settings.dict_find_int("port"); + if (val) m_settings.set_int(settings_pack::proxy_port, val.int_value()); + val = settings.dict_find_int("type"); + if (val) m_settings.set_int(settings_pack::proxy_type, val.int_value()); + val = settings.dict_find_int("proxy_hostnames"); + if (val) m_settings.set_bool(settings_pack::proxy_hostnames, val.int_value()); + val = settings.dict_find_int("proxy_peer_connections"); + if (val) m_settings.set_bool(settings_pack::proxy_peer_connections, val.int_value()); + val = settings.dict_find_string("hostname"); + if (val) m_settings.set_str(settings_pack::proxy_hostname, val.string_value()); + val = settings.dict_find_string("password"); + if (val) m_settings.set_str(settings_pack::proxy_password, val.string_value()); + val = settings.dict_find_string("username"); + if (val) m_settings.set_str(settings_pack::proxy_username, val.string_value()); + need_update_proxy = true; + } + } + + settings = e->dict_find_dict("encryption"); + if (settings) + { + bdecode_node val; + val = settings.dict_find_int("prefer_rc4"); + if (val) m_settings.set_bool(settings_pack::prefer_rc4, val.int_value()); + val = settings.dict_find_int("out_enc_policy"); + if (val) m_settings.set_int(settings_pack::out_enc_policy, val.int_value()); + val = settings.dict_find_int("in_enc_policy"); + if (val) m_settings.set_int(settings_pack::in_enc_policy, val.int_value()); + val = settings.dict_find_int("allowed_enc_level"); + if (val) m_settings.set_int(settings_pack::allowed_enc_level, val.int_value()); + } + + if (flags & session::save_feeds) + { + settings = e->dict_find_list("feeds"); + if (settings) + { + m_feeds.reserve(settings.list_size()); + for (int i = 0; i < settings.list_size(); ++i) + { + if (settings.list_at(i).type() != bdecode_node::dict_t) continue; + boost::shared_ptr f(new_feed(*this, feed_settings())); + f->load_state(settings.list_at(i)); + f->update_feed(); + m_feeds.push_back(f); + } + update_rss_feeds(); + } + } +#endif + + if (flags & session::save_settings) + { + settings = e->dict_find_dict("settings"); + if (settings) + { + // apply_settings_pack will update dht and proxy + boost::shared_ptr pack = load_pack_from_dict(settings); + apply_settings_pack(pack); +#ifndef TORRENT_DISABLE_DHT + need_update_dht = false; +#endif +#ifndef TORRENT_NO_DEPRECATE + need_update_proxy = false; +#endif + } + } + +#ifndef TORRENT_DISABLE_DHT + if (need_update_dht) update_dht(); +#endif +#ifndef TORRENT_NO_DEPRECATE + if (need_update_proxy) update_proxy(); +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (ses_extension_list_t::iterator i = m_ses_extensions.begin() + , end(m_ses_extensions.end()); i != end; ++i) + { + TORRENT_TRY { + (*i)->load_state(*e); + } TORRENT_CATCH(std::exception&) {} + } +#endif + } + +#ifndef TORRENT_DISABLE_EXTENSIONS + + typedef boost::function(torrent_handle const&, void*)> ext_function_t; + + struct session_plugin_wrapper : plugin + { + session_plugin_wrapper(ext_function_t const& f) : m_f(f) {} + + virtual boost::shared_ptr new_torrent(torrent_handle const& t, void* user) + { return m_f(t, user); } + ext_function_t m_f; + }; + + void session_impl::add_extension(ext_function_t ext) + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT_VAL(ext, ext); + + boost::shared_ptr p(new session_plugin_wrapper(ext)); + + m_ses_extensions.push_back(p); + m_session_extension_features |= p->implemented_features(); + } + + void session_impl::add_ses_extension(boost::shared_ptr ext) + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT_VAL(ext, ext); + + m_ses_extensions.push_back(ext); + m_alerts.add_extension(ext); + ext->added(session_handle(this)); + m_session_extension_features |= ext->implemented_features(); + + // get any DHT queries the plugin would like to handle + // and record them in m_extension_dht_queries for lookup + // later + dht_extensions_t dht_ext; + ext->register_dht_extensions(dht_ext); + for (dht_extensions_t::iterator e = dht_ext.begin(); + e != dht_ext.end(); ++e) + { + TORRENT_ASSERT(e->first.size() <= max_dht_query_length); + if (e->first.size() > max_dht_query_length) continue; + extension_dht_query registration; + registration.query_len = e->first.size(); + std::copy(e->first.begin(), e->first.end(), registration.query.begin()); + registration.handler = e->second; + m_extension_dht_queries.push_back(registration); + } + } + +#endif // TORRENT_DISABLE_EXTENSIONS + +#ifndef TORRENT_NO_DEPRECATE + feed_handle session_impl::add_feed(feed_settings const& sett) + { + TORRENT_ASSERT(is_single_thread()); + + // look for duplicates. If we already have a feed with this + // URL, return a handle to the existing one + for (std::vector >::const_iterator i + = m_feeds.begin(), end(m_feeds.end()); i != end; ++i) + { + if (sett.url != (*i)->m_settings.url) continue; + return feed_handle(*i); + } + + boost::shared_ptr f(new_feed(*this, sett)); + m_feeds.push_back(f); + update_rss_feeds(); + return feed_handle(f); + } + + void session_impl::remove_feed(feed_handle h) + { + TORRENT_ASSERT(is_single_thread()); + + boost::shared_ptr f = h.m_feed_ptr.lock(); + if (!f) return; + + std::vector >::iterator i + = std::find(m_feeds.begin(), m_feeds.end(), f); + + if (i == m_feeds.end()) return; + + m_feeds.erase(i); + } + + void session_impl::get_feeds(std::vector* ret) const + { + TORRENT_ASSERT(is_single_thread()); + + ret->clear(); + ret->reserve(m_feeds.size()); + for (std::vector >::const_iterator i = m_feeds.begin() + , end(m_feeds.end()); i != end; ++i) + ret->push_back(feed_handle(*i)); + } +#endif + + void session_impl::pause() + { + TORRENT_ASSERT(is_single_thread()); + + if (m_paused) return; +#ifndef TORRENT_DISABLE_LOGGING + session_log(" *** session paused ***"); +#endif + m_paused = true; + for (torrent_map::iterator i = m_torrents.begin() + , end(m_torrents.end()); i != end; ++i) + { + torrent& t = *i->second; + t.do_pause(); + } + } + + void session_impl::resume() + { + TORRENT_ASSERT(is_single_thread()); + + if (!m_paused) return; + m_paused = false; + for (torrent_map::iterator i = m_torrents.begin() + , end(m_torrents.end()); i != end; ++i) + { + torrent& t = *i->second; + t.do_resume(); + if (t.should_check_files()) t.start_checking(); + } + } + + void session_impl::abort() + { + 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(boost::function()); + + // this will cancel requests that are not critical for shutting down + // cleanly. i.e. essentially tracker hostname lookups that we're not + // about to send event=stopped to + m_host_resolver.abort(); + + // abort the main thread + m_abort = true; + error_code ec; +#if TORRENT_USE_I2P + m_i2p_conn.close(ec); +#endif + stop_lsd(); + stop_upnp(); + stop_natpmp(); +#ifndef TORRENT_DISABLE_DHT + stop_dht(); + m_dht_announce_timer.cancel(ec); +#endif + m_lsd_announce_timer.cancel(ec); + + for (std::set >::iterator i = m_incoming_sockets.begin() + , end(m_incoming_sockets.end()); i != end; ++i) + { + (*i)->close(ec); + TORRENT_ASSERT(!ec); + } + m_incoming_sockets.clear(); + + // close the listen sockets + for (std::list::iterator i = m_listen_sockets.begin() + , end(m_listen_sockets.end()); i != end; ++i) + { + i->sock->close(ec); + TORRENT_ASSERT(!ec); + } + m_listen_sockets.clear(); + if (m_socks_listen_socket && m_socks_listen_socket->is_open()) + { + m_socks_listen_socket->close(ec); + TORRENT_ASSERT(!ec); + } + m_socks_listen_socket.reset(); + +#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 (torrent_map::iterator i = m_torrents.begin() + , end(m_torrents.end()); i != end; ++i) + { + i->second->abort(); + } + m_torrents.clear(); + +#ifndef TORRENT_DISABLE_LOGGING + session_log(" aborting all tracker requests"); +#endif + m_tracker_manager.abort_all_requests(); + +#ifndef TORRENT_DISABLE_LOGGING + session_log(" aborting all connections (%d)", int(m_connections.size())); +#endif + // abort all connections + while (!m_connections.empty()) + { +#if TORRENT_USE_ASSERTS + int conn = m_connections.size(); +#endif + (*m_connections.begin())->disconnect(errors::stopping_torrent, op_bittorrent); + TORRENT_ASSERT_VAL(conn == int(m_connections.size()) + 1, conn); + } + + // 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 tor resume the + // shutdown, but if there are, we have to wait for them to be cleared out + // first. In session_impl::on_tick() we check them periodically. If we're + // shutting down and we remove the last one, we'll initiate + // shutdown_stage2 from there. + if (m_undead_peers.empty()) + { + m_io_service.post(boost::bind(&session_impl::abort_stage2, this)); + } + } + + void session_impl::abort_stage2() + { + m_download_rate.close(); + m_upload_rate.close(); + + m_udp_socket.close(); + m_external_udp_port = 0; +#ifdef TORRENT_USE_OPENSSL + m_ssl_udp_socket.close(); +#endif + + // 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(boost::shared_ptr const& c) + { + TORRENT_ASSERT(!c->m_in_constructor); + m_connections.insert(c); + } + + 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 (torrent_map::iterator i = m_torrents.begin() + , end(m_torrents.end()); i != end; ++i) + i->second->port_filter_updated(); + } + + void session_impl::set_ip_filter(boost::shared_ptr const& f) + { + INVARIANT_CHECK; + + m_ip_filter = f; + + // Close connections whose endpoint is filtered + // by the new ip-filter + for (torrent_map::iterator i = m_torrents.begin() + , end(m_torrents.end()); i != end; ++i) + i->second->set_ip_filter(m_ip_filter); + } + + void session_impl::ban_ip(address addr) + { + TORRENT_ASSERT(is_single_thread()); + if (!m_ip_filter) m_ip_filter = boost::make_shared(); + m_ip_filter->add_rule(addr, addr, ip_filter::blocked); + for (torrent_map::iterator i = m_torrents.begin() + , end(m_torrents.end()); i != end; ++i) + i->second->set_ip_filter(m_ip_filter); + } + + ip_filter const& session_impl::get_ip_filter() + { + TORRENT_ASSERT(is_single_thread()); + if (!m_ip_filter) m_ip_filter = boost::make_shared(); + return *m_ip_filter; + } + + port_filter const& session_impl::get_port_filter() const + { + TORRENT_ASSERT(is_single_thread()); + return m_port_filter; + } + + namespace + { + + template + void set_socket_buffer_size(Socket& s, session_settings const& sett, error_code& ec) + { + int 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 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; + } + } + } + } + + } // anonymous namespace + + int 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(int cid) + { + TORRENT_ASSERT(is_single_thread()); + // if you hit this assert, you're deleting a non-existent peer class + TORRENT_ASSERT(m_classes.at(cid)); + if (m_classes.at(cid) == 0) return; + m_classes.decref(cid); + } + + peer_class_info session_impl::get_peer_class(int cid) + { + peer_class_info ret; + peer_class* pc = m_classes.at(cid); + // if you hit this assert, you're passing in an invalid cid + TORRENT_ASSERT(pc); + if (pc == 0) + { +#ifdef TORRENT_DEBUG + // make it obvious that the return value is undefined + ret.upload_limit = 0xf0f0f0f; + ret.download_limit = 0xf0f0f0f; + ret.label.resize(20); + url_random(&ret.label[0], &ret.label[0] + 20); + 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; + } + + void session_impl::queue_tracker_request(tracker_request& req + , boost::weak_ptr c) + { + req.listen_port = listen_port(); + if (m_key) req.key = m_key; + +#ifdef TORRENT_USE_OPENSSL + // SSL torrents use the SSL listen port + if (req.ssl_ctx) req.listen_port = ssl_listen_port(); + req.ssl_ctx = &m_ssl_ctx; +#endif +#if TORRENT_USE_I2P + if (!m_settings.get_str(settings_pack::i2p_hostname).empty()) + { + req.i2pconn = &m_i2p_conn; + } +#endif + + if (is_any(req.bind_ip)) req.bind_ip = m_listen_interface.address(); + m_tracker_manager.queue_request(get_io_service(), req, c); + } + + void session_impl::set_peer_class(int 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(pc); + if (pc == 0) return; + + pc->set_info(&pci); + } + + void session_impl::set_peer_class_filter(ip_filter const& f) + { + INVARIANT_CHECK; + m_peer_class_filter = f; + } + + ip_filter const& session_impl::get_peer_class_filter() const + { + return m_peer_class_filter; + } + + void session_impl::set_peer_class_type_filter(peer_class_type_filter f) + { + m_peer_class_type_filter = f; + } + + peer_class_type_filter session_impl::get_peer_class_type_filter() + { + return m_peer_class_type_filter; + } + + void session_impl::set_peer_classes(peer_class_set* s, address const& a, int st) + { + boost::uint32_t peer_class_mask = m_peer_class_filter.access(a); + + // assign peer class based on socket type + static const int mapping[] = { 0, 0, 0, 0, 1, 4, 2, 2, 2, 3}; + int 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) == 0) 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 == 0) 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; + } + + // the back argument determines whether this bump causes the torrent + // to be the most recently used or the least recently used. Putting + // the torrent at the back of the queue makes it the most recently + // used and the least likely to be evicted. This is the default. + // if back is false, the torrent is moved to the front of the queue, + // and made the most likely to be evicted. This is used for torrents + // that are paused, to give up their slot among the loaded torrents + void session_impl::bump_torrent(torrent* t, bool back) + { + if (t->is_aborted()) return; + + bool new_torrent = false; + + // if t is the only torrent in the LRU list, both + // its prev and next links will be NULL, even though + // it's already in the list. Cover this case by also + // checking to see if it's the first item + if (t->next != NULL || t->prev != NULL || m_torrent_lru.front() == t) + { +#ifdef TORRENT_DEBUG + torrent* i = m_torrent_lru.front(); + while (i != NULL && i != t) i = i->next; + TORRENT_ASSERT(i == t); +#endif + + // this torrent is in the list already. + // first remove it + m_torrent_lru.erase(t); + } + else + { + new_torrent = true; + } + + // pinned torrents should not be part of the LRU, since + // the LRU is only used to evict torrents + if (t->is_pinned()) return; + + if (back) + m_torrent_lru.push_back(t); + else + m_torrent_lru.push_front(t); + + if (new_torrent) evict_torrents_except(t); + } + + void session_impl::evict_torrent(torrent* t) + { + TORRENT_ASSERT(!t->is_pinned()); + + // if there's no user-load function set, we cannot evict + // torrents. The feature is not enabled + if (!m_user_load_torrent) return; + + // if it's already evicted, there's nothing to do + if (!t->is_loaded() || !t->should_be_loaded()) return; + + TORRENT_ASSERT(t->next != NULL || t->prev != NULL || m_torrent_lru.front() == t); + +#if defined TORRENT_DEBUG && defined TORRENT_EXPENSIVE_INVARIANT_CHECKS + torrent* i = m_torrent_lru.front(); + while (i != NULL && i != t) i = i->next; + TORRENT_ASSERT(i == t); +#endif + + int loaded_limit = m_settings.get_int(settings_pack::active_loaded_limit); + + // 0 means unlimited, never evict anything + if (loaded_limit == 0) return; + + if (m_torrent_lru.size() > loaded_limit) + { + // just evict the torrent + m_stats_counters.inc_stats_counter(counters::torrent_evicted_counter); + TORRENT_ASSERT(t->is_pinned() == false); + t->unload(); + m_torrent_lru.erase(t); + return; + } + + // move this torrent to be the first to be evicted whenever + // another torrent need its slot + bump_torrent(t, false); + } + + void session_impl::evict_torrents_except(torrent* ignore) + { + if (!m_user_load_torrent) return; + + int loaded_limit = m_settings.get_int(settings_pack::active_loaded_limit); + + // 0 means unlimited, never evict anything + if (loaded_limit == 0) return; + + // if the torrent we're ignoring (i.e. making room for), allow + // one more torrent in the list. + if (ignore->next != NULL || ignore->prev != NULL || m_torrent_lru.front() == ignore) + { +#ifdef TORRENT_DEBUG + torrent* i = m_torrent_lru.front(); + while (i != NULL && i != ignore) i = i->next; + TORRENT_ASSERT(i == ignore); +#endif + ++loaded_limit; + } + + while (m_torrent_lru.size() >= loaded_limit) + { + // we're at the limit of loaded torrents. Find the least important + // torrent and unload it. This is done with an LRU. + torrent* i = m_torrent_lru.front(); + + if (i == ignore) + { + i = i->next; + if (i == NULL) break; + } + m_stats_counters.inc_stats_counter(counters::torrent_evicted_counter); + TORRENT_ASSERT(i->is_pinned() == false); + i->unload(); + m_torrent_lru.erase(i); + } + } + + bool session_impl::load_torrent(torrent* t) + { + TORRENT_ASSERT(is_single_thread()); + evict_torrents_except(t); + + // we wouldn't be loading the torrent if it was already + // in the LRU (and loaded) + TORRENT_ASSERT(t->next == NULL && t->prev == NULL && m_torrent_lru.front() != t); + TORRENT_ASSERT(m_user_load_torrent); + + // now, load t into RAM + std::vector buffer; + error_code ec; + m_user_load_torrent(t->info_hash(), buffer, ec); + if (ec) + { + t->set_error(ec, torrent_status::error_file_metadata); + t->pause(false); + return false; + } + bool ret = t->load(buffer); + if (ret) bump_torrent(t); + return ret; + } + + void session_impl::deferred_submit_jobs() + { + if (m_deferred_submit_disk_jobs) return; + m_deferred_submit_disk_jobs = true; + m_io_service.post(boost::bind(&session_impl::submit_disk_jobs, 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 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 == 0) 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 amount_down, int amount_up) + { + int ret = 0; + int num = set.num_classes(); + for (int i = 0; i < num; ++i) + { + peer_class* p = m_classes.at(set.class_at(i)); + if (p == 0) 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(boost::shared_ptr pack) + { + apply_settings_pack_impl(*pack); + } + + settings_pack session_impl::get_settings() const + { + settings_pack ret; + // TODO: it would be nice to reserve() these vectors up front + for (int i = settings_pack::string_type_base; + i < settings_pack::max_string_setting_internal; ++i) + { + ret.set_str(i, m_settings.get_str(i)); + } + for (int i = settings_pack::int_type_base; + i < settings_pack::max_int_setting_internal; ++i) + { + ret.set_int(i, m_settings.get_int(i)); + } + for (int i = settings_pack::bool_type_base; + i < settings_pack::max_bool_setting_internal; ++i) + { + ret.set_bool(i, m_settings.get_bool(i)); + } + return ret; + } + + void session_impl::apply_settings_pack_impl(settings_pack const& pack) + { + bool reopen_listen_port = + (pack.has_val(settings_pack::ssl_listen) + && pack.get_int(settings_pack::ssl_listen) + != m_settings.get_int(settings_pack::ssl_listen)) + || (pack.has_val(settings_pack::listen_interfaces) + && pack.get_str(settings_pack::listen_interfaces) + != m_settings.get_str(settings_pack::listen_interfaces)); + + apply_pack(&pack, m_settings, this); + m_disk_thread.set_settings(&pack, m_alerts); + + if (reopen_listen_port) + { + error_code ec; + open_listen_port(); + } + } + +#ifndef TORRENT_NO_DEPRECATE + void session_impl::set_settings(libtorrent::session_settings const& s) + { + INVARIANT_CHECK; + TORRENT_ASSERT(is_single_thread()); + boost::shared_ptr p = load_pack_from_struct(m_settings, s); + apply_settings_pack(p); + } + + libtorrent::session_settings session_impl::deprecated_settings() const + { + libtorrent::session_settings ret; + + load_struct_from_settings(m_settings, ret); + return ret; + } +#endif + + tcp::endpoint session_impl::get_ipv6_interface() const + { + return m_ipv6_interface; + } + + tcp::endpoint session_impl::get_ipv4_interface() const + { + return m_ipv4_interface; + } + + enum { listen_no_system_port = 0x02 }; + + listen_socket_t session_impl::setup_listener(std::string const& device + , boost::asio::ip::tcp const& protocol, int port, int flags, error_code& ec) + { + int retries = m_settings.get_int(settings_pack::max_retry_port_bind); + + listen_socket_t ret; + ret.ssl = flags & open_ssl_socket; + int last_op = 0; + listen_failed_alert::socket_type_t sock_type = (flags & open_ssl_socket) + ? listen_failed_alert::tcp_ssl : listen_failed_alert::tcp; + ret.sock.reset(new tcp::acceptor(m_io_service)); + ret.sock->open(protocol, ec); + last_op = listen_failed_alert::open; + if (ec) + { + if (m_alerts.should_post()) + m_alerts.emplace_alert(device, port, last_op + , ec, sock_type); + +#ifndef TORRENT_DISABLE_LOGGING + session_log("failed to open socket: %s: %s" + , device.c_str(), ec.message().c_str()); +#endif + return ret; + } + + { + // this is best-effort. ignore errors + error_code err; +#ifdef TORRENT_WINDOWS + ret.sock->set_option(exclusive_address_use(true), err); +#endif + ret.sock->set_option(tcp::acceptor::reuse_address(true), err); + } + +#if TORRENT_USE_IPV6 + if (protocol == boost::asio::ip::tcp::v6()) + { + error_code err; // ignore errors here + ret.sock->set_option(boost::asio::ip::v6_only(true), err); +#ifdef TORRENT_WINDOWS + // enable Teredo on windows + ret.sock->set_option(v6_protection_level(PROTECTION_LEVEL_UNRESTRICTED), err); +#endif // TORRENT_WINDOWS + } +#endif // TORRENT_USE_IPV6 + + address bind_ip = bind_to_device(m_io_service, *ret.sock, protocol + , device.c_str(), port, ec); + + while (ec == error_code(error::address_in_use) && retries > 0) + { + TORRENT_ASSERT_VAL(ec, ec); +#ifndef TORRENT_DISABLE_LOGGING + error_code ignore; + session_log("failed to bind to interface [%s %d] \"%s\" : %s (%d) : %s " + "(retries: %d)" + , device.c_str(), port, bind_ip.to_string(ignore).c_str() + , ec.category().name(), ec.value(), ec.message().c_str(), retries); +#endif + ec.clear(); + TORRENT_ASSERT_VAL(!ec, ec); + --retries; + port += 1; + bind_ip = bind_to_device(m_io_service, *ret.sock, protocol + , device.c_str(), port, ec); + last_op = listen_failed_alert::bind; + } + if (ec == error_code(error::address_in_use) + && !(flags & listen_no_system_port)) + { + // instead of giving up, try let the OS pick a port + port = 0; + ec.clear(); + bind_ip = bind_to_device(m_io_service, *ret.sock, protocol + , device.c_str(), port, ec); + last_op = listen_failed_alert::bind; + } + if (ec) + { + TORRENT_ASSERT_VAL(ec.value() != 0, ec); + + // not even that worked, give up + if (m_alerts.should_post()) + m_alerts.emplace_alert(device, port, last_op, ec, sock_type); +#ifndef TORRENT_DISABLE_LOGGING + error_code err; + session_log("cannot to bind to interface [%s %d] \"%s : %s\": %s" + , device.c_str(), port, bind_ip.to_string(err).c_str() + , ec.category().name(), ec.message().c_str()); +#endif + return ret; + } + ret.external_port = ret.sock->local_endpoint(ec).port(); + TORRENT_ASSERT(ret.external_port == port || port == 0); + last_op = listen_failed_alert::get_peer_name; + if (!ec) + { + ret.sock->listen(m_settings.get_int(settings_pack::listen_queue_size), ec); + last_op = listen_failed_alert::listen; + } + if (ec) + { + if (m_alerts.should_post()) + m_alerts.emplace_alert(device, port, last_op, ec, sock_type); +#ifndef TORRENT_DISABLE_LOGGING + session_log("cannot listen on interface \"%s\": %s" + , device.c_str(), ec.message().c_str()); +#endif + return ret; + } + + // if we asked the system to listen on port 0, which + // socket did it end up choosing? + if (port == 0) + { + port = ret.sock->local_endpoint(ec).port(); + last_op = listen_failed_alert::get_peer_name; + if (ec) + { + if (m_alerts.should_post()) + m_alerts.emplace_alert(device, port, last_op, ec, sock_type); +#ifndef TORRENT_DISABLE_LOGGING + session_log("failed to get peer name \"%s\": %s" + , device.c_str(), ec.message().c_str()); +#endif + return ret; + } + } + +#ifndef TORRENT_DISABLE_LOGGING + session_log(" listening on: %s external port: %d" + , print_endpoint(tcp::endpoint(bind_ip, port)).c_str(), ret.external_port); +#endif + return ret; + } + + void session_impl::open_listen_port() + { +#ifndef TORRENT_DISABLE_LOGGING + session_log("open listen port"); +#endif + + TORRENT_ASSERT(is_single_thread()); + + TORRENT_ASSERT(!m_abort); + int flags = m_settings.get_bool(settings_pack::listen_system_port_fallback) + ? 0 : listen_no_system_port; + error_code ec; + + int listen_port_retries = m_settings.get_int(settings_pack::max_retry_port_bind); + +retry: + + // close the open listen sockets + // close the listen sockets + for (std::list::iterator i = m_listen_sockets.begin() + , end(m_listen_sockets.end()); i != end; ++i) + i->sock->close(ec); + m_listen_sockets.clear(); + m_stats_counters.set_value(counters::has_incoming_connections, 0); + ec.clear(); + + if (m_abort) return; + + m_ipv6_interface = tcp::endpoint(); + m_ipv4_interface = tcp::endpoint(); + + // TODO: instead of having a special case for this, just make the + // default listen interfaces be "0.0.0.0:6881,[::]:6881" and use + // the generic path. That would even allow for not listening at all. + if (m_listen_interfaces.empty()) + { + // this means we should open two listen sockets + // one for IPv4 and one for IPv6 + listen_socket_t s = setup_listener("0.0.0.0", boost::asio::ip::tcp::v4() + , m_listen_interface.port() + , flags, ec); + + if (!ec && s.sock) + { + // update the listen_interface member with the + // actual port we ended up listening on, so that the other + // sockets can be bound to the same one + m_listen_interface.port(s.external_port); + + TORRENT_ASSERT(!m_abort); + m_listen_sockets.push_back(s); + } + +#ifdef TORRENT_USE_OPENSSL + if (m_settings.get_int(settings_pack::ssl_listen)) + { + s = setup_listener("0.0.0.0", boost::asio::ip::tcp::v4() + , m_settings.get_int(settings_pack::ssl_listen) + , flags | open_ssl_socket, ec); + + if (!ec && s.sock) + { + TORRENT_ASSERT(!m_abort); + m_listen_sockets.push_back(s); + } + } +#endif + +#if TORRENT_USE_IPV6 + // only try to open the IPv6 port if IPv6 is installed + if (supports_ipv6()) + { + s = setup_listener("::", boost::asio::ip::tcp::v6() + , m_listen_interface.port() + , flags, ec); + + if (!ec && s.sock) + { + TORRENT_ASSERT(!m_abort); + m_listen_sockets.push_back(s); + } + +#ifdef TORRENT_USE_OPENSSL + if (m_settings.get_int(settings_pack::ssl_listen)) + { + s.ssl = true; + s = setup_listener("::", boost::asio::ip::tcp::v6() + , m_settings.get_int(settings_pack::ssl_listen) + , flags | open_ssl_socket, ec); + + if (!ec && s.sock) + { + TORRENT_ASSERT(!m_abort); + m_listen_sockets.push_back(s); + } + } +#endif // TORRENT_USE_OPENSSL + } +#endif // TORRENT_USE_IPV6 + + // set our main IPv4 and IPv6 interfaces + // used to send to the tracker + std::vector ifs = enum_net_interfaces(m_io_service, ec); + for (std::vector::const_iterator i = ifs.begin() + , end(ifs.end()); i != end; ++i) + { + address const& addr = i->interface_address; + if (addr.is_v6() && !is_local(addr) && !is_loopback(addr)) + m_ipv6_interface = tcp::endpoint(addr, m_listen_interface.port()); + else if (addr.is_v4() && !is_local(addr) && !is_loopback(addr)) + m_ipv4_interface = tcp::endpoint(addr, m_listen_interface.port()); + } + } + else + { + // TODO: 2 the udp socket(s) should be using the same generic + // mechanism and not be restricted to a single one + // we should open a one listen socket for each entry in the + // listen_interfaces list + for (int i = 0; i < m_listen_interfaces.size(); ++i) + { + std::string const& device = m_listen_interfaces[i].first; + int port = m_listen_interfaces[i].second; + + int num_device_fails = 0; + +#if TORRENT_USE_IPV6 + const int first_family = 0; +#else + const int first_family = 1; +#endif + boost::asio::ip::tcp protocol[] + = { boost::asio::ip::tcp::v6(), boost::asio::ip::tcp::v4() }; + + for (int address_family = first_family; address_family < 2; ++address_family) + { + error_code err; + address test_family = address::from_string(device.c_str(), err); + if (!err + && test_family.is_v4() != address_family + && !is_any(test_family)) + continue; + + listen_socket_t s = setup_listener(device, protocol[address_family] + , port, flags, ec); + + if (ec == error_code(boost::system::errc::no_such_device, generic_category())) + { + ++num_device_fails; + continue; + } + + if (!ec && s.sock) + { + // update the listen_interface member with the + // actual port we ended up listening on, so that the other + // sockets can be bound to the same one + m_listen_interface.port(s.external_port); + + TORRENT_ASSERT(!m_abort); + m_listen_sockets.push_back(s); + + tcp::endpoint bind_ep = s.sock->local_endpoint(ec); +#if TORRENT_USE_IPV6 + if (bind_ep.address().is_v6()) + m_ipv6_interface = bind_ep; + else +#endif + m_ipv4_interface = bind_ep; + } + +#ifdef TORRENT_USE_OPENSSL + if (m_settings.get_int(settings_pack::ssl_listen)) + { + listen_socket_t ssl_s = setup_listener(device + , protocol[address_family] + , m_settings.get_int(settings_pack::ssl_listen) + , flags | open_ssl_socket, ec); + + if (!ec && ssl_s.sock) + { + TORRENT_ASSERT(!m_abort); + m_listen_sockets.push_back(ssl_s); + } + } +#endif + } + } + } + + if (m_listen_sockets.empty() && ec) + { +#ifndef TORRENT_DISABLE_LOGGING + session_log("cannot bind TCP listen socket to interface \"%s\": %s" + , print_endpoint(m_listen_interface).c_str(), ec.message().c_str()); +#endif + if (m_alerts.should_post()) + m_alerts.emplace_alert( + m_listen_interface.address().to_string() + , m_listen_interface.port() + , listen_failed_alert::bind + , ec, listen_failed_alert::tcp); + if (listen_port_retries > 0) + { + m_listen_interface.port(m_listen_interface.port() + 1); + // update the actual port m_listen_interface was derived from also + if (!m_listen_interfaces.empty()) + m_listen_interfaces[0].second += 1; + --listen_port_retries; + goto retry; + } + return; + } + +#ifdef TORRENT_USE_OPENSSL + int const ssl_port = m_settings.get_int(settings_pack::ssl_listen); + udp::endpoint ssl_bind_if(m_listen_interface.address(), ssl_port); + tcp::endpoint ssl_bind_ep(m_listen_interface.address(), ssl_port); + + // if ssl port is 0, we don't want to listen on an SSL port + if (ssl_port != 0) + { + // if the socket is already open with the port we want, just leave it + error_code err; + if (!m_ssl_udp_socket.is_open() + || m_ssl_udp_socket.local_endpoint(err) != ssl_bind_ep + || err) + { + m_ssl_udp_socket.bind(ssl_bind_if, ec); + if (ec) + { +#ifndef TORRENT_DISABLE_LOGGING + session_log("SSL: cannot bind to UDP interface \"%s\": %s" + , print_endpoint(m_listen_interface).c_str(), ec.message().c_str()); +#endif + if (m_alerts.should_post()) + { + m_alerts.emplace_alert(ssl_bind_if.address().to_string() + , ssl_port, listen_failed_alert::bind, ec, listen_failed_alert::utp_ssl); + } + m_ssl_udp_socket.close(); + ec.clear(); + } + else + { + maybe_update_udp_mapping(0, true, ssl_port, ssl_port); + maybe_update_udp_mapping(1, true, ssl_port, ssl_port); + } + } + } + else + { + m_ssl_udp_socket.close(); + + // if there are mappings for the SSL socket, delete them now + if (m_ssl_udp_mapping[0] != -1 && m_natpmp) + { + m_natpmp->delete_mapping(m_ssl_udp_mapping[0]); + m_ssl_udp_mapping[0] = -1; + } + if (m_ssl_udp_mapping[1] != -1 && m_upnp) + { + m_upnp->delete_mapping(m_ssl_udp_mapping[1]); + m_ssl_udp_mapping[1] = -1; + } + } +#endif // TORRENT_USE_OPENSSL + + udp::endpoint const udp_bind_ep(m_listen_interface.address() + , m_listen_interface.port()); + + // if the socket is already open with the port we want, just leave it + error_code err; + if (!m_udp_socket.is_open() + || m_udp_socket.local_endpoint(err) != m_listen_interface + || err) + { + m_udp_socket.bind(udp_bind_ep, ec); + if (ec) + { +#ifndef TORRENT_DISABLE_LOGGING + session_log("cannot bind to UDP interface \"%s\": %s" + , print_endpoint(m_listen_interface).c_str(), ec.message().c_str()); +#endif + if (m_alerts.should_post()) + { + m_alerts.emplace_alert(m_listen_interface.address().to_string() + , m_listen_interface.port() + , listen_failed_alert::bind + , ec, listen_failed_alert::udp); + } + m_udp_socket.close(); + if (listen_port_retries > 0) + { + m_listen_interface.port(m_listen_interface.port() + 1); + // update the actual port m_listen_interface was derived from also + if (!m_listen_interfaces.empty()) + m_listen_interfaces[0].second += 1; + --listen_port_retries; + goto retry; + } + return; + } + else + { + m_external_udp_port = m_udp_socket.local_port(); + maybe_update_udp_mapping(0, false, m_listen_interface.port(), m_listen_interface.port()); + maybe_update_udp_mapping(1, false, m_listen_interface.port(), m_listen_interface.port()); + } + } + + // we made it! now post all the listen_succeeded_alerts + + for (std::list::iterator i = m_listen_sockets.begin() + , end(m_listen_sockets.end()); i != end; ++i) + { + listen_succeeded_alert::socket_type_t const socket_type = i->ssl + ? listen_succeeded_alert::tcp_ssl + : listen_succeeded_alert::tcp; + + if (!m_alerts.should_post()) continue; + + error_code error; + tcp::endpoint bind_ep = i->sock->local_endpoint(error); + if (error) continue; + + m_alerts.emplace_alert(bind_ep, socket_type); + } + +#ifdef TORRENT_USE_OPENSSL + if (m_ssl_udp_socket.is_open()) + { + if (m_alerts.should_post()) + m_alerts.emplace_alert( + ssl_bind_ep, listen_succeeded_alert::utp_ssl); + } +#endif + + if (m_udp_socket.is_open()) + { + if (m_alerts.should_post()) + m_alerts.emplace_alert(m_listen_interface + , listen_succeeded_alert::udp); + } + + if (m_settings.get_int(settings_pack::peer_tos) != 0) + { + update_peer_tos(); + } + + ec.clear(); + + set_socket_buffer_size(m_udp_socket, m_settings, ec); + if (ec) + { + if (m_alerts.should_post()) + m_alerts.emplace_alert(udp::endpoint(), ec); + } + + // initiate accepting on the listen sockets + for (std::list::iterator i = m_listen_sockets.begin() + , end(m_listen_sockets.end()); i != end; ++i) + async_accept(i->sock, i->ssl); + + open_new_incoming_socks_connection(); +#if TORRENT_USE_I2P + open_new_incoming_i2p_connection(); +#endif + + if (!m_listen_sockets.empty()) + { + tcp::endpoint local = m_listen_sockets.front().sock->local_endpoint(ec); + if (!ec) remap_tcp_ports(3, local.port(), ssl_listen_port()); + } + } + + void session_impl::remap_tcp_ports(boost::uint32_t mask, int tcp_port, int ssl_port) + { +#ifndef TORRENT_USE_OPENSSL + TORRENT_UNUSED(ssl_port); +#endif + if ((mask & 1) && m_natpmp) + { + if (m_tcp_mapping[0] != -1) m_natpmp->delete_mapping(m_tcp_mapping[0]); + m_tcp_mapping[0] = m_natpmp->add_mapping(natpmp::tcp, tcp_port, tcp_port); +#ifdef TORRENT_USE_OPENSSL + if (m_ssl_tcp_mapping[0] != -1) + { + m_natpmp->delete_mapping(m_ssl_tcp_mapping[0]); + m_ssl_tcp_mapping[0] = -1; + } + if (ssl_port > 0) m_ssl_tcp_mapping[0] = m_natpmp->add_mapping(natpmp::tcp + , ssl_port, ssl_port); +#endif + } + if ((mask & 2) && m_upnp) + { + if (m_tcp_mapping[1] != -1) m_upnp->delete_mapping(m_tcp_mapping[1]); + m_tcp_mapping[1] = m_upnp->add_mapping(upnp::tcp, tcp_port, tcp_port); +#ifdef TORRENT_USE_OPENSSL + if (m_ssl_tcp_mapping[1] != -1) + { + m_upnp->delete_mapping(m_ssl_tcp_mapping[1]); + m_ssl_tcp_mapping[1] = -1; + } + if (ssl_port > 0) m_ssl_tcp_mapping[1] = m_upnp->add_mapping(upnp::tcp + , ssl_port, ssl_port); +#endif + } + } + + void session_impl::open_new_incoming_socks_connection() + { + int const proxy_type = m_settings.get_int(settings_pack::proxy_type); + + if (proxy_type != settings_pack::socks5 + && proxy_type != settings_pack::socks5_pw + && proxy_type != settings_pack::socks4) + return; + + if (m_socks_listen_socket) return; + + m_socks_listen_socket = boost::make_shared(boost::ref(m_io_service)); + bool const ret = instantiate_connection(m_io_service, proxy() + , *m_socks_listen_socket, NULL, NULL, false, false); + TORRENT_ASSERT_VAL(ret, ret); + TORRENT_UNUSED(ret); + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("session_impl::on_socks_listen"); +#endif + socks5_stream& s = *m_socks_listen_socket->get(); + + m_socks_listen_port = m_listen_interface.port(); + if (m_socks_listen_port == 0) m_socks_listen_port = 2000 + random() % 60000; + s.async_listen(tcp::endpoint(address_v4::any(), m_socks_listen_port) + , boost::bind(&session_impl::on_socks_listen, this + , m_socks_listen_socket, _1)); + } + + void session_impl::on_socks_listen(boost::shared_ptr const& sock + , error_code const& e) + { +#if defined TORRENT_ASIO_DEBUGGING + complete_async("session_impl::on_socks_listen"); +#endif + + TORRENT_ASSERT(sock == m_socks_listen_socket || !m_socks_listen_socket); + + if (e) + { + m_socks_listen_socket.reset(); + if (e == boost::asio::error::operation_aborted) return; + if (m_alerts.should_post()) + m_alerts.emplace_alert("socks5" + , -1, listen_failed_alert::accept, e + , listen_failed_alert::socks5); + return; + } + + error_code ec; + tcp::endpoint ep = sock->local_endpoint(ec); + TORRENT_ASSERT(!ec); + TORRENT_UNUSED(ec); + + if (m_alerts.should_post()) + m_alerts.emplace_alert( + ep, listen_succeeded_alert::socks5); + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("session_impl::on_socks_accept"); +#endif + socks5_stream& s = *m_socks_listen_socket->get(); + s.async_accept(boost::bind(&session_impl::on_socks_accept, this + , m_socks_listen_socket, _1)); + } + + void session_impl::on_socks_accept(boost::shared_ptr const& s + , error_code const& e) + { +#if defined TORRENT_ASIO_DEBUGGING + complete_async("session_impl::on_socks_accept"); +#endif + TORRENT_ASSERT(s == m_socks_listen_socket || !m_socks_listen_socket); + m_socks_listen_socket.reset(); + if (e == boost::asio::error::operation_aborted) return; + if (e) + { + if (m_alerts.should_post()) + m_alerts.emplace_alert("socks5" + , -1, listen_failed_alert::accept, e + , listen_failed_alert::socks5); + return; + } + open_new_incoming_socks_connection(); + incoming_connection(s); + } + + 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) + , boost::bind(&session_impl::on_i2p_open, this, _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 = 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 + 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 = boost::shared_ptr(new socket_type(m_io_service)); + bool ret = instantiate_connection(m_io_service, m_i2p_conn.proxy() + , *m_i2p_listen_socket, NULL, NULL, true, false); + TORRENT_ASSERT_VAL(ret, ret); + TORRENT_UNUSED(ret); + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("session_impl::on_i2p_accept"); +#endif + i2p_stream& s = *m_i2p_listen_socket->get(); + s.set_command(i2p_stream::cmd_accept); + s.set_session_id(m_i2p_conn.session_id()); + s.async_connect(tcp::endpoint(address_v4::any(), m_listen_interface.port()) + , boost::bind(&session_impl::on_i2p_accept, this, m_i2p_listen_socket, _1)); + } + + void session_impl::on_i2p_accept(boost::shared_ptr const& s + , error_code const& e) + { +#if defined TORRENT_ASIO_DEBUGGING + complete_async("session_impl::on_i2p_accept"); +#endif + m_i2p_listen_socket.reset(); + if (e == boost::asio::error::operation_aborted) return; + if (e) + { + if (m_alerts.should_post()) + m_alerts.emplace_alert("i2p" + , m_listen_interface.port() + , listen_failed_alert::accept + , e, listen_failed_alert::i2p); +#ifndef TORRENT_DISABLE_LOGGING + session_log("cannot bind to port %d: %s" + , m_listen_interface.port(), e.message().c_str()); +#endif + return; + } + open_new_incoming_i2p_connection(); + incoming_connection(s); + } +#endif + + bool session_impl::incoming_packet(error_code const& ec + , udp::endpoint const& ep, char const*, int) + { + m_stats_counters.inc_stats_counter(counters::on_udp_counter); + + if (ec) + { + // don't bubble up operation aborted errors to the user + if (ec != boost::asio::error::operation_aborted + && m_alerts.should_post()) + m_alerts.emplace_alert(ep, ec); + +#ifndef TORRENT_DISABLE_LOGGING + session_log("UDP socket error: (%d) %s", ec.value(), ec.message().c_str()); +#endif + } + return false; + } + + void session_impl::async_accept(boost::shared_ptr const& listener, bool ssl) + { + TORRENT_ASSERT(!m_abort); + shared_ptr c(new socket_type(m_io_service)); + tcp::socket* str = 0; + +#ifdef TORRENT_USE_OPENSSL + if (ssl) + { + // accept connections initializing the SSL connection to + // use the generic m_ssl_ctx context. However, since it has + // the servername callback set on it, we will switch away from + // this context into a specific torrent once we start handshaking + c->instantiate >(m_io_service, &m_ssl_ctx); + str = &c->get >()->next_layer(); + } + else +#endif + { + c->instantiate(m_io_service); + str = c->get(); + } + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("session_impl::on_accept_connection"); +#endif + +#ifdef TORRENT_USE_OPENSSL + TORRENT_ASSERT(ssl == is_ssl(*c)); +#endif + + listener->async_accept(*str + , boost::bind(&session_impl::on_accept_connection, this, c + , boost::weak_ptr(listener), _1, ssl)); + } + + void session_impl::on_accept_connection(shared_ptr const& s + , weak_ptr listen_socket, error_code const& e, bool ssl) + { +#if defined TORRENT_ASIO_DEBUGGING + complete_async("session_impl::on_accept_connection"); +#endif + m_stats_counters.inc_stats_counter(counters::on_accept_counter); + TORRENT_ASSERT(is_single_thread()); + boost::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 ep = listener->local_endpoint(ec); +#ifndef TORRENT_DISABLE_LOGGING + 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 + // elsewere. + if (m_settings.get_int(settings_pack::connections_limit) > 10) + { + // now, disconnect a random peer + torrent_map::iterator i = std::max_element(m_torrents.begin() + , m_torrents.end() + , boost::bind(&torrent::num_peers + , boost::bind(&torrent_map::value_type::second, _1)) + < boost::bind(&torrent::num_peers + , boost::bind(&torrent_map::value_type::second, _2)) + ); + + if (m_alerts.should_post()) + m_alerts.emplace_alert( + torrent_handle(), performance_alert::too_few_file_descriptors); + + if (i != m_torrents.end()) + { + i->second->disconnect_peers(1, e); + } + + m_settings.set_int(settings_pack::connections_limit, m_connections.size()); + } + // try again, but still alert the user of the problem + async_accept(listener, ssl); + } + if (m_alerts.should_post()) + { + error_code err; + m_alerts.emplace_alert(ep.address().to_string() + , ep.port(), listen_failed_alert::accept, e + , ssl ? listen_failed_alert::tcp_ssl : listen_failed_alert::tcp); + } + return; + } + async_accept(listener, ssl); + +#ifdef TORRENT_USE_OPENSSL + if (ssl) + { + TORRENT_ASSERT(is_ssl(*s)); + + // for SSL connections, incoming_connection() is called + // after the handshake is done +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("session_impl::ssl_handshake"); +#endif + s->get >()->async_accept_handshake( + boost::bind(&session_impl::ssl_handshake, this, _1, s)); + m_incoming_sockets.insert(s); + } + else +#endif + { + incoming_connection(s); + } + } + +#ifdef TORRENT_USE_OPENSSL + + void session_impl::on_incoming_utp_ssl(boost::shared_ptr const& s) + { + TORRENT_ASSERT(is_ssl(*s)); + + // for SSL connections, incoming_connection() is called + // after the handshake is done +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("session_impl::ssl_handshake"); +#endif + s->get >()->async_accept_handshake( + boost::bind(&session_impl::ssl_handshake, this, _1, s)); + m_incoming_sockets.insert(s); + } + + // to test SSL connections, one can use this openssl command template: + // + // openssl s_client -cert .pem -key .pem + // -CAfile .pem -debug -connect 127.0.0.1:4433 -tls1 + // -servername + + void session_impl::ssl_handshake(error_code const& ec, boost::shared_ptr s) + { +#if defined TORRENT_ASIO_DEBUGGING + complete_async("session_impl::ssl_handshake"); +#endif + TORRENT_ASSERT(is_ssl(*s)); + + m_incoming_sockets.erase(s); + + error_code e; + tcp::endpoint endp = s->remote_endpoint(e); + if (e) return; + +#ifndef TORRENT_DISABLE_LOGGING + session_log(" *** peer SSL handshake done [ ip: %s ec: %s socket: %s ]" + , print_endpoint(endp).c_str(), ec.message().c_str(), s->type_name()); +#endif + + if (ec) + { + if (m_alerts.should_post()) + { + m_alerts.emplace_alert(torrent_handle(), endp + , peer_id(), op_ssl_handshake, ec); + } + return; + } + + incoming_connection(s); + } + +#endif // TORRENT_USE_OPENSSL + + void session_impl::incoming_connection(boost::shared_ptr const& s) + { + TORRENT_ASSERT(is_single_thread()); + +#ifdef TORRENT_USE_OPENSSL + // add the current time to the PRNG, to add more unpredictability + boost::uint64_t now = clock_type::now().time_since_epoch().count(); + // assume 12 bits of entropy (i.e. about 8 milliseconds) + RAND_add(&now, 8, 1.5); +#endif + + 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 + session_log(" <== INCOMING CONNECTION FAILED, could " + "not retrieve remote endpoint: %s" + , ec.message().c_str()); +#endif + return; + } + +#ifndef TORRENT_DISABLE_LOGGING + session_log(" <== INCOMING CONNECTION %s type: %s" + , print_endpoint(endp).c_str(), s->type_name()); +#endif + + if (!m_settings.get_bool(settings_pack::enable_incoming_utp) + && is_utp(*s)) + { +#ifndef TORRENT_DISABLE_LOGGING + session_log(" rejected uTP connection"); +#endif + if (m_alerts.should_post()) + m_alerts.emplace_alert(torrent_handle() + , endp.address(), peer_blocked_alert::utp_disabled); + return; + } + + if (!m_settings.get_bool(settings_pack::enable_incoming_tcp) + && s->get()) + { +#ifndef TORRENT_DISABLE_LOGGING + session_log(" rejected TCP connection"); +#endif + if (m_alerts.should_post()) + m_alerts.emplace_alert(torrent_handle() + , endp.address(), peer_blocked_alert::tcp_disabled); + return; + } + + // if there are outgoing interfaces specified, verify this + // peer is correctly bound to on of them + if (!m_settings.get_str(settings_pack::outgoing_interfaces).empty()) + { + tcp::endpoint local = s->local_endpoint(ec); + if (ec) + { +#ifndef TORRENT_DISABLE_LOGGING + session_log(" rejected connection: (%d) %s", ec.value() + , ec.message().c_str()); +#endif + return; + } + if (!verify_bound_address(local.address() + , is_utp(*s), ec)) + { + if (ec) + { +#ifndef TORRENT_DISABLE_LOGGING + session_log(" rejected connection, not allowed local interface: (%d) %s" + , ec.value(), ec.message().c_str()); +#endif + return; + } + +#ifndef TORRENT_DISABLE_LOGGING + error_code err; + session_log(" rejected connection, not allowed local interface: %s" + , local.address().to_string(err).c_str()); +#endif + if (m_alerts.should_post()) + m_alerts.emplace_alert(torrent_handle() + , endp.address(), 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("filtered blocked ip"); +#endif + if (m_alerts.should_post()) + m_alerts.emplace_alert(torrent_handle() + , endp.address(), 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(" There are no torrents, disconnect"); +#endif + return; + } + + // figure out which peer classes this is connections has, + // to get connection_limit_factor + peer_class_set pcs; + set_peer_classes(&pcs, endp.address(), s->type()); + int connection_limit_factor = 0; + for (int i = 0; i < pcs.num_classes(); ++i) + { + int pc = pcs.class_at(i); + if (m_classes.at(pc) == NULL) 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; + + boost::uint64_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() + , op_bittorrent, s->type() + , error_code(errors::too_many_connections, get_libtorrent_category()) + , close_no_reason); + } +#ifndef TORRENT_DISABLE_LOGGING + session_log("number of connections limit exceeded (conns: %d, limit: %d, slack: %d), connection rejected" + , 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 = false; + for (torrent_map::iterator i = m_torrents.begin() + , end(m_torrents.end()); i != end; ++i) + { + if (i->second->allows_peers()) + { + has_active_torrent = true; + break; + } + } + if (!has_active_torrent) + { +#ifndef TORRENT_DISABLE_LOGGING + session_log(" There are no _active_ torrents, disconnect"); +#endif + return; + } + } + + m_stats_counters.inc_stats_counter(counters::incoming_connections); + + if (m_alerts.should_post()) + m_alerts.emplace_alert(s->type(), endp); + + setup_socket_buffers(*s); + + peer_connection_args pack; + pack.ses = this; + pack.sett = &m_settings; + pack.stats_counters = &m_stats_counters; + pack.allocator = this; + pack.disk_thread = &m_disk_thread; + pack.ios = &m_io_service; + pack.tor = boost::weak_ptr(); + pack.s = s; + pack.endp = endp; + pack.peerinfo = 0; + + boost::shared_ptr c + = boost::make_shared(boost::cref(pack) + , get_peer_id()); +#if TORRENT_USE_ASSERTS + c->m_in_constructor = false; +#endif + + 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); + m_connections.insert(c); + c->start(); + } + } + + void session_impl::setup_socket_buffers(socket_type& s) + { + error_code ec; + set_socket_buffer_size(s, m_settings, ec); + } + + // if cancel_with_cq is set, the peer connection is + // currently expected to be scheduled for a connection + // with the connection queue, and should be cancelled + // TODO: should this function take a shared_ptr instead? + void session_impl::close_connection(peer_connection* p + , error_code const& ec) + { + TORRENT_ASSERT(is_single_thread()); + boost::shared_ptr sp(p->self()); + + // 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. + if (!sp.unique()) + m_undead_peers.push_back(sp); + +// too expensive +// INVARIANT_CHECK; + +#ifdef TORRENT_DEBUG +// for (aux::session_impl::torrent_map::const_iterator i = m_torrents.begin() +// , end(m_torrents.end()); i != end; ++i) +// TORRENT_ASSERT(!i->second->has_peer((peer_connection*)p)); +#endif + +#ifndef TORRENT_DISABLE_LOGGING + session_log(" CLOSING CONNECTION %s : %s" + , print_endpoint(p->remote()).c_str(), ec.message().c_str()); +#else + TORRENT_UNUSED(ec); +#endif + + TORRENT_ASSERT(p->is_disconnecting()); + + TORRENT_ASSERT(sp.use_count() > 0); + + connection_map::iterator 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); + } + + void session_impl::set_peer_id(peer_id const& id) + { + m_peer_id = id; + } + + void session_impl::set_key(int key) + { + m_key = key; + } + + 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 == 0) 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 == 0) return; + if (limit <= 0) limit = 0; + 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::find_if(m_connections.begin(), m_connections.end() + , boost::bind(&boost::shared_ptr::get, _1) == p) + != m_connections.end(); + } + + bool session_impl::any_torrent_has_peer(peer_connection const* p) const + { + for (aux::session_impl::torrent_map::const_iterator i = m_torrents.begin() + , end(m_torrents.end()); i != end; ++i) + if (i->second->has_peer(p)) return true; + return false; + } +#endif + + void session_impl::sent_bytes(int bytes_payload, int bytes_protocol) + { + 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) + { + 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) + { + m_stat.trancieve_ip_packet(bytes, ipv6); + } + + void session_impl::sent_syn(bool ipv6) + { + m_stat.sent_syn(ipv6); + } + + void session_impl::received_synack(bool ipv6) + { + m_stat.received_synack(ipv6); + } + + void session_impl::on_tick(error_code const& e) + { +#if defined TORRENT_ASIO_DEBUGGING + complete_async("session_impl::on_tick"); +#endif + m_stats_counters.inc_stats_counter(counters::on_tick_counter); + + TORRENT_ASSERT(is_single_thread()); + + // submit all disk jobs when we leave this function + deferred_submit_jobs(); + + aux::update_time_now(); + time_point now = aux::time_now(); + + // remove undead peers that only have this list as their reference keeping them alive + if (!m_undead_peers.empty()) + { + std::vector >::iterator remove_it + = std::remove_if(m_undead_peers.begin(), m_undead_peers.end() + , boost::bind(&boost::shared_ptr::unique, _1)); + m_undead_peers.erase(remove_it, m_undead_peers.end()); + if (m_undead_peers.empty()) + { + // we just removed our last "undead" peer (i.e. a peer connection + // that had some external reference to it). It's now safe to + // shut-down + if (m_abort) + { + m_io_service.post(boost::bind(&session_impl::abort_stage2, this)); + } + } + } + +// too expensive +// INVARIANT_CHECK; + + // we have to keep ticking the utp socket manager + // until they're all closed + if (m_abort) + { + if (m_utp_socket_manager.num_sockets() == 0 + && m_undead_peers.empty()) + return; +#if defined TORRENT_ASIO_DEBUGGING + fprintf(stderr, "uTP sockets left: %d undead-peers left: %d\n" + , m_utp_socket_manager.num_sockets() + , int(m_undead_peers.size())); +#endif + } + + if (e == boost::asio::error::operation_aborted) return; + + if (e) + { +#ifndef TORRENT_DISABLE_LOGGING + session_log("*** TICK TIMER FAILED %s", e.message().c_str()); +#endif + std::abort(); + } + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("session_impl::on_tick"); +#endif + error_code ec; + m_timer.expires_at(now + milliseconds(m_settings.get_int(settings_pack::tick_interval)), ec); + m_timer.async_wait(make_tick_handler(boost::bind(&session_impl::on_tick, this, _1))); + + m_download_rate.update_quotas(now - m_last_tick); + m_upload_rate.update_quotas(now - m_last_tick); + + m_last_tick = now; + + m_utp_socket_manager.tick(now); +#ifdef TORRENT_USE_OPENSSL + m_ssl_utp_socket_manager.tick(now); +#endif + + // only tick the following once per second + if (now - m_last_second_tick < seconds(1)) return; + +#ifndef TORRENT_DISABLE_DHT + if (m_dht + && m_dht_interval_update_torrents < 40 + && m_dht_interval_update_torrents != int(m_torrents.size())) + update_dht_announce_interval(); +#endif + + int tick_interval_ms = int(total_milliseconds(now - m_last_second_tick)); + m_last_second_tick = now; + m_tick_residual += tick_interval_ms - 1000; + + boost::int64_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); + + const int four_hours = 60 * 60 * 4; + for (torrent_map::iterator i = m_torrents.begin() + , end(m_torrents.end()); i != end; ++i) + { + i->second->step_session_time(four_hours); + } + } + +#ifndef TORRENT_DISABLE_EXTENSIONS + if (m_session_extension_features & plugin::tick_feature) + { + for (ses_extension_list_t::const_iterator i = m_ses_extensions.begin() + , end(m_ses_extensions.end()); i != end; ++i) + { + TORRENT_TRY { + (*i)->on_tick(); + } TORRENT_CATCH(std::exception&) {} + } + } +#endif + + // don't do any of the following while we're shutting down + if (m_abort) return; + +#ifndef TORRENT_NO_DEPRECATE + // -------------------------------------------------------------- + // RSS feeds + // -------------------------------------------------------------- + if (now > m_next_rss_update) + update_rss_feeds(); +#endif + + 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 (connection_map::iterator i = m_connections.begin() + , end(m_connections.end());i != end; ++i) + { + peer_connection& p = *(*i); + if (p.in_handshake()) continue; + int protocol = 0; + if (is_utp(*p.get_socket())) protocol = 1; + + if (p.download_queue().size() + p.request_queue().size() > 0) + ++num_peers[protocol][peer_connection::download_channel]; + if (p.upload_queue().size() > 0) + ++num_peers[protocol][peer_connection::upload_channel]; + } + + peer_class* pc = m_classes.at(m_tcp_peer_class); + bandwidth_channel* tcp_channel = pc->channel; + int stat_rate[] = {m_stat.upload_rate(), m_stat.download_rate() }; + // never throttle below this + int lower_limit[] = {5000, 30000}; + + for (int i = 0; i < 2; ++i) + { + // if there are no uploading uTP peers, don't throttle TCP up + if (num_peers[1][i] == 0) + { + tcp_channel[i].throttle(0); + } + else + { + if (num_peers[0][i] == 0) num_peers[0][i] = 1; + int total_peers = num_peers[0][i] + num_peers[1][i]; + // this are 64 bits since it's multiplied by the number + // of peers, which otherwise might overflow an int + boost::uint64_t rate = stat_rate[i]; + tcp_channel[i].throttle((std::max)(int(rate * num_peers[0][i] / total_peers), lower_limit[i])); + } + } + } + break; + } + + // -------------------------------------------------------------- + // auto managed torrent + // -------------------------------------------------------------- + if (!m_paused) m_auto_manage_time_scaler--; + if (m_auto_manage_time_scaler < 0) + { + INVARIANT_CHECK; + 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 (connection_map::iterator 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, op_bittorrent); + } + + // -------------------------------------------------------------- + // second_tick every torrent (that wants it) + // -------------------------------------------------------------- + +#if TORRENT_DEBUG_STREAMING > 0 + printf("\033[2J\033[0;0H"); +#endif + + std::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 up_limit = upload_rate_limit(m_global_class); + int 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); + } + } + + m_peak_up_rate = (std::max)(m_stat.upload_rate(), m_peak_up_rate); + m_peak_down_rate = (std::max)(m_stat.download_rate(), m_peak_down_rate); + + m_stat.second_tick(tick_interval_ms); + + // -------------------------------------------------------------- + // scrape paused torrents that are auto managed + // (unless the session is paused) + // -------------------------------------------------------------- + if (!is_paused()) + { + INVARIANT_CHECK; + --m_auto_scrape_time_scaler; + if (m_auto_scrape_time_scaler <= 0) + { + std::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; + + } + } + } + + // -------------------------------------------------------------- + // refresh torrent suggestions + // -------------------------------------------------------------- + --m_suggest_timer; + if (m_settings.get_int(settings_pack::suggest_mode) != settings_pack::no_piece_suggestions + && m_suggest_timer <= 0) + { + INVARIANT_CHECK; + m_suggest_timer = 10; + + torrent_map::iterator least_recently_refreshed = m_torrents.begin(); + if (m_next_suggest_torrent >= int(m_torrents.size())) + m_next_suggest_torrent = 0; + + std::advance(least_recently_refreshed, m_next_suggest_torrent); + + if (least_recently_refreshed != m_torrents.end()) + least_recently_refreshed->second->refresh_suggest_pieces(); + ++m_next_suggest_torrent; + } + +#ifndef TORRENT_NO_DEPRECATE + // -------------------------------------------------------------- + // refresh explicit disk read cache + // -------------------------------------------------------------- + --m_cache_rotation_timer; + if (m_settings.get_bool(settings_pack::explicit_read_cache) + && m_cache_rotation_timer <= 0) + { + INVARIANT_CHECK; + m_cache_rotation_timer = m_settings.get_int(settings_pack::explicit_cache_interval); + + torrent_map::iterator least_recently_refreshed = m_torrents.begin(); + if (m_next_explicit_cache_torrent >= int(m_torrents.size())) + m_next_explicit_cache_torrent = 0; + + std::advance(least_recently_refreshed, m_next_explicit_cache_torrent); + + // how many blocks does this torrent get? + int cache_size = (std::max)(0, m_settings.get_int(settings_pack::cache_size) * 9 / 10); + + if (m_connections.empty()) + { + // if we don't have any connections at all, split the + // cache evenly across all torrents + cache_size = cache_size / (std::max)(int(m_torrents.size()), 1); + } + else + { + cache_size = cache_size * least_recently_refreshed->second->num_peers() + / m_connections.size(); + } + + if (least_recently_refreshed != m_torrents.end()) + least_recently_refreshed->second->refresh_explicit_cache(cache_size); + ++m_next_explicit_cache_torrent; + } +#endif + + // -------------------------------------------------------------- + // connect new peers + // -------------------------------------------------------------- + + try_connect_more_peers(); + + // -------------------------------------------------------------- + // unchoke set calculations + // -------------------------------------------------------------- + m_unchoke_time_scaler--; + if (m_unchoke_time_scaler <= 0 && !m_connections.empty()) + { + m_unchoke_time_scaler = settings().get_int(settings_pack::unchoke_interval); + recalculate_unchoke_slots(); + } + + // -------------------------------------------------------------- + // optimistic unchoke calculation + // -------------------------------------------------------------- + m_optimistic_unchoke_time_scaler--; + if (m_optimistic_unchoke_time_scaler <= 0) + { + m_optimistic_unchoke_time_scaler + = settings().get_int(settings_pack::optimistic_unchoke_interval); + recalculate_optimistic_unchoke_slots(); + } + + // -------------------------------------------------------------- + // disconnect peers when we have too many + // -------------------------------------------------------------- + --m_disconnect_time_scaler; + if (m_disconnect_time_scaler <= 0) + { + m_disconnect_time_scaler = m_settings.get_int(settings_pack::peer_turnover_interval); + + // if the connections_limit is too low, the disconnect + // logic is disabled, since it is too disruptive + if (m_settings.get_int(settings_pack::connections_limit) > 5) + { + if (num_connections() >= m_settings.get_int(settings_pack::connections_limit) + * m_settings.get_int(settings_pack::peer_turnover_cutoff) / 100 + && !m_torrents.empty()) + { + // every 90 seconds, disconnect the worst peers + // if we have reached the connection limit + torrent_map::iterator i = std::max_element(m_torrents.begin(), m_torrents.end() + , boost::bind(&torrent::num_peers, boost::bind(&torrent_map::value_type::second, _1)) + < boost::bind(&torrent::num_peers, boost::bind(&torrent_map::value_type::second, _2))); + + TORRENT_ASSERT(i != m_torrents.end()); + int peers_to_disconnect = (std::min)((std::max)( + int(i->second->num_peers() * m_settings.get_int(settings_pack::peer_turnover) / 100), 1) + , i->second->num_connect_candidates()); + i->second->disconnect_peers(peers_to_disconnect + , error_code(errors::optimistic_disconnect, get_libtorrent_category())); + } + else + { + // if we haven't reached the global max. see if any torrent + // has reached its local limit + for (torrent_map::iterator i = m_torrents.begin() + , end(m_torrents.end()); i != end; ++i) + { + boost::shared_ptr t = i->second; + + // ths disconnect logic is disabled for torrents with + // too low connection limit + if (t->num_peers() < t->max_connections() + * m_settings.get_int(settings_pack::peer_turnover_cutoff) / 100 + || t->max_connections() < 6) + continue; + + int peers_to_disconnect = (std::min)((std::max)(int(t->num_peers() + * m_settings.get_int(settings_pack::peer_turnover) / 100), 1) + , t->num_connect_candidates()); + t->disconnect_peers(peers_to_disconnect + , error_code(errors::optimistic_disconnect, get_libtorrent_category())); + } + } + } + } + + m_tick_residual = m_tick_residual % 1000; +// m_peer_pool.release_memory(); + } + + namespace { + // returns the index of the first set bit. + int log2(boost::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[boost::uint32_t(v * 0x07C4ACDDU) >> 27]; + } + + } // anonymous namespace + + void session_impl::received_buffer(int s) + { + int index = (std::min)(log2(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)(log2(s >> 3), 17); + m_stats_counters.inc_stats_counter(counters::socket_send_size3 + index); + } + +#ifndef TORRENT_NO_DEPRECATE + void session_impl::update_rss_feeds() + { + time_t now_posix = time(0); + time_point min_update = max_time(); + time_point now = aux::time_now(); + for (std::vector >::iterator i + = m_feeds.begin(), end(m_feeds.end()); i != end; ++i) + { + feed& f = **i; + int delta = f.next_update(now_posix); + if (delta <= 0) + delta = f.update_feed(); + TORRENT_ASSERT(delta >= 0); + time_point next_update = now + seconds(delta); + if (next_update < min_update) min_update = next_update; + } + m_next_rss_update = min_update; + } +#endif + + void session_impl::prioritize_connections(boost::weak_ptr t) + { + m_prio_torrents.push_back(std::make_pair(t, 10)); + } + +#ifndef TORRENT_DISABLE_DHT + + void session_impl::add_dht_node(udp::endpoint n) + { + TORRENT_ASSERT(is_single_thread()); + + if (m_dht) m_dht->add_node(n); + else m_dht_nodes.push_back(n); + } + + bool session_impl::has_dht() const + { + return m_dht.get(); + } + + void session_impl::prioritize_dht(boost::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 + boost::shared_ptr tor = t.lock(); + if (tor) + 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) + { +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("session_impl::on_dht_announce"); +#endif + error_code ec; + m_dht_announce_timer.expires_from_now(seconds(0), ec); + m_dht_announce_timer.async_wait( + bind(&session_impl::on_dht_announce, this, _1)); + } + } + + void session_impl::on_dht_announce(error_code const& e) + { +#if defined TORRENT_ASIO_DEBUGGING + complete_async("session_impl::on_dht_announce"); +#endif + TORRENT_ASSERT(is_single_thread()); + if (e) + { +#ifndef TORRENT_DISABLE_LOGGING + session_log("aborting DHT announce timer (%d): %s" + , e.value(), e.message().c_str()); +#endif + return; + } + + if (m_abort) + { +#ifndef TORRENT_DISABLE_LOGGING + session_log("aborting DHT announce timer: m_abort set"); +#endif + return; + } + + if (!m_dht) + { + m_dht_torrents.clear(); + return; + } + + TORRENT_ASSERT(m_dht); + + // announce to DHT every 15 minutes + int delay = (std::max)(m_settings.get_int(settings_pack::dht_announce_interval) + / (std::max)(int(m_torrents.size()), 1), 1); + + if (!m_dht_torrents.empty()) + { + // we have prioritized torrents that need + // an initial DHT announce. Don't wait too long + // until we announce those. + delay = (std::min)(4, delay); + } + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("session_impl::on_dht_announce"); +#endif + error_code ec; + m_dht_announce_timer.expires_from_now(seconds(delay), ec); + m_dht_announce_timer.async_wait( + bind(&session_impl::on_dht_announce, this, _1)); + + if (!m_dht_torrents.empty()) + { + boost::shared_ptr t; + do + { + t = m_dht_torrents.front().lock(); + m_dht_torrents.pop_front(); + } while (!t && !m_dht_torrents.empty()); + + if (t) + { + t->dht_announce(); + return; + } + } + if (m_torrents.empty()) return; + + if (m_next_dht_torrent == m_torrents.end()) + m_next_dht_torrent = m_torrents.begin(); + m_next_dht_torrent->second->dht_announce(); + // TODO: 2 make a list for torrents that want to be announced on the DHT so we + // don't have to loop over all torrents, just to find the ones that want to announce + ++m_next_dht_torrent; + if (m_next_dht_torrent == m_torrents.end()) + m_next_dht_torrent = m_torrents.begin(); + } +#endif + + void session_impl::on_lsd_announce(error_code const& e) + { +#if defined TORRENT_ASIO_DEBUGGING + complete_async("session_impl::on_lsd_announce"); +#endif + m_stats_counters.inc_stats_counter(counters::on_lsd_counter); + TORRENT_ASSERT(is_single_thread()); + if (e) return; + + if (m_abort) return; + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("session_impl::on_lsd_announce"); +#endif + // announce on local network every 5 minutes + int delay = (std::max)(m_settings.get_int(settings_pack::local_service_announce_interval) + / (std::max)(int(m_torrents.size()), 1), 1); + error_code ec; + m_lsd_announce_timer.expires_from_now(seconds(delay), ec); + m_lsd_announce_timer.async_wait( + bind(&session_impl::on_lsd_announce, this, _1)); + + if (m_torrents.empty()) return; + + if (m_next_lsd_torrent == m_torrents.end()) + m_next_lsd_torrent = m_torrents.begin(); + m_next_lsd_torrent->second->lsd_announce(); + ++m_next_lsd_torrent; + if (m_next_lsd_torrent == m_torrents.end()) + m_next_lsd_torrent = m_torrents.begin(); + } + + void session_impl::auto_manage_checking_torrents(std::vector& list + , int& limit) + { + for (std::vector::iterator i = list.begin() + , end(list.end()); i != end; ++i) + { + torrent* t = *i; + + TORRENT_ASSERT(t->state() == torrent_status::checking_files); + TORRENT_ASSERT(t->is_auto_managed()); + if (limit <= 0) + { + t->pause(); + } + else + { + t->resume(); + 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 (std::vector::iterator i = list.begin() + , end(list.end()); i != end; ++i) + { + torrent* t = *i; + + 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->allows_peers()) + t->log_to_all_peers("auto manager starting (inactive) torrent"); +#endif + t->set_allow_peers(true); + t->update_gauge(); + t->update_want_peers(); + 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->allows_peers()) + t->log_to_all_peers("auto manager starting torrent"); +#endif + t->set_allow_peers(true); + t->update_gauge(); + t->update_want_peers(); + continue; + } + +#ifndef TORRENT_DISABLE_LOGGING + if (t->allows_peers()) + t->log_to_all_peers("auto manager pausing torrent"); +#endif + // use graceful pause for auto-managed torrents + t->set_allow_peers(false, torrent::flag_graceful_pause + | torrent::flag_clear_disk_cache); + t->set_announce_to_dht(false); + t->set_announce_to_trackers(false); + t->set_announce_to_lsd(false); + t->update_gauge(); + t->update_want_peers(); + } + } + + 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 (is_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() + , boost::bind(&torrent::sequence_number, _1) < boost::bind(&torrent::sequence_number, _2)); + + std::partial_sort(downloaders.begin(), downloaders.begin() + + (std::min)(hard_limit, int(downloaders.size())), downloaders.end() + , boost::bind(&torrent::sequence_number, _1) < boost::bind(&torrent::sequence_number, _2)); + + std::partial_sort(seeds.begin(), seeds.begin() + + (std::min)(hard_limit, int(seeds.size())), seeds.end() + , boost::bind(&torrent::seed_rank, _1, boost::ref(m_settings)) + > boost::bind(&torrent::seed_rank, _2, boost::ref(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 { + bool last_optimistic_unchoke_cmp(torrent_peer const* const l + , torrent_peer const* const r) + { + return l->last_optimistically_unchoked + < r->last_optimistically_unchoked; + } + } + + void session_impl::recalculate_optimistic_unchoke_slots() + { + INVARIANT_CHECK; + + TORRENT_ASSERT(is_single_thread()); + if (m_stats_counters[counters::num_unchoke_slots] == 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 (connection_map::iterator i = m_connections.begin() + , end(m_connections.end()); i != end; ++i) + { + peer_connection* p = i->get(); + TORRENT_ASSERT(p); + torrent_peer* pi = p->peer_info_struct(); + if (!pi) continue; + if (pi->web_seed) continue; + + if (pi->optimistically_unchoked) + { + prev_opt_unchoke.push_back(pi); + } + + torrent* 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.push_back(pi); + } + } + + // 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 = 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(), &last_optimistic_unchoke_cmp); + +#ifndef TORRENT_DISABLE_EXTENSIONS + if (m_session_extension_features & plugin::optimistic_unchoke_feature) + { + // if there is an extension that wants to reorder the optimistic + // unchoke peers, first convert the vector into one containing + // peer_connection_handles, since that's the exported API + std::vector peers; + peers.reserve(opt_unchoke.size()); + for (std::vector::iterator i = opt_unchoke.begin() + , end(opt_unchoke.end()); i != end; ++i) + { + peers.push_back(peer_connection_handle(static_cast((*i)->connection)->self())); + } + for (ses_extension_list_t::iterator i = m_ses_extensions.begin() + , end(m_ses_extensions.end()); i != end; ++i) + { + if ((*i)->on_optimistic_unchoke(peers)) + break; + } + // then convert back to the internal torrent_peer pointers + opt_unchoke.clear(); + for (std::vector::iterator i = peers.begin() + , end(peers.end()); i != end; ++i) + { + opt_unchoke.push_back(i->native_handle()->peer_info_struct()); + } + } +#endif + + // unchoke the first num_opt_unchoke peers in the candidate set + // and make sure that the others are choked + std::vector::iterator opt_unchoke_end = opt_unchoke.begin() + + num_opt_unchoke; + + for (std::vector::iterator i = opt_unchoke.begin(); + i != opt_unchoke_end; ++i) + { + torrent_peer* pi = *i; + peer_connection* p = static_cast(pi->connection); + if (pi->optimistically_unchoked) + { +#ifndef TORRENT_DISABLE_LOGGING + p->peer_log(peer_log_alert::info, "OPTIMISTIC UNCHOKE" + , "already unchoked | session-time: %d" + , pi->last_optimistically_unchoked); +#endif + TORRENT_ASSERT(!pi->connection->is_choked()); + // remove this peer from prev_opt_unchoke, to prevent us from + // choking it later. This peer gets another round of optimistic + // unchoke + std::vector::iterator 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()); + boost::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 = boost::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 (std::vector::iterator i = prev_opt_unchoke.begin() + , end(prev_opt_unchoke.end()); i != end; ++i) + { + torrent_peer* pi = *i; + TORRENT_ASSERT(pi->optimistically_unchoked); + peer_connection* p = static_cast(pi->connection); + boost::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); + + // zero connections speeds are allowed, we just won't make any connections + if (max_connections <= 0) return; + + // 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 this second + 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; + } + } + + // TODO: use a lower limit than m_settings.connections_limit + // to allocate the to 10% or so of connection slots for incoming + // connections + int limit = m_settings.get_int(settings_pack::connections_limit) + - num_connections(); + + // 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; + + std::vector& want_peers_download = m_torrent_lists[torrent_want_peers_download]; + std::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; + + INVARIANT_CHECK; + + int steps_since_last_connect = 0; + int 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 = NULL; + // 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 != NULL + && t->want_peers()) break; + m_prio_torrents.pop_front(); + t = NULL; + } + + if (t == NULL) + { + if ((m_download_connect_attempts >= m_settings.get_int( + settings_pack::connect_seed_every_n_download) + && want_peers_finished.size()) + || 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->allows_peers()); + + TORRENT_TRY + { + if (t->try_connect_peer()) + { + --max_connections; + steps_since_last_connect = 0; + m_stats_counters.inc_stats_counter(counters::connection_attempts); + } + } + TORRENT_CATCH(std::bad_alloc&) + { + // we ran out of memory trying to connect to a peer + // lower the global limit to the number of peers + // we already have + m_settings.set_int(settings_pack::connections_limit, num_connections()); + if (m_settings.get_int(settings_pack::connections_limit) < 2) + m_settings.set_int(settings_pack::connections_limit, 2); + } + + ++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()); + INVARIANT_CHECK; + + time_point const now = aux::time_now(); + time_duration const unchoke_interval = now - m_last_choke; + m_last_choke = now; + + // 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 (connection_map::iterator i = m_connections.begin(); + i != m_connections.end();) + { + boost::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 == 0 || pi == 0 + || 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()); + } + + // the unchoker wants an estimate of our upload rate capacity + // (used by bittyrant) + int max_upload_rate = upload_rate_limit(m_global_class); + if (m_settings.get_int(settings_pack::choking_algorithm) + == settings_pack::bittyrant_choker + && max_upload_rate == 0) + { + // we don't know at what rate we can upload. If we have a + // measurement of the peak, use that + 10kB/s, otherwise + // assume 20 kB/s + max_upload_rate = (std::max)(20000, m_peak_up_rate + 10000); + if (m_alerts.should_post()) + m_alerts.emplace_alert(torrent_handle() + , performance_alert::bittyrant_with_no_uplimit); + } + + int const allowed_upload_slots = unchoke_sort(peers, max_upload_rate + , unchoke_interval, m_settings); + + m_stats_counters.set_value(counters::num_unchoke_slots + , allowed_upload_slots); + +#ifndef TORRENT_DISABLE_LOGGING + session_log("RECALCULATE UNCHOKE SLOTS: [ peers: %d " + "eligible-peers: %d" + " max_upload_rate: %d" + " allowed-slots: %d ]" + , int(m_connections.size()) + , int(peers.size()) + , max_upload_rate + , allowed_upload_slots); +#endif + + int const unchoked_counter_optimistic + = 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 (std::vector::iterator i = peers.begin() + , end(peers.end()); i != end; ++i) + { + peer_connection* p = *i; + TORRENT_ASSERT(p); + 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); + } + } + } + + void session_impl::cork_burst(peer_connection* p) + { + TORRENT_ASSERT(is_single_thread()); + if (p->is_corked()) return; + p->cork_socket(); + m_delayed_uncorks.push_back(p); + } + + void session_impl::do_delayed_uncork() + { + m_stats_counters.inc_stats_counter(counters::on_disk_counter); + TORRENT_ASSERT(is_single_thread()); + for (std::vector::iterator i = m_delayed_uncorks.begin() + , end(m_delayed_uncorks.end()); i != end; ++i) + { + (*i)->uncork_socket(); + } + m_delayed_uncorks.clear(); + } + + boost::shared_ptr session_impl::delay_load_torrent(sha1_hash const& info_hash + , peer_connection* pc) + { +#ifndef TORRENT_DISABLE_EXTENSIONS + for (ses_extension_list_t::iterator i = m_ses_extensions.begin() + , end(m_ses_extensions.end()); i != end; ++i) + { + add_torrent_params p; + if ((*i)->on_unknown_torrent(info_hash, peer_connection_handle(pc->self()), p)) + { + error_code ec; + torrent_handle handle = add_torrent(p, ec); + + return handle.native_handle(); + } + } +#else + TORRENT_UNUSED(pc); + TORRENT_UNUSED(info_hash); +#endif + return boost::shared_ptr(); + } + + // the return value from this function is valid only as long as the + // session is locked! + boost::weak_ptr session_impl::find_torrent(sha1_hash const& info_hash) const + { + TORRENT_ASSERT(is_single_thread()); + + torrent_map::const_iterator i = m_torrents.find(info_hash); +#if defined TORRENT_DEBUG && defined TORRENT_EXPENSIVE_INVARIANT_CHECKS + for (torrent_map::const_iterator j + = m_torrents.begin(); j != m_torrents.end(); ++j) + { + torrent* p = boost::get_pointer(j->second); + TORRENT_ASSERT(p); + } +#endif + if (i != m_torrents.end()) return i->second; + return boost::weak_ptr(); + } + + void session_impl::insert_torrent(sha1_hash const& ih, boost::shared_ptr const& t + , std::string uuid) + { + m_torrents.insert(std::make_pair(ih, t)); + if (!uuid.empty()) m_uuids.insert(std::make_pair(uuid, t)); + + TORRENT_ASSERT(m_torrents.size() >= m_torrent_lru.size()); + } + + void session_impl::set_queue_position(torrent* me, int p) + { + if (p >= 0 && me->queue_position() == -1) + { + for (session_impl::torrent_map::iterator i = m_torrents.begin() + , end(m_torrents.end()); i != end; ++i) + { + torrent* t = i->second.get(); + if (t->queue_position() >= p) + { + t->set_queue_position_impl(t->queue_position()+1); + t->state_updated(); + } + if (t->queue_position() >= p) t->set_queue_position_impl(t->queue_position()+1); + } + ++m_max_queue_pos; + me->set_queue_position_impl((std::min)(m_max_queue_pos, p)); + } + else if (p < 0) + { + TORRENT_ASSERT(me->queue_position() >= 0); + TORRENT_ASSERT(p == -1); + for (session_impl::torrent_map::iterator i = m_torrents.begin() + , end(m_torrents.end()); i != end; ++i) + { + torrent* t = i->second.get(); + if (t == me) continue; + if (t->queue_position() == -1) continue; + if (t->queue_position() >= me->queue_position()) + { + t->set_queue_position_impl(t->queue_position()-1); + t->state_updated(); + } + } + --m_max_queue_pos; + me->set_queue_position_impl(p); + } + else if (p < me->queue_position()) + { + for (session_impl::torrent_map::iterator i = m_torrents.begin() + , end(m_torrents.end()); i != end; ++i) + { + torrent* t = i->second.get(); + if (t == me) continue; + if (t->queue_position() == -1) continue; + if (t->queue_position() >= p + && t->queue_position() < me->queue_position()) + { + t->set_queue_position_impl(t->queue_position()+1); + t->state_updated(); + } + } + me->set_queue_position_impl(p); + } + else if (p > me->queue_position()) + { + for (session_impl::torrent_map::iterator i = m_torrents.begin() + , end(m_torrents.end()); i != end; ++i) + { + torrent* t = i->second.get(); + int pos = t->queue_position(); + if (t == me) continue; + if (pos == -1) continue; + + if (pos <= p + && pos > me->queue_position() + && pos != -1) + { + t->set_queue_position_impl(t->queue_position()-1); + t->state_updated(); + } + + } + me->set_queue_position_impl((std::min)(m_max_queue_pos, p)); + } + + trigger_auto_manage(); + } + +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + 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; + + torrent_map::iterator i = m_obfuscated_torrents.find(obfuscated); + if (i == m_obfuscated_torrents.end()) return NULL; + return i->second.get(); + } +#endif + + boost::weak_ptr session_impl::find_torrent(std::string const& uuid) const + { + TORRENT_ASSERT(is_single_thread()); + + std::map >::const_iterator i + = m_uuids.find(uuid); + if (i != m_uuids.end()) return i->second; + return boost::weak_ptr(); + } + +#ifndef TORRENT_DISABLE_MUTABLE_TORRENTS + std::vector > session_impl::find_collection( + std::string const& collection) const + { + std::vector > ret; + for (session_impl::torrent_map::const_iterator i = m_torrents.begin() + , end(m_torrents.end()); i != end; ++i) + { + boost::shared_ptr t = i->second; + if (!t) continue; + std::vector const& c = t->torrent_file().collections(); + if (std::count(c.begin(), c.end(), collection) == 0) continue; + ret.push_back(t); + } + return ret; + } +#endif //TORRENT_DISABLE_MUTABLE_TORRENTS + + namespace { + + // returns true if lhs is a better disconnect candidate than rhs + bool compare_disconnect_torrent(session_impl::torrent_map::value_type const& lhs + , session_impl::torrent_map::value_type const& rhs) + { + // a torrent with 0 peers is never a good disconnect candidate + // since there's nothing to disconnect + if ((lhs.second->num_peers() == 0) != (rhs.second->num_peers() == 0)) + return lhs.second->num_peers() != 0; + + // other than that, always prefer to disconnect peers from seeding torrents + // in order to not harm downloading ones + if (lhs.second->is_seed() != rhs.second->is_seed()) + return lhs.second->is_seed(); + + return lhs.second->num_peers() > rhs.second->num_peers(); + } + + } // anonymous namespace + + boost::weak_ptr session_impl::find_disconnect_candidate_torrent() const + { + aux::session_impl::torrent_map::const_iterator 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 boost::shared_ptr(); + + return i->second; + } + +#ifndef TORRENT_DISABLE_LOGGING + TORRENT_FORMAT(2,3) + void session_impl::session_log(char const* fmt, ...) const + { + if (!m_alerts.should_post()) return; + + va_list v; + va_start(v, fmt); + session_vlog(fmt, v); + va_end(v); + } + + TORRENT_FORMAT(2, 0) + void session_impl::session_vlog(char const* fmt, va_list& v) const + { + if (!m_alerts.should_post()) return; + + char buf[1024]; + vsnprintf(buf, sizeof(buf), fmt, v); + m_alerts.emplace_alert(buf); + } +#endif + + void session_impl::get_torrent_status(std::vector* ret + , boost::function const& pred + , boost::uint32_t flags) const + { + for (torrent_map::const_iterator i + = m_torrents.begin(), end(m_torrents.end()); + i != end; ++i) + { + if (i->second->is_aborted()) continue; + torrent_status st; + i->second->status(&st, flags); + if (!pred(st)) continue; + ret->push_back(st); + } + } + + void session_impl::refresh_torrent_status(std::vector* ret + , boost::uint32_t flags) const + { + for (std::vector::iterator i + = ret->begin(), end(ret->end()); i != end; ++i) + { + boost::shared_ptr t = i->handle.m_torrent.lock(); + if (!t) continue; + t->status(&*i, flags); + } + } + + void session_impl::post_torrent_updates(boost::uint32_t 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 (std::vector::iterator i = state_updates.begin() + , end(state_updates.end()); i != end; ++i) + { + torrent* t = *i; + TORRENT_ASSERT(t->m_links[aux::session_impl::torrent_state_updates].in_list()); + status.push_back(torrent_status()); + // 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(status); + } + + void session_impl::post_session_stats() + { + 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::sent_ip_overhead_bytes + , m_stat.total_transfer(stat::upload_ip_protocol)); + + m_stats_counters.set_value(counters::recv_ip_overhead_bytes + , m_stat.total_transfer(stat::download_ip_protocol)); + + m_stats_counters.set_value(counters::limiter_up_queue + , m_upload_rate.queue_size()); + m_stats_counters.set_value(counters::limiter_down_queue + , m_download_rate.queue_size()); + + m_stats_counters.set_value(counters::limiter_up_bytes + , m_upload_rate.queued_bytes()); + m_stats_counters.set_value(counters::limiter_down_bytes + , m_download_rate.queued_bytes()); + + m_alerts.emplace_alert(m_stats_counters); + } + + void session_impl::post_dht_stats() + { + std::vector requests; + std::vector table; + +#ifndef TORRENT_DISABLE_DHT + if (m_dht) + m_dht->dht_status(table, requests); +#endif + + m_alerts.emplace_alert(table, requests); + } + + std::vector session_impl::get_torrents() const + { + std::vector ret; + + for (torrent_map::const_iterator i + = m_torrents.begin(), end(m_torrents.end()); + i != end; ++i) + { + if (i->second->is_aborted()) continue; + ret.push_back(torrent_handle(i->second)); + } + return ret; + } + + torrent_handle session_impl::find_torrent_handle(sha1_hash const& info_hash) + { + return torrent_handle(find_torrent(info_hash)); + } + + void session_impl::async_add_torrent(add_torrent_params* params) + { + if (string_begins_no_case("file://", params->url.c_str()) && !params->ti) + { + m_disk_thread.async_load_torrent(params + , boost::bind(&session_impl::on_async_load_torrent, this, _1)); + return; + } + + error_code ec; + torrent_handle handle = add_torrent(*params, ec); + delete params; + } + + void session_impl::on_async_load_torrent(disk_io_job const* j) + { + add_torrent_params* params = static_cast(j->requester); + error_code ec; + torrent_handle handle; + if (j->error.ec) + { + ec = j->error.ec; + m_alerts.emplace_alert(handle, *params, ec); + } + else + { + params->url.clear(); + params->ti = boost::shared_ptr(j->buffer.torrent_file); + handle = add_torrent(*params, ec); + } + + delete params; + } + +#ifndef TORRENT_DISABLE_EXTENSIONS + void session_impl::add_extensions_to_torrent( + boost::shared_ptr const& torrent_ptr, void* userdata) + { + for (ses_extension_list_t::iterator i = m_ses_extensions.begin() + , end(m_ses_extensions.end()); i != end; ++i) + { + boost::shared_ptr tp((*i)->new_torrent(torrent_ptr->get_handle(), userdata)); + if (tp) torrent_ptr->add_extension(tp); + } + } +#endif + + torrent_handle session_impl::add_torrent(add_torrent_params const& p + , error_code& ec) + { + // params is updated by add_torrent_impl() + add_torrent_params params = p; + boost::shared_ptr torrent_ptr; + bool added; + boost::tie(torrent_ptr, added) = add_torrent_impl(params, ec); + + torrent_handle const handle(torrent_ptr); + m_alerts.emplace_alert(handle, params, ec); + + if (!torrent_ptr) return handle; + + // params.info_hash should have been initialized by add_torrent_impl() + TORRENT_ASSERT(params.info_hash != sha1_hash(0)); + + // --- PEERS --- (delete when merged to master) + std::vector peers; + parse_magnet_uri_peers(p.url, peers); + + for (std::vector::const_iterator i = peers.begin() + , end(peers.end()); i != end; ++i) + { + torrent_ptr->add_peer(*i , peer_info::resume_data); + } + + if (!peers.empty()) + torrent_ptr->update_want_peers(); + +#ifndef TORRENT_DISABLE_DHT + if (params.ti) + { + torrent_info::nodes_t const& nodes = params.ti->nodes(); + for (std::vector >::const_iterator i = nodes.begin() + , end(nodes.end()); i != end; ++i) + { + add_dht_node_name(*i); + } + } +#endif + + if (m_alerts.should_post()) + m_alerts.emplace_alert(handle); + + // if this was an existing torrent, we can't start it again, or add + // another set of plugins etc. we're done + if (!added) return handle; + + torrent_ptr->set_ip_filter(m_ip_filter); + torrent_ptr->start(params); + +#ifndef TORRENT_DISABLE_EXTENSIONS + typedef std::vector(torrent_handle const&, void*)> > + torrent_plugins_t; + + for (torrent_plugins_t::const_iterator i = params.extensions.begin() + , end(params.extensions.end()); i != end; ++i) + { + torrent_ptr->add_extension((*i)(handle, params.userdata)); + } + + add_extensions_to_torrent(torrent_ptr, params.userdata); +#endif + +#if TORRENT_HAS_BOOST_UNORDERED + sha1_hash next_lsd(0); + sha1_hash next_dht(0); + if (m_next_lsd_torrent != m_torrents.end()) + next_lsd = m_next_lsd_torrent->first; +#ifndef TORRENT_DISABLE_DHT + if (m_next_dht_torrent != m_torrents.end()) + next_dht = m_next_dht_torrent->first; +#endif + float load_factor = m_torrents.load_factor(); +#endif // TORRENT_HAS_BOOST_UNORDERED + + m_torrents.insert(std::make_pair(params.info_hash, torrent_ptr)); + + TORRENT_ASSERT(m_torrents.size() >= m_torrent_lru.size()); + +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + hasher h; + h.update("req2", 4); + h.update(params.info_hash.data(), 20); + // this is SHA1("req2" + info-hash), used for + // encrypted hand shakes + m_obfuscated_torrents.insert(std::make_pair(h.final(), torrent_ptr)); +#endif + + if (torrent_ptr->is_pinned() == false) + { + evict_torrents_except(torrent_ptr.get()); + bump_torrent(torrent_ptr.get()); + } + +#if TORRENT_HAS_BOOST_UNORDERED + // if this insert made the hash grow, the iterators became invalid + // we need to reset them + if (m_torrents.load_factor() < load_factor) + { + // this indicates the hash table re-hashed + if (!next_lsd.is_all_zeros()) + m_next_lsd_torrent = m_torrents.find(next_lsd); +#ifndef TORRENT_DISABLE_DHT + if (!next_dht.is_all_zeros()) + m_next_dht_torrent = m_torrents.find(next_dht); +#endif + } +#endif // TORRENT_HAS_BOOST_UNORDERED + if (!params.uuid.empty() || !params.url.empty()) + m_uuids.insert(std::make_pair(params.uuid.empty() + ? params.url : params.uuid, torrent_ptr)); + + // recalculate auto-managed torrents sooner (or put it off) + // if another torrent will be added within one second from now + // we want to put it off again anyway. So that while we're adding + // a boat load of torrents, we postpone the recalculation until + // we're done adding them all (since it's kind of an expensive operation) + if (params.flags & add_torrent_params::flag_auto_managed) + { + const int max_downloading = settings().get_int(settings_pack::active_downloads); + const int max_seeds = settings().get_int(settings_pack::active_seeds); + const int max_active = settings().get_int(settings_pack::active_limit); + + const int num_downloading + = int(torrent_list(session_interface::torrent_downloading_auto_managed).size()); + const int num_seeds + = int(torrent_list(session_interface::torrent_seeding_auto_managed).size()); + const int num_active = num_downloading + num_seeds; + + // there's no point in triggering the auto manage logic early if we + // don't have a reason to believe anything will change. It's kind of + // expensive. + if ((num_downloading < max_downloading + || num_seeds < max_seeds) + && num_active < max_active) + { + trigger_auto_manage(); + } + } + + return handle; + } + + std::pair, bool> + session_impl::add_torrent_impl( + add_torrent_params& params + , error_code& ec) + { + TORRENT_ASSERT(!params.save_path.empty()); + + typedef boost::shared_ptr ptr_t; + +#ifndef TORRENT_NO_DEPRECATE + params.update_flags(); +#endif + + if (string_begins_no_case("magnet:", params.url.c_str())) + { + parse_magnet_uri(params.url, params, ec); + if (ec) return std::make_pair(ptr_t(), false); + params.url.clear(); + } + + if (string_begins_no_case("file://", params.url.c_str()) && !params.ti) + { + std::string filename = resolve_file_url(params.url); + boost::shared_ptr t = boost::make_shared(filename, boost::ref(ec), 0); + if (ec) return std::make_pair(ptr_t(), false); + params.url.clear(); + params.ti = t; + } + + if (params.ti && !params.ti->is_valid()) + { + ec = errors::no_metadata; + return std::make_pair(ptr_t(), false); + } + + if (params.ti && params.ti->is_valid() && params.ti->num_files() == 0) + { + ec = errors::no_files_in_torrent; + return std::make_pair(ptr_t(), false); + } + +#ifndef TORRENT_DISABLE_DHT + // add params.dht_nodes to the DHT, if enabled + if (!params.dht_nodes.empty()) + { + for (std::vector >::const_iterator i = params.dht_nodes.begin() + , end(params.dht_nodes.end()); i != end; ++i) + { + add_dht_node_name(*i); + } + } +#endif + + INVARIANT_CHECK; + + if (is_aborted()) + { + ec = errors::session_is_closing; + return std::make_pair(ptr_t(), false); + } + + // figure out the info hash of the torrent and make sure params.info_hash + // is set correctly + if (params.ti) params.info_hash = params.ti->info_hash(); + else if (!params.url.empty()) + { + // in order to avoid info-hash collisions, for + // torrents where we don't have an info-hash, but + // just a URL, set the temporary info-hash to the + // hash of the URL. This will be changed once we + // have the actual .torrent file + params.info_hash = hasher(¶ms.url[0], params.url.size()).final(); + } + + // we don't have a torrent file. If the user provided + // resume data, there may be some metadata in there + // TODO: this logic could probably be less spaghetti looking by being + // moved to a function with early exits + if ((!params.ti || !params.ti->is_valid()) + && !params.resume_data.empty()) + { + int pos; + error_code err; + bdecode_node root; + bdecode_node info; +#ifndef TORRENT_DISABLE_LOGGING + session_log("adding magnet link with resume data"); +#endif + if (bdecode(¶ms.resume_data[0], ¶ms.resume_data[0] + + params.resume_data.size(), root, err, &pos) == 0 + && root.type() == bdecode_node::dict_t + && (info = root.dict_find_dict("info"))) + { +#ifndef TORRENT_DISABLE_LOGGING + session_log("found metadata in resume data"); +#endif + // verify the info-hash of the metadata stored in the resume file matches + // the torrent we're loading + + std::pair const buf = info.data_section(); + sha1_hash const resume_ih = hasher(buf.first, buf.second).final(); + + // if url is set, the info_hash is not actually the info-hash of the + // torrent, but the hash of the URL, until we have the full torrent + // only require the info-hash to match if we actually passed in one + if (resume_ih == params.info_hash + || !params.url.empty() + || params.info_hash.is_all_zeros()) + { +#ifndef TORRENT_DISABLE_LOGGING + session_log("info-hash matched"); +#endif + params.ti = boost::make_shared(resume_ih); + + if (params.ti->parse_info_section(info, err, 0)) + { +#ifndef TORRENT_DISABLE_LOGGING + session_log("successfully loaded metadata from resume file"); +#endif + // make the info-hash be the one in the resume file + params.info_hash = resume_ih; + } + else + { +#ifndef TORRENT_DISABLE_LOGGING + session_log("failed to load metadata from resume file: %s" + , err.message().c_str()); +#endif + } + } +#ifndef TORRENT_DISABLE_LOGGING + else + { + session_log("metadata info-hash failed"); + } +#endif + } +#ifndef TORRENT_DISABLE_LOGGING + else + { + session_log("no metadata found (\"%s\")", err.message().c_str()); + } +#endif + } + + // is the torrent already active? + boost::shared_ptr torrent_ptr = find_torrent(params.info_hash).lock(); + if (!torrent_ptr && !params.uuid.empty()) torrent_ptr = find_torrent(params.uuid).lock(); + // if we still can't find the torrent, look for it by url + if (!torrent_ptr && !params.url.empty()) + { + torrent_map::iterator i = std::find_if(m_torrents.begin() + , m_torrents.end(), boost::bind(&torrent::url, boost::bind(&std::pair >::second, _1)) == params.url); + if (i != m_torrents.end()) + torrent_ptr = i->second; + } + + if (torrent_ptr) + { + if ((params.flags & add_torrent_params::flag_duplicate_is_error) == 0) + { + if (!params.uuid.empty() && torrent_ptr->uuid().empty()) + torrent_ptr->set_uuid(params.uuid); + if (!params.url.empty() && torrent_ptr->url().empty()) + torrent_ptr->set_url(params.url); + if (!params.source_feed_url.empty() && torrent_ptr->source_feed_url().empty()) + torrent_ptr->set_source_feed_url(params.source_feed_url); + return std::make_pair(torrent_ptr, false); + } + + ec = errors::duplicate_torrent; + return std::make_pair(ptr_t(), false); + } + + int queue_pos = ++m_max_queue_pos; + + torrent_ptr = boost::make_shared(boost::ref(*this) + , 16 * 1024, queue_pos, boost::cref(params), boost::cref(params.info_hash)); + + return std::make_pair(torrent_ptr, true); + } + + void session_impl::update_outgoing_interfaces() + { + INVARIANT_CHECK; + std::string net_interfaces = m_settings.get_str(settings_pack::outgoing_interfaces); + + // declared in string_util.hpp + parse_comma_separated_string(net_interfaces, m_net_interfaces); + } + + 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); +#endif + s.set_option(tcp::acceptor::reuse_address(true), ec); + // 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(next_port()); + } + + if (!m_net_interfaces.empty()) + { + if (m_interface_index >= m_net_interfaces.size()) m_interface_index = 0; + std::string const& ifname = m_net_interfaces[m_interface_index++]; + + if (ec) return bind_ep; + + bind_ep.address(bind_to_device(m_io_service, s + , remote_address.is_v4() + ? boost::asio::ip::tcp::v4() + : boost::asio::ip::tcp::v6() + , ifname.c_str(), bind_ep.port(), ec)); + return bind_ep; + } + + // if we're not binding to a specific interface, bind + // to the same protocol family as the target endpoint + if (is_any(bind_ep.address())) + { +#if TORRENT_USE_IPV6 + if (remote_address.is_v6()) + bind_ep.address(address_v6::any()); + else +#endif + bind_ep.address(address_v4::any()); + } + + s.bind(bind_ep, ec); + return bind_ep; + } + + // 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. first look for the address + for (int i = 0; i < int(m_net_interfaces.size()); ++i) + { + error_code err; + address ip = address::from_string(m_net_interfaces[i].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 device = device_for_address(addr, m_io_service, ec); + if (ec) return false; + + // if no device was found to have this address, we fail + if (device.empty()) return false; + + for (int i = 0; i < int(m_net_interfaces.size()); ++i) + { + if (m_net_interfaces[i] == device) return true; + } + + return false; + } + + void session_impl::remove_torrent(const torrent_handle& h, int options) + { + INVARIANT_CHECK; + + boost::shared_ptr tptr = h.m_torrent.lock(); + if (!tptr) return; + + m_alerts.emplace_alert(tptr->get_handle() + , tptr->info_hash()); + + remove_torrent_impl(tptr, options); + + tptr->abort(); + tptr->set_queue_position(-1); + } + + void session_impl::remove_torrent_impl(boost::shared_ptr tptr + , int options) + { + // remove from uuid list + if (!tptr->uuid().empty()) + { + std::map >::iterator j + = m_uuids.find(tptr->uuid()); + if (j != m_uuids.end()) m_uuids.erase(j); + } + + torrent_map::iterator i = + m_torrents.find(tptr->torrent_file().info_hash()); + + // this torrent might be filed under the URL-hash + if (i == m_torrents.end() && !tptr->url().empty()) + { + std::string const& url = tptr->url(); + sha1_hash urlhash = hasher(&url[0], url.size()).final(); + i = m_torrents.find(urlhash); + } + + if (i == m_torrents.end()) return; + + torrent& t = *i->second; + if (options) + { + if (!t.delete_files(options)) + { + if (m_alerts.should_post()) + m_alerts.emplace_alert(t.get_handle() + , error_code(), t.torrent_file().info_hash()); + } + } + + if (m_torrent_lru.size() > 0 + && (t.prev != NULL || t.next != NULL || m_torrent_lru.front() == &t)) + m_torrent_lru.erase(&t); + + TORRENT_ASSERT(t.prev == NULL && t.next == NULL); + + tptr->update_gauge(); + +#if TORRENT_USE_ASSERTS + sha1_hash i_hash = t.torrent_file().info_hash(); +#endif +#ifndef TORRENT_DISABLE_DHT + if (i == m_next_dht_torrent) + ++m_next_dht_torrent; +#endif + if (i == m_next_lsd_torrent) + ++m_next_lsd_torrent; + + m_torrents.erase(i); + + TORRENT_ASSERT(m_torrents.size() >= m_torrent_lru.size()); + +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + hasher h; + h.update("req2", 4); + h.update(tptr->info_hash().data(), 20); + m_obfuscated_torrents.erase(h.final()); +#endif + +#ifndef TORRENT_DISABLE_DHT + if (m_next_dht_torrent == m_torrents.end()) + m_next_dht_torrent = m_torrents.begin(); +#endif + if (m_next_lsd_torrent == m_torrents.end()) + m_next_lsd_torrent = m_torrents.begin(); + + // this torrent may open up a slot for a queued torrent + trigger_auto_manage(); + + TORRENT_ASSERT(m_torrents.find(i_hash) == m_torrents.end()); + } + + void session_impl::update_listen_interfaces() + { + INVARIANT_CHECK; + + std::string net_interfaces = m_settings.get_str(settings_pack::listen_interfaces); + std::vector > new_listen_interfaces; + + // declared in string_util.hpp + parse_comma_separated_string_port(net_interfaces, new_listen_interfaces); + +#ifndef TORRENT_DISABLE_LOGGING + session_log("update listen interfaces: %s", net_interfaces.c_str()); +#endif + + // if the interface is the same and the socket is open + // don't do anything + if (new_listen_interfaces == m_listen_interfaces + && !m_listen_sockets.empty()) + return; + + m_listen_interfaces = new_listen_interfaces; + + // for backwards compatibility. Some components still only supports + // a single listen interface + m_listen_interface.address(address_v4::any()); + m_listen_interface.port(0); + if (m_listen_interfaces.size() > 0) + { + error_code ec; + m_listen_interface.port(m_listen_interfaces[0].second); + char const* device_name = m_listen_interfaces[0].first.c_str(); + + // if the first character is [, skip it since it may be an + // IPv6 address + m_listen_interface.address(address::from_string( + device_name[0] == '[' ? device_name + 1 : device_name, ec)); + if (ec) + { +#ifndef TORRENT_DISABLE_LOGGING + session_log("failed to treat %s as an IP address [ %s ]" + , device_name, ec.message().c_str()); +#endif + // it may have been a device name. + std::vector ifs = enum_net_interfaces(m_io_service, ec); + +#ifndef TORRENT_DISABLE_LOGGING + if (ec) + session_log("failed to enumerate interfaces [ %s ]" + , ec.message().c_str()); +#endif + + bool found = false; + for (int i = 0; i < int(ifs.size()); ++i) + { + // 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 (strcmp(ifs[i].name, device_name) != 0) continue; + m_listen_interface.address(ifs[i].interface_address); +#ifndef TORRENT_DISABLE_LOGGING + error_code err; + session_log("binding to %s" + , m_listen_interface.address().to_string(err).c_str()); +#endif + found = true; + break; + } + + if (!found) + { +#ifndef TORRENT_DISABLE_LOGGING + session_log("failed to find device %s", device_name); +#endif + // effectively disable whatever socket decides to bind to this + m_listen_interface.address(address_v4::loopback()); + } + } + } + } + + 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 (torrent_map::iterator i = m_torrents.begin() + , end(m_torrents.end()); i != end; ++i) + i->second->port_filter_updated(); + } + else + { + m_port_filter.add_rule(0, 1024, 0); + } + } + + void session_impl::update_auto_sequential() + { + for (torrent_map::iterator i = m_torrents.begin() + , end(m_torrents.end()); i != end; ++i) + i->second->update_auto_sequential(); + } + + void session_impl::update_max_failcount() + { + for (torrent_map::iterator i = m_torrents.begin() + , end(m_torrents.end()); i != end; ++i) + { + i->second->update_max_failcount(); + } + } + + void session_impl::update_proxy() + { + // in case we just set a socks proxy, we might have to + // open the socks incoming connection + if (!m_socks_listen_socket) open_new_incoming_socks_connection(); + m_udp_socket.set_proxy_settings(proxy()); + +#ifdef TORRENT_USE_OPENSSL + m_ssl_udp_socket.set_proxy_settings(proxy()); +#endif + } + + 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)) + start_dht(); + else + stop_dht(); +#endif + } + + void session_impl::update_peer_fingerprint() + { + // ---- generate a peer id ---- + std::string print = m_settings.get_str(settings_pack::peer_fingerprint); + if (print.size() > 20) print.resize(20); + + // the client's fingerprint + std::copy(print.begin(), print.begin() + print.length(), m_peer_id.begin()); + if (print.length() < 20) + { + url_random(m_peer_id.data() + print.length(), m_peer_id.data() + 20); + } + } + + void session_impl::update_dht_bootstrap_nodes() + { +#ifndef TORRENT_DISABLE_DHT + 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); + + for (int i = 0; i < nodes.size(); ++i) + { + add_dht_router(nodes[i]); + } +#endif + } + + void session_impl::update_count_slow() + { + error_code ec; + for (torrent_map::const_iterator i = m_torrents.begin() + , end(m_torrents.end()); i != end; ++i) + { + i->second->on_inactivity_tick(ec); + } + } + + address session_impl::listen_address() const + { + for (std::list::const_iterator i = m_listen_sockets.begin() + , end(m_listen_sockets.end()); i != end; ++i) + { + if (i->external_address != address()) return i->external_address; + } + return address(); + } + + boost::uint16_t session_impl::listen_port() const + { + // if peer connections are set up to be received over a socks + // proxy, and it's the same one as we're using for the tracker + // just tell the tracker the socks5 port we're listening on + if (m_socks_listen_socket && m_socks_listen_socket->is_open()) + return m_socks_listen_socket->local_endpoint().port(); + + // if not, don't tell the tracker anything if we're in force_proxy + // mode. We don't want to leak our listen port since it can + // potentially identify us if it is leaked elsewere + if (m_settings.get_bool(settings_pack::force_proxy)) return 0; + if (m_listen_sockets.empty()) return 0; + return m_listen_sockets.front().external_port; + } + + boost::uint16_t session_impl::ssl_listen_port() const + { +#ifdef TORRENT_USE_OPENSSL + // if peer connections are set up to be received over a socks + // proxy, and it's the same one as we're using for the tracker + // just tell the tracker the socks5 port we're listening on + if (m_socks_listen_socket && m_socks_listen_socket->is_open()) + return m_socks_listen_port; + + // if not, don't tell the tracker anything if we're in force_proxy + // mode. We don't want to leak our listen port since it can + // potentially identify us if it is leaked elsewere + if (m_settings.get_bool(settings_pack::force_proxy)) return 0; + for (std::list::const_iterator i = m_listen_sockets.begin() + , end(m_listen_sockets.end()); i != end; ++i) + { + if (i->ssl) return i->external_port; + } + + if (m_ssl_udp_socket.is_open()) + return m_ssl_udp_socket.local_port(); +#endif + return 0; + } + + void session_impl::announce_lsd(sha1_hash const& ih, int port, bool broadcast) + { + // use internal listen port for local peers + if (m_lsd) + m_lsd->announce(ih, port, broadcast); + } + + void session_impl::on_lsd_peer(tcp::endpoint peer, sha1_hash const& ih) + { + m_stats_counters.inc_stats_counter(counters::on_lsd_peer_counter); + TORRENT_ASSERT(is_single_thread()); + + INVARIANT_CHECK; + + boost::shared_ptr t = find_torrent(ih).lock(); + if (!t) return; + // don't add peers from lsd to private torrents + if (t->torrent_file().priv() || (t->torrent_file().is_i2p() + && !m_settings.get_bool(settings_pack::allow_i2p_mixed))) return; + +#ifndef TORRENT_DISABLE_LOGGING + session_log("added peer from local discovery: %s", print_endpoint(peer).c_str()); +#endif + t->add_peer(peer, peer_info::lsd); + t->do_connect_boost(); + + if (m_alerts.should_post()) + m_alerts.emplace_alert(t->get_handle(), peer); + } + + // TODO: perhaps this function should not exist when logging is disabled + void session_impl::on_port_map_log( + char const* msg, int map_transport) + { +#ifndef TORRENT_DISABLE_LOGGING + TORRENT_ASSERT(map_transport >= 0 && map_transport <= 1); + // log message + if (m_alerts.should_post()) + m_alerts.emplace_alert(map_transport, msg); +#else + TORRENT_UNUSED(msg); + TORRENT_UNUSED(map_transport); +#endif + } + + void session_impl::on_port_mapping(int mapping, address const& ip, int port + , int protocol, error_code const& ec, int map_transport) + { + TORRENT_ASSERT(is_single_thread()); + + TORRENT_ASSERT(map_transport >= 0 && map_transport <= 1); + + if (mapping == m_udp_mapping[map_transport] && port != 0) + { + m_external_udp_port = port; + if (m_alerts.should_post()) + m_alerts.emplace_alert(mapping, port + , map_transport, protocol == natpmp::udp + ? portmap_alert::udp : portmap_alert::tcp); + return; + } + + if (mapping == m_tcp_mapping[map_transport] && port != 0) + { + if (ip != address()) + { + // TODO: 1 report the proper address of the router as the source IP of + // this understanding of our external address, instead of the empty address + set_external_address(ip, source_router, address()); + } + + if (!m_listen_sockets.empty()) { + m_listen_sockets.front().external_address = ip; + m_listen_sockets.front().external_port = port; + } + if (m_alerts.should_post()) + m_alerts.emplace_alert(mapping, port + , map_transport, protocol == natpmp::udp + ? portmap_alert::udp : portmap_alert::tcp); + return; + } + + if (ec) + { + if (m_alerts.should_post()) + m_alerts.emplace_alert(mapping + , map_transport, ec); + } + else + { + if (m_alerts.should_post()) + m_alerts.emplace_alert(mapping, port + , map_transport, protocol == natpmp::udp + ? portmap_alert::udp : portmap_alert::tcp); + } + } + +#ifndef TORRENT_NO_DEPRECATE + 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 = m_stats_counters[counters::num_peers_connected]; + s.num_unchoked = m_stats_counters[counters::num_peers_up_unchoked_all]; + s.allowed_upload_slots = m_stats_counters[counters::num_unchoke_slots]; + + s.num_torrents + = 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 + = 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 = m_stats_counters[counters::limiter_up_queue]; + s.down_bandwidth_queue = m_stats_counters[counters::limiter_down_queue]; + + s.up_bandwidth_bytes_queue = m_stats_counters[counters::limiter_up_bytes]; + s.down_bandwidth_bytes_queue = m_stats_counters[counters::limiter_down_bytes]; + + s.disk_write_queue = m_stats_counters[counters::num_peers_down_disk]; + s.disk_read_queue = m_stats_counters[counters::num_peers_up_disk]; + + s.has_incoming_connections = m_stats_counters[counters::has_incoming_connections]; + + // 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_stat.total_transfer(stat::download_ip_protocol); + s.ip_overhead_upload_rate = m_stat.transfer_rate(stat::upload_ip_protocol); + s.total_ip_overhead_upload = m_stat.total_transfer(stat::upload_ip_protocol); + + // 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 = m_stats_counters[counters::utp_packet_loss]; + s.utp_stats.timeout = m_stats_counters[counters::utp_timeout]; + s.utp_stats.packets_in = m_stats_counters[counters::utp_packets_in]; + s.utp_stats.packets_out = m_stats_counters[counters::utp_packets_out]; + s.utp_stats.fast_retransmit = m_stats_counters[counters::utp_fast_retransmit]; + s.utp_stats.packet_resend = m_stats_counters[counters::utp_packet_resend]; + s.utp_stats.samples_above_target = m_stats_counters[counters::utp_samples_above_target]; + s.utp_stats.samples_below_target = m_stats_counters[counters::utp_samples_below_target]; + s.utp_stats.payload_pkts_in = m_stats_counters[counters::utp_payload_pkts_in]; + s.utp_stats.payload_pkts_out = m_stats_counters[counters::utp_payload_pkts_out]; + s.utp_stats.invalid_pkts_in = m_stats_counters[counters::utp_invalid_pkts_in]; + s.utp_stats.redundant_pkts_in = m_stats_counters[counters::utp_redundant_pkts_in]; + + s.utp_stats.num_idle = m_stats_counters[counters::num_utp_idle]; + s.utp_stats.num_syn_sent = m_stats_counters[counters::num_utp_syn_sent]; + s.utp_stats.num_connected = m_stats_counters[counters::num_utp_connected]; + s.utp_stats.num_fin_sent = m_stats_counters[counters::num_utp_fin_sent]; + s.utp_stats.num_close_wait = m_stats_counters[counters::num_utp_close_wait]; + + // this loop is potentially expensive. It could be optimized by + // simply keeping a global counter + int peerlist_size = 0; + for (torrent_map::const_iterator i = m_torrents.begin() + , end(m_torrents.end()); i != end; ++i) + { + peerlist_size += i->second->num_known_peers(); + } + + s.peerlist_size = peerlist_size; + + return s; + } +#endif // TORRENT_NO_DEPRECATE + +#ifndef TORRENT_DISABLE_DHT + + void session_impl::start_dht() + { start_dht(m_dht_state); } + + namespace { + + void on_bootstrap(alert_manager& alerts) + { + if (alerts.should_post()) + alerts.emplace_alert(); + } + } + + void session_impl::start_dht(entry const& startup_state) + { + INVARIANT_CHECK; + + stop_dht(); + + // postpone starting the DHT if we're still resolving the DHT router + if (m_outstanding_router_lookups > 0) return; + + m_dht = boost::make_shared(static_cast(this) + , boost::ref(m_udp_socket), boost::cref(m_dht_settings) + , boost::ref(m_stats_counters) + , m_dht_storage_constructor + , startup_state); + + for (std::vector::iterator i = m_dht_router_nodes.begin() + , end(m_dht_router_nodes.end()); i != end; ++i) + { + m_dht->add_router_node(*i); + } + + for (std::vector::iterator i = m_dht_nodes.begin() + , end(m_dht_nodes.end()); i != end; ++i) + { + m_dht->add_node(*i); + } + m_dht_nodes.clear(); + + m_dht->start(startup_state, boost::bind(&on_bootstrap, boost::ref(m_alerts))); + + m_udp_socket.subscribe(m_dht.get()); + } + + void session_impl::stop_dht() + { + if (!m_dht) return; + m_udp_socket.unsubscribe(m_dht.get()); + m_dht->stop(); + m_dht.reset(); + } + + void session_impl::set_dht_settings(dht_settings const& settings) + { + m_dht_settings = settings; + } + + void session_impl::set_dht_storage(dht::dht_storage_constructor_type sc) + { + m_dht_storage_constructor = sc; + } + +#ifndef TORRENT_NO_DEPRECATE + entry session_impl::dht_state() const + { + if (!m_dht) return entry(); + return m_dht->state(); + } + + void session_impl::start_dht_deprecated(entry const& startup_state) + { + m_settings.set_bool(settings_pack::enable_dht, true); + start_dht(startup_state); + } +#endif + + void session_impl::add_dht_node_name(std::pair const& node) + { +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("session_impl::on_dht_name_lookup"); +#endif + m_host_resolver.async_resolve(node.first, resolver_interface::abort_on_shutdown + , boost::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) + { +#if defined TORRENT_ASIO_DEBUGGING + complete_async("session_impl::on_dht_name_lookup"); +#endif + + if (e) + { + if (m_alerts.should_post()) + m_alerts.emplace_alert( + dht_error_alert::hostname_lookup, e); + return; + } + + for (std::vector
    ::const_iterator i = addresses.begin() + , end(addresses.end()); i != end; ++i) + { + udp::endpoint ep(*i, port); + add_dht_node(ep); + } + } + + void session_impl::add_dht_router(std::pair const& node) + { +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("session_impl::on_dht_router_name_lookup"); +#endif + ++m_outstanding_router_lookups; + m_host_resolver.async_resolve(node.first, resolver_interface::abort_on_shutdown + , boost::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) + { +#if defined TORRENT_ASIO_DEBUGGING + complete_async("session_impl::on_dht_router_name_lookup"); +#endif + --m_outstanding_router_lookups; + + if (e) + { + if (m_alerts.should_post()) + m_alerts.emplace_alert( + dht_error_alert::hostname_lookup, e); + + if (m_outstanding_router_lookups == 0) update_dht(); + return; + } + + + for (std::vector
    ::const_iterator i = addresses.begin() + , end(addresses.end()); i != end; ++i) + { + // router nodes should be added before the DHT is started (and bootstrapped) + udp::endpoint ep(*i, port); + if (m_dht) m_dht->add_router_node(ep); + m_dht_router_nodes.push_back(ep); + } + + if (m_outstanding_router_lookups == 0) update_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, boost::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 authoritative) + { + TORRENT_ASSERT(i.is_mutable()); + m_alerts.emplace_alert(i.pk(), i.sig(), i.seq() + , i.salt(), i.value(), authoritative); + } + + // key is a 32-byte binary string, the public key to look up. + // the salt is optional + void session_impl::dht_get_mutable_item(boost::array key + , std::string salt) + { + if (!m_dht) return; + m_dht->get_item(key.data(), boost::bind(&session_impl::get_mutable_callback + , this, _1, _2), salt); + } + + namespace { + + void on_dht_put_immutable_item(alert_manager& alerts, sha1_hash target, int num) + { + if (alerts.should_post()) + alerts.emplace_alert(target, num); + } + + void on_dht_put_mutable_item(alert_manager& alerts, dht::item const& i, int num) + { + boost::array sig = i.sig(); + boost::array pk = i.pk(); + boost::uint64_t seq = i.seq(); + std::string salt = i.salt(); + + if (alerts.should_post()) + alerts.emplace_alert(pk, sig, salt, seq, num); + } + + void put_mutable_callback(dht::item& i + , boost::function& + , boost::uint64_t&, std::string const&)> cb) + { + entry value = i.value(); + boost::array sig = i.sig(); + boost::array pk = i.pk(); + boost::uint64_t seq = i.seq(); + std::string salt = i.salt(); + cb(value, sig, seq, salt); + i.assign(value, salt, seq, pk.data(), sig.data()); + } + + void on_dht_get_peers(alert_manager& alerts, sha1_hash info_hash, std::vector const& peers) + { + if (alerts.should_post()) + alerts.emplace_alert(info_hash, peers); + } + + void on_direct_response(alert_manager& alerts, void* userdata, dht::msg const& msg) + { + if (msg.message.type() == bdecode_node::none_t) + alerts.emplace_alert(userdata, msg.addr); + else + alerts.emplace_alert(userdata, msg.addr, msg.message); + } + + } // anonymous namespace + + void session_impl::dht_put_immutable_item(entry const& data, sha1_hash target) + { + if (!m_dht) return; + m_dht->put_item(data, boost::bind(&on_dht_put_immutable_item, boost::ref(m_alerts) + , target, _1)); + } + + void session_impl::dht_put_mutable_item(boost::array key + , boost::function& + , boost::uint64_t&, std::string const&)> cb + , std::string salt) + { + if (!m_dht) return; + m_dht->put_item(key.data(), + boost::bind(&on_dht_put_mutable_item, boost::ref(m_alerts), _1, _2), + boost::bind(&put_mutable_callback, _1, cb), salt); + } + + void session_impl::dht_get_peers(sha1_hash const& info_hash) + { + if (!m_dht) return; + m_dht->get_peers(info_hash, boost::bind(&on_dht_get_peers, boost::ref(m_alerts), info_hash, _1)); + } + + void session_impl::dht_announce(sha1_hash const& info_hash, int port, int flags) + { + if (!m_dht) return; + m_dht->announce(info_hash, port, flags, boost::bind(&on_dht_get_peers, boost::ref(m_alerts), info_hash, _1)); + } + + void session_impl::dht_direct_request(udp::endpoint ep, entry& e, void* userdata) + { + if (!m_dht) return; + m_dht->direct_request(ep, e, boost::bind(&on_direct_response, boost::ref(m_alerts), userdata, _1)); + } + +#endif + + void session_impl::maybe_update_udp_mapping(int const nat, bool const ssl + , int const local_port, int const external_port) + { + int local, external, protocol; +#ifdef TORRENT_USE_OPENSSL + int* mapping = ssl ? m_ssl_udp_mapping : m_udp_mapping; +#else + TORRENT_UNUSED(ssl); + int* mapping = m_udp_mapping; +#endif + if (nat == 0 && m_natpmp) + { + if (mapping[nat] != -1) + { + if (m_natpmp->get_mapping(mapping[nat], local, external, protocol)) + { + // we already have a mapping. If it's the same, don't do anything + if (local == local_port && external == external_port && protocol == natpmp::udp) + return; + } + m_natpmp->delete_mapping(mapping[nat]); + } + mapping[nat] = m_natpmp->add_mapping(natpmp::udp + , local_port, external_port); + return; + } + else if (nat == 1 && m_upnp) + { + if (mapping[nat] != -1) + { + if (m_upnp->get_mapping(mapping[nat], local, external, protocol)) + { + // we already have a mapping. If it's the same, don't do anything + if (local == local_port && external == external_port && protocol == natpmp::udp) + return; + } + m_upnp->delete_mapping(mapping[nat]); + } + mapping[nat] = m_upnp->add_mapping(upnp::udp + , local_port, external_port); + return; + } + } + +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + void session_impl::add_obfuscated_hash(sha1_hash const& obfuscated + , boost::weak_ptr const& t) + { + m_obfuscated_torrents.insert(std::make_pair(obfuscated, t.lock())); + } +#endif // !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + + bool session_impl::is_listening() const + { + return !m_listen_sockets.empty(); + } + + session_impl::~session_impl() + { + // this is not allowed to be the network thread! +// TORRENT_ASSERT(is_not_thread()); + + m_udp_socket.unsubscribe(this); + m_udp_socket.unsubscribe(&m_utp_socket_manager); + m_udp_socket.unsubscribe(&m_tracker_manager); + +#ifdef TORRENT_USE_OPENSSL + m_ssl_udp_socket.unsubscribe(this); + m_ssl_udp_socket.unsubscribe(&m_ssl_utp_socket_manager); +#endif + + TORRENT_ASSERT(m_torrents.empty()); + TORRENT_ASSERT(m_connections.empty()); + +#if defined TORRENT_ASIO_DEBUGGING + FILE* f = fopen("wakeups.log", "w+"); + if (f != NULL) + { + time_point m = min_time(); + if (_wakeups.size() > 0) m = _wakeups[0].timestamp; + time_point prev = m; + boost::uint64_t prev_csw = 0; + if (_wakeups.size() > 0) prev_csw = _wakeups[0].context_switches; + fprintf(f, "abs. time\trel. time\tctx switch\tidle-wakeup\toperation\n"); + for (int i = 0; i < _wakeups.size(); ++i) + { + wakeup_t const& w = _wakeups[i]; + bool idle_wakeup = w.context_switches > prev_csw; + 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 + + // clear the torrent LRU. We do this to avoid having the torrent + // destructor assert because it's still linked into the lru list +#if TORRENT_USE_ASSERTS + list_node* i = m_torrent_lru.get_all(); + // clear the prev and next pointers in all torrents + // to avoid the assert when destructing them + while (i) + { + list_node* tmp = i; + i = i->next; + tmp->next = NULL; + tmp->prev = NULL; + } +#endif + + } + +#ifndef TORRENT_NO_DEPRECATE + 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) + { + 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) + { + 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(int bytes_per_second) + { + 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(int bytes_per_second) + { + 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) + { + settings_pack p; + p.set_int(settings_pack::connections_limit, limit); + apply_settings_pack_impl(p); + } + + void session_impl::set_max_uploads(int limit) + { + 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() const + { + return upload_rate_limit(m_global_class); + } + + int session_impl::download_rate_limit() const + { + return download_rate_limit(m_global_class); + } +#endif + + // TODO: 2 this should be factored into the udp socket, so we only have the + // code once + void session_impl::update_peer_tos() + { + error_code ec; + +#if TORRENT_USE_IPV6 && defined IPV6_TCLASS + if (m_udp_socket.local_endpoint(ec).address().is_v6()) + m_udp_socket.set_option(traffic_class(m_settings.get_int(settings_pack::peer_tos)), ec); + else +#endif + m_udp_socket.set_option(type_of_service(m_settings.get_int(settings_pack::peer_tos)), ec); + +#ifdef TORRENT_USE_OPENSSL +#if TORRENT_USE_IPV6 && defined IPV6_TCLASS + if (m_ssl_udp_socket.local_endpoint(ec).address().is_v6()) + m_ssl_udp_socket.set_option(traffic_class(m_settings.get_int(settings_pack::peer_tos)), ec); + else +#endif + m_ssl_udp_socket.set_option(type_of_service(m_settings.get_int(settings_pack::peer_tos)), ec); +#endif + +#ifndef TORRENT_DISABLE_LOGGING + session_log(">>> SET_TOS [ udp_socket tos: %x e: %s ]" + , m_settings.get_int(settings_pack::peer_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); + } + } + + void session_impl::update_connection_speed() + { + if (m_settings.get_int(settings_pack::connection_speed) < 0) + m_settings.set_int(settings_pack::connection_speed, 200); + } + + void session_impl::update_queued_disk_bytes() + { + boost::uint64_t cache_size = m_settings.get_int(settings_pack::cache_size); + if (m_settings.get_int(settings_pack::max_queued_disk_bytes) / 16 / 1024 + > cache_size / 2 + && cache_size > 5 + && m_alerts.should_post()) + { + m_alerts.emplace_alert(torrent_handle() + , performance_alert::too_high_disk_queue_limit); + } + } + + void session_impl::update_alert_queue_size() + { + m_alerts.set_alert_queue_size_limit(m_settings.get_int(settings_pack::alert_queue_size)); + } + + bool session_impl::preemptive_unchoke() const + { + 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() + { + m_udp_socket.set_rate_limit(m_settings.get_int(settings_pack::dht_upload_rate_limit)); + } + + void session_impl::update_disk_threads() + { + if (m_settings.get_int(settings_pack::aio_threads) < 0) + m_settings.set_int(settings_pack::aio_threads, 0); + +#if !TORRENT_USE_PREAD && !TORRENT_USE_PREADV + // if we don't have pread() nor preadv() there's no way + // to perform concurrent file operations on the same file + // handle, so we must limit the disk thread to a single one + + if (m_settings.get_int(settings_pack::aio_threads) > 1) + m_settings.set_int(settings_pack::aio_threads, 1); +#endif + + m_disk_thread.set_num_threads(m_settings.get_int(settings_pack::aio_threads)); + } + + void session_impl::update_network_threads() + { + int num_threads = m_settings.get_int(settings_pack::network_threads); + int num_pools = num_threads > 0 ? num_threads : 1; + while (num_pools > m_net_thread_pool.size()) + { + m_net_thread_pool.push_back(boost::make_shared()); + m_net_thread_pool.back()->set_num_threads(num_threads > 0 ? 1 : 0); + } + + while (num_pools < m_net_thread_pool.size()) + { + m_net_thread_pool.erase(m_net_thread_pool.end() - 1); + } + + if (num_threads == 0 && m_net_thread_pool.size() > 0) + { + m_net_thread_pool[0]->set_num_threads(0); + } + } + + void session_impl::post_socket_job(socket_job& j) + { + uintptr_t idx = 0; + if (m_net_thread_pool.size() > 1) + { + // each peer needs to be pinned to a specific thread + // since reading and writing simultaneously on the same + // socket from different threads is not supported by asio. + // as long as a specific socket is consistently used from + // the same thread, it's safe + idx = uintptr_t(j.peer.get()); + idx ^= idx >> 8; + idx %= m_net_thread_pool.size(); + } + m_net_thread_pool[idx]->post_job(j); + } + + void session_impl::update_cache_buffer_chunk_size() + { + if (m_settings.get_int(settings_pack::cache_buffer_chunk_size) <= 0) + m_settings.set_int(settings_pack::cache_buffer_chunk_size, 1); + } + + 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 (connection_map::iterator i = m_connections.begin() + , end(m_connections.end()); i != end; ++i) + { + int type = (*i)->type(); + if (type == peer_connection::url_seed_connection + || type == peer_connection::http_seed_connection) + (*i)->ignore_stats(!report); + } + } + + void session_impl::trigger_auto_manage() + { + if (m_pending_auto_manage || m_abort) return; + + // we recalculated auto-managed torrents less than a second ago, + // put it off one second. + if (time_now() - m_last_auto_manage < seconds(1)) + { + m_auto_manage_time_scaler = 0; + return; + } + m_pending_auto_manage = true; + m_need_auto_manage = true; + + m_io_service.post(boost::bind(&session_impl::on_trigger_auto_manage, this)); + } + + 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() + { + error_code ec; + set_socket_buffer_size(m_udp_socket, m_settings, ec); + if (ec) + { + if (m_alerts.should_post()) + m_alerts.emplace_alert(udp::endpoint(), ec); + } + +#ifdef TORRENT_USE_OPENSSL + set_socket_buffer_size(m_ssl_udp_socket, m_settings, ec); + if (ec) + { + if (m_alerts.should_post()) + m_alerts.emplace_alert(udp::endpoint(), ec); + } +#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 == NULL"); +#endif + return; + } + + m_dht_interval_update_torrents = m_torrents.size(); + + if (m_abort) + { +#ifndef TORRENT_DISABLE_LOGGING + session_log("not starting DHT announce timer: m_abort set"); +#endif + return; + } + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("session_impl::on_dht_announce"); +#endif + error_code ec; + int delay = (std::max)(m_settings.get_int(settings_pack::dht_announce_interval) + / (std::max)(int(m_torrents.size()), 1), 1); + m_dht_announce_timer.expires_from_now(seconds(delay), ec); + m_dht_announce_timer.async_wait( + boost::bind(&session_impl::on_dht_announce, this, _1)); +#endif + } + + void session_impl::update_anonymous_mode() + { + if (!m_settings.get_bool(settings_pack::anonymous_mode)) return; + + m_settings.set_str(settings_pack::user_agent, ""); + url_random(m_peer_id.data(), m_peer_id.data() + 20); + } + + void session_impl::update_force_proxy() + { + m_udp_socket.set_force_proxy(m_settings.get_bool(settings_pack::force_proxy)); +#ifdef TORRENT_USE_OPENSSL + m_ssl_udp_socket.set_force_proxy(m_settings.get_bool(settings_pack::force_proxy)); +#endif + + if (!m_settings.get_bool(settings_pack::force_proxy)) return; + + // enable force_proxy mode. We don't want to accept any incoming + // connections, except through a proxy. + stop_lsd(); + stop_upnp(); + stop_natpmp(); +#ifndef TORRENT_DISABLE_DHT + stop_dht(); +#endif + // close the listen sockets + error_code ec; + for (std::list::iterator i = m_listen_sockets.begin() + , end(m_listen_sockets.end()); i != end; ++i) + i->sock->close(ec); + m_listen_sockets.clear(); + } + +#ifndef TORRENT_NO_DEPRECATE + 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 = (std::numeric_limits::max)(); + + limit = (std::max)(5, (std::min)(limit + , max_open_files() - 20 - m_settings.get_int(settings_pack::file_pool_size))); + + 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) / m_torrents.size(); + + // the number of slots that are unused by torrents + int extra = m_settings.get_int(settings_pack::connections_limit) % 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 (torrent_map::iterator i = m_torrents.begin() + , end(m_torrents.end()); i != end; ++i) + { + int num = i->second->num_peers(); + if (num <= last_average) continue; + if (num > average) ++num_above; + if (num < average) extra += average - num; + } + + // distribute extra among the torrents that are above average + if (num_above == 0) num_above = 1; + last_average = average; + average += extra / num_above; + if (extra == 0) break; + // save the remainder for the next iteration + extra = extra % num_above; + } + + for (torrent_map::iterator i = m_torrents.begin() + , end(m_torrents.end()); i != end; ++i) + { + int num = i->second->num_peers(); + if (num <= average) continue; + + // distribute the remainder + int my_average = average; + if (extra > 0) + { + ++my_average; + --extra; + } + + int disconnect = (std::min)(to_disconnect, num - my_average); + to_disconnect -= disconnect; + i->second->disconnect_peers(disconnect + , error_code(errors::too_many_connections, get_libtorrent_category())); + } + } + } + + void session_impl::update_alert_mask() + { + m_alerts.set_alert_mask(m_settings.get_int(settings_pack::alert_mask)); + } + + void session_impl::pop_alerts(std::vector* alerts) + { + int num_resume = 0; + m_alerts.get_all(*alerts, num_resume); + if (num_resume > 0) + { + // we can only issue more resume data jobs from + // the network thread + m_io_service.post(boost::bind(&session_impl::async_resume_dispatched + , this)); + } + } + +#ifndef TORRENT_NO_DEPRECATE + 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.add(peer_class_type_filter::utp_socket + , m_local_peer_class); + m_peer_class_type_filter.add(peer_class_type_filter::utp_socket + , m_global_class); + m_peer_class_type_filter.add(peer_class_type_filter::ssl_utp_socket + , m_local_peer_class); + m_peer_class_type_filter.add(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.remove(peer_class_type_filter::utp_socket + , m_local_peer_class); + m_peer_class_type_filter.remove(peer_class_type_filter::utp_socket + , m_global_class); + m_peer_class_type_filter.remove(peer_class_type_filter::ssl_utp_socket + , m_local_peer_class); + m_peer_class_type_filter.remove(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 >= 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 >= m_alert_pointers.size()) + { + pop_alerts(); + if (m_alert_pointers.empty()) + return NULL; + } + + if (m_alert_pointers.empty()) return NULL; + + // clone here to be backwards compatible, to make the client delete the + // alert object + return m_alert_pointers[m_alert_pointer_pos++]; + } + + + void session_impl::pop_alerts(std::deque* alerts) + { + alerts->clear(); + if (m_alert_pointer_pos >= m_alert_pointers.size()) + { + pop_alerts(); + if (m_alert_pointers.empty()) + return; + } + + for (std::vector::iterator i = m_alert_pointers.begin() + + m_alert_pointer_pos, end(m_alert_pointers.end()); + i != end; ++i) + { + alerts->push_back((*i)->clone().release()); + } + m_alert_pointer_pos = m_alert_pointers.size(); + } +#endif + + alert* session_impl::wait_for_alert(time_duration max_wait) + { + return m_alerts.wait_for_alert(max_wait); + } + +#ifndef TORRENT_NO_DEPRECATE + size_t session_impl::set_alert_queue_size_limit(size_t queue_size_limit_) + { + m_settings.set_int(settings_pack::alert_queue_size, queue_size_limit_); + return m_alerts.set_alert_queue_size_limit(queue_size_limit_); + } +#endif + + void session_impl::start_lsd() + { + INVARIANT_CHECK; + + if (m_lsd) return; + + m_lsd = boost::make_shared(boost::ref(m_io_service) + , boost::bind(&session_impl::on_lsd_peer, this, _1, _2) +#ifndef TORRENT_DISABLE_LOGGING + , boost::bind(&session_impl::on_lsd_log, this, _1) +#endif + ); + error_code ec; + m_lsd->start(ec); + if (ec && m_alerts.should_post()) + m_alerts.emplace_alert(ec); + } + +#ifndef TORRENT_DISABLE_LOGGING + void session_impl::on_lsd_log(char const* log) + { + if (!m_alerts.should_post()) return; + m_alerts.emplace_alert(log); + } +#endif + + natpmp* session_impl::start_natpmp() + { + INVARIANT_CHECK; + + if (m_natpmp) return m_natpmp.get(); + + // the natpmp constructor may fail and call the callbacks + // into the session_impl. + m_natpmp = boost::make_shared(boost::ref(m_io_service) + , boost::bind(&session_impl::on_port_mapping + , this, _1, _2, _3, _4, _5, 0) + , boost::bind(&session_impl::on_port_map_log + , this, _1, 0)); + m_natpmp->start(); + + int const ssl_port = ssl_listen_port(); + + if (m_listen_interface.port() > 0) + { + remap_tcp_ports(1, m_listen_interface.port(), ssl_port); + } + if (m_udp_socket.is_open()) + { + m_udp_mapping[0] = m_natpmp->add_mapping(natpmp::udp + , m_listen_interface.port(), m_listen_interface.port()); + } +#ifdef TORRENT_USE_OPENSSL + if (m_ssl_udp_socket.is_open() && ssl_port > 0) + { + m_ssl_udp_mapping[0] = m_natpmp->add_mapping(natpmp::udp + , ssl_port, ssl_port); + } +#endif + return m_natpmp.get(); + } + + upnp* session_impl::start_upnp() + { + INVARIANT_CHECK; + + if (m_upnp) return m_upnp.get(); + + // the upnp constructor may fail and call the callbacks + m_upnp = boost::make_shared(boost::ref(m_io_service) + , m_listen_interface.address() + , m_settings.get_str(settings_pack::user_agent) + , boost::bind(&session_impl::on_port_mapping + , this, _1, _2, _3, _4, _5, 1) + , boost::bind(&session_impl::on_port_map_log + , this, _1, 1) + , m_settings.get_bool(settings_pack::upnp_ignore_nonrouters)); + m_upnp->start(); + + int ssl_port = ssl_listen_port(); + + m_upnp->discover_device(); + if (m_listen_interface.port() > 0 || ssl_port > 0) + { + remap_tcp_ports(2, m_listen_interface.port(), ssl_port); + } + if (m_udp_socket.is_open()) + { + m_udp_mapping[1] = m_upnp->add_mapping(upnp::udp + , m_listen_interface.port(), m_listen_interface.port()); + } +#ifdef TORRENT_USE_OPENSSL + if (m_ssl_udp_socket.is_open() && ssl_port > 0) + { + m_ssl_udp_mapping[1] = m_upnp->add_mapping(upnp::udp + , ssl_port, ssl_port); + } +#endif + return m_upnp.get(); + } + + int session_impl::add_port_mapping(int t, int external_port + , int local_port) + { + int ret = 0; + if (m_upnp) ret = m_upnp->add_mapping(static_cast(t), external_port + , local_port); + if (m_natpmp) ret = m_natpmp->add_mapping(static_cast(t), external_port + , local_port); + return ret; + } + + void session_impl::delete_port_mapping(int handle) + { + if (m_upnp) m_upnp->delete_mapping(handle); + if (m_natpmp) m_natpmp->delete_mapping(handle); + } + + void session_impl::stop_lsd() + { + if (m_lsd) + m_lsd->close(); + m_lsd.reset(); + } + + void session_impl::stop_natpmp() + { + if (m_natpmp) + { + m_natpmp->close(); + m_udp_mapping[0] = -1; + m_tcp_mapping[0] = -1; +#ifdef TORRENT_USE_OPENSSL + m_ssl_tcp_mapping[0] = -1; + m_ssl_udp_mapping[0] = -1; +#endif + } + m_natpmp.reset(); + } + + void session_impl::stop_upnp() + { + if (m_upnp) + { + m_upnp->close(); + m_udp_mapping[1] = -1; + m_tcp_mapping[1] = -1; +#ifdef TORRENT_USE_OPENSSL + m_ssl_tcp_mapping[1] = -1; + m_ssl_udp_mapping[1] = -1; +#endif + } + m_upnp.reset(); + } + + external_ip const& session_impl::external_address() const + { + return m_external_ip; + } + + // this is the DHT observer version. DHT is the implied source + void session_impl::set_external_address(address const& ip + , address const& source) + { + set_external_address(ip, source_dht, source); + } + + address session_impl::external_address() + { + return m_external_ip.external_address(address_v4()); + } + + 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); + } + + // TODO: 2 perhaps DHT logging should be disabled by TORRENT_DISABLE_LOGGING + // too + TORRENT_FORMAT(3,4) + void session_impl::log(libtorrent::dht::dht_logger::module_t m, char const* fmt, ...) + { + if (!m_alerts.should_post()) return; + + va_list v; + va_start(v, fmt); + char buf[1024]; + vsnprintf(buf, sizeof(buf), fmt, v); + va_end(v); + m_alerts.emplace_alert(static_cast(m), buf); + } + + void session_impl::log_packet(message_direction_t dir, char const* pkt, int len + , udp::endpoint node) + { + if (!m_alerts.should_post()) return; + + dht_pkt_alert::direction_t d = dir == dht_logger::incoming_message + ? dht_pkt_alert::incoming : dht_pkt_alert::outgoing; + + m_alerts.emplace_alert(pkt, len, d, node); + } + + bool session_impl::on_dht_request(char const* query, int query_len + , dht::msg const& request, entry& response) + { +#ifndef TORRENT_DISABLE_EXTENSIONS + if (query_len > max_dht_query_length) return false; + + for (m_extension_dht_queries_t::iterator i = m_extension_dht_queries.begin(); + i != m_extension_dht_queries.end(); ++i) + { + if (query_len == i->query_len + && memcmp(i->query.data(), query, query_len) == 0 + && i->handler(request.addr, request.message, response)) + return true; + } +#else + TORRENT_UNUSED(query); + TORRENT_UNUSED(query_len); + TORRENT_UNUSED(request); + TORRENT_UNUSED(response); +#endif + return false; + } + + void session_impl::set_external_address(address const& ip + , int source_type, address const& source) + { +#ifndef TORRENT_DISABLE_LOGGING + session_log(": set_external_address(%s, %d, %s)", print_address(ip).c_str() + , source_type, print_address(source).c_str()); +#endif + + if (!m_external_ip.cast_vote(ip, source_type, source)) return; + +#ifndef TORRENT_DISABLE_LOGGING + session_log(" external IP updated"); +#endif + + if (m_alerts.should_post()) + m_alerts.emplace_alert(ip); + + for (torrent_map::iterator i = m_torrents.begin() + , end(m_torrents.end()); i != end; ++i) + { + i->second->new_external_ip(); + } + + // since we have a new external IP now, we need to + // restart the DHT with a new node ID + +#ifndef TORRENT_DISABLE_DHT + if (m_dht) m_dht->update_node_id(); +#endif + } + + // decrement the refcount of the block in the disk cache + // since the network thread doesn't need it anymore + void session_impl::reclaim_block(block_cache_reference ref) + { + m_disk_thread.reclaim_block(ref); + } + + char* session_impl::allocate_disk_buffer(char const* category) + { + return m_disk_thread.allocate_disk_buffer(category); + } + + void session_impl::free_disk_buffer(char* buf) + { + m_disk_thread.free_disk_buffer(buf); + } + + char* session_impl::allocate_disk_buffer(bool& exceeded + , boost::shared_ptr o + , char const* category) + { + return m_disk_thread.allocate_disk_buffer(exceeded, o, category); + } + + char* session_impl::allocate_buffer() + { + TORRENT_ASSERT(is_single_thread()); + +#ifdef TORRENT_DISABLE_POOL_ALLOCATOR + int num_bytes = send_buffer_size(); + return static_cast(malloc(num_bytes)); +#else + return static_cast(m_send_buffers.malloc()); +#endif + } + + void session_impl::free_buffer(char* buf) + { + TORRENT_ASSERT(is_single_thread()); + +#ifdef TORRENT_DISABLE_POOL_ALLOCATOR + free(buf); +#else + m_send_buffers.free(buf); +#endif + } + +#if TORRENT_USE_INVARIANT_CHECKS + void session_impl::check_invariant() const + { + TORRENT_ASSERT(is_single_thread()); + + int loaded_limit = m_settings.get_int(settings_pack::active_loaded_limit); + TORRENT_ASSERT(m_num_save_resume <= loaded_limit); +// if (m_num_save_resume < loaded_limit) +// TORRENT_ASSERT(m_save_resume_queue.empty()); + + TORRENT_ASSERT(m_torrents.size() >= m_torrent_lru.size()); + + 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 (int l = 0; l < num_torrent_lists; ++l) + { + std::vector const& list = m_torrent_lists[l]; + for (std::vector::const_iterator i = list.begin() + , end(list.end()); i != end; ++i) + { + TORRENT_ASSERT((*i)->m_links[l].in_list()); + } + } + +#if TORRENT_HAS_BOOST_UNORDERED + boost::unordered_set unique_torrents; +#else + std::set unique_torrents; +#endif + for (list_iterator i = m_torrent_lru.iterate(); i.get(); i.next()) + { + torrent* t = i.get(); + TORRENT_ASSERT(t->is_loaded()); + TORRENT_ASSERT(unique_torrents.count(t) == 0); + unique_torrents.insert(t); + } + TORRENT_ASSERT(unique_torrents.size() == m_torrent_lru.size()); + + int torrent_state_gauges[counters::num_error_torrents - counters::num_checking_torrents + 1]; + memset(torrent_state_gauges, 0, sizeof(torrent_state_gauges)); + +#if defined TORRENT_EXPENSIVE_INVARIANT_CHECKS + +#if TORRENT_HAS_BOOST_UNORDERED + boost::unordered_set unique; +#else + std::set unique; +#endif +#endif + + int num_active_downloading = 0; + int num_active_finished = 0; + int total_downloaders = 0; + for (torrent_map::const_iterator i = m_torrents.begin() + , end(m_torrents.end()); i != end; ++i) + { + boost::shared_ptr t = i->second; + if (t->want_peers_download()) ++num_active_downloading; + if (t->want_peers_finished()) ++num_active_finished; + TORRENT_ASSERT(!(t->want_peers_download() && t->want_peers_finished())); + + ++torrent_state_gauges[t->current_stats_state() - counters::num_checking_torrents]; + + int pos = t->queue_position(); + if (pos < 0) + { + TORRENT_ASSERT(pos == -1); + 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 == m_torrent_lists[torrent_want_peers_download].size()); + TORRENT_ASSERT(num_active_finished == m_torrent_lists[torrent_want_peers_finished].size()); + +#if TORRENT_HAS_BOOST_UNORDERED + boost::unordered_set unique_peers; +#else + std::set unique_peers; +#endif + TORRENT_ASSERT(m_settings.get_int(settings_pack::connections_limit) > 0); + + int unchokes = 0; + int unchokes_all = 0; + int num_optimistic = 0; + int disk_queue[2] = {0, 0}; + for (connection_map::const_iterator i = m_connections.begin(); + i != m_connections.end(); ++i) + { + TORRENT_ASSERT(*i); + boost::shared_ptr t = (*i)->associated_torrent().lock(); + TORRENT_ASSERT(unique_peers.find(i->get()) == unique_peers.end()); + unique_peers.insert(i->get()); + + if ((*i)->m_channel_state[0] & peer_info::bw_disk) ++disk_queue[0]; + if ((*i)->m_channel_state[1] & peer_info::bw_disk) ++disk_queue[1]; + + peer_connection* p = i->get(); + TORRENT_ASSERT(!p->is_disconnecting()); + 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 (std::vector >::const_iterator i + = m_undead_peers.begin(); i != m_undead_peers.end(); ++i) + { + peer_connection* p = i->get(); + 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 = m_stats_counters[counters::num_peers_up_unchoked_all]; + int const unchoked_counter = m_stats_counters[counters::num_peers_up_unchoked]; + int const unchoked_counter_optimistic + = 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 (torrent_map::const_iterator j + = m_torrents.begin(); j != m_torrents.end(); ++j) + { + TORRENT_ASSERT(boost::get_pointer(j->second)); + } + } +#endif // TORRENT_USE_INVARIANT_CHECKS + +#ifndef TORRENT_DISABLE_LOGGING + tracker_logger::tracker_logger(session_interface& ses): m_ses(ses) {} + void tracker_logger::tracker_warning(tracker_request const& + , std::string const& str) + { + debug_log("*** tracker warning: %s", str.c_str()); + } + + void tracker_logger::tracker_response(tracker_request const& + , libtorrent::address const& tracker_ip + , std::list
    const& tracker_ips + , struct tracker_response const& resp) + { + TORRENT_UNUSED(tracker_ips); + debug_log("TRACKER RESPONSE\n" + "interval: %d\n" + "external ip: %s\n" + "we connected to: %s\n" + "peers:" + , resp.interval + , print_address(resp.external_ip).c_str() + , print_address(tracker_ip).c_str()); + + for (std::vector::const_iterator i = resp.peers.begin(); + i != resp.peers.end(); ++i) + { + debug_log(" %16s %5d %s %s", i->hostname.c_str(), i->port + , i->pid.is_all_zeros()?"":to_hex(i->pid.to_string()).c_str() + , identify_client(i->pid).c_str()); + } + for (std::vector::const_iterator i = resp.peers4.begin(); + i != resp.peers4.end(); ++i) + { + debug_log(" %s:%d", print_address(address_v4(i->ip)).c_str(), i->port); + } +#if TORRENT_USE_IPV6 + for (std::vector::const_iterator i = resp.peers6.begin(); + i != resp.peers6.end(); ++i) + { + debug_log(" [%s]:%d", print_address(address_v6(i->ip)).c_str(), i->port); + } +#endif + } + + void tracker_logger::tracker_request_timed_out( + tracker_request const&) + { + debug_log("*** tracker timed out"); + } + + void tracker_logger::tracker_request_error(tracker_request const& + , int response_code, error_code const& ec, const std::string& str + , int retry_interval) + { + TORRENT_UNUSED(retry_interval); + debug_log("*** tracker error: %d: %s %s" + , response_code, ec.message().c_str(), str.c_str()); + } + + void tracker_logger::debug_log(const char* fmt, ...) const + { + va_list v; + va_start(v, fmt); + char usr[1024]; + vsnprintf(usr, sizeof(usr), fmt, v); + va_end(v); + m_ses.session_log("%s", usr); + } +#endif // TORRENT_DISABLE_LOGGING +}} + diff --git a/src/session_settings.cpp b/src/session_settings.cpp new file mode 100644 index 0000000..cd50642 --- /dev/null +++ b/src/session_settings.cpp @@ -0,0 +1,42 @@ +/* + +Copyright (c) 2015-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/aux_/session_settings.hpp" + +namespace libtorrent { namespace aux +{ + session_settings::session_settings() + { + initialize_default_settings(*this); + } +} } + diff --git a/src/session_stats.cpp b/src/session_stats.cpp new file mode 100644 index 0000000..9d7e316 --- /dev/null +++ b/src/session_stats.cpp @@ -0,0 +1,552 @@ +/* + +Copyright (c) 2012-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_stats.hpp" // for stats_metric +#include "libtorrent/aux_/session_interface.hpp" // for stats counter names +#include "libtorrent/performance_counters.hpp" // for counters +#include + +namespace libtorrent +{ + + struct stats_metric_impl + { + char const* name; + int value_index; + }; + +#define METRIC(category, name) { #category "." #name, counters:: name }, + static const stats_metric_impl 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, incoming_connections) + + // the number of peer connections for each kind of socket. + // these counts include half-open (connecting) 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 + // bandwidht 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) + + // the number of torrents that are currently loaded + METRIC(ses, num_loaded_torrents) + METRIC(ses, num_pinned_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) + + // this counts the number of times a torrent has been + // evicted (only applies when `dynamic loading of torrent files`_ + // is enabled). + METRIC(ses, torrent_evicted_counter) + + // the number of allowed unchoked peers + METRIC(ses, num_unchoke_slots) + + // bittorrent message counters. These counters are incremented + // every time a message of the corresponding type is received from + // or sent to a bittorrent peer. + METRIC(ses, num_incoming_choke) + METRIC(ses, num_incoming_unchoke) + METRIC(ses, num_incoming_interested) + METRIC(ses, num_incoming_not_interested) + METRIC(ses, num_incoming_have) + METRIC(ses, num_incoming_bitfield) + METRIC(ses, num_incoming_request) + METRIC(ses, num_incoming_piece) + METRIC(ses, num_incoming_cancel) + METRIC(ses, num_incoming_dht_port) + METRIC(ses, num_incoming_suggest) + METRIC(ses, num_incoming_have_all) + METRIC(ses, num_incoming_have_none) + METRIC(ses, num_incoming_reject) + METRIC(ses, num_incoming_allowed_fast) + METRIC(ses, num_incoming_ext_handshake) + METRIC(ses, num_incoming_pex) + METRIC(ses, num_incoming_metadata) + METRIC(ses, num_incoming_extended) + + METRIC(ses, num_outgoing_choke) + METRIC(ses, num_outgoing_unchoke) + METRIC(ses, num_outgoing_interested) + METRIC(ses, num_outgoing_not_interested) + METRIC(ses, num_outgoing_have) + METRIC(ses, num_outgoing_bitfield) + METRIC(ses, num_outgoing_request) + METRIC(ses, num_outgoing_piece) + METRIC(ses, num_outgoing_cancel) + METRIC(ses, num_outgoing_dht_port) + METRIC(ses, num_outgoing_suggest) + METRIC(ses, num_outgoing_have_all) + METRIC(ses, num_outgoing_have_none) + METRIC(ses, num_outgoing_reject) + METRIC(ses, num_outgoing_allowed_fast) + METRIC(ses, num_outgoing_ext_handshake) + METRIC(ses, num_outgoing_pex) + METRIC(ses, num_outgoing_metadata) + METRIC(ses, num_outgoing_extended) + + // the number of wasted downloaded bytes by reason of the bytes being + // wasted. + METRIC(ses, waste_piece_timed_out) + METRIC(ses, waste_piece_cancelled) + METRIC(ses, waste_piece_unknown) + METRIC(ses, waste_piece_seed) + METRIC(ses, waste_piece_end_game) + METRIC(ses, waste_piece_closing) + + // the number of pieces considered while picking pieces + METRIC(picker, piece_picker_partial_loops) + METRIC(picker, piece_picker_suggest_loops) + METRIC(picker, piece_picker_sequential_loops) + METRIC(picker, piece_picker_reverse_rare_loops) + METRIC(picker, piece_picker_rare_loops) + METRIC(picker, piece_picker_rand_start_loops) + METRIC(picker, piece_picker_rand_loops) + METRIC(picker, piece_picker_busy_loops) + + // This breaks down the piece picks into the event that + // triggered it + METRIC(picker, reject_piece_picks) + METRIC(picker, unchoke_piece_picks) + METRIC(picker, incoming_redundant_piece_picks) + METRIC(picker, incoming_piece_picks) + METRIC(picker, end_game_piece_picks) + METRIC(picker, snubbed_piece_picks) + METRIC(picker, interesting_piece_picks) + METRIC(picker, hash_fail_piece_picks) + + METRIC(disk, write_cache_blocks) + METRIC(disk, read_cache_blocks) + + // the number of microseconds it takes from receiving a request from a + // peer until we're sending the response back on the socket. + METRIC(disk, request_latency) + + METRIC(disk, pinned_blocks) + METRIC(disk, disk_blocks_in_use) + 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, num_writing_threads) + METRIC(disk, num_running_threads) + METRIC(disk, blocked_disk_jobs) + + // the number of bytes we have sent to the disk I/O + // thread for writing. Every time we hear back from + // the disk I/O thread with a completed write job, this + // is updated to the number of bytes the disk I/O thread + // is actually waiting for to be written (as opposed to + // bytes just hanging out in the cache) + METRIC(disk, queued_write_bytes) + METRIC(disk, arc_mru_size) + METRIC(disk, arc_mru_ghost_size) + METRIC(disk, arc_mfu_size) + METRIC(disk, arc_mfu_ghost_size) + METRIC(disk, arc_write_size) + METRIC(disk, arc_volatile_size) + + // the number of blocks written and read from disk in total. A block is + // 16 kiB. + METRIC(disk, num_blocks_written) + METRIC(disk, num_blocks_read) + + // the total number of blocks run through SHA-1 hashing + METRIC(disk, num_blocks_hashed) + + // the number of blocks read from the disk cache + METRIC(disk, num_blocks_cache_hits) + + // the number of disk I/O operation for reads and writes. One disk + // operation may transfer more then one block. + 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_cache_piece) + METRIC(disk, num_fenced_flush_piece) + METRIC(disk, num_fenced_flush_hashed) + METRIC(disk, num_fenced_flush_storage) + METRIC(disk, num_fenced_trim_cache) + METRIC(disk, num_fenced_file_priority) + METRIC(disk, num_fenced_load_torrent) + METRIC(disk, num_fenced_clear_piece) + METRIC(disk, num_fenced_tick_storage) + + // The number of nodes in the DHT routing table + METRIC(dht, dht_nodes) + + // The number of replacement nodes in the DHT routing table + METRIC(dht, dht_node_cache) + + // the number of torrents currently tracked by our DHT node + METRIC(dht, dht_torrents) + + // the number of peers currently tracked by our DHT node + METRIC(dht, dht_peers) + + // the number of immutable data items tracked by our DHT node + METRIC(dht, dht_immutable_data) + + // the number of mutable data items tracked by our DHT node + METRIC(dht, dht_mutable_data) + + // the number of RPC observers currently allocated + METRIC(dht, dht_allocated_observers) + + // the total number of DHT messages sent and received + METRIC(dht, dht_messages_in) + METRIC(dht, dht_messages_out) + + // the number of 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) + + // 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_put) + METRIC(dht, dht_invalid_get) + + // uTP counters. Each counter represents the number of time each event + // has occurred. + METRIC(utp, utp_packet_loss) + METRIC(utp, utp_timeout) + METRIC(utp, utp_packets_in) + METRIC(utp, utp_packets_out) + METRIC(utp, utp_fast_retransmit) + METRIC(utp, utp_packet_resend) + METRIC(utp, utp_samples_above_target) + METRIC(utp, utp_samples_below_target) + METRIC(utp, utp_payload_pkts_in) + METRIC(utp, utp_payload_pkts_out) + METRIC(utp, utp_invalid_pkts_in) + METRIC(utp, utp_redundant_pkts_in) + + // the number of uTP sockets in each respective state + METRIC(utp, num_utp_idle) + METRIC(utp, num_utp_syn_sent) + METRIC(utp, num_utp_connected) + METRIC(utp, num_utp_fin_sent) + METRIC(utp, num_utp_close_wait) + METRIC(utp, num_utp_deleted) + + // the buffer sizes accepted by + // socket send and receive calls respectively. + // The larger the buffers are, the more efficient, + // because it reqire fewer system calls per byte. + // The size is 1 << n, where n is the number + // at the end of the counter name. i.e. + // 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, + // 16384, 32768, 65536, 131072, 262144, 524288, 1048576 + // bytes + METRIC(sock_bufs, socket_send_size3) + METRIC(sock_bufs, socket_send_size4) + METRIC(sock_bufs, socket_send_size5) + METRIC(sock_bufs, socket_send_size6) + METRIC(sock_bufs, socket_send_size7) + METRIC(sock_bufs, socket_send_size8) + METRIC(sock_bufs, socket_send_size9) + METRIC(sock_bufs, socket_send_size10) + METRIC(sock_bufs, socket_send_size11) + METRIC(sock_bufs, socket_send_size12) + METRIC(sock_bufs, socket_send_size13) + METRIC(sock_bufs, socket_send_size14) + METRIC(sock_bufs, socket_send_size15) + METRIC(sock_bufs, socket_send_size16) + METRIC(sock_bufs, socket_send_size17) + METRIC(sock_bufs, socket_send_size18) + METRIC(sock_bufs, socket_send_size19) + METRIC(sock_bufs, socket_send_size20) + METRIC(sock_bufs, socket_recv_size3) + METRIC(sock_bufs, socket_recv_size4) + METRIC(sock_bufs, socket_recv_size5) + METRIC(sock_bufs, socket_recv_size6) + METRIC(sock_bufs, socket_recv_size7) + METRIC(sock_bufs, socket_recv_size8) + METRIC(sock_bufs, socket_recv_size9) + METRIC(sock_bufs, socket_recv_size10) + METRIC(sock_bufs, socket_recv_size11) + METRIC(sock_bufs, socket_recv_size12) + METRIC(sock_bufs, socket_recv_size13) + METRIC(sock_bufs, socket_recv_size14) + METRIC(sock_bufs, socket_recv_size15) + METRIC(sock_bufs, socket_recv_size16) + METRIC(sock_bufs, socket_recv_size17) + METRIC(sock_bufs, socket_recv_size18) + METRIC(sock_bufs, socket_recv_size19) + METRIC(sock_bufs, socket_recv_size20) + + // ... more + }; +#undef METRIC + + std::vector session_stats_metrics() + { + std::vector stats; + const int num = sizeof(metrics)/sizeof(metrics[0]); + stats.resize(num); + for (int i = 0; i < num; ++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 + ? stats_metric::type_gauge : stats_metric::type_counter; + } + return stats; + } + + int find_metric_idx(char const* name) + { + stats_metric_impl const* end = metrics + sizeof(metrics)/sizeof(metrics[0]); + stats_metric_impl const* i = std::find_if(metrics, end , boost::bind(&strcmp + , boost::bind(&stats_metric_impl::name, _1), name) == 0); + if (i == end) return -1; + return i->value_index; + } + +} + diff --git a/src/settings_pack.cpp b/src/settings_pack.cpp new file mode 100644 index 0000000..c67ea6d --- /dev/null +++ b/src/settings_pack.cpp @@ -0,0 +1,801 @@ +/* + +Copyright (c) 2012-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/aux_/session_impl.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 const& v) + { + typedef std::vector > container_t; + typename container_t::iterator i = std::lower_bound(c.begin(), c.end(), v + , &compare_first); + if (i != c.end() && i->first == v.first) i->second = v.second; + else c.insert(i, v); + } +} + +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; +#ifndef TORRENT_NO_DEPRECATE + // offset into session_settings, used to map + // settings to the deprecated settings struct + int offset; +#endif + }; + + 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; +#ifndef TORRENT_NO_DEPRECATE + // offset into session_settings, used to map + // settings to the deprecated settings struct + int offset; +#endif + }; + + 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; +#ifndef TORRENT_NO_DEPRECATE + // offset into session_settings, used to map + // settings to the deprecated settings struct + int offset; +#endif + }; + + +// SET_NOPREV - this is used for new settings that don't exist in the +// deprecated session_settings. + +#ifdef TORRENT_NO_DEPRECATE +#define SET(name, default_value, fun) { #name, fun, default_value } +#define SET_NOPREV(name, default_value, fun) { #name, fun, default_value } +#define DEPRECATED_SET(name, default_value, fun) { "", NULL, 0 } +#else +#define SET(name, default_value, fun) { #name, fun, default_value, offsetof(libtorrent::session_settings, name) } +#define SET_NOPREV(name, default_value, fun) { #name, fun, default_value, 0 } +#define DEPRECATED_SET(name, default_value, fun) { #name, fun, default_value, offsetof(libtorrent::session_settings, name) } +#endif + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Winvalid-offsetof" +#endif + + namespace { + + using aux::session_impl; + + str_setting_entry_t str_settings[settings_pack::num_string_settings] = + { + SET(user_agent, "libtorrent/" LIBTORRENT_VERSION, &session_impl::update_user_agent), + SET(announce_ip, 0, 0), + SET(mmap_cache, 0, 0), + SET(handshake_client_version, 0, 0), + SET_NOPREV(outgoing_interfaces, "", &session_impl::update_outgoing_interfaces), + SET_NOPREV(listen_interfaces, "0.0.0.0:6881", &session_impl::update_listen_interfaces), + SET_NOPREV(proxy_hostname, "", &session_impl::update_proxy), + SET_NOPREV(proxy_username, "", &session_impl::update_proxy), + SET_NOPREV(proxy_password, "", &session_impl::update_proxy), + SET_NOPREV(i2p_hostname, "", &session_impl::update_i2p_bridge), + SET_NOPREV(peer_fingerprint, "-LT1110-", &session_impl::update_peer_fingerprint), + SET_NOPREV(dht_bootstrap_nodes, "dht.libtorrent.org:25401", &session_impl::update_dht_bootstrap_nodes) + }; + + bool_setting_entry_t bool_settings[settings_pack::num_bool_settings] = + { + SET(allow_multiple_connections_per_ip, false, 0), + DEPRECATED_SET(ignore_limits_on_local_network, true, &session_impl::update_ignore_rate_limits_on_local_network), + SET(send_redundant_have, true, 0), + SET(lazy_bitfields, false, 0), + SET(use_dht_as_fallback, false, 0), + SET(upnp_ignore_nonrouters, false, 0), + SET(use_parole_mode, true, 0), + SET(use_read_cache, true, 0), + DEPRECATED_SET(use_write_cache, true, 0), + SET(dont_flush_write_cache, false, 0), + DEPRECATED_SET(explicit_read_cache, false, 0), + SET(coalesce_reads, false, 0), + SET(coalesce_writes, false, 0), + SET(auto_manage_prefer_seeds, false, 0), + SET(dont_count_slow_torrents, true, &session_impl::update_count_slow), + SET(close_redundant_connections, true, 0), + SET(prioritize_partial_pieces, false, 0), + SET(rate_limit_ip_overhead, true, 0), + SET(announce_to_all_trackers, false, 0), + SET(announce_to_all_tiers, false, 0), + SET(prefer_udp_trackers, true, 0), + SET(strict_super_seeding, false, 0), + DEPRECATED_SET(lock_disk_cache, false, 0), + SET(disable_hash_checks, false, 0), + SET(allow_i2p_mixed, false, 0), + SET(low_prio_disk, true, 0), + SET(volatile_read_cache, false, 0), + SET(guided_read_cache, false, 0), + SET(no_atime_storage, true, 0), + SET(incoming_starts_queued_torrents, false, 0), + SET(report_true_downloaded, false, 0), + SET(strict_end_game_mode, true, 0), + SET(broadcast_lsd, true, 0), + SET(enable_outgoing_utp, true, 0), + SET(enable_incoming_utp, true, 0), + SET(enable_outgoing_tcp, true, 0), + SET(enable_incoming_tcp, true, 0), + SET(ignore_resume_timestamps, false, 0), + SET(no_recheck_incomplete_resume, false, 0), + SET(anonymous_mode, false, &session_impl::update_anonymous_mode), + SET(report_web_seed_downloads, true, &session_impl::update_report_web_seed_downloads), + DEPRECATED_SET(rate_limit_utp, false, &session_impl::update_rate_limit_utp), + SET(announce_double_nat, false, 0), + SET(seeding_outgoing_connections, true, 0), + SET(no_connect_privileged_ports, false, &session_impl::update_privileged_ports), + SET(smooth_connects, true, 0), + SET(always_send_user_agent, false, 0), + SET(apply_ip_filter_to_trackers, true, 0), + SET(use_disk_read_ahead, true, 0), + SET(lock_files, false, 0), + SET(contiguous_recv_buffer, true, 0), + SET(ban_web_seeds, true, 0), + SET_NOPREV(allow_partial_disk_writes, true, 0), + SET(force_proxy, false, &session_impl::update_force_proxy), + SET(support_share_mode, true, 0), + SET(support_merkle_torrents, true, 0), + SET(report_redundant_bytes, true, 0), + SET_NOPREV(listen_system_port_fallback, true, 0), + SET(use_disk_cache_pool, true, 0), + SET_NOPREV(announce_crypto_support, true, 0), + SET_NOPREV(enable_upnp, true, &session_impl::update_upnp), + SET_NOPREV(enable_natpmp, true, &session_impl::update_natpmp), + SET_NOPREV(enable_lsd, true, &session_impl::update_lsd), + SET_NOPREV(enable_dht, true, &session_impl::update_dht), + SET_NOPREV(prefer_rc4, false, 0), + SET_NOPREV(proxy_hostnames, true, 0), + SET_NOPREV(proxy_peer_connections, true, 0), + SET_NOPREV(auto_sequential, true, &session_impl::update_auto_sequential), + SET_NOPREV(proxy_tracker_connections, true, 0), + }; + + int_setting_entry_t int_settings[settings_pack::num_int_settings] = + { + SET(tracker_completion_timeout, 30, 0), + SET(tracker_receive_timeout, 10, 0), + SET(stop_tracker_timeout, 5, 0), + SET(tracker_maximum_response_length, 1024*1024, 0), + SET(piece_timeout, 20, 0), + SET(request_timeout, 60, 0), + SET(request_queue_time, 3, 0), + SET(max_allowed_in_request_queue, 500, 0), + SET(max_out_request_queue, 500, 0), + SET(whole_pieces_threshold, 20, 0), + SET(peer_timeout, 120, 0), + SET(urlseed_timeout, 20, 0), + SET(urlseed_pipeline_size, 5, 0), + SET(urlseed_wait_retry, 30, 0), + SET(file_pool_size, 40, 0), + SET(max_failcount, 3, &session_impl::update_max_failcount), + SET(min_reconnect_time, 60, 0), + SET(peer_connect_timeout, 15, 0), + SET(connection_speed, 10, &session_impl::update_connection_speed), + SET(inactivity_timeout, 600, 0), + SET(unchoke_interval, 15, 0), + SET(optimistic_unchoke_interval, 30, 0), + SET(num_want, 200, 0), + SET(initial_picker_threshold, 4, 0), + SET(allowed_fast_set_size, 10, 0), + SET(suggest_mode, settings_pack::no_piece_suggestions, 0), + SET(max_queued_disk_bytes, 1024 * 1024, &session_impl::update_queued_disk_bytes), + SET(handshake_timeout, 10, 0), + SET(send_buffer_low_watermark, 10 * 1024, 0), + SET(send_buffer_watermark, 500 * 1024, 0), + SET(send_buffer_watermark_factor, 50, 0), + SET(choking_algorithm, settings_pack::fixed_slots_choker, 0), + SET(seed_choking_algorithm, settings_pack::round_robin, 0), + SET(cache_size, 1024, 0), + SET(cache_buffer_chunk_size, 0, &session_impl::update_cache_buffer_chunk_size), + SET(cache_expiry, 300, 0), + DEPRECATED_SET(explicit_cache_interval, 30, 0), + SET(disk_io_write_mode, settings_pack::enable_os_cache, 0), + SET(disk_io_read_mode, settings_pack::enable_os_cache, 0), + SET(outgoing_port, 0, 0), + SET(num_outgoing_ports, 0, 0), + SET(peer_tos, 0, &session_impl::update_peer_tos), + SET(active_downloads, 3, &session_impl::trigger_auto_manage), + SET(active_seeds, 5, &session_impl::trigger_auto_manage), + SET_NOPREV(active_checking, 1, &session_impl::trigger_auto_manage), + SET(active_dht_limit, 88, 0), + SET(active_tracker_limit, 1600, 0), + SET(active_lsd_limit, 60, 0), + SET(active_limit, 15, &session_impl::trigger_auto_manage), + SET_NOPREV(active_loaded_limit, 100, &session_impl::trigger_auto_manage), + SET(auto_manage_interval, 30, 0), + SET(seed_time_limit, 24 * 60 * 60, 0), + SET(auto_scrape_interval, 1800, 0), + SET(auto_scrape_min_interval, 300, 0), + SET(max_peerlist_size, 3000, 0), + SET(max_paused_peerlist_size, 1000, 0), + SET(min_announce_interval, 5 * 60, 0), + SET(auto_manage_startup, 60, 0), + SET(seeding_piece_quota, 20, 0), + SET(max_rejects, 50, 0), + 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(file_checks_delay_per_block, 0, 0), + SET(read_cache_line_size, 32, 0), + SET(write_cache_line_size, 16, 0), + SET(optimistic_disk_retry, 10 * 60, 0), + SET(max_suggest_pieces, 10, 0), + SET(local_service_announce_interval, 5 * 60, 0), + SET(dht_announce_interval, 15 * 60, &session_impl::update_dht_announce_interval), + SET(udp_tracker_token_expiry, 60, 0), + SET(default_cache_min_age, 1, 0), + SET(num_optimistic_unchoke_slots, 0, 0), + SET(default_est_reciprocation_rate, 16000, 0), + SET(increase_est_reciprocation_rate, 20, 0), + SET(decrease_est_reciprocation_rate, 3, 0), + SET(max_pex_peers, 50, 0), + SET(tick_interval, 500, 0), + SET(share_mode_target, 3, 0), + 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, 4000, &session_impl::update_dht_upload_rate_limit), + SET(unchoke_slots_limit, 8, &session_impl::update_unchoke_limit), + DEPRECATED_SET(half_open_limit, 0, 0), + SET(connections_limit, 200, &session_impl::update_connections_limit), + SET(connections_slack, 10, 0), + SET(utp_target_delay, 100, 0), + SET(utp_gain_factor, 3000, 0), + SET(utp_min_timeout, 500, 0), + SET(utp_syn_resends, 2, 0), + SET(utp_fin_resends, 2, 0), + SET(utp_num_resends, 3, 0), + SET(utp_connect_timeout, 3000, 0), + SET(utp_delayed_ack, 0, 0), + SET(utp_loss_multiplier, 50, 0), + SET(mixed_mode_algorithm, settings_pack::peer_proportional, 0), + SET(listen_queue_size, 5, 0), + SET(torrent_connect_boost, 10, 0), + SET(alert_queue_size, 1000, &session_impl::update_alert_queue_size), + SET(max_metadata_size, 3 * 1024 * 10240, 0), + DEPRECATED_SET(hashing_threads, 1, 0), + SET(checking_mem_usage, 256, 0), + SET(predictive_piece_announce, 0, 0), + SET(aio_threads, 4, &session_impl::update_disk_threads), + SET(aio_max, 300, 0), + SET(network_threads, 0, &session_impl::update_network_threads), + SET(ssl_listen, 0, 0), + SET(tracker_backoff, 250, 0), + SET_NOPREV(share_ratio_limit, 200, 0), + SET_NOPREV(seed_time_ratio_limit, 700, 0), + SET_NOPREV(peer_turnover, 4, 0), + SET_NOPREV(peer_turnover_cutoff, 90, 0), + SET(peer_turnover_interval, 300, 0), + SET_NOPREV(connect_seed_every_n_download, 10, 0), + SET(max_http_recv_buffer_size, 4*1024*204, 0), + SET_NOPREV(max_retry_port_bind, 10, 0), + SET_NOPREV(alert_mask, alert::error_notification, &session_impl::update_alert_mask), + SET_NOPREV(out_enc_policy, settings_pack::pe_enabled, 0), + SET_NOPREV(in_enc_policy, settings_pack::pe_enabled, 0), + SET_NOPREV(allowed_enc_level, settings_pack::pe_both, 0), + SET(inactive_down_rate, 2048, 0), + SET(inactive_up_rate, 2048, 0), + SET_NOPREV(proxy_type, settings_pack::none, &session_impl::update_proxy), + SET_NOPREV(proxy_port, 0, &session_impl::update_proxy), + SET_NOPREV(i2p_port, 0, &session_impl::update_i2p_bridge), + SET_NOPREV(cache_size_volatile, 256, 0) + }; + +#undef SET + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + + } // anonymous namespace + + int setting_by_name(std::string const& key) + { + for (int k = 0; k < sizeof(str_settings)/sizeof(str_settings[0]); ++k) + { + if (key != str_settings[k].name) continue; + return settings_pack::string_type_base + k; + } + for (int k = 0; k < sizeof(int_settings)/sizeof(int_settings[0]); ++k) + { + if (key != int_settings[k].name) continue; + return settings_pack::int_type_base + k; + } + for (int k = 0; k < sizeof(bool_settings)/sizeof(bool_settings[0]); ++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 ""; + } + + boost::shared_ptr load_pack_from_dict(bdecode_node const& settings) + { + boost::shared_ptr pack = boost::make_shared(); + + for (int i = 0; i < settings.dict_size(); ++i) + { + std::string key; + bdecode_node val; + boost::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 < sizeof(int_settings)/sizeof(int_settings[0]); ++k) + { + if (key != int_settings[k].name) continue; + pack->set_int(settings_pack::int_type_base + k, val.int_value()); + found = true; + break; + } + if (found) continue; + for (int k = 0; k < sizeof(bool_settings)/sizeof(bool_settings[0]); ++k) + { + if (key != bool_settings[k].name) continue; + pack->set_bool(settings_pack::bool_type_base + k, val.int_value()); + break; + } + } + break; + case bdecode_node::string_t: + for (int k = 0; k < sizeof(str_settings)/sizeof(str_settings[0]); ++k) + { + if (key != str_settings[k].name) continue; + pack->set_str(settings_pack::string_type_base + k, val.string_value()); + break; + } + break; + case bdecode_node::none_t: + break; + } + } + return pack; + } + + void save_settings_to_dict(aux::session_settings const& s, entry::dictionary_type& sett) + { + // loop over all settings that differ from default + for (int i = 0; i < settings_pack::num_string_settings; ++i) + { + char const* cmp = str_settings[i].default_value == 0 ? "" : str_settings[i].default_value; + if (cmp == s.m_strings[i]) continue; + sett[str_settings[i].name] = s.m_strings[i]; + } + + for (int i = 0; i < settings_pack::num_int_settings; ++i) + { + if (int_settings[i].default_value == s.m_ints[i]) continue; + sett[int_settings[i].name] = s.m_ints[i]; + } + + for (int i = 0; i < settings_pack::num_bool_settings; ++i) + { + if (bool_settings[i].default_value == s.m_bools[i]) continue; + sett[bool_settings[i].name] = s.m_bools[i]; + } + } + +#ifndef TORRENT_NO_DEPRECATE + +#include "libtorrent/aux_/disable_warnings_push.hpp" + + boost::shared_ptr load_pack_from_struct( + aux::session_settings const& current, session_settings const& s) + { + boost::shared_ptr p = boost::make_shared(); + + for (int i = 0; i < settings_pack::num_string_settings; ++i) + { + if (str_settings[i].offset == 0) continue; + std::string& val = *(std::string*)(((char*)&s) + str_settings[i].offset); + int setting_name = settings_pack::string_type_base + i; + if (val == current.get_str(setting_name)) continue; + p->set_str(setting_name, val); + } + + for (int i = 0; i < settings_pack::num_int_settings; ++i) + { + if (int_settings[i].offset == 0) continue; + int& val = *(int*)(((char*)&s) + int_settings[i].offset); + int setting_name = settings_pack::int_type_base + i; + if (val == current.get_int(setting_name)) continue; + p->set_int(setting_name, val); + } + + for (int i = 0; i < settings_pack::num_bool_settings; ++i) + { + if (bool_settings[i].offset == 0) continue; + bool& val = *(bool*)(((char*)&s) + bool_settings[i].offset); + int setting_name = settings_pack::bool_type_base + i; + if (val == current.get_bool(setting_name)) continue; + p->set_bool(setting_name, val); + } + + // special case for deprecated float values + int val = current.get_int(settings_pack::share_ratio_limit); + if (fabs(s.share_ratio_limit - float(val) / 100.f) > 0.001f) + p->set_int(settings_pack::share_ratio_limit, s.share_ratio_limit * 100); + + val = current.get_int(settings_pack::seed_time_ratio_limit); + if (fabs(s.seed_time_ratio_limit - float(val) / 100.f) > 0.001f) + p->set_int(settings_pack::seed_time_ratio_limit, s.seed_time_ratio_limit * 100); + + val = current.get_int(settings_pack::peer_turnover); + if (fabs(s.peer_turnover - float(val) / 100.f) > 0.001) + p->set_int(settings_pack::peer_turnover, s.peer_turnover * 100); + + val = current.get_int(settings_pack::peer_turnover_cutoff); + if (fabs(s.peer_turnover_cutoff - float(val) / 100.f) > 0.001) + p->set_int(settings_pack::peer_turnover_cutoff, s.peer_turnover_cutoff * 100); + + return p; + } + + void load_struct_from_settings(aux::session_settings const& current, session_settings& ret) + { + for (int i = 0; i < settings_pack::num_string_settings; ++i) + { + if (str_settings[i].offset == 0) continue; + std::string& val = *(std::string*)(((char*)&ret) + str_settings[i].offset); + val = current.get_str(settings_pack::string_type_base + i); + } + + for (int i = 0; i < settings_pack::num_int_settings; ++i) + { + if (int_settings[i].offset == 0) continue; + int& val = *(int*)(((char*)&ret) + int_settings[i].offset); + val = current.get_int(settings_pack::int_type_base + i); + } + + for (int i = 0; i < settings_pack::num_bool_settings; ++i) + { + if (bool_settings[i].offset == 0) continue; + bool& val = *(bool*)(((char*)&ret) + bool_settings[i].offset); + val = current.get_bool(settings_pack::bool_type_base + i); + } + + // special case for deprecated float values + ret.share_ratio_limit = float(current.get_int(settings_pack::share_ratio_limit)) / 100.f; + ret.seed_time_ratio_limit = float(current.get_int(settings_pack::seed_time_ratio_limit)) / 100.f; + ret.peer_turnover = float(current.get_int(settings_pack::peer_turnover)) / 100.f; + ret.peer_turnover_cutoff = float(current.get_int(settings_pack::peer_turnover_cutoff)) / 100.f; + } + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#endif + + void initialize_default_settings(aux::session_settings& s) + { + for (int i = 0; i < settings_pack::num_string_settings; ++i) + { + if (str_settings[i].default_value == 0) 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); + } + + // this seems questionable... +/* + // Some settings have dynamic defaults depending on the machine + // for instance, the disk cache size + + // by default, set the cahe size to an 8:th of the total amount of physical RAM + boost::uint64_t phys_ram = total_physical_ram(); + if (phys_ram > 0) s.set_int(settings_pack::cache_size, phys_ram / 16 / 1024 / 8); +*/ + } + + void apply_pack(settings_pack const* pack, aux::session_settings& sett + , aux::session_impl* ses) + { + typedef void (aux::session_impl::*fun_t)(); + std::vector callbacks; + + for (std::vector >::const_iterator i = pack->m_strings.begin() + , end(pack->m_strings.end()); i != end; ++i) + { + // disregard setting indices that are not string types + if ((i->first & settings_pack::type_mask) != settings_pack::string_type_base) + continue; + + // ignore settings that are out of bounds + int index = i->first & settings_pack::index_mask; + if (index < 0 || index >= settings_pack::num_string_settings) + continue; + + // if the vaue did not change, don't call the update callback + if (sett.get_str(i->first) == i->second) continue; + + sett.set_str(i->first, i->second); + str_setting_entry_t const& sa = str_settings[i->first & settings_pack::index_mask]; + if (sa.fun && ses + && std::find(callbacks.begin(), callbacks.end(), sa.fun) == callbacks.end()) + callbacks.push_back(sa.fun); + } + + for (std::vector >::const_iterator i = pack->m_ints.begin() + , end(pack->m_ints.end()); i != end; ++i) + { + // disregard setting indices that are not int types + if ((i->first & settings_pack::type_mask) != settings_pack::int_type_base) + continue; + + // ignore settings that are out of bounds + int index = i->first & settings_pack::index_mask; + if (index < 0 || index >= settings_pack::num_int_settings) + continue; + + // if the vaue did not change, don't call the update callback + if (sett.get_int(i->first) == i->second) continue; + + sett.set_int(i->first, i->second); + int_setting_entry_t const& sa = int_settings[i->first & settings_pack::index_mask]; + if (sa.fun && ses + && std::find(callbacks.begin(), callbacks.end(), sa.fun) == callbacks.end()) + callbacks.push_back(sa.fun); + } + + for (std::vector >::const_iterator i = pack->m_bools.begin() + , end(pack->m_bools.end()); i != end; ++i) + { + // disregard setting indices that are not bool types + if ((i->first & settings_pack::type_mask) != settings_pack::bool_type_base) + continue; + + // ignore settings that are out of bounds + int index = i->first & settings_pack::index_mask; + if (index < 0 || index >= settings_pack::num_bool_settings) + continue; + + // if the vaue did not change, don't call the update callback + if (sett.get_bool(i->first) == i->second) continue; + + sett.set_bool(i->first, i->second); + bool_setting_entry_t const& sa = bool_settings[i->first & settings_pack::index_mask]; + if (sa.fun && ses + && std::find(callbacks.begin(), callbacks.end(), sa.fun) == callbacks.end()) + callbacks.push_back(sa.fun); + } + + // call the callbacks once all the settings have been applied, and + // only once per callback + for (std::vector::iterator i = callbacks.begin(), end(callbacks.end()); + i != end; ++i) + { + fun_t const& f = *i; + (ses->*f)(); + } + } + + void settings_pack::set_str(int name, std::string val) + { + TORRENT_ASSERT((name & type_mask) == string_type_base); + if ((name & type_mask) != string_type_base) return; + std::pair v(name, val); + insort_replace(m_strings, v); + } + + void settings_pack::set_int(int name, int val) + { + TORRENT_ASSERT((name & type_mask) == int_type_base); + if ((name & type_mask) != int_type_base) return; + std::pair v(name, val); + insort_replace(m_ints, v); + } + + void settings_pack::set_bool(int name, bool val) + { + TORRENT_ASSERT((name & type_mask) == bool_type_base); + if ((name & type_mask) != bool_type_base) return; + std::pair v(name, val); + insort_replace(m_bools, v); + } + + bool settings_pack::has_val(int 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(name, std::string()); + std::vector >::const_iterator 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(name, 0); + std::vector >::const_iterator 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(name, false); + std::vector >::const_iterator i = + std::lower_bound(m_bools.begin(), m_bools.end(), v + , &compare_first); + return i != m_bools.end() && i->first == name; + } + } + TORRENT_ASSERT(false); + return false; + } + + std::string settings_pack::get_str(int name) const + { + TORRENT_ASSERT((name & type_mask) == string_type_base); + if ((name & type_mask) != string_type_base) return std::string(); + + // 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(name, std::string()); + std::vector >::const_iterator 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 std::string(); + } + + int settings_pack::get_int(int name) const + { + TORRENT_ASSERT((name & type_mask) == int_type_base); + if ((name & type_mask) != int_type_base) return 0; + + // this is an optimization. If the settings pack is complete, + // i.e. has every key, we don't need to search, it's just a lookup + if (m_ints.size() == settings_pack::num_int_settings) + { + TORRENT_ASSERT(m_ints[name & index_mask].first == name); + return m_ints[name & index_mask].second; + } + std::pair v(name, 0); + std::vector >::const_iterator i + = std::lower_bound(m_ints.begin(), m_ints.end(), v + , &compare_first); + if (i != m_ints.end() && i->first == name) return i->second; + return 0; + } + + bool settings_pack::get_bool(int name) const + { + TORRENT_ASSERT((name & type_mask) == bool_type_base); + if ((name & type_mask) != bool_type_base) return false; + + // this is an optimization. If the settings pack is complete, + // i.e. has every key, we don't need to search, it's just a lookup + if (m_bools.size() == settings_pack::num_bool_settings) + { + TORRENT_ASSERT(m_bools[name & index_mask].first == name); + return m_bools[name & index_mask].second; + } + std::pair v(name, false); + std::vector >::const_iterator 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(); + } +} + diff --git a/src/sha1.cpp b/src/sha1.cpp new file mode 100644 index 0000000..8085731 --- /dev/null +++ b/src/sha1.cpp @@ -0,0 +1,320 @@ +/* +SHA-1 C++ conversion + +original version: + +SHA-1 in C +By Steve Reid +100% Public Domain + +changelog at the end of the file. +*/ + +#include +#include + +#include "libtorrent/sha1.hpp" + +#include // for BIG_ENDIAN and LITTLE_ENDIAN macros + +typedef boost::uint32_t u32; +typedef boost::uint8_t u8; + +namespace libtorrent +{ + +namespace +{ + 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(sha_ctx *context, char *msg) + { + using namespace std; + printf("%s (%d,%d) %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(sha_ctx* context, u8 const* data, u32 len) + { + using namespace std; + u32 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 !defined BOOST_BIG_ENDIAN && !defined BOOST_LITTLE_ENDIAN + bool is_big_endian() + { + u32 test = 1; + return *reinterpret_cast(&test) == 0; + } +#endif +} + +// SHA1Init - Initialize new context + +void SHA1_init(sha_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(sha_ctx* context, u8 const* data, u32 len) +{ + // GCC standard defines for endianness + // test with: cpp -dM /dev/null +#if defined BOOST_BIG_ENDIAN + internal_update(context, data, len); +#elif defined BOOST_LITTLE_ENDIAN + 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, sha_ctx* context) +{ + u8 finalcount[8]; + + for (u32 i = 0; i < 8; ++i) + { + // Endian independent + finalcount[i] = static_cast( + (context->count[(i >= 4 ? 0 : 1)] + >> ((3-(i & 3)) * 8) ) & 255); + } + + SHA1_update(context, reinterpret_cast("\200"), 1); + while ((context->count[0] & 504) != 448) + SHA1_update(context, reinterpret_cast("\0"), 1); + SHA1_update(context, finalcount, 8); // Should cause a SHA1transform() + + for (u32 i = 0; i < 20; ++i) + { + digest[i] = static_cast( + (context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); + } +} + +} // libtorrent namespace + +/************************************************************ + +----------------- +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/smart_ban.cpp b/src/smart_ban.cpp new file mode 100644 index 0000000..65b15d3 --- /dev/null +++ b/src/smart_ban.cpp @@ -0,0 +1,408 @@ +/* + +Copyright (c) 2007-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_DISABLE_EXTENSIONS + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include + +#include +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/hasher.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/extensions.hpp" +#include "libtorrent/extensions/smart_ban.hpp" +#include "libtorrent/disk_io_thread.hpp" +#include "libtorrent/aux_/session_impl.hpp" +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/peer_info.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/operations.hpp" // for operation_t enum + +//#define TORRENT_LOG_HASH_FAILURES + +#ifndef TORRENT_DISABLE_LOGGING +#include "libtorrent/socket_io.hpp" +#endif + +#ifdef TORRENT_LOG_HASH_FAILURES + +#include "libtorrent/peer_id.hpp" // sha1_hash +#include "libtorrent/hex.hpp" // to_hex +#include "libtorrent/socket_io.hpp" + +void log_hash_block(FILE** f, libtorrent::torrent const& t, int piece, int block + , libtorrent::address a, char const* bytes, int len, bool corrupt) +{ + using namespace libtorrent; + + mkdir("hash_failures", 0755); + + if (*f == NULL) + { + char filename[1024]; + snprintf(filename, sizeof(filename), "hash_failures/%s.log" + , to_hex(t.info_hash().to_string()).c_str()); + *f = fopen(filename, "w"); + } + + file_storage const& fs = t.torrent_file().files(); + std::vector files = fs.map_block(piece, block * 0x4000, len); + + std::string fn = fs.file_path(fs.internal_at(files[0].file_index)); + + char filename[4094]; + int offset = 0; + for (int i = 0; i < files.size(); ++i) + { + offset += snprintf(filename+offset, sizeof(filename)-offset + , "%s[%" PRId64 ",%d]", libtorrent::filename(fn).c_str(), files[i].offset, int(files[i].size)); + if (offset >= sizeof(filename)) break; + } + + fprintf(*f, "%s\t%04d\t%04d\t%s\t%s\t%s\n", to_hex(t.info_hash().to_string()).c_str(), piece + , block, corrupt ? " bad" : "good", print_address(a).c_str(), filename); + + snprintf(filename, sizeof(filename), "hash_failures/%s_%d_%d_%s.block" + , to_hex(t.info_hash().to_string()).c_str(), piece, block, corrupt ? "bad" : "good"); + FILE* data = fopen(filename, "w+"); + fwrite(bytes, 1, len, data); + fclose(data); +} + +#endif + + +namespace libtorrent { + + class torrent; + +namespace +{ + + struct smart_ban_plugin TORRENT_FINAL + : torrent_plugin + , boost::enable_shared_from_this + { + smart_ban_plugin(torrent& t) + : m_torrent(t) + , m_salt(random()) + { +#ifdef TORRENT_LOG_HASH_FAILURES + m_log_file = NULL; +#endif + } + +#ifdef TORRENT_LOG_HASH_FAILURES + ~smart_ban_plugin() + { fclose(m_log_file); } +#endif + + virtual void on_piece_pass(int p) TORRENT_OVERRIDE + { +#ifndef TORRENT_DISABLE_LOGGING + m_torrent.debug_log(" PIECE PASS [ p: %d | block_hash_size: %d ]" + , p, int(m_block_hashes.size())); +#endif + // 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 + std::map::iterator i = m_block_hashes.lower_bound(piece_block(p, 0)); + if (i == m_block_hashes.end() || int(i->first.piece_index) != p) return; + + 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, boost::bind(&smart_ban_plugin::on_read_ok_block + , shared_from_this(), *i, i->second.peer->address(), _1) + , reinterpret_cast(1)); + m_block_hashes.erase(i++); + } + else + { + TORRENT_ASSERT(i->first.block_index > pb.block_index); + } + + if (i == m_block_hashes.end() || int(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() || int(i->first.piece_index) != p); +#endif + + if (m_torrent.is_seed()) + { + std::map().swap(m_block_hashes); + return; + } + } + + virtual void on_piece_failed(int p) TORRENT_OVERRIDE + { + // The piece failed the hash check. Record + // the CRC and origin peer of every block + + // if the torrent is aborted, no point in starting + // a bunch of read operations on it + if (m_torrent.is_aborted()) return; + + std::vector downloaders; + m_torrent.picker().get_downloaders(downloaders, p); + + int size = m_torrent.torrent_file().piece_size(p); + peer_request r = {p, 0, (std::min)(16*1024, size)}; + piece_block pb(p, 0); + for (std::vector::iterator i = downloaders.begin() + , end(downloaders.end()); i != end; ++i) + { + if (*i != NULL) + { + // 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 + , boost::bind(&smart_ban_plugin::on_read_failed_block + , shared_from_this(), pb, (*i)->address(), _1) + , reinterpret_cast(1) + , disk_io_job::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 b, address a, disk_io_job const* j) + { + TORRENT_ASSERT(m_torrent.session().is_single_thread()); + + disk_buffer_holder buffer(m_torrent.session(), *j); + + // ignore read errors + if (j->ret != j->d.io.buffer_size) return; + + hasher h; + h.update(j->buffer.disk_block, j->d.io.buffer_size); + h.update(reinterpret_cast(&m_salt), sizeof(m_salt)); + + std::pair 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()}; + +#ifdef TORRENT_LOG_HASH_FAILURES + log_hash_block(&m_log_file, m_torrent, b.piece_index + , b.block_index, p->address(), j->buffer.disk_block, j->buffer_size, true); +#endif + + std::map::iterator 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 + 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 ]" + , b.piece_index, b.block_index, client + , to_hex(i->second.digest.to_string()).c_str() + , to_hex(e.digest.to_string()).c_str() + , print_endpoint(p->ip()).c_str()); +#endif + m_torrent.ban_peer(p); + if (p->connection) p->connection->disconnect( + errors::peer_banned, op_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 + 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 ]" + , b.piece_index, b.block_index, client + , to_hex(e.digest.to_string()).c_str() + , print_address(p->ip().address()).c_str()); +#endif + } + + void on_read_ok_block(std::pair b, address a, disk_io_job const* j) + { + TORRENT_ASSERT(m_torrent.session().is_single_thread()); + + disk_buffer_holder buffer(m_torrent.session(), *j); + + // ignore read errors + if (j->ret != j->d.io.buffer_size) return; + + hasher h; + h.update(j->buffer.disk_block, j->d.io.buffer_size); + h.update(reinterpret_cast(&m_salt), sizeof(m_salt)); + sha1_hash ok_digest = h.final(); + + if (b.second.digest == ok_digest) return; + +#ifdef TORRENT_LOG_HASH_FAILURES + log_hash_block(&m_log_file, m_torrent, b.first.piece_index + , b.first.block_index, a, j->buffer.disk_block, j->buffer_size, false); +#endif + + // find the peer + std::pair range + = m_torrent.find_peers(a); + if (range.first == range.second) return; + torrent_peer* p = NULL; + for (; range.first != range.second; ++range.first) + { + if (b.second.peer != *range.first) continue; + p = *range.first; + } + if (p == NULL) return; + +#ifndef TORRENT_DISABLE_LOGGING + 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 ]" + , b.first.piece_index, b.first.block_index, client + , to_hex(ok_digest.to_string()).c_str() + , to_hex(b.second.digest.to_string()).c_str() + , print_address(p->ip().address()).c_str()); +#endif + m_torrent.ban_peer(p); + if (p->connection) p->connection->disconnect( + errors::peer_banned, op_bittorrent); + } + + torrent& m_torrent; + + // This table maps a piece_block (piece and block index + // pair) to a peer and the block CRC. The CRC is calculated + // from the data in the block + the salt + std::map m_block_hashes; + + // This salt is a random value used to calculate the block CRCs + // Since the CRC function that is used is not a one way function + // the salt is required to avoid attacks where bad data is sent + // that is forged to match the CRC of the good data. + int m_salt; + +#ifdef TORRENT_LOG_HASH_FAILURES + FILE* m_log_file; +#endif + // explicitly disallow assignment, to silence msvc warning + smart_ban_plugin& operator=(smart_ban_plugin const&); + }; + +} } + +namespace libtorrent +{ + + boost::shared_ptr create_smart_ban_plugin(torrent_handle const& th, void*) + { + torrent* t = th.native_handle().get(); + return boost::shared_ptr(new smart_ban_plugin(*t)); + } + +} + +#endif + diff --git a/src/socket_io.cpp b/src/socket_io.cpp new file mode 100644 index 0000000..bbea473 --- /dev/null +++ b/src/socket_io.cpp @@ -0,0 +1,161 @@ +/* + +Copyright (c) 2009-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 "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 + +namespace libtorrent +{ + + std::string print_address(address const& addr) + { + error_code ec; + return addr.to_string(ec); + } + + std::string address_to_bytes(address const& a) + { + std::string ret; + std::back_insert_iterator out(ret); + detail::write_address(a, out); + return ret; + } + + std::string endpoint_to_bytes(udp::endpoint const& ep) + { + std::string ret; + std::back_insert_iterator out(ret); + detail::write_endpoint(ep, out); + return ret; + } + + std::string print_endpoint(tcp::endpoint const& ep) + { + error_code ec; + char buf[200]; + address const& addr = ep.address(); +#if TORRENT_USE_IPV6 + if (addr.is_v6()) + snprintf(buf, sizeof(buf), "[%s]:%d", addr.to_string(ec).c_str(), ep.port()); + else +#endif + snprintf(buf, sizeof(buf), "%s:%d", addr.to_string(ec).c_str(), ep.port()); + return buf; + } + + std::string print_endpoint(udp::endpoint const& ep) + { + return print_endpoint(tcp::endpoint(ep.address(), ep.port())); + } + + tcp::endpoint parse_endpoint(std::string str, error_code& ec) + { + tcp::endpoint ret; + + std::string::iterator start = str.begin(); + std::string::iterator port_pos; + // remove white spaces in front of the string + while (start != str.end() && is_space(*start)) + ++start; + + // this is for IPv6 addresses + if (start != str.end() && *start == '[') + { + ++start; + port_pos = std::find(start, str.end(), ']'); + if (port_pos == str.end()) + { + ec = errors::expected_close_bracket_in_address; + return ret; + } + *port_pos = '\0'; + ++port_pos; + if (port_pos == str.end() || *port_pos != ':') + { + ec = errors::invalid_port; + return ret; + } +#if TORRENT_USE_IPV6 + ret.address(address_v6::from_string(&*start, ec)); +#else + ec = boost::asio::error::address_family_not_supported; +#endif + if (ec) return ret; + } + else + { + port_pos = std::find(start, str.end(), ':'); + if (port_pos == str.end()) + { + ec = errors::invalid_port; + return ret; + } + *port_pos = '\0'; + ret.address(address_v4::from_string(&*start, ec)); + if (ec) return ret; + } + + ++port_pos; + if (port_pos == str.end()) + { + ec = errors::invalid_port; + return ret; + } + + ret.port(std::atoi(&*port_pos)); + return ret; + } + + void hash_address(address const& ip, sha1_hash& h) + { +#if TORRENT_USE_IPV6 + if (ip.is_v6()) + { + address_v6::bytes_type b = ip.to_v6().to_bytes(); + h = hasher(reinterpret_cast(&b[0]), b.size()).final(); + } + else +#endif + { + address_v4::bytes_type b = ip.to_v4().to_bytes(); + h = hasher(reinterpret_cast(&b[0]), b.size()).final(); + } + } + +} + diff --git a/src/socket_type.cpp b/src/socket_type.cpp new file mode 100644 index 0000000..2da3327 --- /dev/null +++ b/src/socket_type.cpp @@ -0,0 +1,400 @@ +/* + +Copyright (c) 2009-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/socket_type.hpp" +#include "libtorrent/aux_/openssl.hpp" + +#ifdef TORRENT_USE_OPENSSL +#include + +#if BOOST_VERSION >= 104700 +#include +#endif + +#endif + +#if defined TORRENT_ASIO_DEBUGGING +#include "libtorrent/debug.hpp" +#endif + +namespace libtorrent +{ + + bool is_ssl(socket_type const& s) + { +#ifdef TORRENT_USE_OPENSSL +#define CASE(t) case socket_type_int_impl >::value: + switch (s.type()) + { + CASE(tcp::socket) + CASE(socks5_stream) + CASE(http_stream) + CASE(utp_stream) + return true; + default: return false; + }; +#undef CASE +#else + TORRENT_UNUSED(s); + return false; +#endif + } + + bool is_utp(socket_type const& s) + { + return s.get() +#ifdef TORRENT_USE_OPENSSL + || s.get >() +#endif + ; + } + +#if TORRENT_USE_I2P + bool is_i2p(socket_type const& s) + { + return s.get() +#ifdef TORRENT_USE_OPENSSL + || s.get >() +#endif + ; + } +#endif + + void setup_ssl_hostname(socket_type& s, std::string const& hostname, error_code& ec) + { +#if defined TORRENT_USE_OPENSSL && BOOST_VERSION >= 104700 + // for SSL connections, make sure to authenticate the hostname + // of the certificate +#define CASE(t) case socket_type_int_impl >::value: \ + s.get >()->set_verify_callback( \ + boost::asio::ssl::rfc2818_verification(hostname), ec); \ + ssl = s.get >()->native_handle(); \ + ctx = SSL_get_SSL_CTX(ssl); \ + break; + + SSL* ssl = 0; + SSL_CTX* ctx = 0; + + switch(s.type()) + { + CASE(tcp::socket) + CASE(socks5_stream) + CASE(http_stream) + CASE(utp_stream) + } +#undef CASE + +#if OPENSSL_VERSION_NUMBER >= 0x90812f + if (ctx) + { + aux::openssl_set_tlsext_servername_callback(ctx, 0); + aux::openssl_set_tlsext_servername_arg(ctx, 0); + } +#endif // OPENSSL_VERSION_NUMBER + +#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + if (ssl) + { + aux::openssl_set_tlsext_hostname(ssl, hostname.c_str()); + } +#endif + +#else + TORRENT_UNUSED(ec); + TORRENT_UNUSED(hostname); + TORRENT_UNUSED(s); +#endif + } + +#ifdef TORRENT_USE_OPENSSL + namespace { + + void on_close_socket(socket_type* s, boost::shared_ptr) + { +#if defined TORRENT_ASIO_DEBUGGING + complete_async("on_close_socket"); +#endif + error_code ec; + s->close(ec); + } + + } // anonymous namespace +#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, boost::shared_ptr holder) + { + error_code e; + +#ifdef TORRENT_USE_OPENSSL + // for SSL connections, first do an async_shutdown, before closing the socket +#if defined TORRENT_ASIO_DEBUGGING +#define MAYBE_ASIO_DEBUGGING add_outstanding_async("on_close_socket"); +#else +#define MAYBE_ASIO_DEBUGGING +#endif + +#define CASE(t) case socket_type_int_impl >::value: \ + MAYBE_ASIO_DEBUGGING \ + s.get >()->async_shutdown(boost::bind(&on_close_socket, &s, holder)); \ + break; + + switch (s.type()) + { + CASE(tcp::socket) + CASE(socks5_stream) + CASE(http_stream) + CASE(utp_stream) + default: s.close(e); break; + } +#undef CASE +#else + TORRENT_UNUSED(holder); + s.close(e); +#endif // TORRENT_USE_OPENSSL + } + + void socket_type::destruct() + { + typedef tcp::socket tcp_socket; + switch (m_type) + { + case 0: break; + case socket_type_int_impl::value: + get()->~tcp_socket(); + break; + case socket_type_int_impl::value: + get()->~socks5_stream(); + break; + case socket_type_int_impl::value: + get()->~http_stream(); + break; + case socket_type_int_impl::value: + get()->~utp_stream(); + break; +#if TORRENT_USE_I2P + case socket_type_int_impl::value: + get()->~i2p_stream(); + break; +#endif +#ifdef TORRENT_USE_OPENSSL + case socket_type_int_impl >::value: + get >()->~ssl_stream(); + break; + case socket_type_int_impl >::value: + get >()->~ssl_stream(); + break; + case socket_type_int_impl >::value: + get >()->~ssl_stream(); + break; + case socket_type_int_impl >::value: + get >()->~ssl_stream(); + break; +#endif + default: TORRENT_ASSERT(false); + } + m_type = 0; + } + + void socket_type::construct(int type, void* userdata) + { +#ifndef TORRENT_USE_OPENSSL + TORRENT_UNUSED(userdata); +#endif + + destruct(); + switch (type) + { + case 0: break; + case socket_type_int_impl::value: + new (reinterpret_cast(&m_data)) tcp::socket(m_io_service); + break; + case socket_type_int_impl::value: + new (reinterpret_cast(&m_data)) socks5_stream(m_io_service); + break; + case socket_type_int_impl::value: + new (reinterpret_cast(&m_data)) http_stream(m_io_service); + break; + case socket_type_int_impl::value: + new (reinterpret_cast(&m_data)) utp_stream(m_io_service); + break; +#if TORRENT_USE_I2P + case socket_type_int_impl::value: + new (reinterpret_cast(&m_data)) i2p_stream(m_io_service); + break; +#endif +#ifdef TORRENT_USE_OPENSSL + case socket_type_int_impl >::value: + TORRENT_ASSERT(userdata); + new (reinterpret_cast*>(&m_data)) ssl_stream(m_io_service + , *static_cast(userdata)); + break; + case socket_type_int_impl >::value: + TORRENT_ASSERT(userdata); + new (reinterpret_cast*>(&m_data)) ssl_stream(m_io_service + , *static_cast(userdata)); + break; + case socket_type_int_impl >::value: + TORRENT_ASSERT(userdata); + new (reinterpret_cast*>(&m_data)) ssl_stream(m_io_service + , *static_cast(userdata)); + break; + case socket_type_int_impl >::value: + TORRENT_ASSERT(userdata); + new (reinterpret_cast*>(&m_data)) ssl_stream(m_io_service + , *static_cast(userdata)); + break; +#endif + default: TORRENT_ASSERT(false); + } + + m_type = type; + } + + char const* socket_type::type_name() const + { + static char const* const names[] = + { + "uninitialized", + "TCP", + "Socks5", + "HTTP", + "uTP", +#if TORRENT_USE_I2P + "I2P", +#else + "", +#endif +#ifdef TORRENT_USE_OPENSSL + "SSL/TCP", + "SSL/Socks5", + "SSL/HTTP", + "SSL/uTP" +#else + "","","","" +#endif + }; + return names[m_type]; + } + + io_service& socket_type::get_io_service() const + { return m_io_service; } + + socket_type::~socket_type() + { destruct(); } + + bool socket_type::is_open() const + { + if (m_type == 0) return false; + TORRENT_SOCKTYPE_FORWARD_RET(is_open(), false) + } + + void socket_type::open(protocol_type const& p, error_code& ec) + { TORRENT_SOCKTYPE_FORWARD(open(p, ec)) } + + void socket_type::close(error_code& ec) + { + if (m_type == 0) return; + TORRENT_SOCKTYPE_FORWARD(close(ec)) + } + + void socket_type::set_close_reason(boost::uint16_t code) + { + switch (m_type) + { + case socket_type_int_impl::value: + get()->set_close_reason(code); + break; +#ifdef TORRENT_USE_OPENSSL + case socket_type_int_impl >::value: + get >()->lowest_layer().set_close_reason(code); + break; +#endif + default: break; + } + } + + boost::uint16_t socket_type::get_close_reason() + { + switch (m_type) + { + case socket_type_int_impl::value: + return get()->get_close_reason(); +#ifdef TORRENT_USE_OPENSSL + case socket_type_int_impl >::value: + return get >()->lowest_layer().get_close_reason(); +#endif + default: return 0; + } + } + + socket_type::endpoint_type socket_type::local_endpoint(error_code& ec) const + { TORRENT_SOCKTYPE_FORWARD_RET(local_endpoint(ec), socket_type::endpoint_type()) } + + socket_type::endpoint_type socket_type::remote_endpoint(error_code& ec) const + { TORRENT_SOCKTYPE_FORWARD_RET(remote_endpoint(ec), socket_type::endpoint_type()) } + + void socket_type::bind(endpoint_type const& endpoint, error_code& ec) + { TORRENT_SOCKTYPE_FORWARD(bind(endpoint, ec)) } + + std::size_t socket_type::available(error_code& ec) const + { TORRENT_SOCKTYPE_FORWARD_RET(available(ec), 0) } + + int socket_type::type() const { return m_type; } + +#ifndef BOOST_NO_EXCEPTIONS + void socket_type::open(protocol_type const& p) + { TORRENT_SOCKTYPE_FORWARD(open(p)) } + + void socket_type::close() + { + if (m_type == 0) return; + TORRENT_SOCKTYPE_FORWARD(close()) + } + + socket_type::endpoint_type socket_type::local_endpoint() const + { TORRENT_SOCKTYPE_FORWARD_RET(local_endpoint(), socket_type::endpoint_type()) } + + socket_type::endpoint_type socket_type::remote_endpoint() const + { TORRENT_SOCKTYPE_FORWARD_RET(remote_endpoint(), socket_type::endpoint_type()) } + + void socket_type::bind(endpoint_type const& endpoint) + { TORRENT_SOCKTYPE_FORWARD(bind(endpoint)) } + + std::size_t socket_type::available() const + { TORRENT_SOCKTYPE_FORWARD_RET(available(), 0) } +#endif + +} + diff --git a/src/socks5_stream.cpp b/src/socks5_stream.cpp new file mode 100644 index 0000000..4ad498e --- /dev/null +++ b/src/socks5_stream.cpp @@ -0,0 +1,551 @@ +/* + +Copyright (c) 2007-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/socks5_stream.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/socket_io.hpp" + +namespace libtorrent +{ + + namespace socks_error + { + boost::system::error_code make_error_code(socks_error_code e) + { + return error_code(e, get_socks_category()); + } + } + + struct socks_error_category : boost::system::error_category + { + virtual const char* name() const BOOST_SYSTEM_NOEXCEPT + { return "socks error"; } + virtual std::string message(int ev) const BOOST_SYSTEM_NOEXCEPT + { + 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]; + } + virtual boost::system::error_condition default_error_condition( + int ev) const BOOST_SYSTEM_NOEXCEPT + { return boost::system::error_condition(ev, *this); } + }; + + TORRENT_EXPORT boost::system::error_category& get_socks_category() + { + static socks_error_category socks_category; + return socks_category; + } + + namespace + { + // parse out the endpoint from a SOCKS response + tcp::endpoint parse_endpoint(std::vector const& buffer + , int const version) + { + using namespace libtorrent::detail; + char const* p = &buffer[0]; + p += 2; // version & response code + if (version == 5) + { + ++p; // reserved byte + int const atyp = read_uint8(p); + + if (atyp == 1) + { + tcp::endpoint ret; + ret.address(read_v4_address(p)); + ret.port(read_uint16(p)); + return ret; + } + else if (atyp == 3) + { + // we don't support resolving the endpoint address + // if we receive a domain name, just set the remote + // endpoint to INADDR_ANY + return tcp::endpoint(); + } + else if (atyp == 4) + { + tcp::endpoint ret; +#if TORRENT_USE_IPV6 + ret.address(read_v6_address(p)); + ret.port(read_uint16(p)); +#endif + return ret; + } + } + else if (version == 4) + { + tcp::endpoint ret; + ret.port(read_uint16(p)); + ret.address(read_v4_address(p)); + return ret; + } + TORRENT_ASSERT(false); + return tcp::endpoint(); + } + } + + void socks5_stream::name_lookup(error_code const& e, tcp::resolver::iterator i + , boost::shared_ptr h) + { +#if defined TORRENT_ASIO_DEBUGGING + complete_async("socks5_stream::name_lookup"); +#endif + if (handle_error(e, h)) return; + + error_code ec; + if (!m_sock.is_open()) + { + m_sock.open(i->endpoint().protocol(), ec); + if (handle_error(ec, h)) return; + } + + // TOOD: we could bind the socket here, since we know what the + // target endpoint is of the proxy +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("socks5_stream::connected"); +#endif + m_sock.async_connect(i->endpoint(), boost::bind( + &socks5_stream::connected, this, _1, h)); + } + + void socks5_stream::connected(error_code const& e, boost::shared_ptr h) + { +#if defined TORRENT_ASIO_DEBUGGING + complete_async("socks5_stream::connected"); +#endif + if (handle_error(e, h)) return; + + using namespace libtorrent::detail; + if (m_version == 5) + { + // send SOCKS5 authentication methods + m_buffer.resize(m_user.empty()?3:4); + char* p = &m_buffer[0]; + write_uint8(5, p); // SOCKS VERSION 5 + if (m_user.empty()) + { + write_uint8(1, p); // 1 authentication method (no auth) + write_uint8(0, p); // no authentication + } + else + { + write_uint8(2, p); // 2 authentication methods + write_uint8(0, p); // no authentication + write_uint8(2, p); // username/password + } +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("socks5_stream::handshake1"); +#endif + async_write(m_sock, boost::asio::buffer(m_buffer) + , boost::bind(&socks5_stream::handshake1, this, _1, h)); + } + else if (m_version == 4) + { + socks_connect(h); + } + else + { + (*h)(socks_error::unsupported_version); + } + } + + void socks5_stream::handshake1(error_code const& e, boost::shared_ptr h) + { +#if defined TORRENT_ASIO_DEBUGGING + complete_async("socks5_stream::handshake1"); +#endif + if (handle_error(e, h)) return; + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("socks5_stream::handshake2"); +#endif + m_buffer.resize(2); + async_read(m_sock, boost::asio::buffer(m_buffer) + , boost::bind(&socks5_stream::handshake2, this, _1, h)); + } + + void socks5_stream::handshake2(error_code const& e, boost::shared_ptr h) + { +#if defined TORRENT_ASIO_DEBUGGING + complete_async("socks5_stream::handshake2"); +#endif + if (handle_error(e, h)) return; + + using namespace libtorrent::detail; + + char* p = &m_buffer[0]; + int version = read_uint8(p); + int method = read_uint8(p); + + if (version < m_version) + { + (*h)(socks_error::unsupported_version); + return; + } + + if (method == 0) + { + socks_connect(h); + } + else if (method == 2) + { + if (m_user.empty()) + { + (*h)(socks_error::username_required); + return; + } + + // start sub-negotiation + m_buffer.resize(m_user.size() + m_password.size() + 3); + p = &m_buffer[0]; + write_uint8(1, p); + write_uint8(m_user.size(), p); + write_string(m_user, p); + write_uint8(m_password.size(), p); + write_string(m_password, p); + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("socks5_stream::handshake3"); +#endif + async_write(m_sock, boost::asio::buffer(m_buffer) + , boost::bind(&socks5_stream::handshake3, this, _1, h)); + } + else + { + (*h)(socks_error::unsupported_authentication_method); + return; + } + } + + void socks5_stream::handshake3(error_code const& e + , boost::shared_ptr h) + { +#if defined TORRENT_ASIO_DEBUGGING + complete_async("socks5_stream::handshake3"); +#endif + if (handle_error(e, h)) return; + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("socks5_stream::handshake4"); +#endif + m_buffer.resize(2); + async_read(m_sock, boost::asio::buffer(m_buffer) + , boost::bind(&socks5_stream::handshake4, this, _1, h)); + } + + void socks5_stream::handshake4(error_code const& e + , boost::shared_ptr h) + { +#if defined TORRENT_ASIO_DEBUGGING + complete_async("socks5_stream::handshake4"); +#endif + if (handle_error(e, h)) return; + + using namespace libtorrent::detail; + + char* p = &m_buffer[0]; + int version = read_uint8(p); + int status = read_uint8(p); + + if (version != 1) + { + (*h)(socks_error::unsupported_authentication_version); + return; + } + + if (status != 0) + { + (*h)(socks_error::authentication_error); + return; + } + + std::vector().swap(m_buffer); + socks_connect(h); + } + + void socks5_stream::socks_connect(boost::shared_ptr h) + { + using namespace libtorrent::detail; + + if (m_version == 5) + { + // send SOCKS5 connect command + m_buffer.resize(6 + (!m_dst_name.empty() + ?m_dst_name.size() + 1 + :(m_remote_endpoint.address().is_v4()?4:16))); + char* p = &m_buffer[0]; + write_uint8(5, p); // SOCKS VERSION 5 + write_uint8(m_command, p); // CONNECT/BIND command + write_uint8(0, p); // reserved + if (!m_dst_name.empty()) + { + write_uint8(3, p); // address type + TORRENT_ASSERT(m_dst_name.size() <= 255); + write_uint8(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_command == socks5_bind + || m_remote_endpoint.address() != address()); + + write_uint8(m_remote_endpoint.address().is_v4()?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 (!m_remote_endpoint.address().is_v4()) + { + (*h)(boost::asio::error::address_family_not_supported); + return; + } + m_buffer.resize(m_user.size() + 9); + char* p = &m_buffer[0]; + write_uint8(4, p); // SOCKS VERSION 4 + write_uint8(m_command, p); // CONNECT/BIND command + write_uint16(m_remote_endpoint.port(), p); + write_uint32(m_remote_endpoint.address().to_v4().to_ulong(), p); + std::copy(m_user.begin(), m_user.end(), p); + p += m_user.size(); + write_uint8(0, p); // NULL terminator + } + else + { + (*h)(socks_error::unsupported_version); + return; + } + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("socks5_stream::connect1"); +#endif + async_write(m_sock, boost::asio::buffer(m_buffer) + , boost::bind(&socks5_stream::connect1, this, _1, h)); + } + + void socks5_stream::connect1(error_code const& e, boost::shared_ptr h) + { +#if defined TORRENT_ASIO_DEBUGGING + complete_async("socks5_stream::connect1"); +#endif + if (handle_error(e, h)) return; + + if (m_version == 5) + m_buffer.resize(6 + 4); // assume an IPv4 address + else if (m_version == 4) + m_buffer.resize(8); + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("socks5_stream::connect2"); +#endif + async_read(m_sock, boost::asio::buffer(m_buffer) + , boost::bind(&socks5_stream::connect2, this, _1, h)); + } + + void socks5_stream::connect2(error_code const& e, boost::shared_ptr h) + { +#if defined TORRENT_ASIO_DEBUGGING + complete_async("socks5_stream::connect2"); +#endif + if (handle_error(e, h)) return; + + using namespace libtorrent::detail; + + char const* p = &m_buffer[0]; + int const version = read_uint8(p); + int const response = read_uint8(p); + + if (m_version == 5) + { + if (version < m_version) + { + (*h)(socks_error::unsupported_version); + return; + } + if (response != 0) + { + error_code ec(socks_error::general_failure, get_socks_category()); + switch (response) + { + case 2: ec = boost::asio::error::no_permission; break; + case 3: ec = boost::asio::error::network_unreachable; break; + case 4: ec = boost::asio::error::host_unreachable; break; + case 5: ec = boost::asio::error::connection_refused; break; + case 6: ec = boost::asio::error::timed_out; break; + case 7: ec = socks_error::command_not_supported; break; + case 8: ec = boost::asio::error::address_family_not_supported; break; + } + (*h)(ec); + return; + } + p += 1; // reserved + int const atyp = read_uint8(p); + // read the proxy IP it was bound to (this is variable length depending + // on address type) + if (atyp == 1) + { + if (m_command == socks5_bind) + { + if (m_listen == 0) + { + m_local_endpoint = parse_endpoint(m_buffer, m_version); + m_listen = 1; + } + else + { + m_remote_endpoint = parse_endpoint(m_buffer, m_version); + } + std::vector().swap(m_buffer); + (*h)(e); + } + else + { + std::vector().swap(m_buffer); + (*h)(e); + } + return; + } + int extra_bytes = 0; + if (atyp == 4) + { + // IPv6 + extra_bytes = 12; + } + else if (atyp == 3) + { + // hostname with length prefix + extra_bytes = read_uint8(p) - 3; + } + else + { + (*h)(boost::asio::error::address_family_not_supported); + return; + } + m_buffer.resize(m_buffer.size() + extra_bytes); + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("socks5_stream::connect3"); +#endif + TORRENT_ASSERT(extra_bytes > 0); + async_read(m_sock, boost::asio::buffer(&m_buffer[m_buffer.size() - extra_bytes], extra_bytes) + , boost::bind(&socks5_stream::connect3, this, _1, h)); + } + else if (m_version == 4) + { + if (version != 0) + { + (*h)(socks_error::general_failure); + return; + } + + // access granted + if (response == 90) + { + if (m_command == socks5_bind) + { + if (m_listen == 0) + { + m_local_endpoint = parse_endpoint(m_buffer, m_version); + m_listen = 1; + } + else + { + m_remote_endpoint = parse_endpoint(m_buffer, m_version); + } + std::vector().swap(m_buffer); + (*h)(e); + } + else + { + std::vector().swap(m_buffer); + (*h)(e); + } + return; + } + + error_code ec(socks_error::general_failure, get_socks_category()); + switch (response) + { + case 91: ec = boost::asio::error::connection_refused; break; + case 92: ec = socks_error::no_identd; break; + case 93: ec = socks_error::identd_error; break; + } + (*h)(ec); + } + } + + void socks5_stream::connect3(error_code const& e, boost::shared_ptr h) + { +#if defined TORRENT_ASIO_DEBUGGING + complete_async("socks5_stream::connect3"); +#endif + using namespace libtorrent::detail; + + if (handle_error(e, h)) return; + + if (m_command == socks5_bind) + { + if (m_listen == 0) + { + m_local_endpoint = parse_endpoint(m_buffer, m_version); + m_listen = 1; + } + else + { + m_remote_endpoint = parse_endpoint(m_buffer, m_version); + } + } + std::vector().swap(m_buffer); + (*h)(e); + } +} + diff --git a/src/stat.cpp b/src/stat.cpp new file mode 100644 index 0000000..e96b3b6 --- /dev/null +++ b/src/stat.cpp @@ -0,0 +1,49 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include + +#include "libtorrent/stat.hpp" + +namespace libtorrent { + +void stat_channel::second_tick(int tick_interval_ms) +{ + int sample = int(boost::int64_t(m_counter) * 1000 / tick_interval_ms); + TORRENT_ASSERT(sample >= 0); + m_5_sec_average = boost::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..289cbf9 --- /dev/null +++ b/src/stat_cache.cpp @@ -0,0 +1,97 @@ +/* + +Copyright (c) 2012-2016, Arvid Norberg, Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/stat_cache.hpp" +#include "libtorrent/assert.hpp" + +namespace libtorrent +{ + stat_cache::stat_cache() {} + stat_cache::~stat_cache() {} + + void stat_cache::set_cache(int i, boost::int64_t size, time_t time) + { + TORRENT_ASSERT(i >= 0); + if (i >= int(m_stat_cache.size())) + m_stat_cache.resize(i + 1, not_in_cache); + m_stat_cache[i].file_size = size; + m_stat_cache[i].file_time = time; + } + + void stat_cache::set_dirty(int i) + { + TORRENT_ASSERT(i >= 0); + if (i >= int(m_stat_cache.size())) return; + m_stat_cache[i].file_size = not_in_cache; + } + + void stat_cache::set_noexist(int i) + { + TORRENT_ASSERT(i >= 0); + if (i >= int(m_stat_cache.size())) + m_stat_cache.resize(i + 1, not_in_cache); + m_stat_cache[i].file_size = no_exist; + } + + void stat_cache::set_error(int i) + { + TORRENT_ASSERT(i >= 0); + if (i >= int(m_stat_cache.size())) + m_stat_cache.resize(i + 1, not_in_cache); + m_stat_cache[i].file_size = cache_error; + } + + boost::int64_t stat_cache::get_filesize(int i) const + { + if (i >= int(m_stat_cache.size())) return not_in_cache; + return m_stat_cache[i].file_size; + } + + time_t stat_cache::get_filetime(int i) const + { + if (i >= int(m_stat_cache.size())) return not_in_cache; + if (m_stat_cache[i].file_size < 0) return m_stat_cache[i].file_size; + return m_stat_cache[i].file_time; + } + + void stat_cache::init(int num_files) + { + m_stat_cache.resize(num_files, not_in_cache); + } + + void stat_cache::clear() + { + std::vector().swap(m_stat_cache); + } + +} + diff --git a/src/storage.cpp b/src/storage.cpp new file mode 100644 index 0000000..18cd4f6 --- /dev/null +++ b/src/storage.cpp @@ -0,0 +1,1920 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg, Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/error_code.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#if defined(__APPLE__) +// for getattrlist() +#include +#include +// for statfs() +#include +#include +#endif + +#if defined(__linux__) +#include +#endif + +#if defined(__FreeBSD__) +// for statfs() +#include +#include +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/storage.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/file.hpp" +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/file_pool.hpp" +#include "libtorrent/aux_/session_impl.hpp" +#include "libtorrent/disk_buffer_holder.hpp" +#include "libtorrent/alloca.hpp" +#include "libtorrent/stat_cache.hpp" + +#include + +//#define TORRENT_PARTIAL_HASH_LOG + +// for convert_to_wstring and convert_to_native +#include "libtorrent/aux_/escape_string.hpp" + +#define DEBUG_STORAGE 0 +#define DEBUG_DELETE_FILES 0 + +#if __cplusplus >= 201103L || defined __clang__ + +#if DEBUG_STORAGE +#define DLOG(...) fprintf(__VA_ARGS__) +#else +#define DLOG(...) do {} while (false) +#endif + +#if DEBUG_DELETE_FILES +#define DFLOG(...) fprintf(__VA_ARGS__) +#else +#define DFLOG(...) do {} while (false) +#endif + +#else + +#if DEBUG_STORAGE +#define DLOG fprintf +#else +#define DLOG TORRENT_WHILE_0 fprintf +#endif + +#if DEBUG_DELETE_FILES +#define DFLOG fprintf +#else +#define DFLOG TORRENT_WHILE_0 fprintf +#endif + +#endif // cplusplus + +namespace libtorrent +{ + int copy_bufs(file::iovec_t const* bufs, int bytes, file::iovec_t* target) + { + int size = 0; + int ret = 1; + for (;;) + { + *target = *bufs; + size += bufs->iov_len; + if (size >= bytes) + { + target->iov_len -= size - bytes; + return ret; + } + ++bufs; + ++target; + ++ret; + } + } + + void advance_bufs(file::iovec_t*& bufs, int bytes) + { + int size = 0; + for (;;) + { + size += bufs->iov_len; + if (size >= bytes) + { + bufs->iov_base = reinterpret_cast(bufs->iov_base) + + bufs->iov_len - (size - bytes); + bufs->iov_len = size - bytes; + return; + } + ++bufs; + } + } + + void clear_bufs(file::iovec_t const* bufs, int num_bufs) + { + for (file::iovec_t const* i = bufs, *end(bufs + num_bufs); i < end; ++i) + std::memset(i->iov_base, 0, i->iov_len); + } + + namespace { + + int count_bufs(file::iovec_t const* bufs, int bytes) + { + int size = 0; + int count = 1; + if (bytes == 0) return 0; + for (file::iovec_t const* i = bufs;; ++i, ++count) + { + size += i->iov_len; + if (size >= bytes) return count; + } + } + +#ifdef TORRENT_DISK_STATS + static boost::atomic event_id; + static mutex disk_access_mutex; + + // this is opened and closed by the disk_io_thread class + FILE* g_access_log = NULL; + + enum access_log_flags_t + { + op_read = 0, + op_write = 1, + op_start = 0, + op_end = 2 + }; + + void write_access_log(boost::uint64_t offset, boost::uint32_t fileid, int flags, time_point timestamp) + { + if (g_access_log == NULL) return; + + // the event format in the log is: + // uint64_t timestamp (microseconds) + // uint64_t file offset + // uint32_t file-id + // uint8_t event (0: start read, 1: start write, 2: complete read, 4: complete write) + char event[29]; + char* ptr = event; + detail::write_uint64(timestamp.time_since_epoch().count(), ptr); + detail::write_uint64(offset, ptr); + detail::write_uint64(static_cast(event_id++), ptr); + detail::write_uint32(fileid, ptr); + detail::write_uint8(flags, ptr); + + mutex::scoped_lock l(disk_access_mutex); + int ret = fwrite(event, 1, sizeof(event), g_access_log); + l.unlock(); + if (ret != sizeof(event)) + { + fprintf(stderr, "ERROR writing to disk access log: (%d) %s\n" + , errno, strerror(errno)); + } + } +#endif + + } // anonymous namespace + + struct write_fileop : fileop + { + write_fileop(default_storage& st, int flags) + : m_storage(st) + , m_flags(flags) + {} + + int file_op(int file_index, boost::int64_t file_offset, int size + , file::iovec_t const* bufs, storage_error& ec) + TORRENT_OVERRIDE TORRENT_FINAL + { + if (m_storage.files().pad_file_at(file_index)) + { + // writing to a pad-file is a no-op + return size; + } + + int num_bufs = count_bufs(bufs, size); + + if (file_index < int(m_storage.m_file_priority.size()) + && m_storage.m_file_priority[file_index] == 0) + { + m_storage.need_partfile(); + + error_code e; + peer_request map = m_storage.files().map_file(file_index + , file_offset, 0); + int ret = m_storage.m_part_file->writev(bufs, num_bufs + , map.piece, map.start, e); + + if (e) + { + ec.ec = e; + ec.file = file_index; + ec.operation = storage_error::partfile_write; + return -1; + } + return ret; + } + + // invalidate our stat cache for this file, since + // we're writing to it + m_storage.m_stat_cache.set_dirty(file_index); + + file_handle handle = m_storage.open_file(file_index + , file::read_write, ec); + if (ec) return -1; + + // please ignore the adjusted_offset. It's just file_offset. + boost::int64_t adjusted_offset = +#ifndef TORRENT_NO_DEPRECATE + m_storage.files().file_base_deprecated(file_index) + +#endif + file_offset; + +#ifdef TORRENT_DISK_STATS + write_access_log(adjusted_offset, handle->file_id(), op_start | op_write, clock_type::now()); +#endif + + error_code e; + int ret = handle->writev(adjusted_offset + , bufs, num_bufs, e, m_flags); + + // set this unconditionally in case the upper layer would like to treat + // short reads as errors + ec.operation = storage_error::write; + + // we either get an error or 0 or more bytes read + TORRENT_ASSERT(e || ret >= 0); + +#ifdef TORRENT_DISK_STATS + write_access_log(adjusted_offset + ret , handle->file_id(), op_end | op_write, clock_type::now()); +#endif + TORRENT_ASSERT(ret <= bufs_size(bufs, num_bufs)); + + if (e) + { + ec.ec = e; + ec.file = file_index; + return -1; + } + + return ret; + } + private: + default_storage& m_storage; + int m_flags; + }; + + struct read_fileop : fileop + { + read_fileop(default_storage& st, int const flags) + : m_storage(st) + , m_flags(flags) + {} + + int file_op(int file_index, boost::int64_t file_offset, int size + , file::iovec_t const* bufs, storage_error& ec) + TORRENT_OVERRIDE TORRENT_FINAL + { + int num_bufs = count_bufs(bufs, size); + + if (m_storage.files().pad_file_at(file_index)) + { + // reading from a pad file yields zeroes + clear_bufs(bufs, num_bufs); + return size; + } + + if (file_index < int(m_storage.m_file_priority.size()) + && m_storage.m_file_priority[file_index] == 0) + { + m_storage.need_partfile(); + + error_code e; + peer_request map = m_storage.files().map_file(file_index + , file_offset, 0); + int ret = m_storage.m_part_file->readv(bufs, num_bufs + , map.piece, map.start, e); + + if (e) + { + ec.ec = e; + ec.file = file_index; + ec.operation = storage_error::partfile_read; + return -1; + } + return ret; + } + + file_handle handle = m_storage.open_file(file_index + , file::read_only | m_flags, ec); + if (ec) return -1; + + // please ignore the adjusted_offset. It's just file_offset. + boost::int64_t adjusted_offset = +#ifndef TORRENT_NO_DEPRECATE + m_storage.files().file_base_deprecated(file_index) + +#endif + file_offset; + +#ifdef TORRENT_DISK_STATS + write_access_log(adjusted_offset, handle->file_id(), op_start | op_read, clock_type::now()); +#endif + + error_code e; + int ret = handle->readv(adjusted_offset + , bufs, num_bufs, e, m_flags); + + // set this unconditionally in case the upper layer would like to treat + // short reads as errors + ec.operation = storage_error::read; + + // we either get an error or 0 or more bytes read + TORRENT_ASSERT(e || ret >= 0); + +#ifdef TORRENT_DISK_STATS + write_access_log(adjusted_offset + ret , handle->file_id(), op_end | op_read, clock_type::now()); +#endif + TORRENT_ASSERT(ret <= bufs_size(bufs, num_bufs)); + + if (e) + { + ec.ec = e; + ec.file = file_index; + return -1; + } + + return ret; + } + + private: + default_storage& m_storage; + int const m_flags; + }; + + default_storage::default_storage(storage_params const& params) + : m_files(*params.files) + , m_pool(*params.pool) + , m_allocate_files(params.mode == storage_mode_allocate) + { + if (params.mapped_files) m_mapped_files.reset(new file_storage(*params.mapped_files)); + if (params.priorities) m_file_priority = *params.priorities; + + TORRENT_ASSERT(m_files.num_files() > 0); + m_save_path = complete(params.path); + m_part_file_name = "." + (params.info + ? to_hex(params.info->info_hash().to_string()) + : params.files->name()) + ".parts"; + } + + default_storage::~default_storage() + { + error_code ec; + if (m_part_file) m_part_file->flush_metadata(ec); + + // this may be called from a different + // thread than the disk thread + m_pool.release(this); + } + + void default_storage::need_partfile() + { + if (m_part_file) return; + + m_part_file.reset(new part_file( + m_save_path, m_part_file_name + , m_files.num_pieces(), m_files.piece_length())); + } + + void default_storage::set_file_priority(std::vector const& prio, storage_error& ec) + { + // extend our file priorities in case it's truncated + // the default assumed priority is 1 + if (prio.size() > m_file_priority.size()) + m_file_priority.resize(prio.size(), 1); + + file_storage const& fs = files(); + for (int i = 0; i < int(prio.size()); ++i) + { + int old_prio = m_file_priority[i]; + int new_prio = prio[i]; + if (old_prio == 0 && new_prio != 0) + { + // move stuff out of the part file + file_handle f = open_file(i, file::read_write, ec); + if (ec) return; + + need_partfile(); + + m_part_file->export_file(*f, fs.file_offset(i), fs.file_size(i), ec.ec); + if (ec) + { + ec.file = i; + ec.operation = storage_error::partfile_write; + return; + } + } + else if (old_prio != 0 && new_prio == 0) + { + // move stuff into the part file + // this is not implemented yet. + // pretend that we didn't set the priority to 0. + + std::string fp = fs.file_path(i, m_save_path); + if (exists(fp)) + new_prio = 1; +/* + file_handle f = open_file(i, file::read_only, ec); + if (ec.ec != boost::system::errc::no_such_file_or_directory) + { + if (ec) 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 = storage_error::partfile_read; + 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 = storage_error::remove; + } + } +*/ + } + ec.ec.clear(); + m_file_priority[i] = new_prio; + } + if (m_part_file) m_part_file->flush_metadata(ec.ec); + if (ec) + { + ec.file = -1; + ec.operation = storage_error::partfile_write; + } + } + + void default_storage::initialize(storage_error& ec) + { + m_stat_cache.init(files().num_files()); + +#ifdef TORRENT_WINDOWS + // don't do full file allocations on network drives +#if TORRENT_USE_WSTRING + std::wstring f = convert_to_wstring(m_save_path); + int drive_type = GetDriveTypeW(f.c_str()); +#else + int drive_type = GetDriveTypeA(m_save_path.c_str()); +#endif + + if (drive_type == DRIVE_REMOTE) + m_allocate_files = false; +#endif + + m_file_created.resize(files().num_files(), false); + + // first, create all missing directories + std::string last_path; + for (int file_index = 0; file_index < files().num_files(); ++file_index) + { + // ignore files that have priority 0 + if (int(m_file_priority.size()) > file_index + && m_file_priority[file_index] == 0) + { + continue; + } + + // ignore pad files + if (files().pad_file_at(file_index)) continue; + + if (m_stat_cache.get_filesize(file_index) == stat_cache::not_in_cache) + { + file_status s; + std::string file_path = files().file_path(file_index, m_save_path); + stat_file(file_path, &s, ec.ec); + if (ec && ec.ec != boost::system::errc::no_such_file_or_directory) + { + m_stat_cache.set_error(file_index); + ec.file = file_index; + ec.operation = storage_error::stat; + break; + } + m_stat_cache.set_cache(file_index, s.file_size, s.mtime); + } + + // if the file already exists, but is larger than what + // it's supposed to be, truncate it + // if the file is empty, just create it either way. + if ((!ec && m_stat_cache.get_filesize(file_index) > files().file_size(file_index)) + || files().file_size(file_index) == 0) + { + std::string file_path = files().file_path(file_index, m_save_path); + std::string dir = parent_path(file_path); + + if (dir != last_path) + { + last_path = dir; + + create_directories(last_path, ec.ec); + if (ec.ec) + { + ec.file = file_index; + ec.operation = storage_error::mkdir; + break; + } + } + ec.ec.clear(); + file_handle f = open_file(file_index, file::read_write + | file::random_access, ec); + if (ec) return; + + boost::int64_t size = files().file_size(file_index); + f->set_size(size, ec.ec); + if (ec) + { + ec.file = file_index; + ec.operation = storage_error::fallocate; + break; + } + size_t mtime = m_stat_cache.get_filetime(file_index); + m_stat_cache.set_cache(file_index, size, mtime); + } + ec.ec.clear(); + } + + // close files that were opened in write mode + m_pool.release(this); + +#if defined TORRENT_DEBUG_FILE_LEAKS + print_open_files("release files", m_files.name().c_str()); +#endif + } + +#ifndef TORRENT_NO_DEPRECATE + void default_storage::finalize_file(int, storage_error&) {} +#endif + + bool default_storage::has_any_file(storage_error& ec) + { + m_stat_cache.init(files().num_files()); + + std::string file_path; + for (int i = 0; i < files().num_files(); ++i) + { + file_status s; + boost::int64_t cache_status = m_stat_cache.get_filesize(i); + if (cache_status < 0 && cache_status != stat_cache::no_exist) + { + file_path = files().file_path(i, m_save_path); + stat_file(file_path, &s, ec.ec); + boost::int64_t r = s.file_size; + if (ec.ec || !(s.mode & file_status::regular_file)) r = -1; + + if (ec && ec.ec == boost::system::errc::no_such_file_or_directory) + { + ec.ec.clear(); + r = -3; + } + m_stat_cache.set_cache(i, r, s.mtime); + + if (ec) + { + ec.file = i; + ec.operation = storage_error::stat; + m_stat_cache.clear(); + return false; + } + } + + // if we didn't find the file, check the next one + if (m_stat_cache.get_filesize(i) == stat_cache::no_exist) continue; + + if (m_stat_cache.get_filesize(i) > 0) + return true; + } + file_status s; + stat_file(combine_path(m_save_path, m_part_file_name), &s, ec.ec); + if (!ec) return true; + + if (ec && ec.ec == boost::system::errc::no_such_file_or_directory) + ec.ec.clear(); + if (ec) + { + ec.file = -1; + ec.operation = storage_error::stat; + return false; + } + return false; + } + + void default_storage::rename_file(int index, std::string const& new_filename + , storage_error& ec) + { + if (index < 0 || index >= files().num_files()) return; + std::string old_name = files().file_path(index, m_save_path); + m_pool.release(this, 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)) + { +#if defined TORRENT_DEBUG_FILE_LEAKS + print_open_files("release files", m_files.name().c_str()); +#endif + + 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 = storage_error::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.file = index; + ec.operation = storage_error::rename; + return; + } + } + else if (ec.ec) + { + // if exists fails, report that error + ec.file = index; + ec.operation = storage_error::rename; + return; + } + + // if old path doesn't exist, just rename the file + // in our file_storage, so that when it is created + // it will get the new name + if (!m_mapped_files) + { m_mapped_files.reset(new file_storage(m_files)); } + m_mapped_files->rename_file(index, new_filename); + } + + void default_storage::release_files(storage_error&) + { + if (m_part_file) + { + error_code ignore; + m_part_file->flush_metadata(ignore); + m_part_file.reset(); + } + + // make sure we don't have the files open + m_pool.release(this); + +#if defined TORRENT_DEBUG_FILE_LEAKS + print_open_files("release files", m_files.name().c_str()); +#endif + } + + void default_storage::delete_one_file(std::string const& p, error_code& ec) + { + remove(p, ec); + + DFLOG(stderr, "[%p] delete_one_file: %s [%s]\n", static_cast(this) + , p.c_str(), ec.message().c_str()); + + if (ec == boost::system::errc::no_such_file_or_directory) + ec.clear(); + } + + void default_storage::delete_files(int const options, storage_error& ec) + { + DFLOG(stderr, "[%p] delete_files [%x]\n", static_cast(this) + , options); + +#if TORRENT_USE_ASSERTS + // this is a fence job, we expect no other + // threads to hold any references to any files + // in this file storage. Assert that that's the + // case + if (!m_pool.assert_idle_files(this)) + { +#if defined TORRENT_DEBUG_FILE_LEAKS + print_open_files("delete-files idle assert failed", m_files.name().c_str()); +#endif + TORRENT_ASSERT(false); + } +#endif + + // make sure we don't have the files open + m_pool.release(this); + + // 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(); + +#if defined TORRENT_DEBUG_FILE_LEAKS + print_open_files("release files", m_files.name().c_str()); +#endif + + if (options == session::delete_files) + { +#if TORRENT_USE_ASSERTS + m_pool.mark_deleted(m_files); +#endif + // delete the files from disk + std::set directories; + typedef std::set::iterator iter_t; + for (int i = 0; i < files().num_files(); ++i) + { + std::string const fp = files().file_path(i); + bool const complete = files().file_absolute_path(i); + std::string p = complete ? fp : combine_path(m_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(m_save_path, bp)); + bp = parent_path(bp); + } + } + delete_one_file(p, ec.ec); + if (ec) { ec.file = i; ec.operation = storage_error::remove; } + } + + // remove the directories. Reverse order to delete + // subdirectories first + + for (std::set::reverse_iterator i = directories.rbegin() + , end(directories.rend()); i != end; ++i) + { + error_code error; + delete_one_file(*i, error); + if (error && !ec) { ec.file = -1; ec.ec = error; ec.operation = storage_error::remove; } + } + } + + if (options == session::delete_files + || options == session::delete_partfile) + { + error_code error; + remove(combine_path(m_save_path, m_part_file_name), error); + DFLOG(stderr, "[%p] delete partfile %s/%s [%s]\n", static_cast(this) + , m_save_path.c_str(), m_part_file_name.c_str(), error.message().c_str()); + if (error && error != boost::system::errc::no_such_file_or_directory) + { + ec.file = -1; + ec.ec = error; + ec.operation = storage_error::remove; + } + } + + DFLOG(stderr, "[%p] delete_files result: %s\n", static_cast(this) + , ec.ec.message().c_str()); + +#if defined TORRENT_DEBUG_FILE_LEAKS + print_open_files("delete-files done", m_files.name().c_str()); +#endif + } + + void default_storage::write_resume_data(entry& rd, storage_error& ec) const + { + TORRENT_ASSERT(rd.type() == entry::dictionary_t); + + entry::list_type& fl = rd["file sizes"].list(); + + if (m_part_file) + { + error_code ignore; + const_cast(*m_part_file).flush_metadata(ignore); + } + + file_storage const& fs = files(); + for (int i = 0; i < fs.num_files(); ++i) + { + boost::int64_t file_size = 0; + time_t file_time = 0; + boost::int64_t cache_state = m_stat_cache.get_filesize(i); + if (cache_state != stat_cache::not_in_cache) + { + if (cache_state >= 0) + { + file_size = cache_state; + file_time = m_stat_cache.get_filetime(i); + } + } + else + { + file_status s; + error_code error; + stat_file(fs.file_path(i, m_save_path), &s, error); + if (!error) + { + file_size = s.file_size; + file_time = s.mtime; + } + else if (error == error_code(boost::system::errc::no_such_file_or_directory + , generic_category())) + { + m_stat_cache.set_noexist(i); + } + else + { + ec.ec = error; + ec.file = i; + ec.operation = storage_error::stat; + m_stat_cache.set_error(i); + } + } + + fl.push_back(entry(entry::list_t)); + entry::list_type& p = fl.back().list(); + p.push_back(entry(file_size)); + p.push_back(entry(file_time)); + } + } + + bool default_storage::verify_resume_data(bdecode_node const& rd + , std::vector const* links + , storage_error& ec) + { + // TODO: make this more generic to not just work if files have been + // renamed, but also if they have been merged into a single file for instance + // maybe use the same format as .torrent files and reuse some code from torrent_info + bdecode_node mapped_files = rd.dict_find_list("mapped_files"); + if (mapped_files && mapped_files.list_size() == m_files.num_files()) + { + m_mapped_files.reset(new file_storage(m_files)); + for (int i = 0; i < m_files.num_files(); ++i) + { + std::string new_filename = mapped_files.list_string_value_at(i); + if (new_filename.empty()) continue; + m_mapped_files->rename_file(i, new_filename); + } + } + + bdecode_node file_priority = rd.dict_find_list("file_priority"); + if (file_priority && file_priority.list_size() + == files().num_files()) + { + m_file_priority.resize(file_priority.list_size()); + for (int i = 0; i < file_priority.list_size(); ++i) + m_file_priority[i] = boost::uint8_t(file_priority.list_int_value_at(i, 1)); + } + + bdecode_node file_sizes_ent = rd.dict_find_list("file sizes"); + if (file_sizes_ent == 0) + { + ec.ec = errors::missing_file_sizes; + ec.file = -1; + ec.operation = storage_error::check_resume; + return false; + } + + if (file_sizes_ent.list_size() == 0) + { + ec.ec = errors::no_files_in_resume_data; + return false; + } + + file_storage const& fs = files(); + if (file_sizes_ent.list_size() != fs.num_files()) + { + ec.ec = errors::mismatching_number_of_files; + ec.file = -1; + ec.operation = storage_error::check_resume; + return false; + } + + bool seed = false; + bdecode_node slots = rd.dict_find_list("slots"); + if (slots) + { + if (slots.list_size() == m_files.num_pieces()) + { + seed = true; + for (int i = 0; i < slots.list_size(); ++i) + { + if (slots.list_int_value_at(i, -1) >= 0) continue; + seed = false; + break; + } + } + } + else if (bdecode_node pieces = rd.dict_find_string("pieces")) + { + if (pieces.string_length() == m_files.num_pieces()) + { + seed = true; + char const* p = pieces.string_ptr(); + for (int i = 0; i < pieces.string_length(); ++i) + { + if ((p[i] & 1) == 1) continue; + seed = false; + break; + } + } + } + else + { + ec.ec = errors::missing_pieces; + ec.file = -1; + ec.operation = storage_error::check_resume; + return false; + } + + for (int i = 0; i < file_sizes_ent.list_size(); ++i) + { + if (fs.pad_file_at(i)) continue; + bdecode_node e = file_sizes_ent.list_at(i); + if (e.type() != bdecode_node::list_t + || e.list_size() < 2 + || e.list_at(0).type() != bdecode_node::int_t + || e.list_at(1).type() != bdecode_node::int_t) + { + ec.ec = errors::missing_file_sizes; + ec.file = i; + ec.operation = storage_error::check_resume; + return false; + } + + boost::int64_t expected_size = e.list_int_value_at(0); + time_t expected_time = e.list_int_value_at(1); + + // if we're a seed, the expected size should match + // the actual full size according to the torrent + if (seed && expected_size < fs.file_size(i)) + { + ec.ec = errors::mismatching_file_size; + ec.file = i; + ec.operation = storage_error::check_resume; + return false; + } + + boost::int64_t file_size = m_stat_cache.get_filesize(i); + time_t file_time; + if (file_size >= 0) + { + file_time = m_stat_cache.get_filetime(i); + } + else + { + file_status s; + error_code error; + std::string file_path = fs.file_path(i, m_save_path); + stat_file(file_path, &s, error); + if (error) + { + if (error != boost::system::errc::no_such_file_or_directory) + { + m_stat_cache.set_error(i); + ec.ec = error; + ec.file = i; + ec.operation = storage_error::stat; + return false; + } + m_stat_cache.set_noexist(i); + if (expected_size != 0) + { + ec.ec = errors::mismatching_file_size; + ec.file = i; + ec.operation = storage_error::none; + return false; + } + file_size = 0; + file_time = 0; + } + else + { + file_size = s.file_size; + file_time = s.mtime; + } + } + + if (expected_size > file_size) + { + ec.ec = errors::mismatching_file_size; + ec.file = i; + ec.operation = storage_error::none; + return false; + } + + if (settings().get_bool(settings_pack::ignore_resume_timestamps)) continue; + + // allow some slack, because of FAT volumes + if (expected_time != 0 && + (file_time > expected_time + 5 * 60 || file_time < expected_time - 5)) + { + ec.ec = errors::mismatching_file_timestamp; + ec.file = i; + ec.operation = storage_error::stat; + return false; + } + } + + // TODO: 2 we probably need to do this unconditionally in this function. + // Even if the resume data file appears stale, we need to create these + // hard links, right? +#ifndef TORRENT_DISABLE_MUTABLE_TORRENTS + if (links) + { + // 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. + int idx = 0; + for (std::vector::const_iterator i = links->begin(); + i != links->end(); ++i, ++idx) + { + if (i->empty()) continue; + + error_code err; + std::string file_path = fs.file_path(idx, m_save_path); + hard_link(*i, file_path, err); + + // if the file already exists, that's not an error + // TODO: 2 is this risky? The upper layer will assume we have the + // whole file. Perhaps we should verify that at least the size + // of the file is correct + if (!err || err == boost::system::errc::file_exists) + continue; + + ec.ec = err; + ec.file = idx; + ec.operation = storage_error::hard_link; + return false; + } + } +#endif // TORRENT_DISABLE_MUTABLE_TORRENTS + + return true; + } + + int default_storage::move_storage(std::string const& sp, int const flags + , storage_error& ec) + { + int ret = piece_manager::no_error; + std::string const save_path = complete(sp); + + // check to see if any of the files exist + file_storage const& f = files(); + + if (flags == fail_if_exist) + { + file_status s; + error_code err; + stat_file(save_path, &s, err); + if (err != boost::system::errc::no_such_file_or_directory) + { + // the directory exists, check all the files + for (int i = 0; i < f.num_files(); ++i) + { + // files moved out to absolute paths are ignored + if (f.file_absolute_path(i)) continue; + + stat_file(f.file_path(i, save_path), &s, err); + if (err != boost::system::errc::no_such_file_or_directory) + { + ec.ec = err; + ec.file = i; + ec.operation = storage_error::stat; + return piece_manager::file_exist; + } + } + } + } + + { + file_status s; + error_code err; + stat_file(save_path, &s, err); + if (err == boost::system::errc::no_such_file_or_directory) + { + err.clear(); + create_directories(save_path, err); + if (err) + { + ec.ec = err; + ec.file = -1; + ec.operation = storage_error::mkdir; + return piece_manager::fatal_disk_error; + } + } + else if (err) + { + ec.ec = err; + ec.file = -1; + ec.operation = storage_error::stat; + return piece_manager::fatal_disk_error; + } + } + + m_pool.release(this); + +#if defined TORRENT_DEBUG_FILE_LEAKS + print_open_files("release files", m_files.name().c_str()); +#endif + + int i; + error_code e; + for (i = 0; i < f.num_files(); ++i) + { + // files moved out to absolute paths are not moved + if (f.file_absolute_path(i)) continue; + + std::string const old_path = combine_path(m_save_path, f.file_path(i)); + std::string const new_path = combine_path(save_path, f.file_path(i)); + + if (flags == dont_replace && exists(new_path)) + { + if (ret == piece_manager::no_error) ret = piece_manager::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(); + + if (e) + { + ec.ec = e; + ec.file = i; + ec.operation = storage_error::rename; + break; + } + } + + if (!e && m_part_file) + { + m_part_file->move_partfile(save_path, e); + if (e) + { + ec.ec = e; + ec.file = -1; + ec.operation = storage_error::partfile_move; + } + } + + if (e) + { + // rollback + while (--i >= 0) + { + // files moved out to absolute paths are not moved + if (f.file_absolute_path(i)) continue; + + std::string const old_path = combine_path(m_save_path, f.file_path(i)); + std::string const new_path = combine_path(save_path, f.file_path(i)); + + if (!exists(old_path)) + { + // ignore errors when rolling back + error_code ignore; + move_file(new_path, old_path, ignore); + } + } + + return piece_manager::fatal_disk_error; + } + + std::string const old_save_path = m_save_path; + m_save_path = save_path; + + std::set subdirs; + for (i = 0; i < f.num_files(); ++i) + { + // 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))); + + std::string const old_path = combine_path(old_save_path, f.file_path(i)); + + // we may still have some files in old old_save_path + // eg. if (flags == dont_replace && exists(new_path)) + // ignore errors when removing + error_code ignore; + remove(old_path, ignore); + } + + for (std::set::iterator it(subdirs.begin()) + , end(subdirs.end()); it != end; ++it) + { + error_code err; + std::string subdir = combine_path(old_save_path, *it); + while (subdir != old_save_path && !err) + { + remove(subdir, err); + subdir = parent_path(subdir); + } + } + + return ret; + } + + int default_storage::readv(file::iovec_t const* bufs, int num_bufs + , int piece, int offset, int flags, storage_error& ec) + { + read_fileop op(*this, flags); + +#ifdef TORRENT_SIMULATE_SLOW_READ + boost::thread::sleep(boost::get_system_time() + + boost::posix_time::milliseconds(1000)); +#endif + return readwritev(files(), bufs, piece, offset, num_bufs, op, ec); + } + + int default_storage::writev(file::iovec_t const* bufs, int num_bufs + , int piece, int offset, int flags, storage_error& ec) + { + write_fileop op(*this, flags); + return readwritev(files(), bufs, piece, offset, num_bufs, op, ec); + } + + // 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, file::iovec_t const* const bufs + , const int piece, const int offset, const int num_bufs, fileop& op + , storage_error& ec) + { + TORRENT_ASSERT(bufs != 0); + TORRENT_ASSERT(piece >= 0); + TORRENT_ASSERT(piece < files.num_pieces()); + TORRENT_ASSERT(offset >= 0); + TORRENT_ASSERT(num_bufs > 0); + + const int size = bufs_size(bufs, num_bufs); + TORRENT_ASSERT(size > 0); + TORRENT_ASSERT(files.is_loaded()); + + // find the file iterator and file offset + boost::uint64_t torrent_offset = piece * boost::uint64_t(files.piece_length()) + offset; + int 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)); + boost::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()) + file::iovec_t* current_buf = TORRENT_ALLOCA(file::iovec_t, num_bufs); + copy_bufs(bufs, size, current_buf); + TORRENT_ASSERT(count_bufs(current_buf, size) == num_bufs); + + file::iovec_t* tmp_buf = TORRENT_ALLOCA(file::iovec_t, num_bufs); + + // 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; + + while (bytes_left > 0) + { + 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.num_files()); + + // 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.num_files()) 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 + copy_bufs(current_buf, file_bytes_left, tmp_buf); + + int bytes_transferred = op.file_op(file_index, file_offset, + file_bytes_left, tmp_buf, ec); + if (ec) return -1; + + // advance our position in the iovec array and the file offset. + advance_bufs(current_buf, bytes_transferred); + bytes_left -= bytes_transferred; + file_offset += bytes_transferred; + + TORRENT_ASSERT(count_bufs(current_buf, bytes_left) <= num_bufs); + + // if the file operation returned 0, we've hit end-of-file. We're done + if (bytes_transferred == 0) + { + if (file_bytes_left > 0 ) + { + // fill in this information in case the caller wants to treat + // a short-read as an error + ec.file = file_index; + } + return size - bytes_left; + } + } + return size; + } + + file_handle default_storage::open_file(int file, int mode + , storage_error& ec) const + { + file_handle h = open_file_impl(file, mode, ec.ec); + if (((mode & file::rw_mask) != file::read_only) + && ec.ec == boost::system::errc::no_such_file_or_directory) + { + // this means the directory the file is in doesn't exist. + // so create it + ec.ec.clear(); + std::string path = files().file_path(file, m_save_path); + create_directories(parent_path(path), ec.ec); + + if (ec.ec) + { + ec.file = file; + ec.operation = storage_error::mkdir; + return file_handle(); + } + + // if the directory creation failed, don't try to open the file again + // but actually just fail + h = open_file_impl(file, mode, ec.ec); + } + if (ec.ec) + { + ec.file = file; + ec.operation = storage_error::open; + return file_handle(); + } + TORRENT_ASSERT(h); + + if (m_allocate_files && (mode & file::rw_mask) != file::read_only) + { + if (m_file_created.size() != files().num_files()) + m_file_created.resize(files().num_files(), false); + + TORRENT_ASSERT(int(m_file_created.size()) == files().num_files()); + TORRENT_ASSERT(file < m_file_created.size()); + // if this is the first time we open this file for writing, + // and we have m_allocate_files enabled, set the final size of + // the file right away, to allocate it on the filesystem. + if (m_file_created[file] == false) + { + error_code e; + h->set_size(files().file_size(file), e); + m_file_created.set_bit(file); + if (e) + { + ec.ec = e; + ec.file = file; + ec.operation = storage_error::fallocate; + return h; + } + } + } + return h; + } + + file_handle default_storage::open_file_impl(int file, int mode + , error_code& ec) const + { + bool lock_files = m_settings ? settings().get_bool(settings_pack::lock_files) : false; + if (lock_files) mode |= file::lock_file; + + if (!m_allocate_files) mode |= file::sparse; + + // files with priority 0 should always be sparse + if (int(m_file_priority.size()) > file && m_file_priority[file] == 0) + mode |= file::sparse; + + if (m_settings && settings().get_bool(settings_pack::no_atime_storage)) mode |= file::no_atime; + + // if we have a cache already, don't store the data twice by leaving it in the OS cache as well + if (m_settings + && settings().get_int(settings_pack::disk_io_write_mode) + == settings_pack::disable_os_cache) + { + mode |= file::no_cache; + } + + file_handle ret = m_pool.open_file(const_cast(this) + , m_save_path, file, files(), mode, ec); + if (ec && (mode & file::lock_file)) + { + // we failed to open the file and we're trying to lock it. It's + // possible we're failing because we have another handle to this + // file in use (but waiting to be closed). Just retry to open it + // without locking. + mode &= ~file::lock_file; + ret = m_pool.open_file(const_cast(this) + , m_save_path, file, files(), mode, ec); + } + return ret; + } + + bool default_storage::tick() + { + error_code ec; + if (m_part_file) m_part_file->flush_metadata(ec); + + return false; + } + +#ifdef TORRENT_DISK_STATS + bool default_storage::disk_write_access_log() { + return g_access_log != NULL; + } + + void default_storage::disk_write_access_log(bool enable) { + if (enable) + { + if (g_access_log == NULL) + { + g_access_log = fopen("file_access.log", "a+"); + } + } + else + { + if (g_access_log != NULL) + { + FILE* f = g_access_log; + g_access_log = NULL; + fclose(f); + } + } + } +#endif + + storage_interface* default_storage_constructor(storage_params const& params) + { + return new default_storage(params); + } + + int disabled_storage::readv(file::iovec_t const* bufs + , int num_bufs, int, int, int, storage_error&) + { + return bufs_size(bufs, num_bufs); + } + + int disabled_storage::writev(file::iovec_t const* bufs + , int num_bufs, int, int, int, storage_error&) + { + return bufs_size(bufs, num_bufs); + } + + storage_interface* disabled_storage_constructor(storage_params const& params) + { + TORRENT_UNUSED(params); + return new disabled_storage; + } + + // -- zero_storage ------------------------------------------------------ + + int zero_storage::readv(file::iovec_t const* bufs, int num_bufs + , int /* piece */, int /* offset */, int /* flags */, storage_error&) + { + int ret = 0; + for (int i = 0; i < num_bufs; ++i) + { + memset(bufs[i].iov_base, 0, bufs[i].iov_len); + ret += bufs[i].iov_len; + } + return 0; + } + + int zero_storage::writev(file::iovec_t const* bufs, int num_bufs + , int /* piece */, int /* offset */, int /* flags */, storage_error&) + { + int ret = 0; + for (int i = 0; i < num_bufs; ++i) + ret += bufs[i].iov_len; + return 0; + } + + storage_interface* zero_storage_constructor(storage_params const&) + { + return new zero_storage; + } + + void storage_piece_set::add_piece(cached_piece_entry* p) + { + TORRENT_ASSERT(p->in_storage == false); + TORRENT_ASSERT(p->storage.get() == this); + TORRENT_ASSERT(m_cached_pieces.count(p) == 0); + m_cached_pieces.insert(p); +#if TORRENT_USE_ASSERTS + p->in_storage = true; +#endif + } + + bool storage_piece_set::has_piece(cached_piece_entry const* p) const + { + return m_cached_pieces.count(const_cast(p)) > 0; + } + + void storage_piece_set::remove_piece(cached_piece_entry* p) + { + TORRENT_ASSERT(p->in_storage == true); + TORRENT_ASSERT(m_cached_pieces.count(p) == 1); + m_cached_pieces.erase(p); +#if TORRENT_USE_ASSERTS + p->in_storage = false; +#endif + } + + // -- piece_manager ----------------------------------------------------- + + piece_manager::piece_manager( + storage_interface* storage_impl + , boost::shared_ptr const& torrent + , file_storage* files) + : m_files(*files) + , m_storage(storage_impl) + , m_torrent(torrent) + { + } + + piece_manager::~piece_manager() + {} + +#ifdef TORRENT_DEBUG + void piece_manager::assert_torrent_refcount() const + { + if (!m_torrent) return; + // sorry about this layer violation, but it's + // quite convenient to make sure the torrent won't + // get unloaded under our feet later + TORRENT_ASSERT(static_cast(m_torrent.get())->refcount() > 0); + } +#endif + + // used in torrent_handle.cpp + void piece_manager::write_resume_data(entry& rd, storage_error& ec) const + { + m_storage->write_resume_data(rd, ec); + } + + int piece_manager::check_no_fastresume(storage_error& ec) + { + bool has_files = false; + if (!m_storage->settings().get_bool(settings_pack::no_recheck_incomplete_resume)) + { + storage_error se; + has_files = m_storage->has_any_file(se); + + if (se) + { + ec = se; + return fatal_disk_error; + } + + if (has_files) + { + // always initialize the storage + int ret = check_init_storage(ec); + return ret != no_error ? ret : need_full_check; + } + } + + return check_init_storage(ec); + } + + int piece_manager::check_init_storage(storage_error& ec) + { + storage_error se; + m_storage->initialize(se); + if (se) + { + ec = se; + return fatal_disk_error; + } + + return no_error; + } + + // 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-null, 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. + int piece_manager::check_fastresume( + bdecode_node const& rd + , std::vector const* links + , storage_error& ec) + { + TORRENT_ASSERT(m_files.piece_length() > 0); + + // if we don't have any resume data, return + if (rd.type() == bdecode_node::none_t) return check_no_fastresume(ec); + + if (rd.type() != bdecode_node::dict_t) + { + ec.ec = errors::not_a_dictionary; + return check_no_fastresume(ec); + } + + int block_size = (std::min)(16 * 1024, m_files.piece_length()); + int blocks_per_piece = int(rd.dict_find_int_value("blocks per piece", -1)); + if (blocks_per_piece != -1 + && blocks_per_piece != m_files.piece_length() / block_size) + { + ec.ec = errors::invalid_blocks_per_piece; + return check_no_fastresume(ec); + } + + if (!m_storage->verify_resume_data(rd, links, ec)) + return check_no_fastresume(ec); + + return check_init_storage(ec); + } + + // ====== disk_job_fence implementation ======== + + disk_job_fence::disk_job_fence() + : m_has_fence(0) + , m_outstanding_jobs(0) + {} + + int disk_job_fence::job_complete(disk_io_job* j, tailqueue& jobs) + { + mutex::scoped_lock l(m_mutex); + + TORRENT_ASSERT(j->flags & disk_io_job::in_progress); + j->flags &= ~disk_io_job::in_progress; + + TORRENT_ASSERT(m_outstanding_jobs > 0); + --m_outstanding_jobs; + if (j->flags & disk_io_job::fence) + { + // a fence job just completed. Make sure the fence logic + // works by asserting m_outstanding_jobs is in fact 0 now + TORRENT_ASSERT(m_outstanding_jobs == 0); + + // the fence can now be lowered + --m_has_fence; + + // now we need to post all jobs that have been queued up + // while this fence was up. However, if there's another fence + // in the queue, stop there and raise the fence again + int ret = 0; + while (m_blocked_jobs.size()) + { + disk_io_job *bj = static_cast(m_blocked_jobs.pop_front()); + if (bj->flags & disk_io_job::fence) + { + // we encountered another fence. We cannot post anymore + // jobs from the blocked jobs queue. We have to go back + // into a raised fence mode and wait for all current jobs + // to complete. The exception is that if there are no jobs + // executing currently, we should add the fence job. + if (m_outstanding_jobs == 0 && jobs.empty()) + { + TORRENT_ASSERT((bj->flags & disk_io_job::in_progress) == 0); + bj->flags |= disk_io_job::in_progress; + ++m_outstanding_jobs; + ++ret; +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(bj->blocked); + bj->blocked = false; +#endif + jobs.push_back(bj); + } + else + { + // put the fence job back in the blocked queue + m_blocked_jobs.push_front(bj); + } + return ret; + } + TORRENT_ASSERT((bj->flags & disk_io_job::in_progress) == 0); + bj->flags |= disk_io_job::in_progress; + + ++m_outstanding_jobs; + ++ret; +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(bj->blocked); + bj->blocked = false; +#endif + jobs.push_back(bj); + } + return ret; + } + + // there are still outstanding jobs, even if we have a + // fence, it's not time to lower it yet + // also, if we don't have a fence, we're done + if (m_outstanding_jobs > 0 || m_has_fence == 0) return 0; + + // there's a fence raised, and no outstanding operations. + // it means we can execute the fence job right now. + TORRENT_ASSERT(m_blocked_jobs.size() > 0); + + // this is the fence job + disk_io_job *bj = static_cast(m_blocked_jobs.pop_front()); + TORRENT_ASSERT(bj->flags & disk_io_job::fence); + + TORRENT_ASSERT((bj->flags & disk_io_job::in_progress) == 0); + bj->flags |= disk_io_job::in_progress; + + ++m_outstanding_jobs; +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(bj->blocked); + bj->blocked = false; +#endif + // prioritize fence jobs since they're blocking other jobs + jobs.push_front(bj); + return 1; + } + + bool disk_job_fence::is_blocked(disk_io_job* j) + { + mutex::scoped_lock l(m_mutex); + DLOG(stderr, "[%p] is_blocked: fence: %d num_outstanding: %d\n" + , static_cast(this), m_has_fence, int(m_outstanding_jobs)); + + // if this is the job that raised the fence, don't block it + // ignore fence can only ignore one fence. If there are several, + // this job still needs to get queued up + if (m_has_fence == 0) + { + TORRENT_ASSERT((j->flags & disk_io_job::in_progress) == 0); + j->flags |= disk_io_job::in_progress; + ++m_outstanding_jobs; + return false; + } + + m_blocked_jobs.push_back(j); + +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(j->blocked == false); + j->blocked = true; +#endif + + return true; + } + + bool disk_job_fence::has_fence() const + { + mutex::scoped_lock l(m_mutex); + return m_has_fence; + } + + int disk_job_fence::num_blocked() const + { + mutex::scoped_lock l(m_mutex); + return m_blocked_jobs.size(); + } + + // j is the fence job. It must have exclusive access to the storage + // fj is the flush job. If the job j is queued, we need to issue + // this job + int disk_job_fence::raise_fence(disk_io_job* j, disk_io_job* fj + , counters& cnt) + { + TORRENT_ASSERT((j->flags & disk_io_job::fence) == 0); + j->flags |= disk_io_job::fence; + + mutex::scoped_lock l(m_mutex); + + DLOG(stderr, "[%p] raise_fence: fence: %d num_outstanding: %d\n" + , static_cast(this), m_has_fence, int(m_outstanding_jobs)); + + if (m_has_fence == 0 && m_outstanding_jobs == 0) + { + ++m_has_fence; + DLOG(stderr, "[%p] raise_fence: need posting\n" + , static_cast(this)); + + // the job j is expected to be put on the job queue + // after this, without being passed through is_blocked() + // that's why we're accounting for it here + + // fj is expected to be discarded by the caller + j->flags |= disk_io_job::in_progress; + ++m_outstanding_jobs; + return fence_post_fence; + } + + ++m_has_fence; + if (m_has_fence > 1) + { +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(fj->blocked == false); + fj->blocked = true; +#endif + m_blocked_jobs.push_back(fj); + cnt.inc_stats_counter(counters::blocked_disk_jobs); + } + else + { + // in this case, fj is expected to be put on the job queue + fj->flags |= disk_io_job::in_progress; + ++m_outstanding_jobs; + } +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(j->blocked == false); + j->blocked = true; +#endif + m_blocked_jobs.push_back(j); + cnt.inc_stats_counter(counters::blocked_disk_jobs); + + return m_has_fence > 1 ? fence_post_none : fence_post_flush; + } +} // namespace libtorrent + diff --git a/src/string_util.cpp b/src/string_util.cpp new file mode 100644 index 0000000..5cfca91 --- /dev/null +++ b/src/string_util.cpp @@ -0,0 +1,281 @@ +/* + +Copyright (c) 2012-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/string_util.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/parse_url.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include + +#include // for malloc +#include // for memmov/strcpy/strlen + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent +{ + + // lexical_cast's result depends on the locale. We need + // a well defined result + boost::array::digits10> + to_string(boost::int64_t n) + { + boost::array::digits10> ret; + char *p = &ret.back(); + *p = '\0'; + boost::uint64_t un = n; + // TODO: warning C4146: unary minus operator applied to unsigned type, + // result still unsigned + if (n < 0) un = -un; + do { + *--p = '0' + un % 10; + un /= 10; + } while (un); + if (n < 0) *--p = '-'; + std::memmove(&ret[0], p, &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) + { + static const char* ws = " \t\n\r\f\v"; + return strchr(ws, c) != 0; + } + + char to_lower(char c) + { + return (c >= 'A' && c <= 'Z') ? c - 'A' + 'a' : c; + } + + int split_string(char const** tags, int buf_size, char* in) + { + int ret = 0; + char* i = in; + for (;*i; ++i) + { + if (!is_print(*i) || is_space(*i)) + { + *i = 0; + if (ret == buf_size) return ret; + continue; + } + if (i == in || i[-1] == 0) + { + tags[ret++] = i; + } + } + return ret; + } + + bool string_begins_no_case(char const* s1, char const* s2) + { + while (*s1 != 0) + { + if (to_lower(*s1) != to_lower(*s2)) return false; + ++s1; + ++s2; + } + return true; + } + + bool string_equal_no_case(char const* s1, char const* s2) + { + while (to_lower(*s1) == to_lower(*s2)) + { + if (*s1 == 0) return true; + ++s1; + ++s2; + } + return false; + } + + // generate a url-safe random string + void url_random(char* begin, char* end) + { + // http-accepted characters: + // excluding ', since some buggy trackers don't support that + static char const printable[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz-_.!~*()"; + + // the random number + while (begin != end) + *begin++ = printable[random() % (sizeof(printable)-1)]; + } + + char* allocate_string_copy(char const* str) + { + if (str == 0) return 0; + char* tmp = static_cast(std::malloc(std::strlen(str) + 1)); + if (tmp == 0) return 0; + std::strcpy(tmp, str); + return tmp; + } + + // 8-byte align pointer + void* align_pointer(void* p) + { + int offset = uintptr_t(p) & 0x7; + // if we're already aligned, don't do anything + if (offset == 0) return p; + + // offset is how far passed the last aligned address + // we are. We need to go forward to the next aligned + // one. Since aligned addresses are 8 bytes apart, add + // 8 - offset. + return static_cast(p) + (8 - offset); + } + + // 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" + 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 = 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.push_back(std::make_pair(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; + } + } + + char* string_tokenize(char* last, char sep, char** next) + { + if (last == 0) return 0; + if (last[0] == '"') + { + *next = strchr(last + 1, '"'); + // consume the actual separator as well. + if (*next != NULL) + *next = strchr(*next, sep); + } + else + { + *next = strchr(last, sep); + } + if (*next == 0) return last; + **next = 0; + ++(*next); + while (**next == sep && **next) ++(*next); + return last; + } + +#if TORRENT_USE_I2P + + bool is_i2p_url(std::string const& url) + { + using boost::tuples::ignore; + std::string hostname; + error_code ec; + boost::tie(ignore, ignore, hostname, ignore, ignore) + = parse_url_components(url, ec); + char const* top_domain = strrchr(hostname.c_str(), '.'); + return top_domain && strcmp(top_domain, ".i2p") == 0; + } + +#endif + +} + diff --git a/src/thread.cpp b/src/thread.cpp new file mode 100644 index 0000000..eec8c10 --- /dev/null +++ b/src/thread.cpp @@ -0,0 +1,195 @@ +/* + +Copyright (c) 2010-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/thread.hpp" +#include "libtorrent/assert.hpp" + +#ifdef TORRENT_BEOS +#include +#endif + +#ifdef BOOST_HAS_PTHREADS +#include // for gettimeofday() +#include +#endif + +#include + +namespace libtorrent +{ + + void sleep(int milliseconds) + { +#if defined TORRENT_WINDOWS || defined TORRENT_CYGWIN + Sleep(milliseconds); +#elif defined TORRENT_BEOS + snooze_until(system_time() + boost::int64_t(milliseconds) * 1000, B_SYSTEM_TIMEBASE); +#else + usleep(milliseconds * 1000); +#endif + } + +#ifdef BOOST_HAS_PTHREADS + + condition_variable::condition_variable() + { + pthread_cond_init(&m_cond, 0); + } + + condition_variable::~condition_variable() + { + pthread_cond_destroy(&m_cond); + } + + void condition_variable::wait(mutex::scoped_lock& l) + { + TORRENT_ASSERT(l.locked()); + // wow, this is quite a hack + pthread_cond_wait(&m_cond, reinterpret_cast(&l.mutex())); + } + + void condition_variable::wait_for(mutex::scoped_lock& l, time_duration rel_time) + { + TORRENT_ASSERT(l.locked()); + + struct timeval tv; + struct timespec ts; + gettimeofday(&tv, NULL); + boost::uint64_t microseconds = tv.tv_usec + total_microseconds(rel_time) % 1000000; + ts.tv_nsec = (microseconds % 1000000) * 1000; + ts.tv_sec = tv.tv_sec + total_seconds(rel_time) + microseconds / 1000000; + + // wow, this is quite a hack + pthread_cond_timedwait(&m_cond, reinterpret_cast(&l.mutex()), &ts); + } + + void condition_variable::notify_all() + { + pthread_cond_broadcast(&m_cond); + } + + void condition_variable::notify() + { + pthread_cond_signal(&m_cond); + } +#elif defined TORRENT_WINDOWS || defined TORRENT_CYGWIN + condition_variable::condition_variable() + : m_num_waiters(0) + { +#if _WIN32_WINNT <= 0x0501 + m_sem = CreateSemaphore(0, 0, INT_MAX, 0); +#else + m_sem = CreateSemaphoreEx(0, 0, INT_MAX, 0, 0, SEMAPHORE_ALL_ACCESS); +#endif + } + + condition_variable::~condition_variable() + { + CloseHandle(m_sem); + } + + void condition_variable::wait(mutex::scoped_lock& l) + { + TORRENT_ASSERT(l.locked()); + ++m_num_waiters; + l.unlock(); + WaitForSingleObjectEx(m_sem, INFINITE, FALSE); + l.lock(); + --m_num_waiters; + } + + void condition_variable::wait_for(mutex::scoped_lock& l, time_duration rel_time) + { + TORRENT_ASSERT(l.locked()); + ++m_num_waiters; + l.unlock(); + WaitForSingleObjectEx(m_sem, total_milliseconds(rel_time), FALSE); + l.lock(); + --m_num_waiters; + } + + void condition_variable::notify_all() + { + ReleaseSemaphore(m_sem, m_num_waiters, 0); + } + + void condition_variable::notify() + { + ReleaseSemaphore(m_sem, (std::min)(m_num_waiters, 1), 0); + } +#elif defined TORRENT_BEOS + condition_variable::condition_variable() + : m_num_waiters(0) + { + m_sem = create_sem(0, 0); + } + + condition_variable::~condition_variable() + { + delete_sem(m_sem); + } + + void condition_variable::wait(mutex::scoped_lock& l) + { + TORRENT_ASSERT(l.locked()); + ++m_num_waiters; + l.unlock(); + acquire_sem(m_sem); + l.lock(); + --m_num_waiters; + } + + void condition_variable::wait_for(mutex::scoped_lock& l, time_duration rel_time) + { + TORRENT_ASSERT(l.locked()); + ++m_num_waiters; + l.unlock(); + acquire_sem_etc(m_sem, 1, B_RELATIVE_TIMEOUT, total_microseconds(rel_time)); + l.lock(); + --m_num_waiters; + } + + void condition_variable::notify_all() + { + release_sem_etc(m_sem, m_num_waiters, 0); + } + + void condition_variable::notify() + { + release_sem_etc(m_sem, (std::min)(m_num_waiters, 1), 0); + } +#else +#error not implemented +#endif + +} + diff --git a/src/time.cpp b/src/time.cpp new file mode 100644 index 0000000..8975cb0 --- /dev/null +++ b/src/time.cpp @@ -0,0 +1,59 @@ +/* + +Copyright (c) 2009-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 "libtorrent/config.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/aux_/time.hpp" + +namespace libtorrent { namespace aux +{ + // used to cache the current time + // every 100 ms. This is cheaper + // than a system call and can be + // used where more accurate time + // is not necessary + namespace { + time_point g_current_time = clock_type::now(); + } + + time_point const& time_now() { return aux::g_current_time; } + + void update_time_now() { g_current_time = clock_type::now(); } + + +} } + diff --git a/src/timestamp_history.cpp b/src/timestamp_history.cpp new file mode 100644 index 0000000..670049b --- /dev/null +++ b/src/timestamp_history.cpp @@ -0,0 +1,107 @@ +/* + +Copyright (c) 2009-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/timestamp_history.hpp" + +namespace libtorrent { + +enum +{ + TIME_MASK = 0xffffffff +}; +// defined in utp_stream.cpp +bool compare_less_wrap(boost::uint32_t lhs, boost::uint32_t rhs + , boost::uint32_t mask); + +boost::uint32_t timestamp_history::add_sample(boost::uint32_t sample, bool step) +{ + if (!initialized()) + { + for (int i = 0; i < history_size; ++i) + m_history[i] = 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; + } + + boost::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 (int i = 0; i < history_size; ++i) + { + if (compare_less_wrap(m_history[i], m_base, TIME_MASK)) + m_base = m_history[i]; + } + } + return ret; +} + +void timestamp_history::adjust_base(int change) +{ + TORRENT_ASSERT(initialized()); + m_base += change; + // make sure this adjustment sticks by updating all history slots + for (int i = 0; i < history_size; ++i) + { + if (compare_less_wrap(m_history[i], m_base, TIME_MASK)) + m_history[i] = m_base; + } +} + +} diff --git a/src/torrent.cpp b/src/torrent.cpp new file mode 100644 index 0000000..ffb611d --- /dev/null +++ b/src/torrent.cpp @@ -0,0 +1,12323 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include // for va_list +#include +#include +#include +#include +#include +#include +#include +#include // for numeric_limits + +#include +#include +#if TORRENT_USE_I2P +# include +#endif + +#ifdef TORRENT_USE_OPENSSL +#include "libtorrent/ssl_stream.hpp" +#include +#include +#if BOOST_VERSION >= 104700 +#include +#endif // BOOST_VERSION +#endif // TORRENT_USE_OPENSSL + +#include "libtorrent/aux_/disable_warnings_pop.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/instantiate_connection.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/broadcast_socket.hpp" +#include "libtorrent/kademlia/dht_tracker.hpp" +#include "libtorrent/peer_info.hpp" +#include "libtorrent/http_connection.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/peer_class.hpp" // for peer_class +#include "libtorrent/socket_io.hpp" // for read_*_endpoint +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/request_blocks.hpp" +#include "libtorrent/performance_counters.hpp" // for counters +#include "libtorrent/resolver_interface.hpp" +#include "libtorrent/alloca.hpp" +#include "libtorrent/resolve_links.hpp" +#include "libtorrent/aux_/file_progress.hpp" +#include "libtorrent/alert_manager.hpp" +#include "libtorrent/disk_interface.hpp" +#include "libtorrent/broadcast_socket.hpp" // for is_ip_address +// TODO: factor out cache_status to its own header +#include "libtorrent/disk_io_thread.hpp" // for cache_status + +#ifndef TORRENT_DISABLE_LOGGING +#include "libtorrent/aux_/session_impl.hpp" // for tracker_logger +#endif + +using namespace libtorrent; +using boost::tuples::tuple; +using boost::tuples::get; +using boost::tuples::make_tuple; + +namespace libtorrent +{ + namespace { + + int root2(int x) + { + int ret = 0; + x >>= 1; + while (x > 0) + { + // if this assert triggers, the block size + // is not an even 2 exponent! + TORRENT_ASSERT(x == 1 || (x & 1) == 0); + ++ret; + x >>= 1; + } + return ret; + } + + } // anonymous namespace + + web_seed_t::web_seed_t(web_seed_entry const& wse) + : web_seed_entry(wse) + , retry(aux::time_now()) + , peer_info(tcp::endpoint(), true, 0) + , supports_keepalive(true) + , resolving(false) + , removed(false) + { + peer_info.web_seed = true; + restart_request.piece = -1; + restart_request.start = -1; + restart_request.length = -1; + } + + 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_) + , retry(aux::time_now()) + , peer_info(tcp::endpoint(), true, 0) + , supports_keepalive(true) + , resolving(false) + , removed(false) + { + peer_info.web_seed = true; + restart_request.piece = -1; + restart_request.start = -1; + restart_request.length = -1; + } + +#ifndef TORRENT_DISABLE_EXTENSIONS + // defined in ut_pex.cpp + bool was_introduced_by(peer_plugin const*, tcp::endpoint const&); +#endif + + torrent_hot_members::torrent_hot_members(aux::session_interface& ses + , add_torrent_params const& p, int block_size) + : m_ses(ses) + , m_complete(0xffffff) + , m_upload_mode((p.flags & add_torrent_params::flag_upload_mode) != 0) + , m_connections_initialized(false) + , m_abort(false) + , m_allow_peers((p.flags & add_torrent_params::flag_paused) == 0) + , m_share_mode((p.flags & add_torrent_params::flag_share_mode) != 0) + , m_have_all(false) + , m_graceful_pause_mode(false) + , m_state_subscription((p.flags & add_torrent_params::flag_update_subscribe) != 0) + , m_max_connections(0xffffff) + , m_block_size_shift(root2(block_size)) + , m_state(torrent_status::checking_resume_data) + {} + + torrent::torrent( + aux::session_interface& ses + , int block_size + , int seq + , add_torrent_params const& p + , sha1_hash const& info_hash) + : torrent_hot_members(ses, p, block_size) + , m_total_uploaded(0) + , m_total_downloaded(0) + , m_tracker_timer(ses.get_io_service()) + , m_inactivity_timer(ses.get_io_service()) + , m_trackerid(p.trackerid) + , m_save_path(complete(p.save_path)) + , m_url(p.url) + , m_uuid(p.uuid) + , m_source_feed_url(p.source_feed_url) + , m_stats_counters(ses.stats_counters()) + , m_storage_constructor(p.storage) + , m_added_time(time(0)) + , m_completed_time(0) + , m_last_seen_complete(0) + , m_swarm_last_seen_complete(0) + , m_info_hash(info_hash) + , m_num_verified(0) + , m_last_saved_resume(ses.session_time()) + , m_started(ses.session_time()) + , m_became_seed(0) + , m_became_finished(0) + , m_checking_piece(0) + , m_num_checked_pieces(0) + , m_refcount(0) + , m_error_file(torrent_status::error_file_none) + , m_average_piece_time(0) + , m_piece_time_deviation(0) + , m_total_failed_bytes(0) + , m_total_redundant_bytes(0) + , m_sequence_number(seq) + , m_peer_class(0) + , m_num_connecting(0) + , m_upload_mode_time(0) + , m_announce_to_trackers((p.flags & add_torrent_params::flag_paused) == 0) + , m_announce_to_lsd((p.flags & add_torrent_params::flag_paused) == 0) + , m_has_incoming(false) + , m_files_checked(false) + , m_storage_mode(p.storage_mode) + , m_announcing(false) + , m_waiting_tracker(false) + , m_active_time(0) + , m_last_working_tracker(-1) + , m_finished_time(0) + , m_sequential_download(false) + , m_auto_sequential(false) + , m_seed_mode(false) + , m_super_seeding(false) + , m_override_resume_data((p.flags & add_torrent_params::flag_override_resume_data) != 0) +#ifndef TORRENT_NO_DEPRECATE +#ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES + , m_resolving_country(false) + , m_resolve_countries(false) +#endif +#endif + , m_need_save_resume_data(true) + , m_seeding_time(0) + , m_max_uploads((1<<24)-1) + , m_save_resume_flags(0) + , m_num_uploads(0) + , m_need_suggest_pieces_refresh(false) + , m_need_connect_boost(true) + , m_lsd_seq(0) + , m_magnet_link(false) + , m_apply_ip_filter((p.flags & add_torrent_params::flag_apply_ip_filter) != 0) + , m_merge_resume_trackers((p.flags & add_torrent_params::flag_merge_resume_trackers) != 0) + , m_padding(0) + , m_priority(0) + , m_incomplete(0xffffff) + , m_announce_to_dht((p.flags & add_torrent_params::flag_paused) == 0) + , m_in_state_updates(false) + , m_is_active_download(false) + , m_is_active_finished(false) + , m_ssl_torrent(false) + , m_deleted(false) + , m_pinned((p.flags & add_torrent_params::flag_pinned) != 0) + , m_should_be_loaded(true) + , m_last_download((std::numeric_limits::min)()) + , m_num_seeds(0) + , m_last_upload((std::numeric_limits::min)()) + , m_storage_tick(0) + , m_auto_managed(p.flags & add_torrent_params::flag_auto_managed) + , m_current_gauge_state(no_gauge_state) + , m_moving_storage(false) + , m_inactive(false) + , m_downloaded(0xffffff) + , m_last_scrape((std::numeric_limits::min)()) + , m_progress_ppm(0) + , m_pending_active_change(false) + , m_use_resume_save_path((p.flags & add_torrent_params::flag_use_resume_save_path) != 0) + , m_merge_resume_http_seeds((p.flags & add_torrent_params::flag_merge_resume_http_seeds) != 0) + , m_stop_when_ready((p.flags & add_torrent_params::flag_stop_when_ready) != 0) +#if TORRENT_USE_ASSERTS + , m_resume_data_loaded(false) + , m_was_started(false) +#endif + { + // we cannot log in the constructor, because it relies on shared_from_this + // being initialized, which happens after the constructor returns. + + if (m_pinned) + inc_stats_counter(counters::num_pinned_torrents); + + inc_stats_counter(counters::num_loaded_torrents); + + // if there is resume data already, we don't need to trigger the initial save + // resume data + if (!p.resume_data.empty() && (p.flags & add_torrent_params::flag_override_resume_data) == 0) + m_need_save_resume_data = false; + +#if TORRENT_USE_UNC_PATHS + m_save_path = canonicalize_path(m_save_path); +#endif + + if (!m_apply_ip_filter) + { + inc_stats_counter(counters::non_filter_torrents); + } + + if (!p.ti || !p.ti->is_valid()) + { + // we don't have metadata for this torrent. We'll download + // it either through the URL passed in, or through a metadata + // extension. Make sure that when we save resume data for this + // torrent, we also save the metadata + m_magnet_link = true; + } + + if (!m_torrent_file) + m_torrent_file = (p.ti ? p.ti : boost::make_shared(info_hash)); + + std::vector const& web_seeds = m_torrent_file->web_seeds(); + m_web_seeds.insert(m_web_seeds.end(), web_seeds.begin(), web_seeds.end()); + + // add web seeds from add_torrent_params + for (std::vector::const_iterator i = p.url_seeds.begin() + , end(p.url_seeds.end()); i != end; ++i) + { + m_web_seeds.push_back(web_seed_t(*i, web_seed_entry::url_seed)); + } + + m_trackers = m_torrent_file->trackers(); + if (m_torrent_file->is_valid()) + { + m_seed_mode = (p.flags & add_torrent_params::flag_seed_mode) != 0; + m_connections_initialized = true; + m_block_size_shift = root2((std::min)(block_size, m_torrent_file->piece_length())); + } + else + { + if (!p.name.empty()) m_name.reset(new std::string(p.name)); + } + + if (!m_url.empty() && m_uuid.empty()) m_uuid = m_url; + + TORRENT_ASSERT(is_single_thread()); + m_file_priority = p.file_priorities; + + if (m_seed_mode) + { + m_verified.resize(m_torrent_file->num_pieces(), false); + m_verifying.resize(m_torrent_file->num_pieces(), false); + } + + if (!p.resume_data.empty()) + { + m_resume_data.reset(new resume_data_t); + m_resume_data->buf = p.resume_data; + } + } + + void torrent::inc_stats_counter(int c, int value) + { m_ses.stats_counters().inc_stats_counter(c, value); } + +#if 0 + + // NON BOTTLED VERSION. SUPPORTS PROGRESS REPORTING + + // since this download is not bottled, this callback will + // be called every time we receive another piece of the + // .torrent file + void torrent::on_torrent_download(error_code const& ec + , http_parser const& parser + , char const* data, int size) + { + if (m_abort) return; + + if (ec && ec != boost::asio::error::eof) + { + set_error(ec, error_file_url); + pause(); + return; + } + + if (size > 0) + { + m_torrent_file_buf.insert(m_torrent_file_buf.end(), data, data + size); + if (parser.content_length() > 0) + set_progress_ppm(boost::int64_t(m_torrent_file_buf.size()) + * 1000000 / parser.content_length()); + } + + if (parser.header_finished() && parser.status_code() != 200) + { + set_error(error_code(parser.status_code(), get_http_category()), error_file_url); + pause(); + return; + } + + if (!ec) return; + + // if this was received with chunked encoding, we need to strip out + // the chunk headers + size = parser.collapse_chunk_headers((char*)&m_torrent_file_buf[0], m_torrent_file_buf.size()); + m_torrent_file_buf.resize(size); + + std::string const& encoding = parser.header("content-encoding"); + if ((encoding == "gzip" || encoding == "x-gzip") && m_torrent_file_buf.size()) + { + std::vector buf; + error_code ec; + inflate_gzip(&m_torrent_file_buf[0], m_torrent_file_buf.size() + , buf, 4 * 1024 * 1024, ex); + if (ec) + { + set_error(ec, error_file_url); + pause(); + std::vector().swap(m_torrent_file_buf); + return; + } + m_torrent_file_buf.swap(buf); + } + + // we're done! + error_code e; + boost::shared_ptr tf(boost::make_shared( + &m_torrent_file_buf[0], m_torrent_file_buf.size(), e)); + if (e) + { + set_error(e, error_file_url); + pause(); + std::vector().swap(m_torrent_file_buf); + return; + } + std::vector().swap(m_torrent_file_buf); + + // update our torrent_info object and move the + // torrent from the old info-hash to the new one + // as we replace the torrent_info object +#if TORRENT_USE_ASSERTS + int num_torrents = m_ses.m_torrents.size(); +#endif + // we're about to erase the session's reference to this + // torrent, create another reference + boost::shared_ptr me(shared_from_this()); + + m_ses.remove_torrent_impl(me, 0); + + m_torrent_file = tf; + + // now, we might already have this torrent in the session. + boost::shared_ptr t = m_ses.find_torrent(m_torrent_file->info_hash()).lock(); + if (t) + { + if (!m_uuid.empty() && t->uuid().empty()) + t->set_uuid(m_uuid); + if (!m_url.empty() && t->url().empty()) + t->set_url(m_url); + if (!m_source_feed_url.empty() && t->source_feed_url().empty()) + t->set_source_feed_url(m_source_feed_url); + + // insert this torrent in the uuid index + if (!m_uuid.empty() || !m_url.empty()) + { + m_ses.insert_uuid_torrent(m_uuid.empty() ? m_url : m_uuid, t); + } + + // TODO: if the existing torrent doesn't have metadata, insert + // the metadata we just downloaded into it. + + set_error(error_code(errors::duplicate_torrent, get_libtorrent_category()), error_file_url); + abort(); + return; + } + + m_ses.insert_torrent(m_torrent_file->info_hash(), me, m_uuid); + + TORRENT_ASSERT(num_torrents == int(m_ses.m_torrents.size())); + + // if the user added any trackers while downloading the + // .torrent file, merge them into the new tracker list + std::vector new_trackers = m_torrent_file->trackers(); + for (std::vector::iterator i = m_trackers.begin() + , end(m_trackers.end()); i != end; ++i) + { + // if we already have this tracker, ignore it + if (std::find_if(new_trackers.begin(), new_trackers.end() + , boost::bind(&announce_entry::url, _1) == i->url) != new_trackers.end()) + continue; + + // insert the tracker ordered by tier + new_trackers.insert(std::find_if(new_trackers.begin(), new_trackers.end() + , boost::bind(&announce_entry::tier, _1) >= i->tier), *i); + } + m_trackers.swap(new_trackers); + +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + hasher h; + h.update("req2", 4); + h.update((char*)&m_torrent_file->info_hash()[0], 20); + // this is SHA1("req2" + info-hash), used for + // encrypted hand shakes + m_ses.add_obfuscated_hash(h.final(), shared_from_this()); +#endif + + if (m_ses.alerts().should_post()) + { + m_ses.alerts().emplace_alert( + get_handle()); + } + + state_updated(); + + set_state(torrent_status::downloading); + + init(); + } +#else // if 0 + + void torrent::on_torrent_download(error_code const& ec + , http_parser const& parser, char const* data, int size) + { + if (m_abort) return; + + if (ec && ec != boost::asio::error::eof) + { + set_error(ec, torrent_status::error_file_url); + pause(); + return; + } + + if (parser.status_code() != 200) + { + set_error(error_code(parser.status_code(), get_http_category()), torrent_status::error_file_url); + pause(); + return; + } + + error_code e; + boost::shared_ptr tf(boost::make_shared(data, size, boost::ref(e), 0)); + if (e) + { + set_error(e, torrent_status::error_file_url); + pause(); + return; + } + + // update our torrent_info object and move the + // torrent from the old info-hash to the new one + // as we replace the torrent_info object + // we're about to erase the session's reference to this + // torrent, create another reference + boost::shared_ptr me(shared_from_this()); + + m_ses.remove_torrent_impl(me, 0); + + if (alerts().should_post()) + alerts().emplace_alert(get_handle(), info_hash(), tf->info_hash()); + + m_torrent_file = tf; + m_info_hash = tf->info_hash(); + + // now, we might already have this torrent in the session. + boost::shared_ptr t = m_ses.find_torrent(m_torrent_file->info_hash()).lock(); + if (t) + { + if (!m_uuid.empty() && t->uuid().empty()) + t->set_uuid(m_uuid); + if (!m_url.empty() && t->url().empty()) + t->set_url(m_url); + if (!m_source_feed_url.empty() && t->source_feed_url().empty()) + t->set_source_feed_url(m_source_feed_url); + + // insert this torrent in the uuid index + if (!m_uuid.empty() || !m_url.empty()) + { + m_ses.insert_uuid_torrent(m_uuid.empty() ? m_url : m_uuid, t); + } + + // TODO: if the existing torrent doesn't have metadata, insert + // the metadata we just downloaded into it. + + set_error(error_code(errors::duplicate_torrent, get_libtorrent_category()), torrent_status::error_file_url); + abort(); + return; + } + + m_ses.insert_torrent(m_torrent_file->info_hash(), me, m_uuid); + + // if the user added any trackers while downloading the + // .torrent file, merge them into the new tracker list + std::vector new_trackers = m_torrent_file->trackers(); + for (std::vector::iterator i = m_trackers.begin() + , end(m_trackers.end()); i != end; ++i) + { + // if we already have this tracker, ignore it + if (std::find_if(new_trackers.begin(), new_trackers.end() + , boost::bind(&announce_entry::url, _1) == i->url) != new_trackers.end()) + continue; + + // insert the tracker ordered by tier + new_trackers.insert(std::find_if(new_trackers.begin(), new_trackers.end() + , boost::bind(&announce_entry::tier, _1) >= i->tier), *i); + } + m_trackers.swap(new_trackers); + + // add the web seeds from the .torrent file + std::vector const& web_seeds = m_torrent_file->web_seeds(); + m_web_seeds.insert(m_web_seeds.end(), web_seeds.begin(), web_seeds.end()); + +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + hasher h; + h.update("req2", 4); + h.update(m_torrent_file->info_hash().data(), 20); + m_ses.add_obfuscated_hash(h.final(), shared_from_this()); +#endif + + if (m_ses.alerts().should_post()) + { + m_ses.alerts().emplace_alert( + get_handle()); + } + + state_updated(); + + set_state(torrent_status::downloading); + + init(); + } + +#endif // if 0 + + int torrent::current_stats_state() const + { + if (m_abort) return counters::num_checking_torrents + no_gauge_state; + + if (has_error()) return counters::num_error_torrents; + if (!m_allow_peers || 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 +#ifndef TORRENT_NO_DEPRECATE + || 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 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 == 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); + + m_current_gauge_state = new_gauge_state; + } + + void torrent::leave_seed_mode(bool skip_checking) + { + if (!m_seed_mode) return; + + if (!skip_checking) + { + // 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)" + , 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 (!skip_checking) + { + 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(int piece) + { + TORRENT_ASSERT(piece < int(m_verified.size())); + TORRENT_ASSERT(piece >= 0); + TORRENT_ASSERT(m_verified.get_bit(piece) == false); + ++m_num_verified; + m_verified.set_bit(piece); + } + + void torrent::start(add_torrent_params const& p) + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(m_was_started == false); +#if TORRENT_USE_ASSERTS + m_was_started = true; +#endif + +#ifndef TORRENT_DISABLE_LOGGING + 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%s" + "save-path: %s" + , torrent_file().name().c_str() + , p.max_uploads + , p.max_connections + , p.upload_limit + , p.download_limit + , (p.flags & add_torrent_params::flag_seed_mode) + ? "seed-mode " : "" + , (p.flags & add_torrent_params::flag_override_resume_data) + ? "override-resume-data " : "" + , (p.flags & add_torrent_params::flag_upload_mode) + ? "upload-mode " : "" + , (p.flags & add_torrent_params::flag_share_mode) + ? "share-mode " : "" + , (p.flags & add_torrent_params::flag_apply_ip_filter) + ? "apply-ip-filter " : "" + , (p.flags & add_torrent_params::flag_paused) + ? "paused " : "" + , (p.flags & add_torrent_params::flag_auto_managed) + ? "auto-managed " : "" + , (p.flags & add_torrent_params::flag_merge_resume_trackers) + ? "merge-resume-trackers " : "" + , (p.flags & add_torrent_params::flag_update_subscribe) + ? "update-subscribe " : "" + , (p.flags & add_torrent_params::flag_super_seeding) + ? "super-seeding " : "" + , (p.flags & add_torrent_params::flag_sequential_download) + ? "sequential-download " : "" + , (p.flags & add_torrent_params::flag_use_resume_save_path) + ? "resume-save-path " : "" + , p.save_path.c_str() + ); +#endif + if (p.flags & add_torrent_params::flag_sequential_download) + m_sequential_download = true; + + if (p.flags & add_torrent_params::flag_super_seeding) + { + m_super_seeding = true; + set_need_save_resume(); + } + + 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); + + if (!m_name && !m_url.empty()) m_name.reset(new std::string(m_url)); + +#ifndef TORRENT_NO_DEPRECATE + if (p.tracker_url && std::strlen(p.tracker_url) > 0) + { + m_trackers.push_back(announce_entry(p.tracker_url)); + m_trackers.back().fail_limit = 0; + m_trackers.back().source = announce_entry::source_magnet_link; + m_torrent_file->add_tracker(p.tracker_url); + } +#endif + + for (std::vector::const_iterator i = p.trackers.begin() + , end(p.trackers.end()); i != end; ++i) + { + m_trackers.push_back(announce_entry(*i)); + m_trackers.back().fail_limit = 0; + m_trackers.back().source = announce_entry::source_magnet_link; + m_torrent_file->add_tracker(*i); + } + + if (settings().get_bool(settings_pack::prefer_udp_trackers)) + prioritize_udp_trackers(); + + // if we don't have metadata, make this torrent pinned. The + // client may unpin it once we have metadata and it has had + // a chance to save it on the metadata_received_alert + if (!valid_metadata()) + { + if (!m_pinned && m_refcount == 0) + inc_stats_counter(counters::num_pinned_torrents); + + m_pinned = true; + } + else + { + inc_stats_counter(counters::num_total_pieces_added + , m_torrent_file->num_pieces()); + } + + update_gauge(); + + m_file_progress.clear(); + + if (m_resume_data) + { + int pos; + error_code ec; + if (bdecode(&m_resume_data->buf[0], &m_resume_data->buf[0] + + m_resume_data->buf.size(), m_resume_data->node, ec, &pos) != 0) + { + m_resume_data.reset(); +#ifndef TORRENT_DISABLE_LOGGING + debug_log("resume data rejected: %s pos: %d", ec.message().c_str(), pos); +#endif + if (m_ses.alerts().should_post()) + m_ses.alerts().emplace_alert(get_handle() + , ec, "", static_cast(0)); + } + } + + update_want_peers(); + update_want_scrape(); + update_want_tick(); + update_state_list(); + + if (!m_torrent_file->is_valid() && !m_url.empty()) + { + // we need to download the .torrent file from m_url + start_download_url(); + } + else 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::start_download_url() + { + TORRENT_ASSERT(!m_url.empty()); + TORRENT_ASSERT(!m_torrent_file->is_valid()); + boost::shared_ptr conn( + new http_connection(m_ses.get_io_service() + , m_ses.get_resolver() + , boost::bind(&torrent::on_torrent_download, shared_from_this() + , _1, _2, _3, _4) + , true // bottled + //bottled buffer size + , settings().get_int(settings_pack::max_http_recv_buffer_size) + , http_connect_handler() + , http_filter_handler() +#ifdef TORRENT_USE_OPENSSL + , m_ssl_ctx.get() +#endif + )); + aux::proxy_settings ps = m_ses.proxy(); + conn->get(m_url, seconds(30), 0, &ps + , 5, settings().get_str(settings_pack::user_agent)); + set_state(torrent_status::downloading_metadata); + } + + void torrent::set_apply_ip_filter(bool b) + { + if (b == m_apply_ip_filter) return; + if (b) + { + inc_stats_counter(counters::non_filter_torrents, -1); + } + else + { + inc_stats_counter(counters::non_filter_torrents); + } + m_apply_ip_filter = b; + ip_filter_updated(); + state_updated(); + } + + void torrent::set_ip_filter(boost::shared_ptr ipf) + { + m_ip_filter = 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_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_allow_peers) return false; + + // if we don't have the metadata, and we're waiting + // for a web server to serve it to us, no need to announce + // because the info-hash is just the URL hash + if (!m_torrent_file->is_valid() && !m_url.empty()) return false; + + // 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; + + int verified_trackers = 0; + for (std::vector::const_iterator i = m_trackers.begin() + , end(m_trackers.end()); i != end; ++i) + if (i->verified) ++verified_trackers; + + return verified_trackers == 0; + } + +#endif + + torrent::~torrent() + { + TORRENT_ASSERT(m_abort); + TORRENT_ASSERT(prev == NULL && next == NULL); + +#if defined TORRENT_DEBUG || defined TORRENT_RELEASE_ASSERTS + for (int i = 0; i < aux::session_interface::num_torrent_lists; ++i) + { + if (!m_links[i].in_list()) continue; + m_links[i].unlink(m_ses.torrent_list(i), i); + } +#endif + + TORRENT_ASSERT(m_refcount == 0); + + if (m_pinned) + inc_stats_counter(counters::num_pinned_torrents, -1); + + if (is_loaded()) + inc_stats_counter(counters::num_loaded_torrents, -1); + + // 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 == 0); + TORRENT_ASSERT(m_abort); + TORRENT_ASSERT(m_connections.empty()); + if (!m_connections.empty()) + disconnect_all(errors::torrent_aborted, op_bittorrent); + } + + void torrent::read_piece(int piece) + { + if (m_abort || m_deleted) + { + // failed + m_ses.alerts().emplace_alert( + get_handle(), piece, error_code(boost::system::errc::operation_canceled, generic_category())); + return; + } + + TORRENT_ASSERT(piece >= 0 && piece < m_torrent_file->num_pieces()); + 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; + } + + boost::shared_ptr rp = boost::make_shared(); + rp->piece_data.reset(new (std::nothrow) char[piece_size]); + rp->blocks_left = 0; + rp->fail = false; + + peer_request r; + r.piece = piece; + r.start = 0; + rp->blocks_left = blocks_in_piece; + if (!need_loaded()) + { + rp->piece_data.reset(); + m_ses.alerts().emplace_alert( + get_handle(), r.piece, rp->piece_data, 0); + return; + } + for (int i = 0; i < blocks_in_piece; ++i, r.start += block_size()) + { + r.length = (std::min)(piece_size - r.start, block_size()); + inc_refcount("read_piece"); + m_ses.disk_thread().async_read(&storage(), r + , boost::bind(&torrent::on_disk_read_complete + , shared_from_this(), _1, r, rp), reinterpret_cast(1)); + } + } + + void torrent::send_share_mode() + { +#ifndef TORRENT_DISABLE_EXTENSIONS + for (peer_iterator i = m_connections.begin() + , end(m_connections.end()); i != end; ++i) + { + if ((*i)->type() != peer_connection::bittorrent_connection) continue; + bt_peer_connection* p = static_cast(*i); + p->write_share_mode(); + } +#endif + } + + void torrent::send_upload_only() + { +#ifndef TORRENT_DISABLE_EXTENSIONS + if (share_mode()) return; + if (super_seeding()) return; + + int idx = 0; + for (peer_iterator i = m_connections.begin(); + i != m_connections.end(); ++idx) + { + // since the call to disconnect_if_redundant() may + // delete the entry from this container, make sure + // to increment the iterator early + bt_peer_connection* p = static_cast(*i); + if (p->type() == peer_connection::bittorrent_connection) + { + boost::shared_ptr me(p->self()); + if (!p->is_disconnecting()) + { + p->send_not_interested(); + p->write_upload_only(); + } + } + + + if (p->is_disconnecting()) + { + i = m_connections.begin() + idx; + --idx; + } + else + { + ++i; + } + } +#endif + } + + void torrent::set_share_mode(bool s) + { + if (s == m_share_mode) return; + + m_share_mode = s; +#ifndef TORRENT_DISABLE_LOGGING + debug_log("*** set-share-mode: %d", s); +#endif + + // in share mode, all pieces have their priorities initialized to 0 + if (m_share_mode && valid_metadata()) + { + m_file_priority.clear(); + m_file_priority.resize(m_torrent_file->num_files(), 0); + } + + update_piece_priorities(); + + if (m_share_mode) recalc_share_mode(); + } + + void torrent::set_upload_mode(bool b) + { + if (b == m_upload_mode) return; + + m_upload_mode = b; +#ifndef TORRENT_DISABLE_LOGGING + debug_log("*** set-upload-mode: %d", b); +#endif + + update_gauge(); + state_updated(); + send_upload_only(); + + if (m_upload_mode) + { + // clear request queues of all peers + for (peer_iterator i = m_connections.begin() + , end(m_connections.end()); i != end; ++i) + { + peer_connection* p = (*i); + // 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 = m_ses.session_time(); + } + else if (m_peer_list) + { + // reset last_connected, to force fast reconnect after leaving upload mode + for (peer_list::iterator i = m_peer_list->begin_peer() + , end(m_peer_list->end_peer()); i != end; ++i) + { + (*i)->last_connected = 0; + } + + // send_block_requests on all peers + for (peer_iterator i = m_connections.begin() + , end(m_connections.end()); i != end; ++i) + { + peer_connection* p = (*i); + // we may be interested now, or no longer interested + p->update_interest(); + p->send_block_requests(); + } + } + } + + void torrent::need_peer_list() + { + if (m_peer_list) return; + m_peer_list.reset(new peer_list); + } + + void torrent::handle_disk_error(disk_io_job const* j, peer_connection* c) + { + TORRENT_ASSERT(is_single_thread()); + if (!j->error) return; + +#ifndef TORRENT_DISABLE_LOGGING + debug_log("disk error: (%d) %s in file: %s", j->error.ec.value(), j->error.ec.message().c_str() + , resolve_filename(j->error.file).c_str()); +#endif + + if (j->action == disk_io_job::write) + { + piece_block block_finished(j->piece, j->d.io.offset / block_size()); + + // we failed to write j->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 (j->error.ec == boost::asio::error::operation_aborted) + { + if (has_picker()) + picker().mark_as_canceled(block_finished, NULL); + } + else + { + // if any other peer has a busy request to this block, we need + // to cancel it too + cancel_block(block_finished); + if (has_picker()) + picker().write_failed(block_finished); + + if (m_storage) + { + // when this returns, all outstanding jobs to the + // piece are done, and we can restore it, allowing + // new requests to it + m_ses.disk_thread().async_clear_piece(m_storage.get(), j->piece + , boost::bind(&torrent::on_piece_fail_sync, shared_from_this(), _1, 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? + disk_io_job sj; + sj.piece = j->piece; + on_piece_fail_sync(&sj, block_finished); + } + } + update_gauge(); + } + + if (j->error.ec == boost::system::errc::not_enough_memory) + { + if (alerts().should_post()) + alerts().emplace_alert(j->error.ec + , resolve_filename(j->error.file), j->error.operation_str(), get_handle()); + if (c) c->disconnect(errors::no_memory, op_file); + return; + } + + if (j->error.ec == boost::asio::error::operation_aborted) return; + + // notify the user of the error + if (alerts().should_post()) + alerts().emplace_alert(j->error.ec + , resolve_filename(j->error.file), j->error.operation_str(), 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 (j->action == disk_io_job::write + && (j->error.ec == boost::system::errc::read_only_file_system + || j->error.ec == boost::system::errc::permission_denied + || j->error.ec == boost::system::errc::operation_not_permitted + || j->error.ec == boost::system::errc::no_space_on_device + || j->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(j->error.ec, j->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(disk_io_job const* j, piece_block b) + { + TORRENT_UNUSED(j); + TORRENT_UNUSED(b); + + if (m_abort) return; + + update_gauge(); + // some peers that previously was no longer interesting may + // now have become interesting, since we lack this one piece now. + for (peer_iterator 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(); + } + } + } + + void torrent::on_disk_read_complete(disk_io_job const* j, peer_request r + , boost::shared_ptr rp) + { + // hold a reference until this function returns + torrent_ref_holder h(this, "read_piece"); + + dec_refcount("read_piece"); + TORRENT_ASSERT(is_single_thread()); + + disk_buffer_holder buffer(m_ses, *j); + + --rp->blocks_left; + if (j->ret != r.length) + { + rp->fail = true; + rp->error = j->error.ec; + handle_disk_error(j); + } + else + { + std::memcpy(rp->piece_data.get() + r.start, j->buffer.disk_block, 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); + } + } + } + + storage_mode_t torrent::storage_mode() const + { return storage_mode_t(m_storage_mode); } + + storage_interface* torrent::get_storage() + { + if (!m_storage) return 0; + return m_storage->get_storage_impl(); + } + + 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 + TORRENT_ASSERT(!m_have_all); + + m_picker.reset(new piece_picker()); + int blocks_per_piece = (m_torrent_file->piece_length() + block_size() - 1) / block_size(); + int blocks_in_last_piece = ((m_torrent_file->total_size() % m_torrent_file->piece_length()) + + block_size() - 1) / block_size(); + m_picker->init(blocks_per_piece, blocks_in_last_piece, m_torrent_file->num_pieces()); + + // initialize the file progress too + if (m_file_progress.empty()) + { + TORRENT_ASSERT(has_picker()); + m_file_progress.init(picker(), m_torrent_file->files()); + } + + update_gauge(); + + for (peer_iterator i = m_connections.begin() + , end(m_connections.end()); i != end; ++i) + { + peer_has((*i)->get_bitfield(), *i); + } + } + + void torrent::add_piece(int piece, char const* data, int flags) + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(piece >= 0 && piece < m_torrent_file->num_pieces()); + int piece_size = m_torrent_file->piece_size(piece); + int 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; + + need_picker(); + + if (picker().have_piece(piece) + && (flags & torrent::overwrite_existing) == 0) + return; + + peer_request p; + p.piece = piece; + p.start = 0; + picker().inc_refcount(piece, 0); + for (int i = 0; i < blocks_in_piece; ++i, p.start += block_size()) + { + if (picker().is_finished(piece_block(piece, i)) + && (flags & torrent::overwrite_existing) == 0) + continue; + + p.length = (std::min)(piece_size - p.start, int(block_size())); + char* buffer = m_ses.allocate_disk_buffer("add piece"); + // out of memory + if (buffer == 0) + { + picker().dec_refcount(piece, 0); + return; + } + disk_buffer_holder holder(m_ses, buffer); + std::memcpy(buffer, data + p.start, p.length); + + if (!need_loaded()) + { + // failed to load .torrent file + picker().dec_refcount(piece, 0); + return; + } + inc_refcount("add_piece"); + m_ses.disk_thread().async_write(&storage(), p, holder + , boost::bind(&torrent::on_disk_write_complete + , shared_from_this(), _1, p)); + piece_block block(piece, i); + picker().mark_as_downloading(block, 0); + picker().mark_as_writing(block, 0); + } + verify_piece(piece); + picker().dec_refcount(piece, 0); + } + + void torrent::schedule_storage_tick() + { + // schedule a disk tick in 2 minutes or so + if (m_storage_tick != 0) return; + m_storage_tick = 120 + (random() % 60); + update_want_tick(); + } + + void torrent::on_disk_write_complete(disk_io_job const* j + , peer_request p) + { + // hold a reference until this function returns + torrent_ref_holder h(this, "add_piece"); + + dec_refcount("add_piece"); + TORRENT_ASSERT(is_single_thread()); + + schedule_storage_tick(); + +// 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 block_finished(p.piece, p.start / block_size()); + + if (j->ret == -1) + { + handle_disk_error(j); + 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, 0); + maybe_done_flushing(); + } + + void torrent::on_disk_cache_complete(disk_io_job const* j) + { + TORRENT_ASSERT(have_piece(j->piece)); + + dec_refcount("cache_piece"); + + if (j->ret < 0) return; + + // suggest this piece to all peers + for (peer_iterator i = m_connections.begin(); + i != m_connections.end(); ++i) + (*i)->send_suggest(j->piece); + } + + void torrent::on_disk_tick_done(disk_io_job const* j) + { + if (j->ret && m_storage_tick == 0) + { + m_storage_tick = 120 + (random() % 20); + update_want_tick(); + } + } + + bool torrent::add_merkle_nodes(std::map const& nodes, int piece) + { + return m_torrent_file->add_merkle_nodes(nodes, piece); + } + + peer_request torrent::to_req(piece_block const& p) const + { + int block_offset = p.block_index * block_size(); + int block = (std::min)(torrent_file().piece_size( + p.piece_index) - block_offset, int(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(boost::shared_ptr ext) + { + m_extensions.push_back(ext); + } + + void torrent::remove_extension(boost::shared_ptr ext) + { + extension_list_t::iterator i = std::find(m_extensions.begin(), m_extensions.end(), ext); + if (i == m_extensions.end()) return; + m_extensions.erase(i); + } + + void torrent::add_extension(boost::function(torrent_handle const&, void*)> const& ext + , void* userdata) + { + boost::shared_ptr tp(ext(get_handle(), userdata)); + if (!tp) return; + + add_extension(tp); + + for (peer_iterator i = m_connections.begin(); + i != m_connections.end(); ++i) + { + peer_connection* p = *i; + boost::shared_ptr pp(tp->new_connection(peer_connection_handle(p->self()))); + if (pp) p->add_extension(pp); + } + + // if files are checked for this torrent, call the extension + // to let it initialize itself + if (m_connections_initialized) + tp->on_files_checked(); + } + +#endif + +#ifdef TORRENT_USE_OPENSSL + +#if BOOST_VERSION >= 104700 + bool torrent::verify_peer_cert(bool preverified, boost::asio::ssl::verify_context& ctx) + { + // if the cert wasn't signed by the correct CA, fail the verification + if (!preverified) return false; + + // we're only interested in checking the certificate at the end of the chain. + // TODO: is verify_peer_cert called once per certificate in the chain, and + // this function just tells us which depth we're at right now? If so, the comment + // makes sense. + // any certificate that isn't the leaf (i.e. the one presented by the peer) + // should be accepted automatically, given preverified is true. The leaf certificate + // need to be verified to make sure its DN matches the info-hash + int depth = X509_STORE_CTX_get_error_depth(ctx.native_handle()); + if (depth > 0) return true; + + X509* cert = X509_STORE_CTX_get_current_cert(ctx.native_handle()); + + // Go through the alternate names in the certificate looking for matching DNS entries + GENERAL_NAMES* gens = static_cast( + X509_get_ext_d2i(cert, NID_subject_alt_name, 0, 0)); + +#ifndef TORRENT_DISABLE_LOGGING + std::string names; + bool match = false; +#endif + for (int i = 0; i < aux::openssl_num_general_names(gens); ++i) + { + GENERAL_NAME* gen = aux::openssl_general_name_value(gens, i); + if (gen->type != GEN_DNS) continue; + ASN1_IA5STRING* domain = gen->d.dNSName; + if (domain->type != V_ASN1_IA5STRING || !domain->data || !domain->length) continue; + const char* torrent_name = reinterpret_cast(domain->data); + std::size_t name_length = domain->length; + +#ifndef TORRENT_DISABLE_LOGGING + if (i > 1) names += " | n: "; + names.append(torrent_name, name_length); +#endif + if (strncmp(torrent_name, "*", name_length) == 0 + || strncmp(torrent_name, m_torrent_file->name().c_str(), name_length) == 0) + { +#ifndef TORRENT_DISABLE_LOGGING + match = true; + // if we're logging, keep looping over all names, + // for completeness of the log + continue; +#else + return true; +#endif + } + } + + // no match in the alternate names, so try the common names. We should only + // use the "most specific" common name, which is the last one in the list. + X509_NAME* name = X509_get_subject_name(cert); + int i = -1; + ASN1_STRING* common_name = 0; + 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) + { + const char* torrent_name = reinterpret_cast(common_name->data); + std::size_t name_length = common_name->length; + +#ifndef TORRENT_DISABLE_LOGGING + if (!names.empty()) names += " | n: "; + names.append(torrent_name, name_length); +#endif + + if (strncmp(torrent_name, "*", name_length) == 0 + || strncmp(torrent_name, m_torrent_file->name().c_str(), name_length) == 0) + { +#ifdef TORRENT_DISABLE_LOGGING + return true; +#else + match = true; +#endif + } + } + +#ifndef TORRENT_DISABLE_LOGGING + debug_log("<== incoming SSL CONNECTION [ n: %s | match: %s ]" + , names.c_str(), match?"yes":"no"); + return match; +#else + return false; +#endif + } +#endif // BOOST_VERSION + + void torrent::init_ssl(std::string const& cert) + { + using boost::asio::ssl::context; + + // this is needed for openssl < 1.0 to decrypt keys created by openssl 1.0+ + OpenSSL_add_all_algorithms(); + + boost::uint64_t now = clock_type::now().time_since_epoch().count(); + // assume 9 bits of entropy (i.e. about 1 millisecond) + RAND_add(&now, 8, 1.125); + RAND_add(&info_hash()[0], 20, 3); + // entropy is also added on incoming and completed connection attempts + + TORRENT_ASSERT(RAND_status() == 1); + +#if BOOST_VERSION >= 104700 + // create the SSL context for this torrent. We need to + // inject the root certificate, and no other, to + // verify other peers against + boost::shared_ptr ctx = boost::make_shared(boost::ref(m_ses.get_io_service()), context::sslv23); + + if (!ctx) + { + error_code ec(::ERR_get_error(), + boost::asio::error::get_ssl_category()); + set_error(ec, torrent_status::error_file_ssl_ctx); + pause(); + return; + } + + ctx->set_options(context::default_workarounds + | boost::asio::ssl::context::no_sslv2 + | boost::asio::ssl::context::single_dh_use); + + error_code ec; + ctx->set_verify_mode(context::verify_peer + | context::verify_fail_if_no_peer_cert + | context::verify_client_once, ec); + if (ec) + { + set_error(ec, torrent_status::error_file_ssl_ctx); + pause(); + return; + } + + // the verification function verifies the distinguished name + // of a peer certificate to make sure it matches the info-hash + // of the torrent, or that it's a "star-cert" + ctx->set_verify_callback(boost::bind(&torrent::verify_peer_cert, this, _1, _2), ec); + if (ec) + { + set_error(ec, torrent_status::error_file_ssl_ctx); + pause(); + return; + } + + SSL_CTX* ssl_ctx = ctx->impl(); + // create a new x.509 certificate store + X509_STORE* cert_store = X509_STORE_new(); + if (!cert_store) + { + ec.assign(::ERR_get_error(), + boost::asio::error::get_ssl_category()); + set_error(ec, torrent_status::error_file_ssl_ctx); + pause(); + return; + } + + // wrap the PEM certificate in a BIO, for openssl to read + BIO* bp = BIO_new_mem_buf( + const_cast(static_cast(cert.c_str())) + , cert.size()); + + // parse the certificate into OpenSSL's internal + // representation + X509* certificate = PEM_read_bio_X509_AUX(bp, 0, 0, 0); + + BIO_free(bp); + + if (!certificate) + { + ec.assign(::ERR_get_error(), + boost::asio::error::get_ssl_category()); + X509_STORE_free(cert_store); + set_error(ec, torrent_status::error_file_ssl_ctx); + pause(); + return; + } + + // add cert to cert_store + X509_STORE_add_cert(cert_store, certificate); + + X509_free(certificate); + + // and lastly, replace the default cert store with ours + SSL_CTX_set_cert_store(ssl_ctx, cert_store); +#if 0 + char filename[100]; + 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 = ctx; + // tell the client we need a cert for this torrent + alerts().emplace_alert(get_handle()); +#else + set_error(boost::asio::error::operation_not_supported, torrent_status::error_file_ssl_ctx); + pause(); +#endif + } + +#endif // TORRENT_OPENSSL + + void torrent::construct_storage() + { + storage_params params; + + if (&m_torrent_file->orig_files() != &m_torrent_file->files()) + { + params.mapped_files = &m_torrent_file->files(); + params.files = &m_torrent_file->orig_files(); + } + else + { + params.files = &m_torrent_file->files(); + params.mapped_files = 0; + } + params.path = m_save_path; + params.pool = &m_ses.disk_thread().files(); + params.mode = static_cast(m_storage_mode); + params.priorities = &m_file_priority; + params.info = m_torrent_file.get(); + + TORRENT_ASSERT(m_storage_constructor); + storage_interface* storage_impl = m_storage_constructor(params); + + // the shared_from_this() will create an intentional + // cycle of ownership, se the hpp file for description. + m_storage = boost::make_shared( + storage_impl, shared_from_this() + , const_cast(&m_torrent_file->files())); + } + + peer_connection* torrent::find_lowest_ranking_peer() const + { + const_peer_iterator lowest_rank = end(); + for (const_peer_iterator 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 NULL; + return *lowest_rank; + } + + // this may not be called from a constructor because of the call to + // shared_from_this() + 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 + + if (!need_loaded()) return; + 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()); + + std::string cert = m_torrent_file->ssl_cert(); + if (!cert.empty()) + { + m_ssl_torrent = true; +#ifdef TORRENT_USE_OPENSSL + init_ssl(cert); +#endif + } + + m_block_size_shift = root2((std::min)(block_size(), m_torrent_file->piece_length())); + + 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; + } + + if (m_resume_data && m_resume_data->node.type() == bdecode_node::dict_t) + { + int ev = 0; + if (m_resume_data->node.dict_find_string_value("file-format") + != "libtorrent resume file") + { + ev = errors::invalid_file_tag; + } + + std::string info_hash = m_resume_data->node.dict_find_string_value("info-hash"); + if (!ev && info_hash.empty()) + ev = errors::missing_info_hash; + + if (!ev && sha1_hash(info_hash) != m_torrent_file->info_hash()) + ev = errors::mismatching_info_hash; + + if (ev && m_ses.alerts().should_post()) + { + error_code ec = error_code(ev, get_libtorrent_category()); + m_ses.alerts().emplace_alert(get_handle() + , ec, "", static_cast(0)); + } + + if (ev) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("fastresume data rejected: %s" + , error_code(ev, get_libtorrent_category()).message().c_str()); +#endif + m_resume_data.reset(); + } + else + { + read_resume_data(m_resume_data->node); + } + } + +#if TORRENT_USE_ASSERTS + m_resume_data_loaded = true; +#endif + + construct_storage(); + + 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(), 0); + } + + // 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 + std::vector peers = m_connections; + for (torrent::peer_iterator i = peers.begin(); + i != peers.end(); ++i) + { + peer_connection* pc = *i; + if (pc->is_disconnecting()) continue; + pc->on_metadata_impl(); + if (pc->is_disconnecting()) continue; + pc->init(); + } + } + + // if we've already loaded file priorities, don't load piece priorities, + // they will interfere. + if (!m_seed_mode && m_resume_data && m_file_priority.empty()) + { + bdecode_node piece_priority = m_resume_data->node + .dict_find_string("piece_priority"); + + if (piece_priority && piece_priority.string_length() + == m_torrent_file->num_pieces()) + { + char const* p = piece_priority.string_ptr(); + for (int i = 0; i < piece_priority.string_length(); ++i) + { + int prio = p[i]; + if (!has_picker() && prio == 4) continue; + need_picker(); + m_picker->set_piece_priority(i, p[i]); + } + update_gauge(); + } + } + + // 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 + if (!m_file_priority.empty() && std::find(m_file_priority.begin() + , m_file_priority.end(), 0) != m_file_priority.end()) + { + update_piece_priorities(); + } + +#if TORRENT_USE_ASSERTS + m_resume_data_loaded = true; +#endif + + if (m_seed_mode) + { + m_have_all = true; + m_ses.get_io_service().post(boost::bind(&torrent::files_checked, shared_from_this())); + m_resume_data.reset(); + update_gauge(); + update_state_list(); + return; + } + + set_state(torrent_status::checking_resume_data); + + int num_pad_files = 0; + TORRENT_ASSERT(block_size() > 0); + file_storage const& fs = m_torrent_file->files(); + for (int i = 0; i < fs.num_files(); ++i) + { + if (fs.pad_file_at(i)) ++num_pad_files; + + if (!fs.pad_file_at(i) || fs.file_size(i) == 0) continue; + m_padding += boost::uint32_t(fs.file_size(i)); + + // TODO: instead of creating the picker up front here, + // maybe this whole section should move to need_picker() + need_picker(); + + peer_request pr = m_torrent_file->map_file(i, 0, fs.file_size(i)); + int off = pr.start & (block_size()-1); + if (off != 0) { pr.length -= block_size() - off; pr.start += block_size() - off; } + TORRENT_ASSERT((pr.start & (block_size()-1)) == 0); + + int block = block_size(); + int blocks_per_piece = m_torrent_file->piece_length() / block; + piece_block pb(pr.piece, pr.start / block); + for (; pr.length >= block; pr.length -= block, ++pb.block_index) + { + if (int(pb.block_index) == blocks_per_piece) { pb.block_index = 0; ++pb.piece_index; } + m_picker->mark_as_finished(pb, 0); + } + // ugly edge case where padfiles are not used they way they're + // supposed to be. i.e. added back-to back or at the end + if (pb.block_index == blocks_per_piece) { pb.block_index = 0; ++pb.piece_index; } + if (pr.length > 0 && ((i+1 != fs.num_files() && fs.pad_file_at(i+1)) + || i + 1 == fs.num_files())) + { + m_picker->mark_as_finished(pb, 0); + } + } + + if (m_padding > 0) + { + // if we marked an entire piece as finished, we actually + // need to consider it finished + + std::vector dq + = m_picker->get_download_queue(); + + std::vector have_pieces; + + for (std::vector::const_iterator i + = dq.begin(); i != dq.end(); ++i) + { + int num_blocks = m_picker->blocks_in_piece(i->index); + if (i->finished < num_blocks) continue; + have_pieces.push_back(i->index); + } + + for (std::vector::iterator i = have_pieces.begin(); + i != have_pieces.end(); ++i) + { + picker().piece_passed(*i); + TORRENT_ASSERT(picker().have_piece(*i)); + we_have(*i); + } + } + + if (!need_loaded()) return; + + if (num_pad_files > 0) + m_picker->set_num_pad_files(num_pad_files); + + std::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); + + std::vector s = m_torrent_file->similar_torrents(); + for (std::vector::iterator i = s.begin(), end(s.end()); + i != end; ++i) + { + boost::shared_ptr t = m_ses.find_torrent(*i).lock(); + if (!t) continue; + + // Only attempt to reuse files from torrents that are seeding. + // TODO: this could be optimized by looking up which files are + // complete and just look at those + if (!t->is_seed()) continue; + + res.match(t->get_torrent_copy(), t->save_path()); + } + std::vector c = m_torrent_file->collections(); + for (std::vector::iterator i = c.begin(), end(c.end()); + i != end; ++i) + { + std::vector > ts = m_ses.find_collection(*i); + + for (std::vector >::iterator k = ts.begin() + , end2(ts.end()); k != end2; ++k) + { + // 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 (!(*k)->is_seed()) continue; + + res.match((*k)->get_torrent_copy(), (*k)->save_path()); + } + } + + std::vector const& l = res.get_links(); + if (!l.empty()) + { + for (std::vector::const_iterator i = l.begin() + , end(l.end()); i != end; ++i) + { + if (!i->ti) continue; + + torrent_info const& ti = *i->ti; + std::string const& save_path = i->save_path; + links.push_back(combine_path(save_path + , ti.files().file_path(i->file_idx))); + } + } + } +#endif // TORRENT_DISABLE_MUTABLE_TORRENTS + + inc_refcount("check_fastresume"); + // async_check_fastresume will gut links + m_ses.disk_thread().async_check_fastresume( + m_storage.get(), m_resume_data ? &m_resume_data->node : NULL + , links, boost::bind(&torrent::on_resume_data_checked + , shared_from_this(), _1)); +#ifndef TORRENT_DISABLE_LOGGING + debug_log("init, async_check_fastresume"); +#endif + + update_want_peers(); + + maybe_done_flushing(); + } + + bool torrent::need_loaded() + { + m_should_be_loaded = true; + + // if we don't have the metadata yet, pretend the file is loaded + if (!m_torrent_file->is_valid() + || m_torrent_file->is_loaded()) + { + // bump this torrent to the top of the torrent LRU of + // which torrents are most active + m_ses.bump_torrent(this); + + return true; + } + + // load the specified torrent and also evict one torrent, + // except for the one specified. if we're not at our limit + // yet, no torrent is evicted + return m_ses.load_torrent(this); + } + + void torrent::dec_refcount(char const* purpose) + { + TORRENT_UNUSED(purpose); + + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(m_refcount > 0); + --m_refcount; + if (m_refcount == 0) + { + if (!m_pinned) + inc_stats_counter(counters::num_pinned_torrents, -1); + + if (m_should_be_loaded == false) + unload(); + } + } + + void torrent::inc_refcount(char const* purpose) + { + TORRENT_UNUSED(purpose); + + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(is_loaded()); + ++m_refcount; + if (!m_pinned && m_refcount == 1) + inc_stats_counter(counters::num_pinned_torrents); + } + + void torrent::set_pinned(bool p) + { + TORRENT_ASSERT(is_single_thread()); + if (m_pinned == p) return; + m_pinned = p; + + if (m_refcount == 0) + inc_stats_counter(counters::num_pinned_torrents, p ? 1 : -1); + + // if the torrent was just un-pinned, we need to insert + // it into the LRU + m_ses.bump_torrent(this, true); + } + + bool torrent::load(std::vector& buffer) + { + error_code ec; + m_torrent_file->load(&buffer[0], buffer.size(), ec); + if (ec) + { + set_error(ec, torrent_status::error_file_metadata); + return false; + } + + state_updated(); +/* +#ifndef TORRENT_DISABLE_EXTENSIONS + // create the extensions again + + // TOOD: should we store add_torrent_params::userdata + // in torrent just to have it available here? + m_ses.add_extensions_to_torrent(shared_from_this(), NULL); + + // and call on_load() on them + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + TORRENT_TRY { + (*i)->on_load(); + } TORRENT_CATCH (std::exception&) {} + } +#endif +*/ + + inc_stats_counter(counters::num_loaded_torrents); + + construct_storage(); + + return true; + } + + // this is called when this torrent hasn't been active in long enough + // to warrant swapping it out, in favor of a more active torrent. + void torrent::unload() + { + TORRENT_ASSERT(is_loaded()); + + // pinned torrents are not allowed to be swapped out + TORRENT_ASSERT(!m_pinned); + + m_should_be_loaded = false; + + // make sure it's not unloaded in the middle of some operation that uses it + if (m_refcount > 0) return; + + // call on_unload() on extensions +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + TORRENT_TRY { + (*i)->on_unload(); + } TORRENT_CATCH (std::exception&) {} + } + + // also remove extensions and re-instantiate them when the torrent is loaded again + // they end up using a significant amount of memory + // TODO: there may be peer extensions relying on the torrent extension + // still being alive. Only do this if there are no peers. And when the last peer + // is disconnected, if the torrent is unloaded, clear the extensions +// m_extensions.clear(); +#endif + + // someone else holds a reference to the torrent_info + // make the torrent release its reference to it, + // after making a copy and then unloading that version + // as soon as the user is done with its copy of torrent_info + // it will be freed, and we'll have the unloaded version left + if (!m_torrent_file.unique()) + m_torrent_file = boost::make_shared(*m_torrent_file); + + m_torrent_file->unload(); + inc_stats_counter(counters::num_loaded_torrents, -1); + + m_storage.reset(); + + state_updated(); + } + + bt_peer_connection* torrent::find_introducer(tcp::endpoint const& ep) const + { +#ifndef TORRENT_DISABLE_EXTENSIONS + for (const_peer_iterator i = m_connections.begin(); i != m_connections.end(); ++i) + { + if ((*i)->type() != peer_connection::bittorrent_connection) continue; + bt_peer_connection* p = static_cast(*i); + if (!p->supports_holepunch()) continue; + peer_plugin const* pp = p->find_plugin("ut_pex"); + if (!pp) continue; + if (was_introduced_by(pp, ep)) return p; + } +#else + TORRENT_UNUSED(ep); +#endif + return NULL; + } + + bt_peer_connection* torrent::find_peer(tcp::endpoint const& ep) const + { + for (const_peer_iterator i = m_connections.begin(); i != m_connections.end(); ++i) + { + peer_connection* p = *i; + if (p->type() != peer_connection::bittorrent_connection) continue; + if (p->remote() == ep) return static_cast(p); + } + return NULL; + } + + peer_connection* torrent::find_peer(sha1_hash const& pid) + { + for (peer_iterator i = m_connections.begin(); i != m_connections.end(); ++i) + { + peer_connection* p = *i; + if (p->pid() == pid) return p; + } + return 0; + } + + void torrent::on_resume_data_checked(disk_io_job const* j) + { + // hold a reference until this function returns + torrent_ref_holder h(this, "check_fastresume"); + + // 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 need_save_resume_data = m_need_save_resume_data; + + dec_refcount("check_fastresume"); + TORRENT_ASSERT(is_single_thread()); + + if (j->ret == piece_manager::fatal_disk_error) + { + m_resume_data.reset(); + handle_disk_error(j); + auto_managed(false); + pause(); + set_state(torrent_status::checking_files); + if (should_check_files()) start_checking(); + return; + } + + if (m_abort) return; + + state_updated(); + + if (m_resume_data && m_resume_data->node.type() == bdecode_node::dict_t) + { + using namespace libtorrent::detail; // for read_*_endpoint() + + if (bdecode_node peers_entry = m_resume_data->node.dict_find_string("peers")) + { + int num_peers = peers_entry.string_length() / (sizeof(address_v4::bytes_type) + 2); + char const* ptr = peers_entry.string_ptr(); + for (int i = 0; i < num_peers; ++i) + { + add_peer(read_v4_endpoint(ptr) + , peer_info::resume_data); + } + update_want_peers(); + } + + if (bdecode_node banned_peers_entry + = m_resume_data->node.dict_find_string("banned_peers")) + { + int num_peers = banned_peers_entry.string_length() / (sizeof(address_v4::bytes_type) + 2); + char const* ptr = banned_peers_entry.string_ptr(); + for (int i = 0; i < num_peers; ++i) + { + std::vector peers; + torrent_peer* p = add_peer(read_v4_endpoint(ptr) + , peer_info::resume_data); + peers_erased(peers); + if (p) ban_peer(p); + } + update_want_peers(); + } + +#if TORRENT_USE_IPV6 + if (bdecode_node peers6_entry = m_resume_data->node.dict_find_string("peers6")) + { + int num_peers = peers6_entry.string_length() / (sizeof(address_v6::bytes_type) + 2); + char const* ptr = peers6_entry.string_ptr(); + for (int i = 0; i < num_peers; ++i) + { + add_peer(read_v6_endpoint(ptr) + , peer_info::resume_data); + } + update_want_peers(); + } + + if (bdecode_node banned_peers6_entry = m_resume_data->node.dict_find_string("banned_peers6")) + { + int num_peers = banned_peers6_entry.string_length() / (sizeof(address_v6::bytes_type) + 2); + char const* ptr = banned_peers6_entry.string_ptr(); + for (int i = 0; i < num_peers; ++i) + { + torrent_peer* p = add_peer(read_v6_endpoint(ptr) + , peer_info::resume_data); + if (p) ban_peer(p); + } + update_want_peers(); + } +#endif + + // parse out "peers" from the resume data and add them to the peer list + if (bdecode_node peers_entry = m_resume_data->node.dict_find_list("peers")) + { + for (int i = 0; i < peers_entry.list_size(); ++i) + { + bdecode_node e = peers_entry.list_at(i); + if (e.type() != bdecode_node::dict_t) continue; + std::string ip = e.dict_find_string_value("ip"); + int port = e.dict_find_int_value("port"); + if (ip.empty() || port == 0) continue; + error_code ec; + tcp::endpoint a(address::from_string(ip, ec), boost::uint16_t(port)); + if (ec) continue; + add_peer(a, peer_info::resume_data); + } + update_want_peers(); + } + + // parse out "banned_peers" and add them as banned + if (bdecode_node banned_peers_entry = m_resume_data->node.dict_find_list("banned_peers")) + { + for (int i = 0; i < banned_peers_entry.list_size(); ++i) + { + bdecode_node e = banned_peers_entry.list_at(i); + if (e.type() != bdecode_node::dict_t) continue; + std::string ip = e.dict_find_string_value("ip"); + int port = e.dict_find_int_value("port"); + if (ip.empty() || port == 0) continue; + error_code ec; + tcp::endpoint a(address::from_string(ip, ec) + , boost::uint16_t(port)); + if (ec) continue; + torrent_peer* p = add_peer(a, peer_info::resume_data); + if (p) ban_peer(p); + } + update_want_peers(); + } + } + +#ifndef TORRENT_DISABLE_LOGGING + if (m_peer_list && m_peer_list->num_peers() > 0) + debug_log("resume added peers (%d)", m_peer_list->num_peers()); +#endif + + // only report this error if the user actually provided resume data + if ((j->error || j->ret != 0) && m_resume_data + && m_ses.alerts().should_post()) + { + m_ses.alerts().emplace_alert(get_handle(), j->error.ec + , resolve_filename(j->error.file), j->error.operation_str()); + } + +#ifndef TORRENT_DISABLE_LOGGING + if (j->ret != 0) + { + debug_log("fastresume data rejected: ret: %d (%d) %s" + , j->ret, j->error.ec.value(), j->error.ec.message().c_str()); + } +#ifndef TORRENT_DISABLE_LOGGING + else + debug_log("fastresume data accepted"); +#endif +#endif + + // 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 (j->ret == 0) + { + // there are either no files for this torrent + // or the resume_data was accepted + + if (!j->error && m_resume_data && m_resume_data->node.type() == bdecode_node::dict_t) + { + // parse have bitmask + bdecode_node pieces = m_resume_data->node.dict_find("pieces"); + if (pieces && pieces.type() == bdecode_node::string_t + && int(pieces.string_length()) == m_torrent_file->num_pieces()) + { + char const* pieces_str = pieces.string_ptr(); + for (int i = 0, end(pieces.string_length()); i < end; ++i) + { + if (pieces_str[i] & 1) + { + need_picker(); + m_picker->we_have(i); + inc_stats_counter(counters::num_piece_passed); + update_gauge(); + we_have(i); + } + + if (m_seed_mode && (pieces_str[i] & 2)) m_verified.set_bit(i); + } + } + else + { + bdecode_node slots = m_resume_data->node.dict_find("slots"); + if (slots && slots.type() == bdecode_node::list_t) + { + for (int i = 0; i < slots.list_size(); ++i) + { + int piece = slots.list_int_value_at(i, -1); + if (piece >= 0) + { + need_picker(); + m_picker->we_have(piece); + update_gauge(); + inc_stats_counter(counters::num_piece_passed); + we_have(piece); + } + } + } + } + + // parse unfinished pieces + int num_blocks_per_piece = torrent_file().piece_length() / block_size(); + + if (bdecode_node unfinished_ent + = m_resume_data->node.dict_find_list("unfinished")) + { + for (int i = 0; i < unfinished_ent.list_size(); ++i) + { + bdecode_node e = unfinished_ent.list_at(i); + if (e.type() != bdecode_node::dict_t) continue; + int piece = e.dict_find_int_value("piece", -1); + if (piece < 0 || piece > torrent_file().num_pieces()) 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(true); + + if (has_picker() && m_picker->have_piece(piece)) + { + m_picker->we_dont_have(piece); + update_gauge(); + } + + std::string bitmask = e.dict_find_string_value("bitmask"); + if (bitmask.empty()) continue; + + need_picker(); + + const int num_bitmask_bytes = (std::max)(num_blocks_per_piece / 8, 1); + if (int(bitmask.size()) != num_bitmask_bytes) continue; + for (int k = 0; k < num_bitmask_bytes; ++k) + { + const boost::uint8_t bits = boost::uint8_t(bitmask[k]); + int num_bits = (std::min)(num_blocks_per_piece - k*8, 8); + for (int b = 0; b < num_bits; ++b) + { + const int block = k * 8 + b; + if (bits & (1 << b)) + { + m_picker->mark_as_finished(piece_block(piece, block), 0); + } + } + } + if (m_picker->is_piece_finished(piece)) + { + verify_piece(piece); + } + } + } + } + + files_checked(); + } + else + { + // either the fastresume data was rejected or there are + // some files + set_state(torrent_status::checking_files); + if (should_check_files()) start_checking(); + + // start the checking right away (potentially) + m_ses.trigger_auto_manage(); + } + + maybe_done_flushing(); + m_resume_data.reset(); + + // restore m_need_save_resume_data to its state when we entered this + // function. + m_need_save_resume_data = need_save_resume_data; + } + + 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(); + + if (!need_loaded()) return; + + disconnect_all(errors::stopping_torrent, op_bittorrent); + stop_announcing(); + + // we're checking everything anyway, no point in assuming we are a seed + // now. + leave_seed_mode(true); + + m_ses.disk_thread().async_release_files(m_storage.get() + , boost::function()); + + // forget that we have any pieces + m_have_all = false; + +// removing the piece picker will clear the user priorities +// instead, just clear which pieces we have + if (m_picker) + { + int blocks_per_piece = (m_torrent_file->piece_length() + block_size() - 1) / block_size(); + int blocks_in_last_piece = ((m_torrent_file->total_size() % m_torrent_file->piece_length()) + + block_size() - 1) / block_size(); + m_picker->init(blocks_per_piece, blocks_in_last_piece, m_torrent_file->num_pieces()); + } + + // file progress is allocated lazily, the first time the client + // asks for it + m_file_progress.clear(); + + // assume that we don't have anything + m_files_checked = false; + + update_gauge(); + update_want_tick(); + set_state(torrent_status::checking_resume_data); + + if (m_auto_managed && !is_finished()) + set_queue_position((std::numeric_limits::max)()); + + m_resume_data.reset(); + + std::vector links; + inc_refcount("force_recheck"); + m_ses.disk_thread().async_check_fastresume(m_storage.get(), NULL + , links, boost::bind(&torrent::on_force_recheck + , shared_from_this(), _1)); + } + + void torrent::on_force_recheck(disk_io_job const* j) + { + TORRENT_ASSERT(is_single_thread()); + + // hold a reference until this function returns + torrent_ref_holder h(this, "force_recheck"); + + dec_refcount("force_recheck"); + state_updated(); + + if (m_abort) return; + + if (j->ret == piece_manager::fatal_disk_error) + { + handle_disk_error(j); + return; + } + if (j->ret == 0) + { + // if there are no files, just start + files_checked(); + } + else + { + m_progress_ppm = 0; + m_checking_piece = 0; + m_num_checked_pieces = 0; + + set_state(torrent_status::checking_files); + if (m_auto_managed) pause(true); + if (should_check_files()) start_checking(); + else m_ses.trigger_auto_manage(); + } + } + + void torrent::start_checking() + { + TORRENT_ASSERT(should_check_files()); + + int num_outstanding = settings().get_int(settings_pack::checking_mem_usage) * block_size() + / m_torrent_file->piece_length(); + // if we only keep a single read operation in-flight at a time, we suffer + // significant performance degradation. Always keep at least two jobs + // outstanding + if (num_outstanding < 2) num_outstanding = 2; + + // 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->num_pieces()) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("start_checking, checking_piece >= num_pieces. %d >= %d" + , m_checking_piece, m_torrent_file->num_pieces()); +#endif + return; + } + + // subtract the number of pieces we already have outstanding + num_outstanding -= (m_checking_piece - m_num_checked_pieces); + if (num_outstanding < 0) num_outstanding = 0; + + if (!need_loaded()) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("start_checking, need_loaded() failed"); +#endif + return; + } + + for (int i = 0; i < num_outstanding; ++i) + { + inc_refcount("start_checking"); + m_ses.disk_thread().async_hash(m_storage.get(), m_checking_piece++ + , disk_io_job::sequential_access | disk_io_job::volatile_read + , boost::bind(&torrent::on_piece_hashed + , shared_from_this(), _1), reinterpret_cast(1)); + if (m_checking_piece >= m_torrent_file->num_pieces()) break; + } +#ifndef TORRENT_DISABLE_LOGGING + debug_log("start_checking, m_checking_piece: %d", m_checking_piece); +#endif + } + + // This is only used for checking of torrents. i.e. force-recheck or initial checking + // of existing files + void torrent::on_piece_hashed(disk_io_job const* j) + { + // hold a reference until this function returns + torrent_ref_holder h(this, "start_checking"); + + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + dec_refcount("start_checking"); + + if (m_abort) return; + + if (j->ret == piece_manager::disk_check_aborted) + { + m_checking_piece = 0; + m_num_checked_pieces = 0; +#ifndef TORRENT_DISABLE_LOGGING + debug_log("on_piece_hashed, disk_check_aborted"); +#endif + pause(); + return; + } + + state_updated(); + + ++m_num_checked_pieces; + + if (j->ret < 0) + { + if (j->error.ec == boost::system::errc::no_such_file_or_directory + || j->error.ec == boost::asio::error::eof +#ifdef TORRENT_WINDOWS + || j->error.ec == error_code(ERROR_HANDLE_EOF, system_category()) +#endif + ) + { + TORRENT_ASSERT(j->error.file >= 0); + + // skip this file by updating m_checking_piece to the first piece following it + file_storage const& st = m_torrent_file->files(); + boost::uint64_t file_size = st.file_size(j->error.file); + int last = st.map_file(j->error.file, file_size, 0).piece; + if (m_checking_piece < last) + { + int diff = last - m_checking_piece; + m_num_checked_pieces += diff; + m_checking_piece += diff; + } + } + else + { + m_checking_piece = 0; + m_num_checked_pieces = 0; + if (m_ses.alerts().should_post()) + m_ses.alerts().emplace_alert(j->error.ec, + resolve_filename(j->error.file), j->error.operation_str(), get_handle()); + +#ifndef TORRENT_DISABLE_LOGGING + debug_log("on_piece_hashed, fatal disk error: (%d) %s", j->error.ec.value(), j->error.ec.message().c_str()); +#endif + auto_managed(false); + pause(); + set_error(j->error.ec, j->error.file); + + // recalculate auto-managed torrents sooner + // in order to start checking the next torrent + m_ses.trigger_auto_manage(); + return; + } + } + + m_progress_ppm = boost::int64_t(m_num_checked_pieces) * 1000000 / torrent_file().num_pieces(); + + // we're using the piece hashes here, we need the torrent to be loaded + if (!need_loaded()) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("on_piece_hashed, need_loaded failed"); +#endif + return; + } + + if (settings().get_bool(settings_pack::disable_hash_checks) + || sha1_hash(j->d.piece_hash) == m_torrent_file->hash_for_piece(j->piece)) + { + if (has_picker() || !m_have_all) + { + need_picker(); + m_picker->we_have(j->piece); + update_gauge(); + } + we_have(j->piece); + } + else + { + // if the hash failed, remove it from the cache + if (m_storage) + m_ses.disk_thread().clear_piece(m_storage.get(), j->piece); + } + + if (m_num_checked_pieces < m_torrent_file->num_pieces()) + { + // we're not done yet, issue another job + if (m_checking_piece >= m_torrent_file->num_pieces()) + { + // actually, we already have outstanding jobs for + // the remaining pieces. We just need to wait for them + // to finish + return; + } + + // we paused the checking + if (!should_check_files()) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("on_piece_hashed, checking paused"); +#endif + if (m_checking_piece == m_num_checked_pieces) + { + // we are paused, and we just completed the last outstanding job. + // now we can be considered paused + if (alerts().should_post()) + alerts().emplace_alert(get_handle()); + } + return; + } + + if (!need_loaded()) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("on_piece_hashed, need_loaded failed"); +#endif + return; + } + + inc_refcount("start_checking"); + m_ses.disk_thread().async_hash(m_storage.get(), m_checking_piece++ + , disk_io_job::sequential_access | disk_io_job::volatile_read + , boost::bind(&torrent::on_piece_hashed + , shared_from_this(), _1), reinterpret_cast(1)); +#ifndef TORRENT_DISABLE_LOGGING + debug_log("on_piece_hashed, m_checking_piece: %d", 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_allow_peers(false, 0); + } + + // we're done checking! (this should cause a call to trigger_auto_manage) + files_checked(); + + // reset the checking state + m_checking_piece = 0; + m_num_checked_pieces = 0; + } + +#ifndef TORRENT_NO_DEPRECATE + void torrent::use_interface(std::string net_interfaces) + { + boost::shared_ptr p = boost::make_shared(); + p->set_str(settings_pack::outgoing_interfaces, net_interfaces); + m_ses.apply_settings_pack(p); + } +#endif + + void torrent::on_tracker_announce_disp(boost::weak_ptr p + , error_code const& e) + { +#if defined TORRENT_ASIO_DEBUGGING + complete_async("tracker::on_tracker_announce_disp"); +#endif + boost::shared_ptr t = p.lock(); + if (!t) return; + t->m_waiting_tracker = false; + + if (e) return; + t->on_tracker_announce(); + } + + void torrent::on_tracker_announce() + { + TORRENT_ASSERT(is_single_thread()); + if (m_abort) return; + announce_with_tracker(); + } + + void torrent::lsd_announce() + { + if (m_abort) return; + + // if the files haven't been checked yet, we're + // not ready for peers. Except, if we don't have metadata, + // we need peers to download from + if (!m_files_checked && valid_metadata()) return; + + if (!m_announce_to_lsd) return; + + // private torrents are never announced on LSD + if (m_torrent_file->is_valid() && m_torrent_file->priv()) return; + + // i2p torrents are also never announced on LSD + // unless we allow mixed swarms + if (m_torrent_file->is_valid() + && (torrent_file().is_i2p() && !settings().get_bool(settings_pack::allow_i2p_mixed))) + return; + + if (is_paused()) return; + + if (!m_ses.has_lsd()) return; + + // TODO: this pattern is repeated in a few places. Factor this into + // a function and generalize the concept of a torrent having a + // dedicated listen port +#ifdef TORRENT_USE_OPENSSL + int port = is_ssl_torrent() ? m_ses.ssl_listen_port() : m_ses.listen_port(); +#else + int port = m_ses.listen_port(); +#endif + + // announce with the local discovery service + m_ses.announce_lsd(m_torrent_file->info_hash(), port + , settings().get_bool(settings_pack::broadcast_lsd) && m_lsd_seq == 0); + ++m_lsd_seq; + } + +#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 (!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_allow_peers) + debug_log("DHT: torrent paused, no DHT announce"); + + if (!m_torrent_file->is_valid() && !m_url.empty()) + debug_log("DHT: no info-hash, waiting for \"%s\"", m_url.c_str()); + + 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 verified_trackers = 0; + for (std::vector::const_iterator i = m_trackers.begin() + , end(m_trackers.end()); i != end; ++i) + if (i->verified) ++verified_trackers; + + 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_allow_peers); + +#ifdef TORRENT_USE_OPENSSL + int port = is_ssl_torrent() ? m_ses.ssl_listen_port() : m_ses.listen_port(); +#else + int port = m_ses.listen_port(); +#endif + +#ifndef TORRENT_DISABLE_LOGGING + debug_log("START DHT announce"); + m_dht_start_time = clock_type::now(); +#endif + + // if we're a seed, we tell the DHT for better scrape stats + int flags = is_seed() ? dht::dht_tracker::flag_seed : 0; + // 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 (settings().get_bool(settings_pack::enable_incoming_utp)) + flags |= dht::dht_tracker::flag_implied_port; + + boost::weak_ptr self(shared_from_this()); + m_ses.dht()->announce(m_torrent_file->info_hash() + , port, flags + , boost::bind(&torrent::on_dht_announce_response_disp, self, _1)); + } + + void torrent::on_dht_announce_response_disp(boost::weak_ptr t + , std::vector const& peers) + { + boost::shared_ptr tor = t.lock(); + if (!tor) return; + tor->on_dht_announce_response(peers); + } + + void torrent::on_dht_announce_response(std::vector const& peers) + { + 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(), peers.size()); + } + + if (torrent_file().priv() || (torrent_file().is_i2p() + && !settings().get_bool(settings_pack::allow_i2p_mixed))) return; + + std::for_each(peers.begin(), peers.end(), boost::bind( + &torrent::add_peer, this, _1, peer_info::dht, 0)); + + do_connect_boost(); + + update_want_peers(); + } + +#endif + + void torrent::announce_with_tracker(boost::uint8_t e + , address const& bind_interface) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + if (m_trackers.empty()) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("*** announce: no trackers"); +#endif + return; + } + + if (m_abort) e = tracker_request::stopped; + + // if we're not announcing to trackers, only allow + // stopping + if (e != tracker_request::stopped && !m_announce_to_trackers) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("*** announce: event != stopped && !m_announce_to_trackers"); +#endif + return; + } + + // if we're not allowing peers, there's no point in announcing + if (e != tracker_request::stopped && !m_allow_peers) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("*** announce: event != stopped && !m_allow_peers"); +#endif + return; + } + + TORRENT_ASSERT(m_allow_peers || e == tracker_request::stopped); + + if (e == tracker_request::none && is_finished() && !is_seed()) + e = tracker_request::paused; + + tracker_request req; + if (settings().get_bool(settings_pack::apply_ip_filter_to_trackers) + && m_apply_ip_filter) + req.filter = m_ip_filter; + + req.info_hash = m_torrent_file->info_hash(); + req.pid = m_ses.get_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 = bytes_left(); + if (req.left == -1) req.left = 16*1024; +#ifdef TORRENT_USE_OPENSSL + // if this torrent contains an SSL certificate, make sure + // any SSL tracker presents a certificate signed by it + req.ssl_ctx = m_ssl_ctx.get(); +#endif + + // exclude redundant bytes if we should + if (!settings().get_bool(settings_pack::report_true_downloaded)) + req.downloaded -= m_total_redundant_bytes; + if (req.downloaded < 0) req.downloaded = 0; + + req.event = e; + +#if TORRENT_USE_IPV6 + // since sending our IPv6 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()) + { + tcp::endpoint ep; + ep = m_ses.get_ipv6_interface(); + if (ep != tcp::endpoint()) req.ipv6 = ep.address().to_v6(); + } +#endif + + // if we are aborting. we don't want any new peers + req.num_want = (req.event == tracker_request::stopped) + ? 0 : settings().get_int(settings_pack::num_want); + + time_point now = clock_type::now(); + + // 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; + + for (int i = 0; i < int(m_trackers.size()); ++i) + { + announce_entry& ae = m_trackers[i]; +#ifndef TORRENT_DISABLE_LOGGING + debug_log("*** tracker: \"%s\" " + "[ tiers: %d trackers: %d" + " i->tier: %d tier: %d" + " working: %d fails: %d limit: %d upd: %d" + " can: %d sent: %d ]" + , ae.url.c_str(), settings().get_bool(settings_pack::announce_to_all_tiers) + , settings().get_bool(settings_pack::announce_to_all_trackers) + , ae.tier, tier, ae.is_working(), ae.fails, ae.fail_limit + , ae.updating, ae.can_announce(now, is_seed()), sent_announce); +#endif + if (settings().get_bool(settings_pack::announce_to_all_tiers) + && !settings().get_bool(settings_pack::announce_to_all_trackers) + && sent_announce + && ae.tier <= tier + && tier != INT_MAX) + continue; + + // if trackerid is not specified for tracker use default one, probably set explicitly + req.trackerid = ae.trackerid.empty() ? m_trackerid : ae.trackerid; + + if (ae.tier > tier && sent_announce + && !settings().get_bool(settings_pack::announce_to_all_tiers)) break; + if (ae.is_working()) { tier = ae.tier; sent_announce = false; } + if (!ae.can_announce(now, is_seed())) + { + // this counts + if (ae.is_working()) sent_announce = true; + continue; + } + + req.url = ae.url; + req.event = e; + if (req.event == tracker_request::none) + { + if (!ae.start_sent) req.event = tracker_request::started; + else if (!ae.complete_sent && is_seed()) req.event = tracker_request::completed; + } + + req.triggered_manually = ae.triggered_manually; + ae.triggered_manually = false; + + req.bind_ip = bind_interface; + + if (settings().get_bool(settings_pack::force_proxy)) + { + // in force_proxy mode we don't talk directly to trackers + // we only allow trackers if there is a proxy and issue + // a warning if there isn't one + std::string protocol = req.url.substr(0, req.url.find(':')); + int proxy_type = settings().get_int(settings_pack::proxy_type); + + // http can run over any proxy, so as long as one is used + // it's OK. If no proxy is configured, skip this tracker + if ((protocol == "http" || protocol == "https") + && proxy_type == settings_pack::none) + { + ae.next_announce = now + minutes(10); + if (m_ses.alerts().should_post() + || req.triggered_manually) + { + m_ses.alerts().emplace_alert(get_handle() + , anonymous_mode_alert::tracker_not_anonymous, req.url); + } + continue; + } + + // for UDP, only socks5 and i2p proxies will work. + // if we're not using one of those proxues with a UDP + // tracker, skip it + if (protocol == "udp" + && proxy_type != settings_pack::socks5 + && proxy_type != settings_pack::socks5_pw + && proxy_type != settings_pack::i2p_proxy) + { + ae.next_announce = now + minutes(10); + if (m_ses.alerts().should_post() + || req.triggered_manually) + { + m_ses.alerts().emplace_alert(get_handle() + , anonymous_mode_alert::tracker_not_anonymous, req.url); + } + continue; + } + } + +#ifndef TORRENT_NO_DEPRECATE + req.auth = tracker_login(); +#endif + req.key = tracker_key(); + +#ifdef TORRENT_USE_OPENSSL + if (is_i2p()) + { + req.kind |= tracker_request::i2p; + } +#endif + +#ifndef TORRENT_DISABLE_LOGGING + debug_log("==> TRACKER REQUEST \"%s\" event: %s abort: %d" + , req.url.c_str() + , (req.event==tracker_request::stopped?"stopped" + :req.event==tracker_request::started?"started":"") + , m_abort); + + // if we're not logging session logs, don't bother creating an + // observer object just for logging + if (m_abort && alerts().should_post()) + { + boost::shared_ptr tl(new aux::tracker_logger(m_ses)); + m_ses.queue_tracker_request(req, tl); + } + else +#endif + { + m_ses.queue_tracker_request(req, shared_from_this()); + } + + ae.updating = true; + ae.next_announce = now + seconds(20); + ae.min_announce = now + seconds(10); + + if (m_ses.alerts().should_post()) + { + m_ses.alerts().emplace_alert( + get_handle(), req.url, req.event); + } + + sent_announce = true; + if (ae.is_working() + && !settings().get_bool(settings_pack::announce_to_all_trackers) + && !settings().get_bool(settings_pack::announce_to_all_tiers)) + break; + } + update_tracker_timer(now); + } + + void torrent::scrape_tracker(int idx, bool user_triggered) + { + TORRENT_ASSERT(is_single_thread()); + m_last_scrape = m_ses.session_time(); + + if (m_trackers.empty()) return; + + if (idx < 0 || idx >= int(m_trackers.size())) idx = m_last_working_tracker; + if (idx < 0) idx = 0; + + tracker_request req; + if (settings().get_bool(settings_pack::apply_ip_filter_to_trackers) + && m_apply_ip_filter) + req.filter = m_ip_filter; + + req.info_hash = m_torrent_file->info_hash(); + req.kind |= tracker_request::scrape_request; + req.url = m_trackers[idx].url; +#ifndef TORRENT_NO_DEPRECATE + req.auth = tracker_login(); +#endif + req.key = tracker_key(); + req.triggered_manually = user_triggered; + 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; + + announce_entry* ae = find_tracker(req); + if (ae) + { + ae->message = msg; + } + + if (m_ses.alerts().should_post()) + m_ses.alerts().emplace_alert(get_handle(), req.url, msg); + } + + void torrent::tracker_scrape_response(tracker_request const& req + , int complete, int incomplete, int downloaded, int /* downloaders */) + { + TORRENT_ASSERT(is_single_thread()); + + INVARIANT_CHECK; + TORRENT_ASSERT(0 != (req.kind & tracker_request::scrape_request)); + + announce_entry* ae = find_tracker(req); + if (ae) + { + if (incomplete >= 0) ae->scrape_incomplete = incomplete; + if (complete >= 0) ae->scrape_complete = complete; + if (downloaded >= 0) ae->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(), 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 (std::vector::iterator i = m_trackers.begin() + , end(m_trackers.end()); i != end; ++i) + { + complete = (std::max)(i->scrape_complete, complete); + incomplete = (std::max)(i->scrape_incomplete, incomplete); + downloaded = (std::max)(i->scrape_downloaded, downloaded); + } + + if ((complete >= 0 && m_complete != complete) + || (incomplete >= 0 && m_incomplete != incomplete) + || (downloaded >= 0 && m_downloaded != downloaded)) + state_updated(); + + if (m_complete != complete + || m_incomplete != incomplete + || m_downloaded != downloaded) + { + m_complete = complete; + m_incomplete = incomplete; + m_downloaded = downloaded; + + update_auto_sequential(); + + // these numbers are cached in the resume data + set_need_save_resume(); + } + } + + void torrent::tracker_response( + tracker_request const& r + , address const& tracker_ip // this is the IP we connected to + , std::list
    const& tracker_ips // these are all the IPs it resolved to + , struct tracker_response const& resp) + { + TORRENT_ASSERT(is_single_thread()); + + INVARIANT_CHECK; + TORRENT_ASSERT(0 == (r.kind & tracker_request::scrape_request)); + + // if the tracker told us what our external IP address is, record it with + // out external IP counter (and pass along the IP of the tracker to know + // who to attribute this vote to) + if (resp.external_ip != address() && !is_any(tracker_ip)) + m_ses.set_external_address(resp.external_ip + , aux::session_interface::source_tracker, tracker_ip); + + time_point now = aux::time_now(); + + int interval = resp.interval; + if (interval < settings().get_int(settings_pack::min_announce_interval)) + interval = settings().get_int(settings_pack::min_announce_interval); + + announce_entry* ae = find_tracker(r); + if (ae) + { + if (resp.incomplete >= 0) ae->scrape_incomplete = resp.incomplete; + if (resp.complete >= 0) ae->scrape_complete = resp.complete; + if (resp.downloaded >= 0) ae->scrape_downloaded = resp.downloaded; + if (!ae->start_sent && r.event == tracker_request::started) + ae->start_sent = true; + if (!ae->complete_sent && r.event == tracker_request::completed) + ae->complete_sent = true; + ae->verified = true; + ae->updating = false; + ae->fails = 0; + ae->next_announce = now + seconds(interval); + ae->min_announce = now + seconds(resp.min_interval); + int tracker_index = ae - &m_trackers[0]; + m_last_working_tracker = prioritize_tracker(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() + , r.url, resp.trackerid); + } + + update_scrape_state(); + } + update_tracker_timer(now); + + if (resp.complete >= 0 && resp.incomplete >= 0) + m_last_scrape = m_ses.session_time(); + +#ifndef TORRENT_DISABLE_LOGGING + std::string resolved_to; + for (std::list
    ::const_iterator i = tracker_ips.begin() + , end(tracker_ips.end()); i != end; ++i) + { + resolved_to += i->to_string(); + resolved_to += ", "; + } + debug_log("TRACKER RESPONSE\n" + "interval: %d\n" + "external ip: %s\n" + "resolved to: %s\n" + "we connected to: %s\n" + "peers:" + , interval + , print_address(resp.external_ip).c_str() + , resolved_to.c_str() + , print_address(tracker_ip).c_str()); + + for (std::vector::const_iterator i = resp.peers.begin(); + i != resp.peers.end(); ++i) + { + debug_log(" %16s %5d %s %s", i->hostname.c_str(), i->port + , i->pid.is_all_zeros()?"":to_hex(i->pid.to_string()).c_str() + , identify_client(i->pid).c_str()); + } + for (std::vector::const_iterator i = resp.peers4.begin(); + i != resp.peers4.end(); ++i) + { + debug_log(" %s:%d", print_address(address_v4(i->ip)).c_str(), i->port); + } +#if TORRENT_USE_IPV6 + for (std::vector::const_iterator i = resp.peers6.begin(); + i != resp.peers6.end(); ++i) + { + debug_log(" [%s]:%d", print_address(address_v6(i->ip)).c_str(), i->port); + } +#endif +#endif + + // for each of the peers we got from the tracker + for (std::vector::const_iterator i = resp.peers.begin(); + i != resp.peers.end(); ++i) + { + // don't make connections to ourself + if (i->pid == m_ses.get_peer_id()) + continue; + +#if TORRENT_USE_I2P + if (r.i2pconn && boost::algorithm::ends_with(i->hostname, ".i2p")) + { + // this is an i2p name, we need to use the sam connection + // to do the name lookup + if (boost::algorithm::ends_with(i->hostname, ".b32.i2p")) + { +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("torrent::on_i2p_resolve"); +#endif + r.i2pconn->async_name_lookup(i->hostname.c_str() + , boost::bind(&torrent::on_i2p_resolve + , shared_from_this(), _1, _2)); + } + else + { + torrent_state st = get_peer_list_state(); + need_peer_list(); + if (m_peer_list->add_i2p_peer(i->hostname.c_str (), peer_info::tracker, 0, &st)) + state_updated(); + peers_erased(st.erased); + } + } + else +#endif + { +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("torrent::on_peer_name_lookup"); +#endif + m_ses.async_resolve(i->hostname, resolver_interface::abort_on_shutdown + , boost::bind(&torrent::on_peer_name_lookup + , shared_from_this(), _1, _2, i->port)); + } + } + + // there are 2 reasons to allow local IPs to be returned from a + // non-local tracker + // 1. retrackers are popular in russia, where an ISP runs a tracker within + // the AS (but not on the local network) giving out peers only from the + // local network + // 2. it might make sense to have a tracker extension in the future where + // trackers records a peer's internal and external IP, and match up + // peers on the same local network + + bool need_update = false; + for (std::vector::const_iterator i = resp.peers4.begin(); + i != resp.peers4.end(); ++i) + { + tcp::endpoint a(address_v4(i->ip), i->port); + need_update |= bool(add_peer(a, peer_info::tracker) != NULL); + } + +#if TORRENT_USE_IPV6 + for (std::vector::const_iterator i = resp.peers6.begin(); + i != resp.peers6.end(); ++i) + { + tcp::endpoint a(address_v6(i->ip), i->port); + need_update |= bool(add_peer(a, peer_info::tracker) != NULL); + } +#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(), resp.peers.size() + resp.peers4.size() +#if TORRENT_USE_IPV6 + + resp.peers6.size() +#endif + , r.url); + } + + // we're listening on an interface type that was not used + // when talking to the tracker. If there is a matching interface + // type in the tracker IP list, make another tracker request + // using that interface + // in order to avoid triggering this case over and over, don't + // do it if the bind IP for the tracker request that just completed + // matches one of the listen interfaces, since that means this + // announce was the second one + + if (((!is_any(m_ses.get_ipv6_interface().address()) && tracker_ip.is_v4()) + || (!is_any(m_ses.get_ipv4_interface().address()) && tracker_ip.is_v6())) + && r.bind_ip != m_ses.get_ipv4_interface().address() + && r.bind_ip != m_ses.get_ipv6_interface().address()) + { + std::list
    ::const_iterator i = std::find_if(tracker_ips.begin() + , tracker_ips.end(), boost::bind(&address::is_v4, _1) != tracker_ip.is_v4()); + if (i != tracker_ips.end()) + { + // the tracker did resolve to a different type of address, so announce + // to that as well + + // TODO 2: there's a bug when removing a torrent or shutting down the session, + // where the second announce is skipped (in this case, the one to the IPv6 + // name). This should be fixed by generalizing the tracker list structure to + // separate the IPv6 and IPv4 addresses as conceptually separate trackers, + // and they should be announced to in parallel + + tracker_request req = r; + // tell the tracker to bind to the opposite protocol type + req.bind_ip = tracker_ip.is_v4() + ? m_ses.get_ipv6_interface().address() + : m_ses.get_ipv4_interface().address(); +#ifndef TORRENT_DISABLE_LOGGING + debug_log("announce again using %s as the bind interface" + , print_address(req.bind_ip).c_str()); +#endif + m_ses.queue_tracker_request(req, shared_from_this()); + } + } + + do_connect_boost(); + + state_updated(); + } + + void torrent::update_auto_sequential() + { + if (!settings().get_bool(settings_pack::auto_sequential)) + { + m_auto_sequential = false; + return; + } + + if (int(m_connections.size()) - 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 downloaders = num_downloaders(); + int seeds = num_seeds(); + m_auto_sequential = downloaders * 10 <= seeds + && seeds > 9; + } + + void torrent::do_connect_boost() + { + if (!m_need_connect_boost) 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)( + settings().get_int(settings_pack::torrent_connect_boost) + , settings().get_int(settings_pack::connections_limit) - m_ses.num_connections()); + + if (conns > 0) m_need_connect_boost = false; + + // if we don't know of any peers + if (!m_peer_list) return; + + while (want_peers() && conns > 0) + { + --conns; + 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 == NULL) + { + update_want_peers(); + continue; + } + +#ifndef TORRENT_DISABLE_LOGGING + 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()); + } + + time_point torrent::next_announce() const + { + return m_waiting_tracker?m_tracker_timer.expires_at():min_time(); + } + + // 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 t, int tracker_idx) + { + if (is_paused()) return; + if (tracker_idx == -1) + { + for (std::vector::iterator i = m_trackers.begin() + , end(m_trackers.end()); i != end; ++i) + { + i->next_announce = (std::max)(t, i->min_announce) + seconds(1); + i->triggered_manually = true; + } + } + else + { + TORRENT_ASSERT(tracker_idx >= 0 && tracker_idx < int(m_trackers.size())); + if (tracker_idx < 0 || tracker_idx >= int(m_trackers.size())) + return; + announce_entry& e = m_trackers[tracker_idx]; + e.next_announce = (std::max)(t, e.min_announce) + seconds(1); + e.triggered_manually = true; + } + update_tracker_timer(clock_type::now()); + } + +#ifndef TORRENT_NO_DEPRECATE + 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) + { + TORRENT_ASSERT(is_single_thread()); + + INVARIANT_CHECK; + +#if defined TORRENT_ASIO_DEBUGGING + complete_async("torrent::on_i2p_resolve"); +#endif +#ifndef TORRENT_DISABLE_LOGGING + if (ec) + 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, 0, &st)) + state_updated(); + peers_erased(st.erased); + } +#endif + + void torrent::on_peer_name_lookup(error_code const& e + , std::vector
    const& host_list, int port) + { + TORRENT_ASSERT(is_single_thread()); + + INVARIANT_CHECK; + +#if defined TORRENT_ASIO_DEBUGGING + complete_async("torrent::on_peer_name_lookup"); +#endif + +#ifndef TORRENT_DISABLE_LOGGING + if (e) + 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(), port); + + if (m_ip_filter && m_ip_filter->access(host.address()) & ip_filter::blocked) + { +#ifndef TORRENT_DISABLE_LOGGING + error_code ec; + debug_log("blocked ip from tracker: %s", host.address().to_string(ec).c_str()); +#endif + if (m_ses.alerts().should_post()) + m_ses.alerts().emplace_alert(get_handle() + , host.address(), peer_blocked_alert::ip_filter); + return; + } + + if (add_peer(host, peer_info::tracker)) + state_updated(); + update_want_peers(); + } + + boost::int64_t torrent::bytes_left() const + { + // if we don't have the metadata yet, we + // cannot tell how big the torrent is. + if (!valid_metadata()) return -1; + return m_torrent_file->total_size() + - quantized_bytes_done(); + } + + boost::int64_t torrent::quantized_bytes_done() const + { +// INVARIANT_CHECK; + + if (!valid_metadata()) return 0; + + if (m_torrent_file->num_pieces() == 0) + return 0; + + // if any piece hash fails, we'll be taken out of seed mode + // and m_seed_mode will be false + if (m_seed_mode) return m_torrent_file->total_size(); + + if (!has_picker()) return m_have_all ? m_torrent_file->total_size() : 0; + + const int last_piece = m_torrent_file->num_pieces() - 1; + + boost::int64_t total_done + = boost::uint64_t(m_picker->num_passed()) * m_torrent_file->piece_length(); + + // if we have the last piece, we have to correct + // the amount we have, since the first calculation + // assumed all pieces were of equal size + if (m_picker->has_piece_passed(last_piece)) + { + int corr = m_torrent_file->piece_size(last_piece) + - m_torrent_file->piece_length(); + total_done += corr; + } + return total_done; + } + + // returns the number of bytes we are interested + // in for the given block. This returns block_size() + // for all blocks except the last one (if it's smaller + // than block_size()) and blocks that overlap a padding + // file + int torrent::block_bytes_wanted(piece_block const& p) const + { + file_storage const& fs = m_torrent_file->files(); + int piece_size = m_torrent_file->piece_size(p.piece_index); + int offset = p.block_index * block_size(); + if (m_padding == 0) return (std::min)(piece_size - offset, block_size()); + + std::vector files = fs.map_block( + p.piece_index, offset, (std::min)(piece_size - offset, block_size())); + int ret = 0; + for (std::vector::iterator i = files.begin() + , end(files.end()); i != end; ++i) + { + if (fs.pad_file_at(i->file_index)) continue; + ret += i->size; + } + TORRENT_ASSERT(ret <= (std::min)(piece_size - offset, block_size())); + return ret; + } + + // fills in total_wanted, total_wanted_done and total_done + void torrent::bytes_done(torrent_status& st, bool accurate) const + { + INVARIANT_CHECK; + + st.total_done = 0; + st.total_wanted_done = 0; + st.total_wanted = m_torrent_file->total_size(); + + TORRENT_ASSERT(st.total_wanted >= m_padding); + TORRENT_ASSERT(st.total_wanted >= 0); + + if (!valid_metadata() || m_torrent_file->num_pieces() == 0) + return; + + TORRENT_ASSERT(st.total_wanted >= boost::int64_t(m_torrent_file->piece_length()) + * (m_torrent_file->num_pieces() - 1)); + + const int last_piece = m_torrent_file->num_pieces() - 1; + const int piece_size = m_torrent_file->piece_length(); + + // if any piece hash fails, we'll be taken out of seed mode + // and m_seed_mode will be false + if (m_seed_mode || is_seed()) + { + st.total_done = m_torrent_file->total_size() - m_padding; + st.total_wanted_done = st.total_done; + st.total_wanted = st.total_done; + return; + } + else if (!has_picker()) + { + st.total_done = 0; + st.total_wanted_done = 0; + st.total_wanted = m_torrent_file->total_size() - m_padding; + return; + } + + TORRENT_ASSERT(num_have() >= m_picker->num_have_filtered()); + st.total_wanted_done = boost::int64_t(num_passed() - m_picker->num_have_filtered()) + * piece_size; + TORRENT_ASSERT(st.total_wanted_done >= 0); + + st.total_done = boost::int64_t(num_passed()) * piece_size; + // if num_passed() == num_pieces(), we should be a seed, and taken the + // branch above + TORRENT_ASSERT(num_passed() <= m_torrent_file->num_pieces()); + + int num_filtered_pieces = m_picker->num_filtered() + + m_picker->num_have_filtered(); + int last_piece_index = m_torrent_file->num_pieces() - 1; + if (m_picker->piece_priority(last_piece_index) == 0) + { + st.total_wanted -= m_torrent_file->piece_size(last_piece_index); + TORRENT_ASSERT(st.total_wanted >= 0); + --num_filtered_pieces; + } + st.total_wanted -= boost::int64_t(num_filtered_pieces) * piece_size; + TORRENT_ASSERT(st.total_wanted >= 0); + + // if we have the last piece, we have to correct + // the amount we have, since the first calculation + // assumed all pieces were of equal size + if (m_picker->has_piece_passed(last_piece)) + { + TORRENT_ASSERT(st.total_done >= piece_size); + int corr = m_torrent_file->piece_size(last_piece) + - piece_size; + TORRENT_ASSERT(corr <= 0); + TORRENT_ASSERT(corr > -piece_size); + st.total_done += corr; + if (m_picker->piece_priority(last_piece) != 0) + { + TORRENT_ASSERT(st.total_wanted_done >= piece_size); + st.total_wanted_done += corr; + } + } + TORRENT_ASSERT(st.total_wanted >= st.total_wanted_done); + + // this is expensive, we might not want to do it all the time + if (!accurate) return; + + // subtract padding files + if (m_padding > 0) + { + // this is a bit unfortunate + // (both the const cast and the requirement to load the torrent) + if (!const_cast(this)->need_loaded()) return; + + file_storage const& files = m_torrent_file->files(); + for (int i = 0; i < files.num_files(); ++i) + { + if (!files.pad_file_at(i)) continue; + peer_request p = files.map_file(i, 0, files.file_size(i)); + for (int j = p.piece; p.length > 0; ++j) + { + int deduction = (std::min)(p.length, piece_size - p.start); + bool done = m_picker->has_piece_passed(j); + bool wanted = m_picker->piece_priority(j) > 0; + if (done) st.total_done -= deduction; + if (wanted) st.total_wanted -= deduction; + if (wanted && done) st.total_wanted_done -= deduction; + TORRENT_ASSERT(st.total_done >= 0); + TORRENT_ASSERT(st.total_wanted >= 0); + TORRENT_ASSERT(st.total_wanted_done >= 0); + p.length -= piece_size - p.start; + p.start = 0; + ++p.piece; + } + } + } + + TORRENT_ASSERT(!accurate || st.total_done <= m_torrent_file->total_size() - m_padding); + TORRENT_ASSERT(st.total_wanted_done >= 0); + TORRENT_ASSERT(st.total_done >= st.total_wanted_done); + + std::vector dl_queue + = m_picker->get_download_queue(); + + const int blocks_per_piece = (piece_size + block_size() - 1) / block_size(); + + // look at all unfinished pieces and add the completed + // blocks to our 'done' counter + for (std::vector::const_iterator i = + dl_queue.begin(); i != dl_queue.end(); ++i) + { + int corr = 0; + int index = i->index; + // completed pieces are already accounted for + if (m_picker->has_piece_passed(index)) continue; + TORRENT_ASSERT(i->finished <= m_picker->blocks_in_piece(index)); + +#if TORRENT_USE_ASSERTS + for (std::vector::const_iterator j = boost::next(i); + j != dl_queue.end(); ++j) + { + TORRENT_ASSERT(j->index != index); + } +#endif + + piece_picker::block_info* info = m_picker->blocks_for_piece(*i); + for (int j = 0; j < blocks_per_piece; ++j) + { +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + TORRENT_ASSERT(m_picker->is_finished(piece_block(index, j)) + == (info[j].state == piece_picker::block_info::state_finished)); +#endif + if (info[j].state == piece_picker::block_info::state_finished) + { + corr += block_bytes_wanted(piece_block(index, j)); + } + TORRENT_ASSERT(corr >= 0); + TORRENT_ASSERT(index != last_piece || j < m_picker->blocks_in_last_piece() + || info[j].state != piece_picker::block_info::state_finished); + } + + st.total_done += corr; + if (m_picker->piece_priority(index) > 0) + st.total_wanted_done += corr; + } + + TORRENT_ASSERT(st.total_wanted <= m_torrent_file->total_size() - m_padding); + TORRENT_ASSERT(st.total_done <= m_torrent_file->total_size() - m_padding); + TORRENT_ASSERT(st.total_wanted_done <= m_torrent_file->total_size() - m_padding); + TORRENT_ASSERT(st.total_wanted_done >= 0); + TORRENT_ASSERT(st.total_done >= st.total_wanted_done); + + std::map downloading_piece; + for (const_peer_iterator i = begin(); i != end(); ++i) + { + peer_connection* pc = *i; + boost::optional p + = pc->downloading_piece_progress(); + if (!p) continue; + + if (m_picker->has_piece_passed(p->piece_index)) + continue; + + piece_block block(p->piece_index, p->block_index); + if (m_picker->is_finished(block)) + continue; + + std::map::iterator dp + = downloading_piece.find(block); + if (dp != downloading_piece.end()) + { + if (dp->second < p->bytes_downloaded) + dp->second = p->bytes_downloaded; + } + else + { + downloading_piece[block] = p->bytes_downloaded; + } +#ifdef TORRENT_DEBUG + TORRENT_ASSERT(p->bytes_downloaded <= p->full_block_bytes); + TORRENT_ASSERT(p->full_block_bytes == to_req(piece_block( + p->piece_index, p->block_index)).length); +#endif + } + for (std::map::iterator i = downloading_piece.begin(); + i != downloading_piece.end(); ++i) + { + int done = (std::min)(block_bytes_wanted(i->first), i->second); + st.total_done += done; + if (m_picker->piece_priority(i->first.piece_index) != 0) + st.total_wanted_done += done; + } + + TORRENT_ASSERT(st.total_done <= m_torrent_file->total_size() - m_padding); + TORRENT_ASSERT(st.total_wanted_done <= m_torrent_file->total_size() - m_padding); + +#ifdef TORRENT_DEBUG + + if (st.total_done >= m_torrent_file->total_size()) + { + // Thist happens when a piece has been downloaded completely + // but not yet verified against the hash + fprintf(stderr, "num_have: %d\nunfinished:\n", num_have()); + for (std::vector::const_iterator i = + dl_queue.begin(); i != dl_queue.end(); ++i) + { + fprintf(stderr, " %d ", i->index); + piece_picker::block_info* info = m_picker->blocks_for_piece(*i); + for (int j = 0; j < blocks_per_piece; ++j) + { + char const* state = info[j].state + == piece_picker::block_info::state_finished ? "1" : "0"; + fputs(state, stderr); + } + fputs("\n", stderr); + } + + fputs("downloading pieces:\n", stderr); + + for (std::map::iterator i = downloading_piece.begin(); + i != downloading_piece.end(); ++i) + { + fprintf(stderr, " %d:%d %d\n", int(i->first.piece_index), int(i->first.block_index), i->second); + } + + } + + TORRENT_ASSERT(st.total_done <= m_torrent_file->total_size()); + TORRENT_ASSERT(st.total_wanted_done <= m_torrent_file->total_size()); + +#endif + + TORRENT_ASSERT(st.total_done >= st.total_wanted_done); + } + + void torrent::on_piece_verified(disk_io_job const* j) + { + TORRENT_ASSERT(is_single_thread()); + + torrent_ref_holder h(this, "verify_piece"); + + dec_refcount("verify_piece"); + + if (m_abort) return; + + int ret = j->ret; + if (settings().get_bool(settings_pack::disable_hash_checks)) + { + ret = 0; + } + else if (ret == -1) + { + handle_disk_error(j); + } + // we're using the piece hashes here, we need the torrent to be loaded + else if (need_loaded()) + { + if (sha1_hash(j->d.piece_hash) != m_torrent_file->hash_for_piece(j->piece)) + ret = -2; + } + else + { + // failing to load the .torrent file counts as disk failure + ret = -1; + } + + // 0: success, piece passed check + // -1: disk failure + // -2: piece failed check + +#ifndef TORRENT_DISABLE_LOGGING + debug_log("*** PIECE_FINISHED [ p: %d | chk: %s | size: %d ]" + , j->piece, ((ret == 0) + ?"passed":ret == -1 + ?"disk failed":"failed") + , m_torrent_file->piece_size(j->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(j->piece)); + +// picker().mark_as_done_checking(j->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(j->piece)) return; + + if (ret == 0) + { + // the following call may cause picker to become invalid + // in case we just became a seed + piece_passed(j->piece); + // if we're in seed mode, we just acquired this piece + // mark it as verified + if (m_seed_mode) verified(j->piece); + } + else if (ret == -2) + { + // piece_failed() will restore the piece + piece_failed(j->piece); + } + else + { + TORRENT_ASSERT(ret == -1); + update_gauge(); + } + + } + + // this is called once we have completely downloaded piece + // 'index', its hash has been verified. It's also called + // during initial file check when we find a piece whose hash + // is correct + void torrent::we_have(int 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; + std::vector::iterator 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); + } + + // make a copy of the peer list since peers + // may disconnect while looping + std::vector peers = m_connections; + + for (peer_iterator i = peers.begin(); i != peers.end(); ++i) + { + boost::shared_ptr p = (*i)->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 (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + TORRENT_TRY { + (*i)->on_piece_pass(index); + } TORRENT_CATCH (std::exception&) {} + } +#endif + + // since this piece just passed, we might have + // become uninterested in some peers where this + // was the last piece we were interested in + for (peer_iterator i = m_connections.begin(); + i != m_connections.end();) + { + peer_connection* p = *i; + // update_interest may disconnect the peer and + // invalidate the iterator + ++i; + // 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(); + } + + 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); + } + + set_need_save_resume(); + state_updated(); + + if (m_ses.alerts().should_post()) + m_ses.alerts().emplace_alert(get_handle(), index); + + // update m_file_progress (if we have one) + m_file_progress.update(m_torrent_file->files(), index + , &m_ses.alerts(), get_handle()); + + remove_time_critical_piece(index, true); + + if (is_finished() + && m_state != torrent_status::finished + && m_state != torrent_status::seeding) + { + // 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 = m_ses.session_time(); + + if (m_share_mode) + recalc_share_mode(); + } + + // 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(int index) + { +// INVARIANT_CHECK; + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(!m_picker->has_piece_passed(index)); + +#ifndef TORRENT_DISABLE_LOGGING + debug_log("PIECE_PASSED (%d)", num_passed()); +#endif + +// fprintf(stderr, "torrent::piece_passed piece:%d\n", index); + + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < m_torrent_file->num_pieces()); + + set_need_save_resume(); + + inc_stats_counter(counters::num_piece_passed); + + remove_time_critical_piece(index, true); + + std::vector downloaders; + m_picker->get_downloaders(downloaders, index); + + // increase the trust point of all peers that sent + // parts of this piece. + std::set peers; + + // these torrent_peer pointers are owned by m_peer_list and they may be + // invalidated if a peer disconnects. We cannot keep them across any + // significant operations, but we should use them right away + // ignore NULL pointers + std::remove_copy(downloaders.begin(), downloaders.end() + , std::inserter(peers, peers.begin()), static_cast(0)); + + for (std::set::iterator i = peers.begin() + , end(peers.end()); i != end; ++i) + { + torrent_peer* p = static_cast(*i); + TORRENT_ASSERT(p != 0); + if (p == 0) 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) + { + peer_connection* peer = static_cast(p->connection); + TORRENT_ASSERT(peer->m_in_use == 1337); + peer->received_valid_data(index); + } + } + // announcing a piece may invalidate the torrent_peer pointers + // so we can't use them anymore + + downloaders.clear(); + peers.clear(); + + // make the disk cache flush the piece to disk + if (m_storage) + m_ses.disk_thread().async_flush_piece(m_storage.get(), index); + m_picker->piece_passed(index); + update_gauge(); + we_have(index); + } + + // 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 + void torrent::predicted_have_piece(int index, int milliseconds) + { + std::vector::iterator i = std::lower_bound(m_predictive_pieces.begin() + , m_predictive_pieces.end(), index); + if (i != m_predictive_pieces.end() && *i == index) return; + + for (peer_iterator p = m_connections.begin() + , end(m_connections.end()); p != end; ++p) + { +#ifndef TORRENT_DISABLE_LOGGING + (*p)->peer_log(peer_log_alert::outgoing, "PREDICTIVE_HAVE", "piece: %d expected in %d ms" + , index, milliseconds); +#else + TORRENT_UNUSED(milliseconds); +#endif + (*p)->announce_piece(index); + } + + m_predictive_pieces.insert(i, index); + } + + void torrent::piece_failed(int index) + { + // if the last piece fails the peer connection will still + // think that it has received all of it until this function + // resets the download queue. So, we cannot do the + // invariant check here since it assumes: + // (total_done == m_torrent_file->total_size()) => is_seed() + INVARIANT_CHECK; + TORRENT_ASSERT(is_single_thread()); + + TORRENT_ASSERT(m_picker.get()); + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < m_torrent_file->num_pieces()); + + inc_stats_counter(counters::num_piece_failed); + + if (m_ses.alerts().should_post()) + m_ses.alerts().emplace_alert(get_handle(), index); + + std::vector::iterator it = std::lower_bound(m_predictive_pieces.begin() + , m_predictive_pieces.end(), index); + if (it != m_predictive_pieces.end() && *it == index) + { + for (peer_iterator p = m_connections.begin() + , end(m_connections.end()); p != end; ++p) + { + // 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); + } + // increase the total amount of failed bytes + add_failed_bytes(m_torrent_file->piece_size(index)); + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + TORRENT_TRY { + (*i)->on_piece_failed(index); + } TORRENT_CATCH (std::exception&) {} + } +#endif + + std::vector downloaders; + if (m_picker) + m_picker->get_downloaders(downloaders, index); + + // decrease the trust point of all peers that sent + // parts of this piece. + // first, build a set of all peers that participated + std::set peers; + std::copy(downloaders.begin(), downloaders.end(), std::inserter(peers, peers.begin())); + +#ifdef TORRENT_DEBUG + for (std::vector::iterator i = downloaders.begin() + , end(downloaders.end()); i != end; ++i) + { + torrent_peer* p = static_cast(*i); + if (p && p->connection) + { + peer_connection* peer = static_cast(p->connection); + peer->piece_failed = true; + } + } +#endif + + // did we receive this piece from a single peer? + bool single_peer = peers.size() == 1; + + for (std::set::iterator i = peers.begin() + , end(peers.end()); i != end; ++i) + { + torrent_peer* p = static_cast(*i); + if (p == 0) continue; + TORRENT_ASSERT(p->in_use); + bool allow_disconnect = true; + if (p->connection) + { + peer_connection* 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 haved. + allow_disconnect = peer->received_invalid_data(index, single_peer); + } + + if (settings().get_bool(settings_pack::use_parole_mode)) + p->on_parole = true; + + int hashfails = p->hashfails; + int trust_points = p->trust_points; + + // we decrease more than we increase, to keep the + // allowed failed/passed ratio low. + trust_points -= 2; + ++hashfails; + if (trust_points < -7) trust_points = -7; + p->trust_points = trust_points; + if (hashfails > 255) hashfails = 255; + p->hashfails = hashfails; + + // either, we have received too many failed hashes + // or this was the only peer that sent us this piece. + // if we have failed more than 3 pieces from this peer, + // don't trust it regardless. + if (p->trust_points <= -7 + || (single_peer && allow_disconnect)) + { + // we don't trust this peer anymore + // ban it. + if (m_ses.alerts().should_post()) + { + peer_id pid(0); + if (p->connection) pid = p->connection->pid(); + 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) + { + peer_connection* peer = static_cast(p->connection); +#ifndef TORRENT_DISABLE_LOGGING + debug_log("*** BANNING PEER: \"%s\" Too many corrupt pieces" + , print_endpoint(p->ip()).c_str()); +#endif +#ifndef TORRENT_DISABLE_LOGGING + peer->peer_log(peer_log_alert::info, "BANNING_PEER", "Too many corrupt pieces"); +#endif + peer->disconnect(errors::too_many_corrupt_pieces, op_bittorrent); + } + } + } + + // If m_storage isn't set here, it means we're shutting down + if (m_storage) + { + // it doesn't make much sense to fail to hash a piece + // without having a storage associated with the torrent. + // restoring the piece in the piece picker without calling + // clear piece on the disk thread will make them out of + // sync, and if we try to write more blocks to this piece + // the disk thread will barf, because it hasn't been cleared + TORRENT_ASSERT(m_storage); + + // don't allow picking any blocks from this piece + // until we're done synchronizing with the disk threads. + m_picker->lock_piece(index); + + // don't do this until after the plugins have had a chance + // to read back the blocks that failed, for blame purposes + // this way they have a chance to hit the cache + m_ses.disk_thread().async_clear_piece(m_storage.get(), index + , boost::bind(&torrent::on_piece_sync, shared_from_this(), _1)); + } + else + { + TORRENT_ASSERT(m_abort); + // it doesn't really matter what we do + // here, since we're about to destruct the + // torrent anyway. + disk_io_job j; + j.piece = index; + on_piece_sync(&j); + } + +#ifdef TORRENT_DEBUG + for (std::vector::iterator i = downloaders.begin() + , end(downloaders.end()); i != end; ++i) + { + torrent_peer* p = *i; + if (p && p->connection) + { + peer_connection* peer = static_cast(p->connection); + peer->piece_failed = false; + } + } +#endif + } + + void torrent::peer_is_interesting(peer_connection& c) + { + INVARIANT_CHECK; + + // no peer should be interesting if we're finished + TORRENT_ASSERT(!is_finished()); + + if (c.in_handshake()) return; + c.send_interested(); + if (c.has_peer_choked() + && c.allowed_fast().empty()) + return; + + if (request_a_block(*this, c)) + inc_stats_counter(counters::interesting_piece_picks); + c.send_block_requests(); + } + + void torrent::on_piece_sync(disk_io_job const* j) + { + // 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(j->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(j->piece) == false); + + // loop over all peers and re-request potential duplicate + // blocks to this piece + for (std::vector::iterator i = m_connections.begin() + , end(m_connections.end()); i != end; ++i) + { + peer_connection* p = *i; + std::vector const& dq = p->download_queue(); + std::vector const& rq = p->request_queue(); + for (std::vector::const_iterator k = dq.begin() + , end2(dq.end()); k != end2; ++k) + { + if (k->timed_out || k->not_wanted) continue; + if (int(k->block.piece_index) != j->piece) continue; + m_picker->mark_as_downloading(k->block, p->peer_info_struct() + , p->picker_options()); + } + for (std::vector::const_iterator k = rq.begin() + , end2(rq.end()); k != end2; ++k) + { + if (int(k->block.piece_index) != j->piece) continue; + m_picker->mark_as_downloading(k->block, p->peer_info_struct() + , p->picker_options()); + } + } + } + + void torrent::peer_has(int index, peer_connection const* peer) + { + if (has_picker()) + { + torrent_peer* pp = peer->peer_info_struct(); + m_picker->inc_refcount(index, pp); + update_suggest_piece(index, 1); + } +#ifdef TORRENT_DEBUG + else + { + TORRENT_ASSERT(is_seed() || !m_have_all); + } +#endif + } + + // when we get a bitfield message, this is called for that piece + void torrent::peer_has(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); + refresh_suggest_pieces(); + } +#ifdef TORRENT_DEBUG + else + { + TORRENT_ASSERT(is_seed() || !m_have_all); + } +#endif + } + + 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); + } +#ifdef TORRENT_DEBUG + else + { + TORRENT_ASSERT(is_seed() || !m_have_all); + } +#endif + } + + void torrent::peer_lost(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); + // TODO: update suggest_piece? + } +#ifdef TORRENT_DEBUG + else + { + TORRENT_ASSERT(is_seed() || !m_have_all); + } +#endif + } + + void torrent::peer_lost(int index, peer_connection const* peer) + { + if (m_picker.get()) + { + torrent_peer* pp = peer->peer_info_struct(); + m_picker->dec_refcount(index, pp); + update_suggest_piece(index, -1); + } +#ifdef TORRENT_DEBUG + else + { + TORRENT_ASSERT(is_seed() || !m_have_all); + } +#endif + } + + void torrent::add_suggest_piece(int index) + { + // it would be nice if we would keep track of piece + // availability even when we're a seed, for + // the suggest piece feature + if (!has_picker()) return; + + int num_peers = m_picker->get_availability(index); + + TORRENT_ASSERT(has_piece_passed(index)); + + // in order to avoid unnecessary churn in the suggested pieces + // the new piece has to beat the existing piece by at least one + // peer in availability. + // m_suggested_pieces is sorted by rarity, the last element + // should have the most peers (num_peers). + if (m_suggested_pieces.empty() + || num_peers < m_suggested_pieces[m_suggested_pieces.size()-1].num_peers - 1) + { + suggest_piece_t sp; + sp.piece_index = index; + sp.num_peers = num_peers; + + typedef std::vector::iterator iter; + + std::pair range = std::equal_range( + m_suggested_pieces.begin(), m_suggested_pieces.end(), sp); + + // make sure this piece isn't already in the suggested set. + // if it is, just ignore it + iter i = std::find_if(range.first, range.second + , boost::bind(&suggest_piece_t::piece_index, _1) == index); + if (i != range.second) return; + + m_suggested_pieces.insert(range.second, sp); + if (m_suggested_pieces.size() > 0) + m_suggested_pieces.pop_back(); + + // tell all peers about this new suggested piece + for (peer_iterator p = m_connections.begin() + , end(m_connections.end()); p != end; ++p) + { + (*p)->send_suggest(index); + } + + refresh_suggest_pieces(); + } + } + + void torrent::update_suggest_piece(int index, int change) + { + for (std::vector::iterator i = m_suggested_pieces.begin() + , end(m_suggested_pieces.end()); i != end; ++i) + { + if (i->piece_index != index) continue; + + i->num_peers += change; + if (change > 0) + std::stable_sort(i, end); + else if (change < 0) + std::stable_sort(m_suggested_pieces.begin(), i + 1); + } + + if (!m_suggested_pieces.empty() && m_suggested_pieces[0].num_peers > m_connections.size() * 2 / 3) + { + // the rarest piece we have in the suggest set is not very + // rare anymore. at least 2/3 of the peers has it now. Refresh + refresh_suggest_pieces(); + } + } + + void torrent::refresh_suggest_pieces() + { + m_need_suggest_pieces_refresh = true; + } + + void torrent::do_refresh_suggest_pieces() + { + m_need_suggest_pieces_refresh = false; + + if (settings().get_int(settings_pack::suggest_mode) + == settings_pack::no_piece_suggestions) + return; + + if (!valid_metadata()) return; + + boost::shared_ptr t = shared_from_this(); + TORRENT_ASSERT(t); + cache_status cs; + m_ses.disk_thread().get_cache_info(&cs, m_storage.get() == NULL, m_storage.get()); + + // remove write cache entries + cs.pieces.erase(std::remove_if(cs.pieces.begin(), cs.pieces.end() + , boost::bind(&cached_piece_info::kind, _1) == cached_piece_info::write_cache) + , cs.pieces.end()); + + std::vector& pieces = m_suggested_pieces; + pieces.clear(); + pieces.reserve(cs.pieces.size()); + + // sort in ascending order, to get most recently used first + std::sort(cs.pieces.begin(), cs.pieces.end() + , boost::bind(&cached_piece_info::last_use, _1) + > boost::bind(&cached_piece_info::last_use, _2)); + + for (std::vector::iterator i = cs.pieces.begin() + , end(cs.pieces.end()); i != end; ++i) + { + TORRENT_ASSERT(i->storage == m_storage.get()); + if (!has_piece_passed(i->piece)) continue; + suggest_piece_t p; + p.piece_index = i->piece; + if (has_picker()) + { + p.num_peers = m_picker->get_availability(i->piece); + } + else + { + // TODO: really, we should just keep the picker around + // in this case to maintain the availability counters + p.num_peers = 0; + for (const_peer_iterator j = m_connections.begin() + , end2(m_connections.end()); j != end2; ++j) + { + peer_connection* peer = *j; + if (peer->has_piece(p.piece_index)) ++p.num_peers; + } + } + pieces.push_back(p); + } + + // sort by rarity (stable, to maintain sort + // by last use) + std::stable_sort(pieces.begin(), pieces.end()); + + // only suggest half of the pieces + pieces.resize(pieces.size() / 2); + + // send new suggests to peers + // the peers will filter out pieces we've + // already suggested to them + for (std::vector::iterator i = pieces.begin() + , end(pieces.end()); i != end; ++i) + { + for (peer_iterator p = m_connections.begin(); + p != m_connections.end(); ++p) + (*p)->send_suggest(i->piece_index); + } + } + + void torrent::abort() + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + if (m_abort) return; + + m_abort = true; + update_want_peers(); + update_want_tick(); + update_want_scrape(); + update_gauge(); + stop_announcing(); + + if (m_peer_class > 0) + { + m_ses.peer_classes().decref(m_peer_class); + m_peer_class = 0; + } + + error_code ec; + m_inactivity_timer.cancel(ec); + +#ifndef TORRENT_DISABLE_LOGGING + log_to_all_peers("aborting"); +#endif + + // disconnect all peers and close all + // files belonging to the torrents + disconnect_all(errors::torrent_aborted, op_bittorrent); + + // post a message to the main thread to destruct + // the torrent object from there + if (m_storage.get()) + { + inc_refcount("release_files"); + m_ses.disk_thread().async_stop_torrent(m_storage.get() + , boost::bind(&torrent::on_cache_flushed, shared_from_this(), _1)); + } + else + { + TORRENT_ASSERT(m_abort); + if (alerts().should_post()) + alerts().emplace_alert(get_handle()); + } + + m_storage.reset(); + + // 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_allow_peers = false; + m_auto_managed = false; + update_state_list(); + for (int i = 0; i < aux::session_interface::num_torrent_lists; ++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; + } + + void torrent::super_seeding(bool on) + { + if (on == m_super_seeding) return; + + m_super_seeding = on; + set_need_save_resume(); + + if (m_super_seeding) return; + + // disable super seeding for all peers + for (peer_iterator i = begin(); i != end(); ++i) + { + (*i)->superseed_piece(-1, -1); + } + } + + int torrent::get_piece_to_super_seed(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); + + if (!need_loaded()) + return -1; + + // do a linear search from the first piece + int min_availability = 9999; + std::vector avail_vec; + for (int i = 0; i < m_torrent_file->num_pieces(); ++i) + { + if (bits[i]) continue; + + int availability = 0; + for (const_peer_iterator j = begin(); j != end(); ++j) + { + if ((*j)->super_seeded_piece(i)) + { + // avoid superseeding the same piece to more than one + // peer if we can avoid it. Do this by artificially + // increase the availability + availability = 999; + break; + } + if ((*j)->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 -1; + return avail_vec[random() % avail_vec.size()]; + } + + void torrent::on_files_deleted(disk_io_job const* j) + { + TORRENT_ASSERT(is_single_thread()); + + dec_refcount("delete_files"); + if (j->ret != 0) + { + if (alerts().should_post()) + alerts().emplace_alert(get_handle() + , j->error.ec, m_torrent_file->info_hash()); + } + else + { + alerts().emplace_alert(get_handle(), m_torrent_file->info_hash()); + } + } + + void torrent::on_save_resume_data(disk_io_job const* j) + { + TORRENT_ASSERT(is_single_thread()); + torrent_ref_holder h(this, "save_resume"); + dec_refcount("save_resume"); + m_ses.done_async_resume(); + + if (!j->buffer.resume_data) + { + alerts().emplace_alert(get_handle(), j->error.ec); + return; + } + + if (!need_loaded()) + { + alerts().emplace_alert(get_handle() + , m_error); + return; + } + + m_need_save_resume_data = false; + m_last_saved_resume = m_ses.session_time(); + write_resume_data(*j->buffer.resume_data); + alerts().emplace_alert( + boost::shared_ptr(j->buffer.resume_data), get_handle()); + const_cast(j)->buffer.resume_data = 0; + state_updated(); + } + + void torrent::on_file_renamed(disk_io_job const* j) + { + TORRENT_ASSERT(is_single_thread()); + dec_refcount("rename_file"); + + if (j->ret == 0) + { + if (alerts().should_post()) + alerts().emplace_alert(get_handle() + , j->buffer.string, j->piece); + m_torrent_file->rename_file(j->piece, j->buffer.string); + } + else + { + if (alerts().should_post()) + alerts().emplace_alert(get_handle() + , j->piece, j->error.ec); + } + } + + void torrent::on_torrent_paused(disk_io_job const*) + { + TORRENT_ASSERT(is_single_thread()); + + if (alerts().should_post()) + alerts().emplace_alert(get_handle()); + } + +#ifndef TORRENT_NO_DEPRECATE + std::string torrent::tracker_login() const + { + if (m_username.empty() && m_password.empty()) return ""; + return m_username + ":" + m_password; + } +#endif + + boost::uint32_t torrent::tracker_key() const + { + uintptr_t self = reinterpret_cast(this); + uintptr_t ses = reinterpret_cast(&m_ses); + uintptr_t storage = reinterpret_cast(m_storage.get()); + sha1_hash h = hasher(reinterpret_cast(&self), sizeof(self)) + .update(reinterpret_cast(&storage), sizeof(storage)) + .update(reinterpret_cast(&ses), sizeof(ses)) + .final(); + unsigned char const* ptr = &h[0]; + return detail::read_uint32(ptr); + } + + void torrent::cancel_non_critical() + { + std::set time_critical; + for (std::vector::iterator i = m_time_critical_pieces.begin() + , end(m_time_critical_pieces.end()); i != end; ++i) + { + time_critical.insert(i->piece); + } + + for (std::vector::iterator i + = m_connections.begin(), end(m_connections.end()); i != end; ++i) + { + // for each peer, go through its download and request queue + // and cancel everything, except pieces that are time critical + peer_connection* p = *i; + + std::vector dq = p->download_queue(); + for (std::vector::iterator k = dq.begin() + , end2(dq.end()); k != end2; ++k) + { + if (time_critical.count(k->block.piece_index)) continue; + if (k->not_wanted || k->timed_out) continue; + p->cancel_request(k->block, true); + } + + std::vector rq = p->request_queue(); + for (std::vector::const_iterator k = rq.begin() + , end2(rq.end()); k != end2; ++k) + { + if (time_critical.count(k->block.piece_index)) continue; + p->cancel_request(k->block, true); + } + } + } + + void torrent::set_piece_deadline(int piece, int t, int flags) + { + INVARIANT_CHECK; + + if (m_abort) + { + // 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 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 + m_ses.get_io_service().post(boost::bind(&torrent::cancel_non_critical, this)); + } + + for (std::vector::iterator 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 (boost::next(i) != m_time_critical_pieces.end() && i->deadline > boost::next(i)->deadline) + { + std::iter_swap(i, boost::next(i)); + ++i; + } + while (i != m_time_critical_pieces.begin() && i->deadline < boost::prior(i)->deadline) + { + std::iter_swap(i, boost::prior(i)); + --i; + } + // just in case this piece had priority 0 + int prev_prio = m_picker->piece_priority(piece); + m_picker->set_piece_priority(piece, 7); + if (prev_prio == 0) update_gauge(); + return; + } + + need_picker(); + + time_critical_piece p; + p.first_requested = min_time(); + p.last_requested = min_time(); + p.flags = flags; + p.deadline = deadline; + p.peers = 0; + p.piece = piece; + std::vector::iterator 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 + int prev_prio = m_picker->piece_priority(piece); + m_picker->set_piece_priority(piece, 7); + if (prev_prio == 0) update_gauge(); + + piece_picker::downloading_piece pi; + m_picker->piece_info(piece, pi); + if (pi.requested == 0) return; + // this means we have outstanding requests (or queued + // up requests that haven't been sent yet). Promote them + // to deadline pieces immediately + std::vector downloaders; + m_picker->get_downloaders(downloaders, piece); + + int block = 0; + for (std::vector::iterator i = downloaders.begin() + , end(downloaders.end()); i != end; ++i, ++block) + { + torrent_peer* tp = *i; + if (tp == 0 || tp->connection == 0) continue; + peer_connection* peer = static_cast(tp->connection); + peer->make_time_critical(piece_block(piece, block)); + } + } + + void torrent::reset_piece_deadline(int piece) + { + remove_time_critical_piece(piece); + } + + void torrent::remove_time_critical_piece(int piece, bool finished) + { + for (std::vector::iterator 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 dl_time = total_milliseconds(aux::time_now() - i->first_requested); + + if (m_average_piece_time == 0) + { + m_average_piece_time = dl_time; + } + else + { + int diff = abs(int(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, 1); + m_time_critical_pieces.erase(i); + return; + } + } + + void torrent::clear_time_critical() + { + for (std::vector::iterator 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, 1); + i = m_time_critical_pieces.erase(i); + } + } + + // remove time critical pieces where priority is 0 + void torrent::remove_time_critical_pieces(std::vector const& priority) + { + for (std::vector::iterator i = m_time_critical_pieces.begin(); + i != m_time_critical_pieces.end();) + { + if (priority[i->piece] == 0) + { + 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; + } + } + + void torrent::piece_availability(std::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(int index, int priority) + { +// INVARIANT_CHECK; + +#ifndef TORRENT_DISABLE_LOGGING + if (!valid_metadata()) + { + debug_log("*** SET_PIECE_PRIORITY [ idx: %d prio: %d ignored. " + "no metadata yet ]", index, priority); + } +#endif + if (!valid_metadata() || is_seed()) return; + + // this call is only valid on torrents with metadata + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < m_torrent_file->num_pieces()); + if (index < 0 || index >= m_torrent_file->num_pieces()) return; + + need_picker(); + + bool was_finished = is_finished(); + bool filter_updated = m_picker->set_piece_priority(index, priority); + TORRENT_ASSERT(num_have() >= m_picker->num_have_filtered()); + + update_gauge(); + + if (filter_updated) + { + update_peer_interest(was_finished); + if (priority == 0) remove_time_critical_piece(index); + } + + } + + int torrent::piece_priority(int index) const + { +// INVARIANT_CHECK; + + if (!has_picker()) return 4; + + // this call is only valid on torrents with metadata + TORRENT_ASSERT(valid_metadata()); + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < m_torrent_file->num_pieces()); + if (index < 0 || index >= m_torrent_file->num_pieces()) return 0; + + 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 was_finished = is_finished(); + for (std::vector >::const_iterator i = pieces.begin() + , end(pieces.end()); i != end; ++i) + { + TORRENT_ASSERT(i->second >= 0); + TORRENT_ASSERT(i->second <= 7); + TORRENT_ASSERT(i->first >= 0); + TORRENT_ASSERT(i->first < m_torrent_file->num_pieces()); + + if (i->first < 0 || i->first >= m_torrent_file->num_pieces() || i->second < 0 || i->second > 7) + continue; + + filter_updated |= m_picker->set_piece_priority(i->first, i->second); + TORRENT_ASSERT(num_have() >= m_picker->num_have_filtered()); + } + 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(std::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(); + + int index = 0; + bool filter_updated = false; + bool was_finished = is_finished(); + for (std::vector::const_iterator i = pieces.begin() + , end(pieces.end()); i != end; ++i, ++index) + { + TORRENT_ASSERT(*i >= 0); + TORRENT_ASSERT(*i <= 7); + filter_updated |= m_picker->set_piece_priority(index, *i); + TORRENT_ASSERT(num_have() >= m_picker->num_have_filtered()); + } + update_gauge(); + update_want_tick(); + + if (filter_updated) + { + // we need to save this new state + set_need_save_resume(); + + update_peer_interest(was_finished); + remove_time_critical_pieces(pieces); + } + + state_updated(); + update_state_list(); + } + + void torrent::piece_priorities(std::vector* pieces) const + { + INVARIANT_CHECK; + + // this call is only valid on torrents with metadata + TORRENT_ASSERT(valid_metadata()); + if (!has_picker()) + { + pieces->clear(); + pieces->resize(m_torrent_file->num_pieces(), 1); + return; + } + + TORRENT_ASSERT(m_picker.get()); + m_picker->piece_priorities(*pieces); + } + + namespace + { + void set_if_greater(int& piece_prio, int file_prio) + { + if (file_prio > piece_prio) piece_prio = file_prio; + } + } + + void torrent::on_file_priority() + { + dec_refcount("file_priority"); + } + + void torrent::prioritize_files(std::vector const& files) + { + INVARIANT_CHECK; + + // this call is only valid on torrents with metadata + if (!valid_metadata() || is_seed()) return; + + // the vector need to have exactly one element for every file + // in the torrent + TORRENT_ASSERT(int(files.size()) == m_torrent_file->num_files()); + + int limit = int(files.size()); + if (valid_metadata() && limit > m_torrent_file->num_files()) + limit = m_torrent_file->num_files(); + + if (int(m_file_priority.size()) < limit) + m_file_priority.resize(limit, 4); + + std::copy(files.begin(), files.begin() + limit, m_file_priority.begin()); + + if (valid_metadata() && m_torrent_file->num_files() > int(m_file_priority.size())) + m_file_priority.resize(m_torrent_file->num_files(), 1); + + // initialize pad files to priority 0 + file_storage const& fs = m_torrent_file->files(); + for (int i = 0; i < (std::min)(fs.num_files(), limit); ++i) + { + if (!fs.pad_file_at(i)) continue; + m_file_priority[i] = 0; + } + + // storage may be NULL during shutdown + if (m_torrent_file->num_pieces() > 0 && m_storage) + { + inc_refcount("file_priority"); + m_ses.disk_thread().async_set_file_priority(m_storage.get() + , m_file_priority, boost::bind(&torrent::on_file_priority, this)); + } + + update_piece_priorities(); + } + + void torrent::set_file_priority(int index, int prio) + { + INVARIANT_CHECK; + + if (is_seed()) return; + + // 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 < 0 || (valid_metadata() && index >= m_torrent_file->num_files())) + { + return; + } + + if (prio < 0) prio = 0; + else if (prio > 7) prio = 7; + if (int(m_file_priority.size()) <= index) + { + // any unallocated slot is assumed to be 1 + if (prio == 1) return; + m_file_priority.resize(index+1, 4); + } + + if (m_file_priority[index] == prio) return; + m_file_priority[index] = prio; + + if (!valid_metadata()) return; + + // stoage may be NULL during shutdown + if (m_storage) + { + inc_refcount("file_priority"); + m_ses.disk_thread().async_set_file_priority(m_storage.get() + , m_file_priority, boost::bind(&torrent::on_file_priority, this)); + } + update_piece_priorities(); + } + + int torrent::file_priority(int index) const + { + TORRENT_ASSERT_PRECOND(index >= 0); + if (index < 0) return 0; + + // if we have metadata, perform additional checks + if (valid_metadata()) + { + TORRENT_ASSERT_PRECOND(index < m_torrent_file->num_files()); + if (index >= m_torrent_file->num_files()) return 0; + + // pad files always have priority 0 + if (m_torrent_file->files().pad_file_at(index)) return 0; + } + + // any unallocated slot is assumed to be 4 (normal priority) + if (int(m_file_priority.size()) <= index) return 4; + + return m_file_priority[index]; + } + + void torrent::file_priorities(std::vector* files) const + { + INVARIANT_CHECK; + + if (!valid_metadata()) + { + files->resize(m_file_priority.size()); + std::copy(m_file_priority.begin(), m_file_priority.end(), files->begin()); + return; + } + + files->clear(); + files->resize(m_torrent_file->num_files(), 4); + TORRENT_ASSERT(int(m_file_priority.size()) <= m_torrent_file->num_files()); + std::copy(m_file_priority.begin(), m_file_priority.end(), files->begin()); + } + + void torrent::update_piece_priorities() + { + INVARIANT_CHECK; + + if (m_torrent_file->num_pieces() == 0) return; + + bool need_update = false; + boost::int64_t position = 0; + int piece_length = m_torrent_file->piece_length(); + // initialize the piece priorities to 0, then only allow + // setting higher priorities + std::vector pieces(m_torrent_file->num_pieces(), 0); + file_storage const& fs = m_torrent_file->files(); + for (int i = 0; i < fs.num_files(); ++i) + { + if (i >= fs.num_files()) break; + + boost::int64_t start = position; + boost::int64_t size = m_torrent_file->files().file_size(i); + if (size == 0) continue; + position += size; + int file_prio; + + // pad files always have priority 0 + if (fs.pad_file_at(i)) + file_prio = 0; + else if (m_file_priority.size() <= i) + file_prio = 4; + else + file_prio = m_file_priority[i]; + + if (file_prio == 0) + { + 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) + int start_piece = int(start / piece_length); + int last_piece = int((position - 1) / piece_length); + TORRENT_ASSERT(last_piece < int(pieces.size())); + // if one piece spans several files, we might + // come here several times with the same start_piece, end_piece + std::for_each(pieces.begin() + start_piece + , pieces.begin() + last_piece + 1 + , boost::bind(&set_if_greater, _1, file_prio)); + + if (has_picker() || file_prio != 1) + 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 was_finished) + { + for (peer_iterator i = begin(); i != end();) + { + peer_connection* p = *i; + // update_interest may disconnect the peer and + // invalidate the iterator + ++i; + p->update_interest(); + } + +#ifndef TORRENT_DISABLE_LOGGING + debug_log("*** UPDATE_PEER_INTEREST [ finished: %d was_finished %d ]" + , is_finished(), was_finished); +#endif + + // the torrent just became finished + if (is_finished() && !was_finished) + { + finished(); + } + else if (!is_finished() && was_finished) + { + // if we used to be finished, but we aren't anymore + // we may need to connect to peers again + resume_download(); + } + } + + void torrent::filter_piece(int index, bool filter) + { + INVARIANT_CHECK; + + TORRENT_ASSERT(valid_metadata()); + if (is_seed()) return; + need_picker(); + + // this call is only valid on torrents with metadata + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < m_torrent_file->num_pieces()); + + if (index < 0 || index >= m_torrent_file->num_pieces()) return; + + bool was_finished = is_finished(); + m_picker->set_piece_priority(index, filter ? 1 : 0); + update_peer_interest(was_finished); + update_gauge(); + } + + void torrent::filter_pieces(std::vector const& bitmask) + { + INVARIANT_CHECK; + + // this call is only valid on torrents with metadata + TORRENT_ASSERT(valid_metadata()); + if (is_seed()) return; + + need_picker(); + + bool was_finished = is_finished(); + int index = 0; + for (std::vector::const_iterator i = bitmask.begin() + , end(bitmask.end()); i != end; ++i, ++index) + { + if ((m_picker->piece_priority(index) == 0) == *i) continue; + if (*i) + m_picker->set_piece_priority(index, 0); + else + m_picker->set_piece_priority(index, 1); + } + update_peer_interest(was_finished); + update_gauge(); + } + + bool torrent::is_piece_filtered(int index) const + { + // this call is only valid on torrents with metadata + TORRENT_ASSERT(valid_metadata()); + if (!has_picker()) return false; + + TORRENT_ASSERT(m_picker.get()); + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < m_torrent_file->num_pieces()); + + if (index < 0 || index >= m_torrent_file->num_pieces()) return true; + + return m_picker->piece_priority(index) == 0; + } + + void torrent::filtered_pieces(std::vector& bitmask) const + { + INVARIANT_CHECK; + + // this call is only valid on torrents with metadata + TORRENT_ASSERT(valid_metadata()); + if (!has_picker()) + { + bitmask.clear(); + bitmask.resize(m_torrent_file->num_pieces(), false); + return; + } + + TORRENT_ASSERT(m_picker.get()); + m_picker->filtered_pieces(bitmask); + } + + void torrent::filter_files(std::vector const& bitmask) + { + INVARIANT_CHECK; + + // this call is only valid on torrents with metadata + if (!valid_metadata() || is_seed()) return; + + // the bitmask need to have exactly one bit for every file + // in the torrent + TORRENT_ASSERT(int(bitmask.size()) == m_torrent_file->num_files()); + + if (int(bitmask.size()) != m_torrent_file->num_files()) return; + + boost::int64_t position = 0; + + if (m_torrent_file->num_pieces()) + { + int piece_length = m_torrent_file->piece_length(); + // mark all pieces as filtered, then clear the bits for files + // that should be downloaded + std::vector piece_filter(m_torrent_file->num_pieces(), true); + for (int i = 0; i < int(bitmask.size()); ++i) + { + boost::int64_t start = position; + position += m_torrent_file->files().file_size(i); + // is the file selected for download? + if (!bitmask[i]) + { + // mark all pieces of the file as downloadable + int start_piece = int(start / piece_length); + int last_piece = int(position / piece_length); + // if one piece spans several files, we might + // come here several times with the same start_piece, end_piece + std::fill(piece_filter.begin() + start_piece, piece_filter.begin() + + last_piece + 1, false); + } + } + filter_pieces(piece_filter); + } + } + + namespace { + + bool has_empty_url(announce_entry const& e) { return e.url.empty(); } + + } + + void torrent::replace_trackers(std::vector const& urls) + { + m_trackers.clear(); + std::remove_copy_if(urls.begin(), urls.end(), back_inserter(m_trackers) + , &has_empty_url); + + m_last_working_tracker = -1; + for (std::vector::iterator i = m_trackers.begin() + , end(m_trackers.end()); i != end; ++i) + { + if (i->source == 0) i->source = announce_entry::source_client; + i->complete_sent = is_seed(); + } + + if (settings().get_bool(settings_pack::prefer_udp_trackers)) + prioritize_udp_trackers(); + + if (!m_trackers.empty()) announce_with_tracker(); + + set_need_save_resume(); + } + + void torrent::prioritize_udp_trackers() + { + // look for udp-trackers + for (std::vector::iterator 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 boost::tuples::ignore; + boost::tie(ignore, ignore, udp_hostname, ignore, ignore) + = parse_url_components(i->url, ec); + for (std::vector::iterator j = m_trackers.begin(); + j != i; ++j) + { + std::string hostname; + boost::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) + { + std::vector::iterator k = std::find_if(m_trackers.begin() + , m_trackers.end(), boost::bind(&announce_entry::url, _1) == url.url); + if (k != m_trackers.end()) + { + k->source |= url.source; + return false; + } + k = std::upper_bound(m_trackers.begin(), m_trackers.end(), url + , boost::bind(&announce_entry::tier, _1) < boost::bind(&announce_entry::tier, _2)); + if (k - m_trackers.begin() < m_last_working_tracker) ++m_last_working_tracker; + k = m_trackers.insert(k, url); + if (k->source == 0) k->source = announce_entry::source_client; + if (m_allow_peers && !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() + { + m_ses.get_io_service().dispatch(boost::bind( + &aux::session_interface::trigger_unchoke, boost::ref(m_ses))); + } + + void torrent::trigger_optimistic_unchoke() + { + m_ses.get_io_service().dispatch(boost::bind( + &aux::session_interface::trigger_optimistic_unchoke, boost::ref(m_ses))); + } + + void torrent::cancel_block(piece_block block) + { + INVARIANT_CHECK; + + for (peer_iterator i = m_connections.begin() + , end(m_connections.end()); i != end; ++i) + { + (*i)->cancel_request(block); + } + } + +#ifdef TORRENT_USE_OPENSSL + namespace { + std::string password_callback(int length, boost::asio::ssl::context::password_purpose p + , std::string pw) + { + TORRENT_UNUSED(length); + + if (p != boost::asio::ssl::context::for_reading) return ""; + return pw; + } + } + + // certificate is a filename to a .pem file which is our + // certificate. The certificate must be signed by the root + // cert of the torrent file. any peer we connect to or that + // connect to use must present a valid certificate signed + // by the torrent root cert as well + void torrent::set_ssl_cert(std::string const& certificate + , std::string const& private_key + , std::string const& dh_params + , std::string const& passphrase) + { + if (!m_ssl_ctx) + { + if (alerts().should_post()) + alerts().emplace_alert(get_handle() + , error_code(errors::not_an_ssl_torrent), ""); + return; + } + + using boost::asio::ssl::context; + error_code ec; + m_ssl_ctx->set_password_callback(boost::bind(&password_callback, _1, _2, passphrase), ec); + if (ec) + { + if (alerts().should_post()) + alerts().emplace_alert(get_handle(), ec, ""); + } + m_ssl_ctx->use_certificate_file(certificate, context::pem, ec); + if (ec) + { + if (alerts().should_post()) + alerts().emplace_alert(get_handle(), ec, certificate); + } +#ifndef TORRENT_DISABLE_LOGGING + debug_log("*** use certificate file: %s", ec.message().c_str()); +#endif + m_ssl_ctx->use_private_key_file(private_key, context::pem, ec); + if (ec) + { + if (alerts().should_post()) + alerts().emplace_alert(get_handle(), ec, private_key); + } +#ifndef TORRENT_DISABLE_LOGGING + 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 + 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; + +#if BOOST_VERSION < 105400 + if (alerts().should_post()) + alerts().emplace_alert(get_handle() + , error_code(boost::system::errc::not_supported, generic_category()), "[certificate]"); +#else + boost::asio::const_buffer certificate_buf(certificate.c_str(), certificate.size()); + + using boost::asio::ssl::context; + error_code ec; + m_ssl_ctx->use_certificate(certificate_buf, context::pem, ec); + if (ec) + { + if (alerts().should_post()) + alerts().emplace_alert(get_handle(), ec, "[certificate]"); + } + + boost::asio::const_buffer private_key_buf(private_key.c_str(), private_key.size()); + m_ssl_ctx->use_private_key(private_key_buf, context::pem, ec); + if (ec) + { + if (alerts().should_post()) + alerts().emplace_alert(get_handle(), ec, "[private key]"); + } + + boost::asio::const_buffer dh_params_buf(dh_params.c_str(), dh_params.size()); + m_ssl_ctx->use_tmp_dh(dh_params_buf, ec); + if (ec) + { + if (alerts().should_post()) + alerts().emplace_alert(get_handle(), ec, "[dh params]"); + } +#endif // BOOST_VERSION + } + +#endif + + void torrent::remove_peer(peer_connection* p) + { + TORRENT_ASSERT(p != 0); + TORRENT_ASSERT(is_single_thread()); + + peer_iterator i = sorted_find(m_connections, p); + if (i == m_connections.end()) + { + TORRENT_ASSERT(false); + return; + } + + torrent_peer* pp = p->peer_info_struct(); + if (ready_for_connections()) + { + TORRENT_ASSERT(p->associated_torrent().lock().get() == NULL + || p->associated_torrent().lock().get() == this); + + if (p->is_seed()) + { + if (has_picker()) + { + m_picker->dec_refcount_all(pp); + } + } + else + { + if (has_picker()) + { + bitfield const& pieces = p->get_bitfield(); + TORRENT_ASSERT(pieces.count() <= int(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 += p->statistics().total_payload_download() >> 10; + pp->prev_amount_upload += p->statistics().total_payload_upload() >> 10; + + if (pp->seed) + { + TORRENT_ASSERT(m_num_seeds > 0); + --m_num_seeds; + } + } + + torrent_state st = get_peer_list_state(); + if (m_peer_list) m_peer_list->connection_closed(*p, m_ses.session_time(), &st); + peers_erased(st.erased); + + p->set_peer_info(0); + TORRENT_ASSERT(i != m_connections.end()); + m_connections.erase(i); + + 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_allow_peers(false); + } + + update_want_peers(); + update_want_tick(); + } + + void torrent::remove_web_seed(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 + + peer_connection* peer = static_cast(web->peer_info.connection); + if (peer) + { + // 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, op_bittorrent); + peer->set_peer_info(0); + } + 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 (int(m_connections.size()) >= 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; + boost::tie(protocol, auth, hostname, port, path) + = parse_url_components(web->url, ec); + if (port == -1) + { + port = protocol == "http" ? 80 : 443; + } + + if (ec) + { +#ifndef TORRENT_DISABLE_LOGGING + 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(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 + , error_code(libtorrent::errors::peer_banned, get_libtorrent_category())); + } + // never try it again + remove_web_seed(web); + return; + } + +#ifdef TORRENT_USE_OPENSSL + if (protocol != "http" && protocol != "https") +#else + if (protocol != "http") +#endif + { + if (m_ses.alerts().should_post()) + { + m_ses.alerts().emplace_alert(get_handle(), web->url, errors::unsupported_url_protocol); + } + // never try it again + remove_web_seed(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(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(web); + return; + } + + if (m_ses.get_port_filter().access(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(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 + + // use proxy + web->resolving = true; + m_ses.async_resolve(ps.hostname, resolver_interface::abort_on_shutdown + , boost::bind(&torrent::on_proxy_name_lookup, shared_from_this() + , _1, _2, web, ps.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, tcp::endpoint(address(), port)); + } + else + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("resolving web seed: \"%s\" %s", hostname.c_str(), web->url.c_str()); +#endif + + web->resolving = true; + m_ses.async_resolve(hostname, resolver_interface::abort_on_shutdown + , boost::bind(&torrent::on_name_lookup, shared_from_this(), _1, _2 + , port, web)); + } + } + + void torrent::on_proxy_name_lookup(error_code const& e + , std::vector
    const& addrs + , std::list::iterator web, int port) + { + TORRENT_ASSERT(is_single_thread()); + + INVARIANT_CHECK; + + TORRENT_ASSERT(web->resolving == true); +#ifndef TORRENT_DISABLE_LOGGING + debug_log("completed resolve proxy hostname for: %s", web->url.c_str()); + if (e) + 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(web); + return; + } + + if (m_abort) return; + + if (e || addrs.empty()) + { + if (m_ses.alerts().should_post()) + { + m_ses.alerts().emplace_alert(get_handle() + , web->url, e); + } + + // the name lookup failed for the http host. Don't try + // this host again + remove_web_seed(web); + return; + } + + if (m_ses.is_aborted()) return; + + if (int(m_connections.size()) >= m_max_connections + || m_ses.num_connections() >= settings().get_int(settings_pack::connections_limit)) + return; + + tcp::endpoint a(addrs[0], port); + + using boost::tuples::ignore; + std::string hostname; + error_code ec; + std::string protocol; + boost::tie(protocol, ignore, hostname, port, 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(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.address(), peer_blocked_alert::ip_filter); + return; + } + + web->resolving = true; + m_ses.async_resolve(hostname, resolver_interface::abort_on_shutdown + , boost::bind(&torrent::on_name_lookup, shared_from_this(), _1, _2 + , port, web)); + } + + void torrent::on_name_lookup(error_code const& e + , std::vector
    const& addrs + , int port + , std::list::iterator web) + { + TORRENT_ASSERT(is_single_thread()); + + INVARIANT_CHECK; + + TORRENT_ASSERT(web->resolving == true); +#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(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 + debug_log("*** HOSTNAME LOOKUP FAILED: %s: (%d) %s" + , web->url.c_str(), e.value(), e.message().c_str()); +#endif + + // unavailable, retry in 30 minutes + web->retry = aux::time_now() + minutes(30); + return; + } + + for (std::vector
    ::const_iterator i = addrs.begin() + , end(addrs.end()); i != end; ++i) + { + // fill in the peer struct's address field + web->endpoints.push_back(tcp::endpoint(*i, port)); + +#ifndef TORRENT_DISABLE_LOGGING + debug_log(" -> %s", print_endpoint(tcp::endpoint(*i, port)).c_str()); +#endif + } + + if (int(m_connections.size()) >= m_max_connections + || m_ses.num_connections() >= settings().get_int(settings_pack::connections_limit)) + return; + + connect_web_seed(web, web->endpoints.front()); + } + + 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.address(), peer_blocked_alert::ip_filter); + return; + } + + TORRENT_ASSERT(web->resolving == false); + TORRENT_ASSERT(web->peer_info.connection == 0); + + if (a.address().is_v4()) + { + web->peer_info.addr = a.address().to_v4(); + web->peer_info.port = a.port(); + } + + if (is_paused()) return; + if (m_ses.is_aborted()) return; + + boost::shared_ptr s + = boost::make_shared(boost::ref(m_ses.get_io_service())); + if (!s) return; + + void* userdata = 0; +#ifdef TORRENT_USE_OPENSSL + const bool ssl = string_begins_no_case("https://", web->url.c_str()); + if (ssl) + { + userdata = m_ssl_ctx.get(); + if (!userdata) userdata = m_ses.ssl_ctx(); + } +#endif + bool ret = instantiate_connection(m_ses.get_io_service(), m_ses.proxy() + , *s, userdata, 0, true, false); + (void)ret; + TORRENT_ASSERT(ret); + + if (s->get()) + { + // the web seed connection will talk immediately to + // the proxy, without requiring CONNECT support + s->get()->set_no_connect(true); + } + + using boost::tuples::ignore; + std::string hostname; + error_code ec; + boost::tie(ignore, ignore, hostname, ignore, ignore) + = parse_url_components(web->url, ec); + if (ec) + { + if (m_ses.alerts().should_post()) + m_ses.alerts().emplace_alert(get_handle(), web->url, ec); + return; + } + + bool const is_ip = is_ip_address(hostname.c_str()); + if (is_ip) a.address(address::from_string(hostname.c_str(), ec)); + bool const proxy_hostnames = settings().get_bool(settings_pack::proxy_hostnames) + && !is_ip; + + if (proxy_hostnames + && (s->get() +#ifdef TORRENT_USE_OPENSSL + || s->get >() +#endif + )) + { + // we're using a socks proxy and we're resolving + // hostnames through it + socks5_stream* str = +#ifdef TORRENT_USE_OPENSSL + ssl ? &s->get >()->next_layer() : +#endif + s->get(); + TORRENT_ASSERT_VAL(str, s->type_name()); + + str->set_dst_name(hostname); + } + + setup_ssl_hostname(*s, hostname, ec); + if (ec) + { + if (m_ses.alerts().should_post()) + m_ses.alerts().emplace_alert(get_handle(), web->url, ec); + return; + } + + boost::shared_ptr c; + peer_connection_args pack; + pack.ses = &m_ses; + pack.sett = &settings(); + pack.stats_counters = &m_ses.stats_counters(); + pack.allocator = &m_ses; + pack.disk_thread = &m_ses.disk_thread(); + pack.ios = &m_ses.get_io_service(); + pack.tor = shared_from_this(); + pack.s = s; + pack.endp = a; + pack.peerinfo = &web->peer_info; + if (web->type == web_seed_entry::url_seed) + { + c = boost::make_shared( + boost::cref(pack), boost::ref(*web)); + } + else if (web->type == web_seed_entry::http_seed) + { + c = boost::make_shared( + boost::cref(pack), boost::ref(*web)); + } + if (!c) return; + +#if TORRENT_USE_ASSERTS + c->m_in_constructor = false; +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + boost::shared_ptr + pp((*i)->new_connection(peer_connection_handle(c.get()->self()))); + if (pp) c->add_extension(pp); + } +#endif + + TORRENT_TRY + { + TORRENT_ASSERT(!c->m_in_constructor); + // add the newly connected peer to this torrent's peer list + sorted_insert(m_connections, boost::get_pointer(c)); + 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(boost::int64_t(web->peer_info.prev_amount_download) << 10 + , boost::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 + 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 + } + TORRENT_CATCH (std::exception& e) + { + TORRENT_DECLARE_DUMMY(std::exception, e); + (void)e; +#ifndef TORRENT_DISABLE_LOGGING + debug_log("*** PEER_ERROR: %s", e.what()); +#endif + c->disconnect(errors::no_error, op_bittorrent, 1); + } + } + +#ifndef TORRENT_NO_DEPRECATE + // deprecated in 1.1 +#ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES + namespace + { + unsigned long swap_bytes(unsigned long a) + { + return (a >> 24) | ((a & 0xff0000) >> 8) | ((a & 0xff00) << 8) | ((a & 0xff) << 24); + } + } + + void torrent::resolve_countries(bool r) + { m_resolve_countries = r; } + + bool torrent::resolving_countries() const + { + return m_resolve_countries && !settings().get_bool(settings_pack::force_proxy); + } + + void torrent::resolve_peer_country(boost::shared_ptr const& p) const + { + TORRENT_ASSERT(is_single_thread()); + if (m_resolving_country + || is_local(p->remote().address()) + || p->has_country() + || p->is_connecting() + || p->in_handshake() + || p->remote().address().is_v6()) return; + + boost::asio::ip::address_v4 reversed(swap_bytes(p->remote().address().to_v4().to_ulong())); + error_code ec; + std::string hostname = reversed.to_string(ec) + ".zz.countries.nerd.dk"; + if (ec) + { + p->set_country("!!"); + return; + } + m_resolving_country = true; + m_ses.async_resolve(hostname, resolver_interface::abort_on_shutdown + , boost::bind(&torrent::on_country_lookup, shared_from_this(), _1, _2, p)); + } + + namespace + { + struct country_entry + { + int code; + char const* name; + }; + } + + void torrent::on_country_lookup(error_code const& error + , std::vector
    const& host_list + , boost::shared_ptr p) const + { + TORRENT_ASSERT(is_single_thread()); + + INVARIANT_CHECK; + + m_resolving_country = false; + + if (m_abort) return; + + // must be ordered in increasing order + static const country_entry country_map[] = + { + { 4, "AF"}, { 8, "AL"}, { 10, "AQ"}, { 12, "DZ"}, { 16, "AS"} + , { 20, "AD"}, { 24, "AO"}, { 28, "AG"}, { 31, "AZ"}, { 32, "AR"} + , { 36, "AU"}, { 40, "AT"}, { 44, "BS"}, { 48, "BH"}, { 50, "BD"} + , { 51, "AM"}, { 52, "BB"}, { 56, "BE"}, { 60, "BM"}, { 64, "BT"} + , { 68, "BO"}, { 70, "BA"}, { 72, "BW"}, { 74, "BV"}, { 76, "BR"} + , { 84, "BZ"}, { 86, "IO"}, { 90, "SB"}, { 92, "VG"}, { 96, "BN"} + , {100, "BG"}, {104, "MM"}, {108, "BI"}, {112, "BY"}, {116, "KH"} + , {120, "CM"}, {124, "CA"}, {132, "CV"}, {136, "KY"}, {140, "CF"} + , {144, "LK"}, {148, "TD"}, {152, "CL"}, {156, "CN"}, {158, "TW"} + , {162, "CX"}, {166, "CC"}, {170, "CO"}, {174, "KM"}, {175, "YT"} + , {178, "CG"}, {180, "CD"}, {184, "CK"}, {188, "CR"}, {191, "HR"} + , {192, "CU"}, {203, "CZ"}, {204, "BJ"}, {208, "DK"}, {212, "DM"} + , {214, "DO"}, {218, "EC"}, {222, "SV"}, {226, "GQ"}, {231, "ET"} + , {232, "ER"}, {233, "EE"}, {234, "FO"}, {238, "FK"}, {239, "GS"} + , {242, "FJ"}, {246, "FI"}, {248, "AX"}, {250, "FR"}, {254, "GF"} + , {258, "PF"}, {260, "TF"}, {262, "DJ"}, {266, "GA"}, {268, "GE"} + , {270, "GM"}, {275, "PS"}, {276, "DE"}, {288, "GH"}, {292, "GI"} + , {296, "KI"}, {300, "GR"}, {304, "GL"}, {308, "GD"}, {312, "GP"} + , {316, "GU"}, {320, "GT"}, {324, "GN"}, {328, "GY"}, {332, "HT"} + , {334, "HM"}, {336, "VA"}, {340, "HN"}, {344, "HK"}, {348, "HU"} + , {352, "IS"}, {356, "IN"}, {360, "ID"}, {364, "IR"}, {368, "IQ"} + , {372, "IE"}, {376, "IL"}, {380, "IT"}, {384, "CI"}, {388, "JM"} + , {392, "JP"}, {398, "KZ"}, {400, "JO"}, {404, "KE"}, {408, "KP"} + , {410, "KR"}, {414, "KW"}, {417, "KG"}, {418, "LA"}, {422, "LB"} + , {426, "LS"}, {428, "LV"}, {430, "LR"}, {434, "LY"}, {438, "LI"} + , {440, "LT"}, {442, "LU"}, {446, "MO"}, {450, "MG"}, {454, "MW"} + , {458, "MY"}, {462, "MV"}, {466, "ML"}, {470, "MT"}, {474, "MQ"} + , {478, "MR"}, {480, "MU"}, {484, "MX"}, {492, "MC"}, {496, "MN"} + , {498, "MD"}, {500, "MS"}, {504, "MA"}, {508, "MZ"}, {512, "OM"} + , {516, "NA"}, {520, "NR"}, {524, "NP"}, {528, "NL"}, {530, "AN"} + , {533, "AW"}, {540, "NC"}, {548, "VU"}, {554, "NZ"}, {558, "NI"} + , {562, "NE"}, {566, "NG"}, {570, "NU"}, {574, "NF"}, {578, "NO"} + , {580, "MP"}, {581, "UM"}, {583, "FM"}, {584, "MH"}, {585, "PW"} + , {586, "PK"}, {591, "PA"}, {598, "PG"}, {600, "PY"}, {604, "PE"} + , {608, "PH"}, {612, "PN"}, {616, "PL"}, {620, "PT"}, {624, "GW"} + , {626, "TL"}, {630, "PR"}, {634, "QA"}, {634, "QA"}, {638, "RE"} + , {642, "RO"}, {643, "RU"}, {646, "RW"}, {654, "SH"}, {659, "KN"} + , {660, "AI"}, {662, "LC"}, {666, "PM"}, {670, "VC"}, {674, "SM"} + , {678, "ST"}, {682, "SA"}, {686, "SN"}, {690, "SC"}, {694, "SL"} + , {702, "SG"}, {703, "SK"}, {704, "VN"}, {705, "SI"}, {706, "SO"} + , {710, "ZA"}, {716, "ZW"}, {724, "ES"}, {732, "EH"}, {736, "SD"} + , {740, "SR"}, {744, "SJ"}, {748, "SZ"}, {752, "SE"}, {756, "CH"} + , {760, "SY"}, {762, "TJ"}, {764, "TH"}, {768, "TG"}, {772, "TK"} + , {776, "TO"}, {780, "TT"}, {784, "AE"}, {788, "TN"}, {792, "TR"} + , {795, "TM"}, {796, "TC"}, {798, "TV"}, {800, "UG"}, {804, "UA"} + , {807, "MK"}, {818, "EG"}, {826, "GB"}, {834, "TZ"}, {840, "US"} + , {850, "VI"}, {854, "BF"}, {858, "UY"}, {860, "UZ"}, {862, "VE"} + , {876, "WF"}, {882, "WS"}, {887, "YE"}, {891, "CS"}, {894, "ZM"} + }; + + if (error || host_list.empty()) + { + // this is used to indicate that we shouldn't + // try to resolve it again + p->set_country("--"); + return; + } + + int idx = 0; + while (idx < host_list.size() && !host_list[idx].is_v4()) + ++idx; + + if (idx >= host_list.size()) + { + p->set_country("--"); + return; + } + + // country is an ISO 3166 country code + int country = host_list[idx].to_v4().to_ulong() & 0xffff; + + // look up the country code in the map + const int size = sizeof(country_map)/sizeof(country_map[0]); + country_entry tmp = {country, ""}; + country_entry const* j = + std::lower_bound(country_map, country_map + size, tmp + , boost::bind(&country_entry::code, _1) < boost::bind(&country_entry::code, _2)); + if (j == country_map + size + || j->code != country) + { + // unknown country! + p->set_country("!!"); +#ifndef TORRENT_DISABLE_LOGGING + debug_log("IP \"%s\" was mapped to unknown country: %d" + , print_address(p->remote().address()).c_str(), country); +#endif + return; + } + + p->set_country(j->name); + } +#endif +#endif // TORRENT_NO_DEPRECATE + + void torrent::read_resume_data(bdecode_node const& rd) + { + m_total_uploaded = rd.dict_find_int_value("total_uploaded"); + m_total_downloaded = rd.dict_find_int_value("total_downloaded"); + m_active_time = rd.dict_find_int_value("active_time"); + m_finished_time = rd.dict_find_int_value("finished_time"); + m_seeding_time = rd.dict_find_int_value("seeding_time"); + m_last_seen_complete = rd.dict_find_int_value("last_seen_complete"); + m_complete = rd.dict_find_int_value("num_complete", 0xffffff); + m_incomplete = rd.dict_find_int_value("num_incomplete", 0xffffff); + m_downloaded = rd.dict_find_int_value("num_downloaded", 0xffffff); + + if (!m_override_resume_data) + { + int up_limit_ = rd.dict_find_int_value("upload_rate_limit", -1); + if (up_limit_ != -1) set_upload_limit(up_limit_); + + int down_limit_ = rd.dict_find_int_value("download_rate_limit", -1); + if (down_limit_ != -1) set_download_limit(down_limit_); + + int max_connections_ = rd.dict_find_int_value("max_connections", -1); + if (max_connections_ != -1) set_max_connections(max_connections_); + + int max_uploads_ = rd.dict_find_int_value("max_uploads", -1); + if (max_uploads_ != -1) set_max_uploads(max_uploads_); + + int seed_mode_ = rd.dict_find_int_value("seed_mode", -1); + if (seed_mode_ != -1) m_seed_mode = seed_mode_ && m_torrent_file->is_valid(); + + int super_seeding_ = rd.dict_find_int_value("super_seeding", -1); + if (super_seeding_ != -1) super_seeding(super_seeding_ != 0); + + int auto_managed_ = rd.dict_find_int_value("auto_managed", -1); + if (auto_managed_ != -1) + { + m_auto_managed = auto_managed_ != 0; + + update_want_scrape(); + update_state_list(); + } + + int sequential_ = rd.dict_find_int_value("sequential_download", -1); + if (sequential_ != -1) set_sequential_download(sequential_ != 0); + + int paused_ = rd.dict_find_int_value("paused", -1); + if (paused_ != -1) + { + set_allow_peers(paused_ == 0); + m_announce_to_dht = (paused_ == 0); + m_announce_to_trackers = (paused_ == 0); + m_announce_to_lsd = (paused_ == 0); + + update_gauge(); + update_want_peers(); + update_want_scrape(); + update_state_list(); + } + int dht_ = rd.dict_find_int_value("announce_to_dht", -1); + if (dht_ != -1) m_announce_to_dht = (dht_ != 0); + int lsd_ = rd.dict_find_int_value("announce_to_lsd", -1); + if (lsd_ != -1) m_announce_to_lsd = (lsd_ != 0); + int track_ = rd.dict_find_int_value("announce_to_trackers", -1); + if (track_ != -1) m_announce_to_trackers = (track_ != 0); + +#ifndef TORRENT_DISABLE_LOGGING + debug_log("loaded resume data: max-uploads: %d max-connections: %d " + "upload-limit: %d download-limit: %d paused: %d sequential-download: %d " + "super-seeding: %d auto-managed: %d" + , max_uploads_, max_connections_, up_limit_, down_limit_ + , paused_, sequential_, super_seeding_, auto_managed_); +#endif + } + + int now = m_ses.session_time(); + int tmp = rd.dict_find_int_value("last_scrape", -1); + m_last_scrape = tmp == -1 ? (std::numeric_limits::min)() : now - tmp; + tmp = rd.dict_find_int_value("last_download", -1); + m_last_download = tmp == -1 ? (std::numeric_limits::min)() : now - tmp; + tmp = rd.dict_find_int_value("last_upload", -1); + m_last_upload = tmp == -1 ? (std::numeric_limits::min)() : now - tmp; + + if (m_use_resume_save_path) + { + std::string p = rd.dict_find_string_value("save_path"); + if (!p.empty()) + { + m_save_path = p; +#ifndef TORRENT_DISABLE_LOGGING + debug_log("loaded resume data: save-path: %s", m_save_path.c_str()); +#endif + } + } + + m_url = rd.dict_find_string_value("url"); + m_uuid = rd.dict_find_string_value("uuid"); + m_source_feed_url = rd.dict_find_string_value("feed"); + + if (!m_uuid.empty() || !m_url.empty()) + { + boost::shared_ptr me(shared_from_this()); + + // insert this torrent in the uuid index + m_ses.insert_uuid_torrent(m_uuid.empty() ? m_url : m_uuid, me); + } + + // TODO: make this more generic to not just work if files have been + // renamed, but also if they have been merged into a single file for instance + // maybe use the same format as .torrent files and reuse some code from torrent_info + // The mapped_files needs to be read both in the network thread + // and in the disk thread, since they both have their own mapped files structures + // which are kept in sync + bdecode_node mapped_files = rd.dict_find_list("mapped_files"); + if (mapped_files && mapped_files.list_size() == m_torrent_file->num_files()) + { + for (int i = 0; i < m_torrent_file->num_files(); ++i) + { + std::string new_filename = mapped_files.list_string_value_at(i); + if (new_filename.empty()) continue; + m_torrent_file->rename_file(i, new_filename); + } + } + + m_added_time = rd.dict_find_int_value("added_time", m_added_time); + m_completed_time = rd.dict_find_int_value("completed_time", m_completed_time); + if (m_completed_time != 0 && m_completed_time < m_added_time) + m_completed_time = m_added_time; + + // load file priorities except if the add_torrent_param file was set to + // override resume data + if (!m_override_resume_data || m_file_priority.empty()) + { + bdecode_node file_priority = rd.dict_find_list("file_priority"); + if (file_priority) + { + const int num_files = (std::min)(file_priority.list_size() + , m_torrent_file->num_files()); + m_file_priority.resize(num_files, 4); + for (int i = 0; i < num_files; ++i) + { + m_file_priority[i] = file_priority.list_int_value_at(i, 1); + // this is suspicious, leave seed mode + if (m_file_priority[i] == 0) m_seed_mode = false; + } + // unallocated slots are assumed to be priority 1, so cut off any + // trailing ones + int end_range = num_files - 1; + for (; end_range >= 0; --end_range) if (m_file_priority[end_range] != 1) break; + m_file_priority.resize(end_range + 1, 4); + + // initialize pad files to priority 0 + file_storage const& fs = m_torrent_file->files(); + for (int i = 0; i < (std::min)(fs.num_files(), end_range + 1); ++i) + { + if (!fs.pad_file_at(i)) continue; + m_file_priority[i] = 0; + } + + update_piece_priorities(); + } + } + + bdecode_node trackers = rd.dict_find_list("trackers"); + if (trackers) + { + if (!m_merge_resume_trackers) m_trackers.clear(); + int tier = 0; + for (int i = 0; i < trackers.list_size(); ++i) + { + bdecode_node 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) + { + announce_entry e(tier_list.list_string_value_at(j)); + if (std::find_if(m_trackers.begin(), m_trackers.end() + , boost::bind(&announce_entry::url, _1) == e.url) != m_trackers.end()) + continue; + e.tier = tier; + e.fail_limit = 0; + m_trackers.push_back(e); + } + ++tier; + } + std::sort(m_trackers.begin(), m_trackers.end(), boost::bind(&announce_entry::tier, _1) + < boost::bind(&announce_entry::tier, _2)); + + if (settings().get_bool(settings_pack::prefer_udp_trackers)) + prioritize_udp_trackers(); + } + + // 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 url_list = rd.dict_find_list("url-list"); + bdecode_node httpseeds = rd.dict_find_list("httpseeds"); + if ((url_list || httpseeds) && !m_merge_resume_http_seeds) + { + m_web_seeds.clear(); + } + + if (url_list) + { + for (int i = 0; i < url_list.list_size(); ++i) + { + std::string url = url_list.list_string_value_at(i); + if (url.empty()) continue; + if (m_torrent_file->num_files() > 1 && url[url.size()-1] != '/') url += '/'; + add_web_seed(url, web_seed_entry::url_seed); + } + } + + if (httpseeds) + { + for (int i = 0; i < httpseeds.list_size(); ++i) + { + std::string url = httpseeds.list_string_value_at(i); + if (url.empty()) continue; + add_web_seed(url, web_seed_entry::http_seed); + } + } + + if (m_torrent_file->is_merkle_torrent()) + { + bdecode_node mt = rd.dict_find_string("merkle tree"); + if (mt) + { + std::vector tree; + tree.resize(m_torrent_file->merkle_tree().size()); + std::memcpy(&tree[0], mt.string_ptr() + , (std::min)(mt.string_length(), int(tree.size()) * 20)); + if (mt.string_length() < int(tree.size()) * 20) + std::memset(&tree[0] + mt.string_length() / 20, 0 + , tree.size() - mt.string_length() / 20); + m_torrent_file->set_merkle_tree(tree); + } + else + { + // TODO: 0 if this is a merkle torrent and we can't + // restore the tree, we need to wipe all the + // bits in the have array, but not necessarily + // we might want to do a full check to see if we have + // all the pieces. This is low priority since almost + // no one uses merkle torrents + TORRENT_ASSERT(false); + } + } + + // updating some of the torrent state may have set need_save_resume_data. + // clear it here since we've just restored the resume data we already + // have. Nothing has changed from that state yet. + m_need_save_resume_data = false; + + if (m_seed_mode) + { + // some sanity checking. Maybe we shouldn't be in seed mode anymore + bdecode_node pieces = rd.dict_find("pieces"); + if (pieces && pieces.type() == bdecode_node::string_t + && pieces.string_length() == m_torrent_file->num_pieces()) + { + char const* pieces_str = pieces.string_ptr(); + for (int i = 0, end(pieces.string_length()); i < end; ++i) + { + // being in seed mode and missing a piece is not compatible. + // Leave seed mode if that happens + if ((pieces_str[i] & 1)) continue; + m_seed_mode = false; + break; + } + } + + bdecode_node piece_priority = rd.dict_find_string("piece_priority"); + if (piece_priority && piece_priority.string_length() + == m_torrent_file->num_pieces()) + { + char const* p = piece_priority.string_ptr(); + for (int i = 0; i < piece_priority.string_length(); ++i) + { + if (p[i] > 0) continue; + m_seed_mode = false; + break; + } + } + + m_verified.resize(m_torrent_file->num_pieces(), false); + } + } + + boost::shared_ptr torrent::get_torrent_copy() + { + if (!m_torrent_file->is_valid()) return boost::shared_ptr(); + if (!need_loaded()) return boost::shared_ptr(); + + return m_torrent_file; + } + + void torrent::write_resume_data(entry& ret) const + { + using namespace libtorrent::detail; // for write_*_endpoint() + ret["file-format"] = "libtorrent resume file"; + ret["file-version"] = 1; + ret["libtorrent-version"] = LIBTORRENT_VERSION; + + ret["total_uploaded"] = m_total_uploaded; + ret["total_downloaded"] = m_total_downloaded; + + ret["active_time"] = active_time(); + ret["finished_time"] = finished_time(); + ret["seeding_time"] = seeding_time(); + ret["last_seen_complete"] = m_last_seen_complete; + + ret["num_complete"] = m_complete; + ret["num_incomplete"] = m_incomplete; + ret["num_downloaded"] = m_downloaded; + + ret["sequential_download"] = m_sequential_download; + + ret["seed_mode"] = m_seed_mode; + ret["super_seeding"] = m_super_seeding; + + ret["added_time"] = m_added_time; + ret["completed_time"] = m_completed_time; + + ret["save_path"] = m_save_path; + + if (!m_url.empty()) ret["url"] = m_url; + if (!m_uuid.empty()) ret["uuid"] = m_uuid; + if (!m_source_feed_url.empty()) ret["feed"] = m_source_feed_url; + + const sha1_hash& info_hash = torrent_file().info_hash(); + ret["info-hash"] = info_hash.to_string(); + + if (valid_metadata()) + { + if (m_magnet_link || (m_save_resume_flags & torrent_handle::save_info_dict)) + { + boost::shared_array const info = torrent_file().metadata(); + int const size = torrent_file().metadata_size(); + ret["info"].preformatted().assign(&info[0], &info[0] + size); + } + } + + // blocks per piece + int num_blocks_per_piece = + static_cast(torrent_file().piece_length()) / block_size(); + ret["blocks per piece"] = num_blocks_per_piece; + + if (m_torrent_file->is_merkle_torrent()) + { + // we need to save the whole merkle hash tree + // in order to resume + std::string& tree_str = ret["merkle tree"].string(); + std::vector const& tree = m_torrent_file->merkle_tree(); + tree_str.resize(tree.size() * 20); + std::memcpy(&tree_str[0], &tree[0], tree.size() * 20); + } + + // 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()) + { + std::vector q + = m_picker->get_download_queue(); + + // unfinished pieces + ret["unfinished"] = entry::list_type(); + entry::list_type& up = ret["unfinished"].list(); + + // info for each unfinished piece + for (std::vector::const_iterator i + = q.begin(); i != q.end(); ++i) + { + if (i->finished == 0) continue; + + entry piece_struct(entry::dictionary_t); + + // the unfinished piece's index + piece_struct["piece"] = i->index; + + std::string bitmask; + const int num_bitmask_bytes + = (std::max)(num_blocks_per_piece / 8, 1); + + piece_picker::block_info const* info = m_picker->blocks_for_piece(*i); + for (int j = 0; j < num_bitmask_bytes; ++j) + { + unsigned char v = 0; + int bits = (std::min)(num_blocks_per_piece - j*8, 8); + for (int k = 0; k < bits; ++k) + v |= (info[j*8+k].state == piece_picker::block_info::state_finished) + ? (1 << k) : 0; + bitmask.append(1, v); + TORRENT_ASSERT(bits == 8 || j == num_bitmask_bytes - 1); + } + piece_struct["bitmask"] = bitmask; + // push the struct onto the unfinished-piece list + up.push_back(piece_struct); + } + } + + // save trackers + entry::list_type& tr_list = ret["trackers"].list(); + tr_list.push_back(entry::list_type()); + int tier = 0; + for (std::vector::const_iterator i = m_trackers.begin() + , end(m_trackers.end()); i != end; ++i) + { + // don't save trackers we can't trust + // TODO: 1 save the send_stats state instead of throwing them away + // it may pose an issue when downgrading though + if (i->send_stats == false) continue; + if (i->tier == tier) + { + tr_list.back().list().push_back(i->url); + } + else + { + tr_list.push_back(entry::list_t); + tr_list.back().list().push_back(i->url); + tier = i->tier; + } + } + + // save web seeds + if (!m_web_seeds.empty()) + { + entry::list_type& url_list = ret["url-list"].list(); + entry::list_type& httpseed_list = ret["httpseeds"].list(); + for (std::list::const_iterator i = m_web_seeds.begin() + , end(m_web_seeds.end()); i != end; ++i) + { + if (i->removed) continue; + if (i->type == web_seed_entry::url_seed) + url_list.push_back(i->url); + else if (i->type == web_seed_entry::http_seed) + httpseed_list.push_back(i->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. + int const max_piece + = is_checking ? m_num_checked_pieces + : m_files_checked ? m_torrent_file->num_pieces() + : 0; + + if (max_piece > 0) + { + entry::string_type& pieces = ret["pieces"].string(); + pieces.resize(max_piece); + if (!has_picker()) + { + std::memset(&pieces[0], m_have_all, pieces.size()); + } + else if (has_picker()) + { + for (int i = 0, end(pieces.size()); i < end; ++i) + pieces[i] = m_picker->have_piece(i) ? 1 : 0; + } + + if (m_seed_mode) + { + TORRENT_ASSERT(m_verified.size() == pieces.size()); + TORRENT_ASSERT(m_verifying.size() == pieces.size()); + for (int i = 0, end(pieces.size()); i < end; ++i) + pieces[i] |= m_verified[i] ? 2 : 0; + } + } + + // write renamed files + // TODO: 0 make this more generic to not just work if files have been + // renamed, but also if they have been merged into a single file for instance. + // using file_base + if (&m_torrent_file->files() != &m_torrent_file->orig_files() + && m_torrent_file->files().num_files() == m_torrent_file->orig_files().num_files()) + { + entry::list_type& fl = ret["mapped_files"].list(); + file_storage const& fs = m_torrent_file->files(); + for (int i = 0; i < fs.num_files(); ++i) + { + fl.push_back(fs.file_path(i)); + } + } + + // write local peers + + std::back_insert_iterator peers(ret["peers"].string()); + std::back_insert_iterator banned_peers(ret["banned_peers"].string()); +#if TORRENT_USE_IPV6 + std::back_insert_iterator peers6(ret["peers6"].string()); + std::back_insert_iterator banned_peers6(ret["banned_peers6"].string()); +#endif + + int num_saved_peers = 0; + + std::vector deferred_peers; + + if (m_peer_list) + { + for (peer_list::const_iterator i = m_peer_list->begin_peer() + , end(m_peer_list->end_peer()); i != end; ++i) + { + error_code ec; + torrent_peer const* p = *i; + address addr = p->address(); + if (p->is_i2p_addr) + continue; + if (p->banned) + { +#if TORRENT_USE_IPV6 + if (addr.is_v6()) + { + write_address(addr, banned_peers6); + write_uint16(p->port, banned_peers6); + } + else +#endif + { + write_address(addr, banned_peers); + write_uint16(p->port, banned_peers); + } + 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 + deferred_peers.push_back(p); + continue; + } + +#if TORRENT_USE_IPV6 + if (addr.is_v6()) + { + write_address(addr, peers6); + write_uint16(p->port, peers6); + } + else +#endif + { + write_address(addr, peers); + write_uint16(p->port, peers); + } + ++num_saved_peers; + } + } + + // if we didn't save 100 peers, fill in with second choice peers + if (num_saved_peers < 100) + { + std::random_shuffle(deferred_peers.begin(), deferred_peers.end(), randint); + for (std::vector::const_iterator i = deferred_peers.begin() + , end(deferred_peers.end()); i != end && num_saved_peers < 100; ++i) + { + torrent_peer const* p = *i; + address addr = p->address(); + +#if TORRENT_USE_IPV6 + if (addr.is_v6()) + { + write_address(addr, peers6); + write_uint16(p->port, peers6); + } + else +#endif + { + write_address(addr, peers); + write_uint16(p->port, peers); + } + ++num_saved_peers; + } + } + + ret["upload_rate_limit"] = upload_limit(); + ret["download_rate_limit"] = download_limit(); + ret["max_connections"] = max_connections(); + ret["max_uploads"] = max_uploads(); + ret["paused"] = is_torrent_paused(); + ret["announce_to_dht"] = m_announce_to_dht; + ret["announce_to_trackers"] = m_announce_to_trackers; + ret["announce_to_lsd"] = m_announce_to_lsd; + ret["auto_managed"] = m_auto_managed; + + // piece priorities and file priorities are mutually exclusive. If there + // are file priorities set, don't save piece priorities. + if (!m_file_priority.empty()) + { + + // 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_seed_mode) + { + // write file priorities + entry::list_type& file_priority = ret["file_priority"].list(); + file_priority.clear(); + for (int i = 0, end(m_file_priority.size()); i < end; ++i) + file_priority.push_back(m_file_priority[i]); + } + } + else if (has_picker()) + { + // write piece priorities + // but only if they are not set to the default + bool default_prio = true; + for (int i = 0, end(m_torrent_file->num_pieces()); i < end; ++i) + { + if (m_picker->piece_priority(i) == 4) continue; + default_prio = false; + break; + } + + if (!default_prio) + { + entry::string_type& piece_priority = ret["piece_priority"].string(); + piece_priority.resize(m_torrent_file->num_pieces()); + + for (int i = 0, end(piece_priority.size()); i < end; ++i) + piece_priority[i] = m_picker->piece_priority(i); + } + } + } + + void torrent::get_full_peer_list(std::vector& v) const + { + v.clear(); + if (!m_peer_list) return; + + v.reserve(m_peer_list->num_peers()); + for (peer_list::const_iterator i = m_peer_list->begin_peer(); + i != m_peer_list->end_peer(); ++i) + { + peer_list_entry e; + e.ip = (*i)->ip(); + e.flags = (*i)->banned ? peer_list_entry::banned : 0; + e.failcount = (*i)->failcount; + e.source = (*i)->source; + v.push_back(e); + } + } + + void torrent::get_peer_info(std::vector& v) + { + v.clear(); + for (peer_iterator i = begin(); + i != end(); ++i) + { + peer_connection* peer = *i; + 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.push_back(peer_info()); + peer_info& p = v.back(); + + peer->get_peer_info(p); +#ifndef TORRENT_NO_DEPRECATE + // deprecated in 1.1 +#ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES + if (resolving_countries()) + resolve_peer_country(peer->self()); +#endif +#endif // TORRENT_NO_DEPRECATE + } + } + + 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(0); + blk.resize(q.size() * blocks_per_piece); + // for some weird reason valgrind claims these are uninitialized + // unless it's zeroed out here (block_info has a construct that's + // supposed to initialize it) + if (!blk.empty()) + memset(&blk[0], 0, sizeof(blk[0]) * blk.size()); + + int counter = 0; + for (std::vector::const_iterator 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); + TORRENT_ASSERT(counter * blocks_per_piece + pi.blocks_in_piece <= int(blk.size())); + pi.blocks = &blk[counter * blocks_per_piece]; + int piece_size = int(torrent_file().piece_size(i->index)); + piece_picker::block_info const* info = m_picker->blocks_for_piece(*i); + for (int j = 0; j < pi.blocks_in_piece; ++j) + { + block_info& bi = pi.blocks[j]; + bi.state = info[j].state; + bi.block_size = j < pi.blocks_in_piece - 1 ? block_size() + : piece_size - (j * block_size()); + bool complete = bi.state == block_info::writing + || bi.state == block_info::finished; + if (info[j].peer == 0) + { + bi.set_peer(tcp::endpoint()); + bi.bytes_progress = complete ? bi.block_size : 0; + } + else + { + torrent_peer* tp = static_cast(info[j].peer); + TORRENT_ASSERT(tp->in_use); + if (tp->connection) + { + peer_connection* peer = static_cast(tp->connection); + TORRENT_ASSERT(peer->m_in_use); + bi.set_peer(peer->remote()); + if (bi.state == block_info::requested) + { + boost::optional pbp + = peer->downloading_piece_progress(); + if (pbp && pbp->piece_index == i->index && pbp->block_index == j) + { + bi.bytes_progress = pbp->bytes_downloaded; + TORRENT_ASSERT(bi.bytes_progress <= bi.block_size); + } + else + { + bi.bytes_progress = 0; + } + } + else + { + bi.bytes_progress = complete ? bi.block_size : 0; + } + } + else + { + bi.set_peer(tp->ip()); + bi.bytes_progress = complete ? bi.block_size : 0; + } + } + + pi.blocks[j].num_peers = info[j].num_peers; + } + pi.piece_index = i->index; + queue->push_back(pi); + } + + } + + bool torrent::connect_to_peer(torrent_peer* peerinfo, bool ignore_limit) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + TORRENT_UNUSED(ignore_limit); + + TORRENT_ASSERT(peerinfo); + TORRENT_ASSERT(peerinfo->connection == 0); + + 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() + , boost::bind(&peer_connection::remote, _1) == peerinfo->ip()); +#if TORRENT_USE_I2P + TORRENT_ASSERT(i_ == m_connections.end() + || (*i_)->type() != peer_connection::bittorrent_connection + || peerinfo->is_i2p_addr); +#else + TORRENT_ASSERT(i_ == m_connections.end() + || (*i_)->type() != peer_connection::bittorrent_connection); +#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); + + boost::shared_ptr s(new socket_type(m_ses.get_io_service())); + +#if TORRENT_USE_I2P + bool i2p = peerinfo->is_i2p_addr; + if (i2p) + { + if (m_ses.i2p_proxy().hostname.empty()) + { + // we have an i2p torrent, but we're not connected to an i2p + // SAM proxy. + if (alerts().should_post()) + alerts().emplace_alert(error_code(errors::no_i2p_router + , get_libtorrent_category())); + return false; + } + + // It's not entirely obvious why this peer connection is not marked as + // one. The main feature of a peer connection is that whether or not we + // proxy it is configurable. When we use i2p, we want to always prox + // everything via i2p. + bool ret = instantiate_connection(m_ses.get_io_service() + , m_ses.i2p_proxy(), *s, NULL, NULL, false, false); + (void)ret; + TORRENT_ASSERT(ret); + s->get()->set_destination(static_cast(peerinfo)->destination); + s->get()->set_command(i2p_stream::cmd_connect); + s->get()->set_session_id(m_ses.i2p_session()); + } + else +#endif + { + // this is where we determine if we open a regular TCP connection + // or a uTP connection. If the utp_socket_manager pointer is not passed in + // we'll instantiate a TCP connection + utp_socket_manager* sm = 0; + + 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 == 0 && !settings().get_bool(settings_pack::enable_outgoing_tcp)) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("discarding peer \"%s\": TCP connections disabled " + "[ supports-utp: %d ]", peerinfo->to_string().c_str() + , peerinfo->supports_utp); +#endif + return false; + } + + void* userdata = 0; +#ifdef TORRENT_USE_OPENSSL + if (is_ssl_torrent()) + { + userdata = m_ssl_ctx.get(); + } +#endif + + bool ret = instantiate_connection(m_ses.get_io_service() + , m_ses.proxy(), *s, userdata, sm, true, false); + (void)ret; + TORRENT_ASSERT(ret); + +#if defined TORRENT_USE_OPENSSL && BOOST_VERSION >= 104700 + if (is_ssl_torrent()) + { + // for ssl sockets, set the hostname + std::string host_name = to_hex(m_torrent_file->info_hash().to_string()); + +#define CASE(t) case socket_type_int_impl >::value: \ + s->get >()->set_host_name(host_name); break; + + switch (s->type()) + { + CASE(tcp::socket) + CASE(socks5_stream) + CASE(http_stream) + CASE(utp_stream) + default: break; + }; + } +#undef CASE +#endif + } + + m_ses.setup_socket_buffers(*s); + + peer_connection_args pack; + pack.ses = &m_ses; + pack.sett = &settings(); + pack.stats_counters = &m_ses.stats_counters(); + pack.allocator = &m_ses; + pack.disk_thread = &m_ses.disk_thread(); + pack.ios = &m_ses.get_io_service(); + pack.tor = shared_from_this(); + pack.s = s; + pack.endp = a; + pack.peerinfo = peerinfo; + + boost::shared_ptr c = boost::make_shared( + boost::cref(pack), m_ses.get_peer_id()); + + TORRENT_TRY + { +#if TORRENT_USE_ASSERTS + c->m_in_constructor = false; +#endif + + c->add_stat(boost::int64_t(peerinfo->prev_amount_download) << 10 + , boost::int64_t(peerinfo->prev_amount_upload) << 10); + peerinfo->prev_amount_download = 0; + peerinfo->prev_amount_upload = 0; + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + TORRENT_TRY { + boost::shared_ptr pp((*i)->new_connection( + peer_connection_handle(c.get()->self()))); + if (pp) c->add_extension(pp); + } TORRENT_CATCH (std::exception&) {} + } +#endif + + // add the newly connected peer to this torrent's peer list + sorted_insert(m_connections, boost::get_pointer(c)); + 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&) + { + peer_iterator i = sorted_find(m_connections, boost::get_pointer(c)); + if (i != m_connections.end()) + { + m_connections.erase(i); + update_want_peers(); + update_want_tick(); + } + c->disconnect(errors::no_error, op_bittorrent, 1); + return false; + } + + if (m_share_mode) + recalc_share_mode(); + + return peerinfo->connection != NULL; + } + + bool torrent::set_metadata(char const* metadata_buf, int metadata_size) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + if (m_torrent_file->is_valid()) return false; + + hasher h; + h.update(metadata_buf, metadata_size); + sha1_hash info_hash = h.final(); + + if (info_hash != m_torrent_file->info_hash()) + { + if (alerts().should_post()) + { + alerts().emplace_alert(get_handle() + , error_code(errors::mismatching_info_hash, get_libtorrent_category())); + } + return false; + } + + bdecode_node metadata; + error_code ec; + int ret = bdecode(metadata_buf, metadata_buf + metadata_size, metadata, ec); + if (ret != 0 || !m_torrent_file->parse_info_section(metadata, ec, 0)) + { + update_gauge(); + // this means the metadata is correct, since we + // verified it against the info-hash, but we + // failed to parse it. Pause the torrent + if (alerts().should_post()) + { + alerts().emplace_alert(get_handle(), ec); + } + set_error(errors::invalid_swarm_metadata, torrent_status::error_file_none); + pause(); + return false; + } + + update_gauge(); + + 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 + int idx = 0; + for (peer_iterator i = m_connections.begin(); + i != m_connections.end(); ++idx) + { + if ((*i)->disconnect_if_redundant()) + { + i = m_connections.begin() + idx; + --idx; + } + else + { + ++i; + } + } + + set_need_save_resume(); + + return true; + } + + namespace { + + bool connecting_time_compare(peer_connection const* lhs, peer_connection const* rhs) + { + bool lhs_connecting = lhs->is_connecting() && !lhs->is_disconnecting(); + bool rhs_connecting = rhs->is_connecting() && !rhs->is_disconnecting(); + if (lhs_connecting > rhs_connecting) return false; + if (lhs_connecting < rhs_connecting) return true; + + // 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 namespaec + + bool torrent::attach_peer(peer_connection* p) + { +// INVARIANT_CHECK; + +#ifdef TORRENT_USE_OPENSSL +#if BOOST_VERSION >= 104700 + if (is_ssl_torrent()) + { + // if this is an SSL torrent, don't allow non SSL peers on it + boost::shared_ptr s = p->get_socket(); + + // +#define SSL(t) socket_type_int_impl >::value: \ + ssl_conn = s->get >()->native_handle(); \ + break; + + SSL* ssl_conn = 0; + + switch (s->type()) + { + case SSL(tcp::socket) + case SSL(socks5_stream) + case SSL(http_stream) + case SSL(utp_stream) + }; + +#undef SSL + + if (ssl_conn == 0) + { + // don't allow non SSL peers on SSL torrents + p->disconnect(errors::requires_ssl_connection, op_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, op_ssl_handshake); + return false; + } + + if (SSL_get_SSL_CTX(ssl_conn) != m_ssl_ctx->native_handle()) + { + // if the SSL_CTX associated with this connection is + // not the one belonging to this torrent, the SSL handshake + // connected to one torrent, and the BitTorrent protocol + // to a different one. This is probably an attempt to circumvent + // access control. Don't allow it. + p->disconnect(errors::invalid_ssl_cert, op_bittorrent); + return false; + } + } +#else // BOOST_VERSION + if (is_ssl_torrent()) + { + p->disconnect(boost::asio::error::operation_not_supported, op_bittorrent); + return false; + } +#endif +#else // TORRENT_USE_OPENSSL + if (is_ssl_torrent()) + { + // Don't accidentally allow seeding of SSL torrents, just + // because libtorrent wasn't built with SSL support + p->disconnect(errors::requires_ssl_connection, op_ssl_handshake); + return false; + } +#endif // TORRENT_USE_OPENSSL + + TORRENT_ASSERT(p != 0); + 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().address(), peer_blocked_alert::ip_filter); + p->disconnect(errors::banned_by_ip_filter, op_bittorrent); + return false; + } + + if ((m_state == torrent_status::checking_files + || m_state == torrent_status::checking_resume_data) + && valid_metadata()) + { + p->disconnect(errors::torrent_not_ready, op_bittorrent); + return false; + } + + if (!m_ses.has_connection(p)) + { + p->disconnect(errors::peer_not_constructed, op_bittorrent); + return false; + } + + if (m_ses.is_aborted()) + { + p->disconnect(errors::session_closing, op_bittorrent); + return false; + } + + int connection_limit_factor = 0; + for (int i = 0; i < p->num_classes(); ++i) + { + int pc = p->class_at(i); + if (m_ses.peer_classes().at(pc) == NULL) 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; + + boost::uint64_t limit = boost::uint64_t(m_max_connections) * 100 / connection_limit_factor; + + bool maybe_replace_peer = false; + + if (m_connections.size() >= 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 wating to establish a connection + // the longest + std::vector::iterator 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, op_bittorrent); + return false; + } + (*i)->disconnect(errors::too_many_connections, op_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; + } + } + + TORRENT_TRY + { +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + boost::shared_ptr pp((*i)->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 + debug_log("CLOSING CONNECTION \"%s\" peer list full " + "connections: %d limit: %d" + , print_endpoint(p->remote()).c_str() + , int(m_connections.size()) + , m_max_connections); +#endif + p->disconnect(errors::too_many_connections, op_bittorrent); + return false; + } + peers_erased(st.erased); + update_want_peers(); + } + TORRENT_CATCH (std::exception& e) + { + TORRENT_DECLARE_DUMMY(std::exception, e); + (void)e; +#ifndef TORRENT_DISABLE_LOGGING + debug_log("CLOSING CONNECTION \"%s\" caught exception: %s" + , print_endpoint(p->remote()).c_str(), e.what()); +#endif + p->disconnect(errors::no_error, op_bittorrent); + return false; + } + TORRENT_ASSERT(sorted_find(m_connections, p) == m_connections.end()); + 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 + debug_log("incoming peer (%d)", int(m_connections.size())); +#endif + +#ifdef TORRENT_DEBUG + error_code ec; + TORRENT_ASSERT(p->remote() == p->get_socket()->remote_endpoint(ec) || ec); +#endif + + TORRENT_ASSERT(p->peer_info_struct() != NULL); + + // 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 && peer->peer_rank() < p->peer_rank()) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("CLOSING CONNECTION \"%s\" peer list full (low peer rank) " + "connections: %d limit: %d" + , print_endpoint(peer->remote()).c_str() + , int(m_connections.size()) + , m_max_connections); +#endif + peer->disconnect(errors::too_many_connections, op_bittorrent); + p->peer_disconnected_other(); + } + else + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("CLOSING CONNECTION \"%s\" peer list full (low peer rank) " + "connections: %d limit: %d" + , print_endpoint(p->remote()).c_str() + , int(m_connections.size()) + , m_max_connections); +#endif + p->disconnect(errors::too_many_connections, op_bittorrent); + // we have to do this here because from the peer's point of + // it wasn't really attached to the torrent, but we do need + // to let peer_list know we're removing it + remove_peer(p); + return false; + } + } + +#if TORRENT_USE_INVARIANT_CHECKS + if (m_peer_list) m_peer_list->check_invariant(); +#endif + + if (m_share_mode) + recalc_share_mode(); + +#ifndef TORRENT_DISABLE_LOGGING + debug_log("ATTACHED CONNECTION \"%s\" connections: %d limit: %d" + , print_endpoint(p->remote()).c_str(), int(m_connections.size()) + , m_max_connections); +#endif + + return true; + } + + bool torrent::want_tick() const + { + if (m_abort) return false; + + if (!m_connections.empty()) return true; + + // there's a deferred storage tick waiting + // to happen + if (m_storage_tick) 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_allow_peers && !m_inactive) return true; + + return false; + } + + void torrent::update_want_tick() + { + update_list(aux::session_interface::torrent_want_tick, want_tick()); + } + + // this function adjusts which lists this torrent is part of (checking, + // seeding or downloading) + void torrent::update_state_list() + { + bool is_checking = false; + bool is_downloading = false; + bool is_seeding = false; + + if (is_auto_managed() && !has_error()) + { + if (m_state == torrent_status::checking_files + || m_state == torrent_status::allocating) + { + is_checking = true; + } + else if (m_state == torrent_status::downloading_metadata + || m_state == torrent_status::downloading + || m_state == torrent_status::finished + || m_state == torrent_status::seeding + || m_state == torrent_status::downloading) + { + // 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 (m_connections.size() >= m_max_connections) return false; + + // if we're paused, obviously we're not connecting to peers + if (is_paused() || m_abort || m_graceful_pause_mode) return false; + + if ((m_state == torrent_status::checking_files + || m_state == torrent_status::checking_resume_data) + && valid_metadata()) + return false; + + // if we don't know of any more potential peers to connect to, there's + // no point in trying + if (!m_peer_list || m_peer_list->num_connect_candidates() == 0) + return false; + + // if the user disabled outgoing connections for seeding torrents, + // don't make any + if (!settings().get_bool(settings_pack::seeding_outgoing_connections) + && (m_state == torrent_status::seeding + || m_state == torrent_status::finished)) + return false; + + return true; + } + + bool torrent::want_peers_download() const + { + return (m_state == torrent_status::downloading + || m_state == torrent_status::downloading_metadata) + && want_peers(); + } + + bool torrent::want_peers_finished() const + { + return (m_state == torrent_status::finished + || m_state == torrent_status::seeding) + && want_peers(); + } + + void torrent::update_want_peers() + { + update_list(aux::session_interface::torrent_want_peers_download, want_peers_download()); + update_list(aux::session_interface::torrent_want_peers_finished, want_peers_finished()); + } + + void torrent::update_want_scrape() + { + update_list(aux::session_interface::torrent_want_scrape + , !m_allow_peers && m_auto_managed && !m_abort); + } + + namespace { + +#ifndef TORRENT_DISABLE_LOGGING + char const* list_name(int idx) + { +#define TORRENT_LIST_NAME(n) case aux::session_interface:: n: return #n; + switch (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_VAL(false, idx); + } +#undef TORRENT_LIST_NAME + return ""; + } +#endif // TORRENT_DISABLE_LOGGING + + } // anonymous namespace + + void torrent::update_list(int list, bool in) + { + link& l = m_links[list]; + std::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 + debug_log("*** UPDATE LIST [ %s : %d ]", list_name(list), int(in)); +#endif + } + + void torrent::disconnect_all(error_code const& ec, operation_t op) + { +// doesn't work with the !m_allow_peers -> m_num_peers == 0 condition +// INVARIANT_CHECK; + + while (!m_connections.empty()) + { + peer_connection* p = *m_connections.begin(); + TORRENT_ASSERT(p->associated_torrent().lock().get() == this); + +#if TORRENT_USE_ASSERTS + std::size_t size = m_connections.size(); +#endif + if (p->is_disconnecting()) + m_connections.erase(m_connections.begin()); + else + p->disconnect(ec, op); + TORRENT_ASSERT(m_connections.size() <= size); + } + + 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 + boost::int64_t lhs_transferred = lhs->statistics().total_payload_download(); + boost::int64_t rhs_transferred = rhs->statistics().total_payload_download(); + + time_point now = aux::time_now(); + boost::int64_t lhs_time_connected = total_seconds(now - lhs->connected_time()); + boost::int64_t 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 num, error_code const& ec) + { + INVARIANT_CHECK; + +#ifdef TORRENT_DEBUG + for (peer_iterator i = m_connections.begin() + , end(m_connections.end()); i != end; ++i) + { + // make sure this peer is not a dangling pointer + TORRENT_ASSERT(m_ses.has_peer(*i)); + } +#endif + int ret = 0; + while (ret < num && !m_connections.empty()) + { + peer_iterator i = std::min_element( + m_connections.begin(), m_connections.end(), compare_disconnect_peer); + + peer_connection* p = *i; + ++ret; + TORRENT_ASSERT(p->associated_torrent().lock().get() == this); +#if TORRENT_USE_ASSERTS + int num_conns = m_connections.size(); +#endif + p->disconnect(ec, op_bittorrent); + TORRENT_ASSERT(int(m_connections.size()) == num_conns - 1); + } + + return ret; + } + + // called when torrent is finished (all interesting + // pieces have been downloaded) + void torrent::finished() + { + update_state_list(); + + INVARIANT_CHECK; + + TORRENT_ASSERT(is_finished()); + + set_state(torrent_status::finished); + set_queue_position(-1); + + m_became_finished = m_ses.session_time(); + + // 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(0); + + // 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 (peer_iterator i = m_connections.begin(); + i != m_connections.end(); ++i) + { + peer_connection* p = *i; + TORRENT_ASSERT(p->associated_torrent().lock().get() == this); + if (p->upload_only()) + { +#ifndef TORRENT_DISABLE_LOGGING + p->peer_log(peer_log_alert::info, "SEED", "CLOSING CONNECTION"); +#endif + seeds.push_back(p); + } + } + std::for_each(seeds.begin(), seeds.end() + , boost::bind(&peer_connection::disconnect, _1, errors::torrent_finished + , op_bittorrent, 0)); + } + + if (m_abort) return; + + update_want_peers(); + + if (m_storage) + { + // we need to keep the object alive during this operation + inc_refcount("release_files"); + m_ses.disk_thread().async_release_files(m_storage.get() + , boost::bind(&torrent::on_cache_flushed, shared_from_this(), _1)); + } + + // 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; + + if (m_state == torrent_status::checking_resume_data + || m_state == torrent_status::checking_files + || m_state == torrent_status::allocating) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("*** RESUME_DOWNLOAD [ skipping, state: %d ]" + , int(m_state)); +#endif + return; + } + + // we're downloading now, which means we're no longer in seed mode + if (m_seed_mode) + leave_seed_mode(false); + + TORRENT_ASSERT(!is_finished()); + set_state(torrent_status::downloading); + set_queue_position((std::numeric_limits::max)()); + + 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; + + // when we're suggesting read cache pieces, we + // still need the piece picker, to keep track + // of availability counts for pieces + if (m_picker->is_seeding() + && settings().get_int(settings_pack::suggest_mode) != settings_pack::suggest_read_cache) + { + // no need for the piece picker anymore + m_picker.reset(); + 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 = m_ses.session_time(); + + // no need for this anymore + m_file_progress.clear(); + + if (!m_announcing) return; + + time_point now = aux::time_now(); + for (std::vector::iterator i = m_trackers.begin() + , end(m_trackers.end()); i != end; ++i) + { + if (i->complete_sent) continue; + i->next_announce = now; + i->min_announce = now; + } + announce_with_tracker(); + } + + // this will move the tracker with the given index + // to a prioritized position in the list (move it towards + // the beginning) and return the new index to the tracker. + int torrent::prioritize_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 > 0 && 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; + } + + 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; + } + + // 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()); + } + + // 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()) + { + // turn off super seeding if we're not a seed + if (m_super_seeding) + { + m_super_seeding = false; + set_need_save_resume(); + } + + if (is_finished() && m_state != torrent_status::finished) + finished(); + } + else + { + for (std::vector::iterator i = m_trackers.begin() + , end(m_trackers.end()); i != end; ++i) + i->complete_sent = true; + + if (m_state != torrent_status::finished + && m_state != torrent_status::seeding) + finished(); + } + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + TORRENT_TRY { + (*i)->on_files_checked(); + } TORRENT_CATCH (std::exception&) {} + } +#endif + + m_connections_initialized = true; + m_files_checked = true; + + update_want_tick(); + + for (torrent::peer_iterator i = m_connections.begin(); + i != m_connections.end();) + { + peer_connection* pc = *i; + ++i; + + // all peer connections have to initialize themselves now that the metadata + // is available + if (!m_connections_initialized) + { + if (pc->is_disconnecting()) continue; + pc->on_metadata_impl(); + if (pc->is_disconnecting()) continue; + pc->init(); + } + +#ifndef TORRENT_DISABLE_LOGGING + pc->peer_log(peer_log_alert::info, "ON_FILES_CHECKED"); +#endif + if (pc->is_interesting() && !pc->has_peer_choked()) + { + if (request_a_block(*this, *pc)) + { + inc_stats_counter(counters::unchoke_piece_picks); + pc->send_block_requests(); + } + } + } + + start_announcing(); + + maybe_connect_web_seeds(); + } + + alert_manager& torrent::alerts() const + { + TORRENT_ASSERT(is_single_thread()); + return m_ses.alerts(); + } + + bool torrent::is_seed() const + { + if (!valid_metadata()) return false; + if (m_seed_mode) return true; + if (m_have_all) return true; + if (m_picker && m_picker->num_passed() == m_picker->num_pieces()) return true; + return m_state == torrent_status::seeding; + } + + bool torrent::is_finished() const + { + if (is_seed()) return true; + + // this is slightly different from m_picker->is_finished() + // because any piece that has *passed* is considered here, + // which may be more than the piece we *have* (i.e. written to disk) + // keep in mind that num_filtered() does not include pieces we + // have that are filtered + return valid_metadata() && has_picker() + && m_torrent_file->num_pieces() - m_picker->num_filtered() - m_picker->num_passed() == 0; + } + + 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(int index, std::string const& name) + { + INVARIANT_CHECK; + + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < m_torrent_file->num_files()); + + // storage may be NULL during shutdown + if (!m_storage.get()) + { + if (alerts().should_post()) + alerts().emplace_alert(get_handle() + , index, error_code(errors::session_is_closing + , get_libtorrent_category())); + return; + } + + inc_refcount("rename_file"); + m_ses.disk_thread().async_rename_file(m_storage.get(), index, name + , boost::bind(&torrent::on_file_renamed, shared_from_this(), _1)); + return; + } + + void torrent::move_storage(std::string const& save_path, int 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 + , "", ""); + 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. Deleting files + // in this mode would cause us to (recursively) delete m_save_path, which + // is bad. + if (!valid_metadata()) + { + alerts().emplace_alert(get_handle(), m_torrent_file->info_hash()); + return; + } + + // storage may be NULL during shutdown + if (m_storage.get()) + { +#if TORRENT_USE_UNC_PATHS + std::string path = canonicalize_path(save_path); +#else + std::string const& path = save_path; +#endif + inc_refcount("move_storage"); + m_ses.disk_thread().async_move_storage(m_storage.get(), path, flags + , boost::bind(&torrent::on_storage_moved, shared_from_this(), _1)); + m_moving_storage = true; + } + else + { +#if TORRENT_USE_UNC_PATHS + m_save_path = canonicalize_path(save_path); +#else + + m_save_path = save_path; +#endif + set_need_save_resume(); + + if (alerts().should_post()) + { + alerts().emplace_alert(get_handle(), m_save_path); + } + } + } + + void torrent::on_storage_moved(disk_io_job const* j) + { + TORRENT_ASSERT(is_single_thread()); + + m_moving_storage = false; + dec_refcount("move_storage"); + if (j->ret == piece_manager::no_error || j->ret == piece_manager::need_full_check) + { + if (alerts().should_post()) + alerts().emplace_alert(get_handle(), j->buffer.string); + m_save_path = j->buffer.string; + set_need_save_resume(); + if (j->ret == piece_manager::need_full_check) + force_recheck(); + } + else + { + if (alerts().should_post()) + alerts().emplace_alert(get_handle(), j->error.ec + , resolve_filename(j->error.file), j->error.operation_str()); + } + } + + piece_manager& torrent::storage() + { + TORRENT_ASSERT(m_storage.get()); + return *m_storage; + } + + + 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(current_stats_state() == m_current_gauge_state + counters::num_checking_torrents + || m_current_gauge_state == no_gauge_state); + + for (std::vector::const_iterator i = m_time_critical_pieces.begin() + , end(m_time_critical_pieces.end()); i != end; ++i) + { + TORRENT_ASSERT(!is_seed()); + TORRENT_ASSERT(!has_picker() || !m_picker->have_piece(i->piece)); + } + + switch (current_stats_state()) + { + case counters::num_error_torrents: TORRENT_ASSERT(has_error()); break; + case counters::num_checking_torrents: +#ifdef TORRENT_NO_DEPRECATE + TORRENT_ASSERT(state() == torrent_status::checking_files); +#else + TORRENT_ASSERT(state() == torrent_status::checking_files + || state() == torrent_status::queued_for_checking); +#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_allow_peers || m_graceful_pause_mode)); + break; + case counters::num_queued_seeding_torrents: + TORRENT_ASSERT((!m_allow_peers || m_graceful_pause_mode) && is_seed()); break; + } + + if (m_torrent_file) + { + TORRENT_ASSERT(m_info_hash == m_torrent_file->info_hash()); + } + +#if defined TORRENT_DEBUG || defined TORRENT_RELEASE_ASSERTS + for (int i = 0; i < aux::session_interface::num_torrent_lists; ++i) + { + if (!m_links[i].in_list()) continue; + int index = m_links[i].index; + + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < int(m_ses.torrent_list(i).size())); + } +#endif + + if (!is_loaded()) return; + + 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_allow_peers && m_auto_managed && !m_abort) == m_links[aux::session_interface::torrent_want_scrape].in_list()); + + bool is_checking = false; + bool is_downloading = false; + bool is_seeding = false; + + if (is_auto_managed() && !has_error()) + { + if (m_state == torrent_status::checking_files + || m_state == torrent_status::allocating) + { + is_checking = true; + } + else if (m_state == torrent_status::downloading_metadata + || m_state == torrent_status::downloading + || m_state == torrent_status::finished + || m_state == torrent_status::seeding + || m_state == torrent_status::downloading) + { + 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); + + TORRENT_ASSERT(!m_resume_data || m_resume_data->node.type() == bdecode_node::dict_t + || m_resume_data->node.type() == bdecode_node::none_t); + + int seeds = 0; + int num_uploads = 0; + std::map num_requests; + for (const_peer_iterator i = this->begin(); i != this->end(); ++i) + { +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + // make sure this peer is not a dangling pointer + TORRENT_ASSERT(m_ses.has_peer(*i)); +#endif + peer_connection const& p = *(*i); + + if (p.peer_info_struct() && p.peer_info_struct()->seed) + ++seeds; + + for (std::vector::const_iterator j = p.request_queue().begin() + , end(p.request_queue().end()); j != end; ++j) + { + if (!j->not_wanted && !j->timed_out) ++num_requests[j->block]; + } + + 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]; + } + + if (!p.is_choked() && !p.ignore_unchoke_slots()) ++num_uploads; + torrent* associated_torrent = p.associated_torrent().lock().get(); + if (associated_torrent != this && associated_torrent != 0) + TORRENT_ASSERT(false); + } + TORRENT_ASSERT(num_uploads == int(m_num_uploads)); + TORRENT_ASSERT(seeds == int(m_num_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) + { + 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); + fprintf(stderr, "peer: %s\n", print_endpoint(p.remote()).c_str()); + for (std::vector::const_iterator k = p.request_queue().begin() + , end2(p.request_queue().end()); k != end2; ++k) + { + fprintf(stderr, " rq: (%d, %d) %s %s %s\n", k->block.piece_index + , k->block.block_index, k->not_wanted ? "not-wanted" : "" + , k->timed_out ? "timed-out" : "", k->busy ? "busy": ""); + } + for (std::vector::const_iterator k = p.download_queue().begin() + , end2(p.download_queue().end()); k != end2; ++k) + { + fprintf(stderr, " dq: (%d, %d) %s %s %s\n", k->block.piece_index + , k->block.block_index, k->not_wanted ? "not-wanted" : "" + , k->timed_out ? "timed-out" : "", k->busy ? "busy": ""); + } + } + TORRENT_ASSERT(false); + } + } + } + TORRENT_ASSERT(num_have() >= m_picker->num_have_filtered()); + } + + 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_peer() != m_peer_list->end_peer()) + { + peer_list::const_iterator i = m_peer_list->begin_peer(); + peer_list::const_iterator prev = i++; + peer_list::const_iterator end(m_peer_list->end_peer()); + peer_address_compare cmp; + for (; i != end; ++i, ++prev) + { + TORRENT_ASSERT(!cmp(*i, *prev)); + } + } +#endif + + boost::int64_t total_done = quantized_bytes_done(); + if (m_torrent_file->is_valid()) + { + if (is_seed()) + TORRENT_ASSERT(total_done == m_torrent_file->total_size()); + else + TORRENT_ASSERT(total_done != m_torrent_file->total_size() || !m_files_checked); + + TORRENT_ASSERT(block_size() <= m_torrent_file->piece_length()); + } + else + { + TORRENT_ASSERT(total_done == 0); + } +/* + 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); + } + + m_file_progress.check_invariant(m_torrent_file->files()); + } +#endif + + void torrent::set_sequential_download(bool 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() + { + set_queue_position(queue_position() == 0 + ? queue_position() : queue_position() - 1); + } + + void torrent::queue_down() + { + set_queue_position(queue_position() + 1); + } + + void torrent::set_queue_position(int p) + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT((p == -1) == is_finished() + || (!m_auto_managed && p == -1) + || (m_abort && p == -1)); + if (is_finished() && p != -1) return; + if (p == m_sequence_number) return; + + TORRENT_ASSERT(p >= -1); + + state_updated(); + + m_ses.set_queue_position(this, p); + } + + void torrent::set_max_uploads(int limit, bool state_update) + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(limit >= -1); + if (limit <= 0) limit = (1<<24)-1; + if (m_max_uploads != limit && state_update) state_updated(); + m_max_uploads = limit; +#ifndef TORRENT_DISABLE_LOGGING + 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 state_update) + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(limit >= -1); + if (limit <= 0) limit = (1<<24)-1; + if (m_max_connections != limit && state_update) state_updated(); + m_max_connections = limit; + update_want_peers(); + +#ifndef TORRENT_DISABLE_LOGGING + debug_log("*** set-max-connections: %d", m_max_connections); +#endif + + if (num_peers() > int(m_max_connections)) + { + disconnect_peers(num_peers() - m_max_connections + , error_code(errors::too_many_connections, get_libtorrent_category())); + } + + if (state_update) + set_need_save_resume(); + } + + void torrent::set_upload_limit(int 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 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 channel, bool state_update) + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(limit >= -1); + if (limit <= 0) limit = 0; + + if (m_peer_class == 0 && limit == 0) return; + + if (m_peer_class == 0) + 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 == 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 channel) const + { + TORRENT_ASSERT(is_single_thread()); + + if (m_peer_class == 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(int const options) + { + TORRENT_ASSERT(is_single_thread()); + +#ifndef TORRENT_DISABLE_LOGGING + log_to_all_peers("deleting files"); +#endif + + disconnect_all(errors::torrent_removed, op_bittorrent); + stop_announcing(); + + // storage may be NULL during shutdown + if (m_storage.get()) + { + TORRENT_ASSERT(m_storage); + inc_refcount("delete_files"); + m_ses.disk_thread().async_delete_files(m_storage.get(), options + , boost::bind(&torrent::on_files_deleted, shared_from_this(), _1)); + m_deleted = true; + return true; + } + return false; + } + + void torrent::clear_error() + { + TORRENT_ASSERT(is_single_thread()); + if (!m_error) return; + bool checking_files = should_check_files(); + m_ses.trigger_auto_manage(); + m_error = error_code(); + m_error_file = torrent_status::error_file_none; + + update_gauge(); + state_updated(); + update_want_peers(); + update_state_list(); + + // if we haven't downloaded the metadata from m_url, try again + if (!m_url.empty() && !m_torrent_file->is_valid()) + { + start_download_url(); + return; + } + // if the error happened during initialization, try again now + if (!m_connections_initialized && valid_metadata()) init(); + if (!checking_files && should_check_files()) + start_checking(); + } + std::string torrent::resolve_filename(int file) const + { + if (file == torrent_status::error_file_none) return ""; + if (file == torrent_status::error_file_url) return m_url; + if (file == torrent_status::error_file_ssl_ctx) return "SSL Context"; + if (file == torrent_status::error_file_metadata) return "metadata (from user load function)"; + + if (m_storage && file >= 0) + { + file_storage const& st = m_torrent_file->files(); + return combine_path(m_save_path, st.file_path(file)); + } + else + { + return m_save_path; + } + } + + void torrent::set_error(error_code const& ec, int 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]; + 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 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 { + + int clamped_subtract(int a, int b) + { + if (a < b) return 0; + return a - b; + } + + int clamped_subtract_s16(int a, int b) + { + if (a + (std::numeric_limits::min)() < b) + return (std::numeric_limits::min)(); + return 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 seconds) + { + if (m_peer_list) + { + for (peer_list::iterator j = m_peer_list->begin_peer() + , end(m_peer_list->end_peer()); j != end; ++j) + { + torrent_peer* pe = *j; + + pe->last_optimistically_unchoked + = clamped_subtract(pe->last_optimistically_unchoked, seconds); + pe->last_connected = clamped_subtract(pe->last_connected, seconds); + } + } + + // m_active_time, m_seeding_time and m_finished_time are absolute cunters + // of the historical time we've spent in each state. The current time + // we've spent in those states (this session) is calculated by + // session_time() - m_started + // session_time() - m_became_seed + // session_time() - m_became_finished respectively. If any of the + // comparison points were pulled back to the oldest representable value (0) + // the left-over time must be transferred into the m_*_time counters. + + if (m_started < seconds && !is_paused()) + { + int lost_seconds = seconds - m_started; + m_active_time += lost_seconds; + } + m_started = clamped_subtract(m_started, seconds); + + if (m_became_seed < seconds && is_seed()) + { + int lost_seconds = seconds - m_became_seed; + m_seeding_time += lost_seconds; + } + m_became_seed = clamped_subtract(m_became_seed, seconds); + + if (m_finished_time < seconds && is_finished()) + { + int lost_seconds = seconds - m_became_finished; + m_finished_time += lost_seconds; + } + m_became_finished = clamped_subtract(m_became_finished, seconds); + + m_last_upload = clamped_subtract_s16(m_last_upload, seconds); + m_last_download = clamped_subtract_s16(m_last_download, seconds); + m_last_scrape = clamped_subtract_s16(m_last_scrape, seconds); + + m_last_saved_resume = clamped_subtract(m_last_saved_resume, seconds); + m_upload_mode_time = clamped_subtract(m_upload_mode_time, 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; + + boost::int64_t fin_time = finished_time(); + boost::int64_t download_time = int(active_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 + boost::int64_t downloaded = (std::max)(m_total_downloaded, m_torrent_file->total_size()); + if (fin_time < s.get_int(settings_pack::seed_time_limit) + && (download_time > 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() && (m_ses.session_time() - m_started) < 30 * 60) + ret |= recently_started; + + // if we have any scrape data, use it to calculate + // seed rank + int seeds = 0; + int downloaders = 0; + + if (m_complete != 0xffffff) seeds = m_complete; + else seeds = m_peer_list ? m_peer_list->num_seeds() : 0; + + if (m_incomplete != 0xffffff) downloaders = m_incomplete; + else downloaders = m_peer_list ? m_peer_list->num_peers() - m_peer_list->num_seeds() : 0; + + if (seeds == 0) + { + ret |= no_seeds; + ret |= downloaders & prio_mask; + } + else + { + ret |= ((1 + downloaders) * scale / seeds) & prio_mask; + } + + return ret; + } + + // this is an async operation triggered by the client + // TODO: add a flag to ignore stats, and only care about resume data for + // content. For unchanged files, don't trigger a load of the metadata + // just to save an empty resume data file + void torrent::save_resume_data(int flags) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + if (!valid_metadata()) + { + alerts().emplace_alert(get_handle() + , errors::no_metadata); + return; + } + + if ((flags & torrent_handle::only_if_modified) && !m_need_save_resume_data) + { + alerts().emplace_alert(get_handle() + , errors::resume_data_not_modified); + return; + } + + m_need_save_resume_data = false; + m_last_saved_resume = m_ses.session_time(); + m_save_resume_flags = boost::uint8_t(flags); + state_updated(); + + if (m_state == torrent_status::checking_files + || m_state == torrent_status::checking_resume_data) + { + if (!need_loaded()) + { + alerts().emplace_alert(get_handle() + , m_error); + return; + } + + // storage may be NULL during shutdown + if (!m_storage) + { + TORRENT_ASSERT(m_abort); + alerts().emplace_alert(get_handle() + , boost::asio::error::operation_aborted); + return; + } + + boost::shared_ptr rd(new entry); + write_resume_data(*rd); + alerts().emplace_alert(rd, get_handle()); + return; + } + + // TODO: 3 this really needs to be moved to do_async_save_resume_data. + // flags need to be passed on + if ((flags & torrent_handle::flush_disk_cache) && m_storage.get()) + m_ses.disk_thread().async_release_files(m_storage.get()); + + m_ses.queue_async_resume_data(shared_from_this()); + } + + bool torrent::do_async_save_resume_data() + { + if (!need_loaded()) + { + alerts().emplace_alert(get_handle(), m_error); + return false; + } + // storage may be NULL during shutdown + if (!m_storage) + { + TORRENT_ASSERT(m_abort); + alerts().emplace_alert(get_handle() + , boost::asio::error::operation_aborted); + return false; + } + inc_refcount("save_resume"); + m_ses.disk_thread().async_save_resume_data(m_storage.get() + , boost::bind(&torrent::on_save_resume_data, shared_from_this(), _1)); + return true; + } + + bool torrent::should_check_files() const + { + TORRENT_ASSERT(is_single_thread()); + // #error should m_allow_peers really affect checking? + return m_state == torrent_status::checking_files + && m_allow_peers + && !has_error() + && !m_abort + && !m_graceful_pause_mode + && !m_ses.is_paused(); + } + + void torrent::flush_cache() + { + TORRENT_ASSERT(is_single_thread()); + + // storage may be NULL during shutdown + if (!m_storage) + { + TORRENT_ASSERT(m_abort); + return; + } + inc_refcount("release_files"); + m_ses.disk_thread().async_release_files(m_storage.get() + , boost::bind(&torrent::on_cache_flushed, shared_from_this(), _1)); + } + + void torrent::on_cache_flushed(disk_io_job const*) + { + dec_refcount("release_files"); + TORRENT_ASSERT(is_single_thread()); + + if (m_ses.is_aborted()) return; + + if (alerts().should_post()) + alerts().emplace_alert(get_handle()); + } + + bool torrent::is_paused() const + { + return !m_allow_peers || m_ses.is_paused() || m_graceful_pause_mode; + } + + void torrent::pause(bool graceful) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + if (m_allow_peers) + { + // we need to save this new state + set_need_save_resume(); + } + + int const flags = graceful ? flag_graceful_pause : 0; + set_allow_peers(false, flags | flag_clear_disk_cache); + } + + void torrent::do_pause(bool const clear_disk_cache) + { + 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 (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + TORRENT_TRY { + if ((*i)->on_pause()) return; + } TORRENT_CATCH (std::exception&) {} + } +#endif + + m_need_connect_boost = true; + m_inactive = false; + + update_state_list(); + update_want_tick(); + + m_active_time += m_ses.session_time() - m_started; + + if (is_seed()) + m_seeding_time += m_ses.session_time() - m_became_seed; + + if (is_finished()) + m_finished_time += m_ses.session_time() - m_became_finished; + + state_updated(); + update_want_peers(); + update_want_scrape(); + +#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, op_bittorrent); + return; + } + + if (!m_graceful_pause_mode) + { + // this will make the storage close all + // files and flush all cached data + if (m_storage.get() && clear_disk_cache) + { + TORRENT_ASSERT(m_storage); + m_ses.disk_thread().async_stop_torrent(m_storage.get() + , boost::bind(&torrent::on_torrent_paused, shared_from_this(), _1)); + } + else + { + if (alerts().should_post()) + alerts().emplace_alert(get_handle()); + } + + disconnect_all(errors::torrent_paused, op_bittorrent); + } + else + { + // disconnect all peers with no outstanding data to receive + // and choke all remaining peers to prevent responding to new + // requests + std::vector to_disconnect; + for (peer_iterator i = m_connections.begin(); + i != m_connections.end(); ++i) + { + peer_connection* p = *i; + 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; + } + + to_disconnect.push_back(p); + } + for (peer_iterator i = to_disconnect.begin(); i != to_disconnect.end(); ++i) + { + peer_connection* p = *i; + + // 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, op_bittorrent); + } + } + + stop_announcing(); + + // if the torrent is pinned, we should not unload it + if (!is_pinned()) + { + m_ses.evict_torrent(this); + } + } + +#ifndef TORRENT_DISABLE_LOGGING + void torrent::log_to_all_peers(char const* message) + { + TORRENT_ASSERT(is_single_thread()); + for (peer_iterator i = m_connections.begin(); + i != m_connections.end(); ++i) + { + (*i)->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. + void torrent::add_web_seed(std::string const& url, web_seed_entry::type_t type) + { + web_seed_t ent(url, type); + // don't add duplicates + if (std::find(m_web_seeds.begin(), m_web_seeds.end(), ent) != m_web_seeds.end()) return; + m_web_seeds.push_back(ent); + set_need_save_resume(); + } + + void torrent::add_web_seed(std::string const& url, web_seed_entry::type_t type + , std::string const& auth, web_seed_entry::headers_t const& extra_headers) + { + web_seed_t ent(url, type, auth, extra_headers); + // don't add duplicates + if (std::find(m_web_seeds.begin(), m_web_seeds.end(), ent) != m_web_seeds.end()) return; + m_web_seeds.push_back(ent); + set_need_save_resume(); + } + + void torrent::set_allow_peers(bool b, int 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 (m_connections.empty()) + flags &= ~flag_graceful_pause; + + if (m_allow_peers == 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_allow_peers == false + && m_graceful_pause_mode == true + && (flags & flag_graceful_pause) == 0) + { + m_graceful_pause_mode = false; + update_gauge(); + do_pause(); + } + return; + } + + m_allow_peers = b; + if (!m_ses.is_paused()) + m_graceful_pause_mode = (flags & flag_graceful_pause) ? true : false; + + if (!b) + { + m_announce_to_dht = false; + m_announce_to_trackers = false; + m_announce_to_lsd = false; + } + + update_gauge(); + update_want_scrape(); + update_want_peers(); + update_state_list(); + state_updated(); + + if (!b) + { + do_pause(flags & flag_clear_disk_cache); + } + else + { + do_resume(); + } + + } + + void torrent::resume() + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + if (m_allow_peers + && 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_allow_peers = true; + if (!m_ses.is_paused()) m_graceful_pause_mode = false; + + update_gauge(); + + // we need to save this new state + set_need_save_resume(); + + update_want_scrape(); + + do_resume(); + } + + void torrent::do_resume() + { + TORRENT_ASSERT(is_single_thread()); + if (is_paused()) + { + update_want_tick(); + return; + } + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + TORRENT_TRY { + if ((*i)->on_resume()) return; + } TORRENT_CATCH (std::exception&) {} + } +#endif + + if (alerts().should_post()) + alerts().emplace_alert(get_handle()); + + m_started = m_ses.session_time(); + if (is_seed()) m_became_seed = m_started; + if (is_finished()) m_became_finished = m_started; + + clear_error(); + + if (m_state == torrent_status::checking_files) + { + if (m_auto_managed) m_ses.trigger_auto_manage(); + if (should_check_files()) start_checking(); + } + + state_updated(); + update_want_peers(); + update_want_tick(); + update_want_scrape(); + + if (m_state == torrent_status::checking_files) return; + + start_announcing(); + + do_connect_boost(); + } + + void torrent::update_tracker_timer(time_point now) + { + TORRENT_ASSERT(is_single_thread()); + if (!m_announcing) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("*** update tracker timer: not announcing"); +#endif + return; + } + + time_point next_announce = max_time(); + int tier = INT_MAX; + + bool found_working = false; + + for (std::vector::iterator i = m_trackers.begin() + , end(m_trackers.end()); i != end; ++i) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("*** tracker: \"%s\" " + "[ tiers: %d trackers: %d" + " found: %d i->tier: %d tier: %d" + " working: %d fails: %d limit: %d upd: %d ]" + , i->url.c_str(), settings().get_bool(settings_pack::announce_to_all_tiers) + , settings().get_bool(settings_pack::announce_to_all_trackers), found_working + , i->tier, tier, i->is_working(), i->fails, i->fail_limit + , i->updating); +#endif + if (settings().get_bool(settings_pack::announce_to_all_tiers) + && found_working + && i->tier <= tier + && tier != INT_MAX) + continue; + + if (i->tier > tier && !settings().get_bool(settings_pack::announce_to_all_tiers)) break; + if (i->is_working()) { tier = i->tier; found_working = false; } + if (i->fails >= i->fail_limit && i->fail_limit != 0) continue; + if (i->updating) + { + found_working = true; + } + else + { + time_point next_tracker_announce = (std::max)(i->next_announce, i->min_announce); + if (next_tracker_announce < next_announce + && (!found_working || i->is_working())) + next_announce = next_tracker_announce; + } + if (i->is_working()) found_working = true; + if (found_working + && !settings().get_bool(settings_pack::announce_to_all_trackers) + && !settings().get_bool(settings_pack::announce_to_all_tiers)) break; + } + + if (next_announce <= now) next_announce = now; + +#ifndef TORRENT_DISABLE_LOGGING + debug_log("*** update tracker timer: next_announce < now %d" + " m_waiting_tracker: %d next_announce_in: %d" + , next_announce <= now, m_waiting_tracker + , int(total_seconds(now - next_announce))); +#endif + + // don't re-issue the timer if it's the same expiration time as last time + // if m_waiting_tracker is false, expires_at() is undefined + if (m_waiting_tracker && m_tracker_timer.expires_at() == next_announce) return; + + m_waiting_tracker = true; + error_code ec; + boost::weak_ptr self(shared_from_this()); + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("tracker::on_tracker_announce_disp"); +#endif + m_tracker_timer.expires_at(next_announce, ec); + m_tracker_timer.async_wait(boost::bind(&torrent::on_tracker_announce_disp, self, _1)); + } + + void torrent::start_announcing() + { + TORRENT_ASSERT(is_single_thread()); + 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_torrent_file->is_valid() && !m_url.empty()) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("start_announcing(), downloading URL"); +#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 + std::for_each(m_trackers.begin(), m_trackers.end() + , boost::bind(&announce_entry::reset, _1)); + } + + // reset the stats, since from the tracker's + // point of view, this is a new session + m_total_failed_bytes = 0; + m_total_redundant_bytes = 0; + m_stat.clear(); + + update_want_tick(); + + announce_with_tracker(); + + lsd_announce(); + } + + void torrent::stop_announcing() + { + TORRENT_ASSERT(is_single_thread()); + if (!m_announcing) return; + + error_code ec; + m_tracker_timer.cancel(ec); + + m_announcing = false; + + time_point now = aux::time_now(); + for (std::vector::iterator i = m_trackers.begin() + , end(m_trackers.end()); i != end; ++i) + { + i->next_announce = now; + i->min_announce = now; + } + announce_with_tracker(tracker_request::stopped); + } + + int torrent::finished_time() const + { + // m_finished_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_finished_time + ((!is_finished() || is_paused()) ? 0 + : (m_ses.session_time() - m_became_finished)); + } + + int torrent::active_time() const + { + // 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 + (is_paused() ? 0 + : m_ses.session_time() - m_started); + } + + int torrent::seeding_time() const + { + // 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 + ((!is_seed() || is_paused()) ? 0 + : m_ses.session_time() - m_became_seed); + } + + void torrent::second_tick(int tick_interval_ms) + { + TORRENT_ASSERT(want_tick()); + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + boost::weak_ptr self(shared_from_this()); + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + TORRENT_TRY { + (*i)->tick(); + } TORRENT_CATCH (std::exception&) {} + } + + 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 + && int(m_ses.session_time() - m_upload_mode_time) + >= settings().get_int(settings_pack::optimistic_disk_retry)) + { + set_upload_mode(false); + } + + if (m_storage_tick > 0 && is_loaded()) + { + --m_storage_tick; + if (m_storage_tick == 0 && m_storage) + { + m_ses.disk_thread().async_tick_torrent(&storage() + , boost::bind(&torrent::on_disk_tick_done + , shared_from_this(), _1)); + update_want_tick(); + } + } + + if (is_paused() && !m_graceful_pause_mode) + { + // let the stats fade out to 0 + m_stat.second_tick(tick_interval_ms); + // 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(); + else + update_want_tick(); + + return; + } + if (m_need_suggest_pieces_refresh) + do_refresh_suggest_pieces(); + + if (settings().get_bool(settings_pack::rate_limit_ip_overhead)) + { + int up_limit = upload_limit(); + int 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); + } + } + + // ---- 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(), boost::bind(&partial_piece_info::piece_index, _1) + < boost::bind(&partial_piece_info::piece_index, _2)); + + printf("average piece download time: %.2f s (+/- %.2f s)\n" + , m_average_piece_time / 1000.f + , m_piece_time_deviation / 1000.f); + for (std::vector::iterator i = queue.begin() + , end(queue.end()); i != end; ++i) + { + 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(); + } + + // ---- WEB SEEDS ---- + + maybe_connect_web_seeds(); + + m_swarm_last_seen_complete = m_last_seen_complete; + int idx = 0; + for (peer_iterator i = m_connections.begin(); + i != m_connections.end(); ++idx) + { + // keep the peer object alive while we're + // inspecting it + boost::shared_ptr p = (*i)->self(); + ++i; + + // 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 + TORRENT_TRY { + p->second_tick(tick_interval_ms); + } + TORRENT_CATCH (std::exception& e) + { + TORRENT_DECLARE_DUMMY(std::exception, e); + (void)e; +#ifndef TORRENT_DISABLE_LOGGING + p->peer_log(peer_log_alert::info, "ERROR", "%s", e.what()); +#endif + p->disconnect(errors::no_error, op_bittorrent, 1); + } + + if (p->is_disconnecting()) + { + i = m_connections.begin() + idx; + --idx; + } + } + if (m_ses.alerts().should_post()) + m_ses.alerts().emplace_alert(get_handle(), tick_interval_ms, m_stat); + + m_total_uploaded += m_stat.last_payload_uploaded(); + m_total_downloaded += m_stat.last_payload_downloaded(); + m_stat.second_tick(tick_interval_ms); + + // these counters are saved in the resume data, since they updated + // we need to save the resume data too + m_need_save_resume_data = true; + + // if the rate is 0, there's no update because of network transfers + if (m_stat.low_pass_upload_rate() > 0 || m_stat.low_pass_download_rate() > 0) + state_updated(); + + // this section determines whether the torrent is active or not. When it + // changes state, it may also trigger the auto-manage logic to reconsider + // which torrents should be queued and started. There is a low pass + // filter in order to avoid flapping (auto_manage_startup). + bool is_inactive = is_inactive_internal(); + + if (settings().get_bool(settings_pack::dont_count_slow_torrents)) + { + if (is_inactive != m_inactive + && !m_pending_active_change) + { + int delay = settings().get_int(settings_pack::auto_manage_startup); + m_inactivity_timer.expires_from_now(seconds(delay)); + m_inactivity_timer.async_wait(boost::bind(&torrent::on_inactivity_tick + , shared_from_this(), _1)); + m_pending_active_change = true; + } + else if (is_inactive == m_inactive + && m_pending_active_change) + { + m_inactivity_timer.cancel(); + } + } + + 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) + { + m_pending_active_change = false; + + if (ec) return; + + bool 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(); + } + + 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 (!is_finished() && !m_web_seeds.empty() && m_files_checked + && int(m_connections.size()) < m_max_connections + && m_ses.num_connections() < settings().get_int(settings_pack::connections_limit)) + { + // keep trying web-seeds if there are any + // first find out which web seeds we are connected to + for (std::list::iterator i = m_web_seeds.begin(); + i != m_web_seeds.end();) + { + std::list::iterator w = i++; + if (w->peer_info.connection) continue; + if (w->retry > aux::time_now()) continue; + if (w->resolving) continue; + if (w->removed) continue; + + connect_to_url_seed(w); + } + } + } + + void torrent::recalc_share_mode() + { + TORRENT_ASSERT(share_mode()); + if (is_seed()) return; + + int 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 (peer_iterator i = m_connections.begin() + , end(m_connections.end()); i != end; ++i) + { + peer_connection* p = *i; + 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 ((*i)->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 90% 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 to_disconnect = num_seeds - num_peers / 2; + std::vector seeds; + seeds.reserve(num_seeds); + for (peer_iterator i = m_connections.begin() + , end(m_connections.end()); i != end; ++i) + { + peer_connection* p = *i; + if (p->is_seed()) seeds.push_back(p); + } + + std::random_shuffle(seeds.begin(), seeds.end(), randint); + TORRENT_ASSERT(to_disconnect <= int(seeds.size())); + for (int i = 0; i < to_disconnect; ++i) + seeds[i]->disconnect(errors::upload_upload_connection + , op_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 num_downloaded_pieces = (std::max)(m_picker->num_have() + , pieces_in_torrent - m_picker->num_filtered()); + + if (boost::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 + + std::vector rarest_pieces; + + int num_pieces = m_torrent_file->num_pieces(); + int rarest_rarity = INT_MAX; + for (int i = 0; i < 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, 1); + continue; + } + // don't count pieces we already have or are trying to download + if (ps.priority > 0 || ps.have) continue; + if (int(ps.peer_count) > rarest_rarity) continue; + if (int(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 pick = random() % rarest_pieces.size(); + bool was_finished = is_finished(); + m_picker->set_piece_priority(rarest_pieces[pick], 1); + update_gauge(); + update_peer_interest(was_finished); + update_want_peers(); + } + +#ifndef TORRENT_NO_DEPRECATE + // TODO: 2 this should probably be removed + void torrent::refresh_explicit_cache(int cache_size) + { + TORRENT_ASSERT(is_single_thread()); + if (!ready_for_connections()) return; + + if (m_abort) return; + TORRENT_ASSERT(m_storage); + + if (!is_loaded()) return; + + // rotate the cached pieces + cache_status status; + m_ses.disk_thread().get_cache_info(&status, false, m_storage.get()); + + // add blocks_per_piece / 2 in order to round to closest whole piece + int blocks_per_piece = m_torrent_file->piece_length() / block_size(); + int num_cache_pieces = (cache_size + blocks_per_piece / 2) / blocks_per_piece; + if (num_cache_pieces > m_torrent_file->num_pieces()) + num_cache_pieces = m_torrent_file->num_pieces(); + + std::vector avail_vec; + if (has_picker()) + { + m_picker->get_availability(avail_vec); + } + else + { + // we don't keep track of availability, do it the expensive way + // do a linear search from the first piece + for (int i = 0; i < m_torrent_file->num_pieces(); ++i) + { + int availability = 0; + if (!have_piece(i)) + { + avail_vec.push_back(INT_MAX); + continue; + } + + for (const_peer_iterator j = this->begin(); j != this->end(); ++j) + if ((*j)->has_piece(i)) ++availability; + avail_vec.push_back(availability); + } + } + + // now pick the num_cache_pieces rarest pieces from avail_vec + std::vector > pieces(m_torrent_file->num_pieces()); + for (int i = 0; i < m_torrent_file->num_pieces(); ++i) + { + pieces[i].second = i; + if (!have_piece(i)) pieces[i].first = INT_MAX; + else pieces[i].first = avail_vec[i]; + } + + // remove write cache entries + status.pieces.erase(std::remove_if(status.pieces.begin(), status.pieces.end() + , boost::bind(&cached_piece_info::kind, _1) == cached_piece_info::write_cache) + , status.pieces.end()); + + // decrease the availability of the pieces that are + // already in the read cache, to move them closer to + // the beginning of the pieces list, and more likely + // to be included in this round of cache pieces + for (std::vector::iterator i = status.pieces.begin() + , end(status.pieces.end()); i != end; ++i) + { + --pieces[i->piece].first; + } + + std::random_shuffle(pieces.begin(), pieces.end(), randint); + std::stable_sort(pieces.begin(), pieces.end() + , boost::bind(&std::pair::first, _1) < + boost::bind(&std::pair::first, _2)); + avail_vec.clear(); + for (int i = 0; i < num_cache_pieces; ++i) + { + if (pieces[i].first == INT_MAX) break; + avail_vec.push_back(pieces[i].second); + } + + if (!avail_vec.empty()) + { + // the number of pieces to cache for this torrent is proportional + // the number of peers it has, divided by the total number of peers. + // Each peer gets an equal share of the cache + + avail_vec.resize((std::min)(num_cache_pieces, int(avail_vec.size()))); + + for (std::vector::iterator i = avail_vec.begin() + , end(avail_vec.end()); i != end; ++i) + { + inc_refcount("cache_piece"); + m_ses.disk_thread().async_cache_piece(m_storage.get(), *i + , boost::bind(&torrent::on_disk_cache_complete + , shared_from_this(), _1)); + } + } + } +#endif // TORRENT_NO_DEPRECATE + + void torrent::sent_bytes(int bytes_payload, int bytes_protocol) + { + m_stat.sent_bytes(bytes_payload, bytes_protocol); + m_ses.sent_bytes(bytes_payload, bytes_protocol); + } + + void torrent::received_bytes(int bytes_payload, int bytes_protocol) + { + m_stat.received_bytes(bytes_payload, bytes_protocol); + m_ses.received_bytes(bytes_payload, bytes_protocol); + } + + void torrent::trancieve_ip_packet(int bytes, bool ipv6) + { + m_stat.trancieve_ip_packet(bytes, ipv6); + m_ses.trancieve_ip_packet(bytes, ipv6); + } + + void torrent::sent_syn(bool ipv6) + { + m_stat.sent_syn(ipv6); + m_ses.sent_syn(ipv6); + } + + void torrent::received_synack(bool ipv6) + { + m_stat.received_synack(ipv6); + m_ses.received_synack(ipv6); + } + +#if TORRENT_DEBUG_STREAMING > 0 + 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; + } + + int peer_index(libtorrent::tcp::endpoint addr + , std::vector const& peers) + { + using namespace libtorrent; + std::vector::const_iterator i = std::find_if(peers.begin() + , peers.end(), boost::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) + { + using namespace libtorrent; + + time_point 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() + , boost::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; + + 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 = ' '; } + + printf("%s%s%c%s", color, multi_req, chr, esc("0")); + } + printf("%s]", esc("0")); + if (deadline != 0.f) + printf(" deadline: %f last-req: %f timed_out: %d\n" + , deadline, last_request, timed_out); + else + printf("\n"); + } +#endif // TORRENT_DEBUG_STREAMING + + namespace { + + struct busy_block_t + { + int peers; + int index; + bool operator<(busy_block_t rhs) const { return peers < rhs.peers; } + }; + + void pick_busy_blocks(piece_picker const* picker + , int piece + , int blocks_in_piece + , int 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" + busy_block_t* busy_blocks + = TORRENT_ALLOCA(busy_block_t, blocks_in_piece); + int busy_count = 0; + + piece_picker::block_info const* info = picker->blocks_for_piece(pi); + + // pick busy blocks from the piece + for (int k = 0; k < blocks_in_piece; ++k) + { + // only consider blocks that have been requested + // and we're still waiting for them + if (info[k].state != piece_picker::block_info::state_requested) + continue; + + piece_block b(piece, k); + + // only allow a single additional request per block, in order + // to spread it out evenly across all stalled blocks + if (info[k].num_peers > timed_out) + continue; + + busy_blocks[busy_count].peers = info[k].num_peers; + busy_blocks[busy_count].index = k; + ++busy_count; + +#if TORRENT_DEBUG_STREAMING > 1 + printf(" [%d (%d)]", b.block_index, info[k].num_peers); +#endif + } +#if TORRENT_DEBUG_STREAMING > 1 + printf("\n"); +#endif + + // 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, busy_blocks + busy_count); + + // then insert them into the interesting_blocks vector + for (int k = 0; k < busy_count; ++k) + { + interesting_blocks.push_back( + piece_block(piece, busy_blocks[k].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 blocks_in_piece + , int timed_out) + { + std::vector interesting_blocks; + std::vector backup1; + std::vector backup2; + std::vector ignore; + + time_point 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 + 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 + std::vector::iterator p = std::find_if(peers.begin(), peers.end() + , boost::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 + 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, 0); + + 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 + 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 + printf("not timed out, moving on to next piece\n"); +#endif + break; + } + +#if TORRENT_DEBUG_STREAMING > 1 + 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 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 already_requested = std::find_if(dq.begin(), dq.end() + , 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 + printf("piece already requested by peer, try next peer\n"); +#endif + // try next peer + continue; + } + + std::vector const& rq = c.request_queue(); + + bool already_in_queue = std::find_if(rq.begin(), rq.end() + , has_block(b)) != rq.end(); + + if (already_in_queue) + { + if (!c.make_time_critical(b)) + { +#if TORRENT_DEBUG_STREAMING > 1 + 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 + 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::req_time_critical + | (busy_mode ? peer_connection::req_busy : 0))) + { +#if TORRENT_DEBUG_STREAMING > 1 + 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 + 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 + 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. + std::vector peers; + peers.reserve(m_connections.size()); + + // 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), !boost::bind(&peer_connection::can_request_time_critical, _1)); + + // 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() + , boost::bind(&peer_connection::download_queue_time, _1, 16*1024) + < boost::bind(&peer_connection::download_queue_time, _2, 16*1024)); + + // remove the bottom 10% of peers from the candidate set. + // this is just to remove outliers that might stall downloads + int new_size = (peers.size() * 9 + 9) / 10; + TORRENT_ASSERT(new_size <= int(peers.size())); + 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 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. + for (std::vector::iterator i = m_time_critical_pieces.begin() + , end(m_time_critical_pieces.end()); i != end; ++i) + { +#if TORRENT_DEBUG_STREAMING > 1 + printf("considering %d\n", i->piece); +#endif + + if (peers.empty()) + { +#if TORRENT_DEBUG_STREAMING > 1 + 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 (i != m_time_critical_pieces.begin() && 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 + printf("reached deadline horizon [%f + %f * 4 + 1]\n" + , m_average_piece_time / 1000.f + , m_piece_time_deviation / 1000.f); +#endif + break; + } + + 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 blocks_in_piece = m_picker->blocks_in_piece(i->piece); + +#if TORRENT_DEBUG_STREAMING > 0 + i->timed_out = timed_out; +#endif + int 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 = 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 + printf("skipping %d (full) [req: %d timed_out: %d ]\n" + , i->piece, pi.requested + , timed_out); +#endif + + // if requested is 0, it meants 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 + 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() + , boost::bind(&peer_connection::download_queue_time, _1, 16*1024) + < boost::bind(&peer_connection::download_queue_time, _2, 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 (std::set::iterator i = peers_with_requests.begin() + , end(peers_with_requests.end()); i != end; ++i) + { + (*i)->send_block_requests(); + } + } + + std::set torrent::web_seeds(web_seed_entry::type_t type) const + { + TORRENT_ASSERT(is_single_thread()); + std::set ret; + for (std::list::const_iterator i = m_web_seeds.begin() + , end(m_web_seeds.end()); i != end; ++i) + { + if (i->peer_info.banned) continue; + if (i->removed) continue; + if (i->type != type) continue; + ret.insert(i->url); + } + return ret; + } + + void torrent::remove_web_seed(std::string const& url, web_seed_entry::type_t type) + { + std::list::iterator i = std::find_if(m_web_seeds.begin() + , m_web_seeds.end() + , (boost::bind(&web_seed_t::url, _1) + == url && boost::bind(&web_seed_t::type, _1) == type)); + + if (i != m_web_seeds.end()) remove_web_seed(i); + } + + void torrent::disconnect_web_seed(peer_connection* p) + { + std::list::iterator i + = std::find_if(m_web_seeds.begin(), m_web_seeds.end() + , (boost::bind(&torrent_peer::connection + , boost::bind(&web_seed_t::peer_info, _1)) == 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 = 0; + } + + void torrent::remove_web_seed(peer_connection* p, error_code const& ec + , operation_t op, int error) + { + std::list::iterator i = std::find_if(m_web_seeds.begin() + , m_web_seeds.end() + , boost::bind(&torrent_peer::connection + , boost::bind(&web_seed_t::peer_info, _1)) == p); + TORRENT_ASSERT(i != m_web_seeds.end()); + if (i == m_web_seeds.end()) return; + + peer_connection* peer = static_cast(i->peer_info.connection); + if (peer) + { + // 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(0); + } + remove_web_seed(i); + } + + void torrent::retry_web_seed(peer_connection* p, int retry) + { + TORRENT_ASSERT(is_single_thread()); + std::list::iterator i = std::find_if(m_web_seeds.begin() + , m_web_seeds.end() + , boost::bind(&torrent_peer::connection + , boost::bind(&web_seed_t::peer_info, _1)) == p); + + TORRENT_ASSERT(i != m_web_seeds.end()); + if (i == m_web_seeds.end()) return; + if (i->removed) return; + if (retry == 0) retry = settings().get_int(settings_pack::urlseed_wait_retry); + i->retry = aux::time_now() + seconds(retry); + } + + torrent_state torrent::get_peer_list_state() + { + torrent_state ret; + ret.is_paused = is_paused(); + ret.is_finished = is_finished(); + ret.allow_multiple_connections_per_ip = settings().get_bool(settings_pack::allow_multiple_connections_per_ip); + ret.max_peerlist_size = is_paused() + ? settings().get_int(settings_pack::max_paused_peerlist_size) + : settings().get_int(settings_pack::max_peerlist_size); + ret.min_reconnect_time = settings().get_int(settings_pack::min_reconnect_time); + + ret.peer_allocator = m_ses.get_peer_allocator(); + 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 == NULL) + { + update_want_peers(); + return false; + } + + if (!connect_to_peer(p)) + { + 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, int source, int flags) + { + TORRENT_ASSERT(is_single_thread()); + +#if !TORRENT_USE_IPV6 + if (!adr.address().is_v4()) + { +#ifndef TORRENT_DISABLE_LOGGING + error_code ec; + debug_log("add_peer() %s unsupported address family" + , adr.address().to_string(ec).c_str()); +#endif + return NULL; + } +#endif + +#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) + udp::endpoint node(adr.address(), adr.port()); + session().add_dht_node(node); + } +#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.address(), peer_blocked_alert::ip_filter); + +#ifndef TORRENT_DISABLE_EXTENSIONS + notify_extension_add_peer(adr, source, torrent_plugin::filtered); +#endif + return NULL; + } + + if (m_ses.get_port_filter().access(adr.port()) & port_filter::blocked) + { + if (alerts().should_post()) + alerts().emplace_alert(get_handle() + , adr.address(), peer_blocked_alert::port_filter); +#ifndef TORRENT_DISABLE_EXTENSIONS + notify_extension_add_peer(adr, source, torrent_plugin::filtered); +#endif + return NULL; + } + +#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.address(), peer_blocked_alert::i2p_mixed); + return NULL; + } +#endif + + if (settings().get_bool(settings_pack::no_connect_privileged_ports) && adr.port() < 1024) + { + if (alerts().should_post()) + alerts().emplace_alert(get_handle() + , adr.address(), peer_blocked_alert::privileged_ports); +#ifndef TORRENT_DISABLE_EXTENSIONS + notify_extension_add_peer(adr, source, torrent_plugin::filtered); +#endif + return NULL; + } + + 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); + +#ifndef TORRENT_DISABLE_LOGGING + error_code ec; + debug_log("add_peer() %s connect-candidates: %d" + , adr.address().to_string(ec).c_str(), m_peer_list->num_connect_candidates()); +#endif + + if (p) + { + state_updated(); +#ifndef TORRENT_DISABLE_EXTENSIONS + notify_extension_add_peer(adr, source, st.first_time_seen ? torrent_plugin::first_time : 0); +#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 s) + { + if (p->seed != s) + { + if (s) + { + TORRENT_ASSERT(m_num_seeds < 0xffff); + ++m_num_seeds; + } + else + { + TORRENT_ASSERT(m_num_seeds > 0); + --m_num_seeds; + } + } + + need_peer_list(); + m_peer_list->set_seed(p, s); + update_auto_sequential(); + } + + void torrent::clear_failcount(torrent_peer* p) + { + need_peer_list(); + m_peer_list->set_failcount(p, 0); + update_want_peers(); + } + + std::pair torrent::find_peers(address const& a) + { + need_peer_list(); + return m_peer_list->find_peers(a); + } + + void torrent::update_peer_port(int port, torrent_peer* p, int 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(int piece) + { +// picker().mark_as_checking(piece); + + TORRENT_ASSERT(m_storage.get()); + + inc_refcount("verify_piece"); + m_ses.disk_thread().async_hash(m_storage.get(), piece, 0 + , boost::bind(&torrent::on_piece_verified, shared_from_this(), _1) + , reinterpret_cast(1)); + } + + announce_entry* torrent::find_tracker(tracker_request const& r) + { + std::vector::iterator i = std::find_if( + m_trackers.begin(), m_trackers.end() + , boost::bind(&announce_entry::url, _1) == r.url); + if (i == m_trackers.end()) return 0; + 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 (std::vector
    ::iterator i = banned.begin() + , end(banned.end()); i != end; ++i) + alerts().emplace_alert(get_handle(), *i + , 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 (std::vector
    ::iterator i = banned.begin() + , end(banned.end()); i != end; ++i) + alerts().emplace_alert(get_handle(), *i + , 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 (std::vector::const_iterator i = peers.begin() + , end(peers.end()); i != end; ++i) + { + m_picker->clear_peer(*i); + } +#if TORRENT_USE_INVARIANT_CHECKS + m_picker->check_peers(); +#endif + } + +#if !TORRENT_NO_FPU + void torrent::file_progress(std::vector& fp) + { + TORRENT_ASSERT(is_single_thread()); + if (!valid_metadata()) + { + fp.clear(); + return; + } + + if (!need_loaded()) return; + fp.resize(m_torrent_file->num_files(), 1.f); + if (is_seed()) return; + + std::vector progress; + file_progress(progress); + for (int i = 0; i < m_torrent_file->num_files(); ++i) + { + boost::int64_t file_size = m_torrent_file->files().file_size(i); + if (file_size == 0) fp[i] = 1.f; + else fp[i] = float(progress[i]) / file_size; + } + } +#endif + + void torrent::file_progress(std::vector& fp, int flags) + { + TORRENT_ASSERT(is_single_thread()); + if (!valid_metadata()) + { + fp.clear(); + return; + } + + if (!need_loaded()) 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 (int i = 0; i < fs.num_files(); ++i) + fp[i] = fs.file_size(i); + return; + } + + if (num_have() == 0) + { + // if we don't have any pieces, just return zeroes + fp.clear(); + fp.resize(m_torrent_file->num_files(), 0); + return; + } + + m_file_progress.export_progress(fp); + + if (flags & torrent_handle::piece_granularity) + return; + + TORRENT_ASSERT(has_picker()); + + std::vector q = m_picker->get_download_queue(); + + if (!q.empty()) + { + if (!const_cast(*this).need_loaded()) return; + } + + file_storage const& fs = m_torrent_file->files(); + for (std::vector::const_iterator + i = q.begin(), end(q.end()); i != end; ++i) + { + boost::int64_t offset = boost::int64_t(i->index) * m_torrent_file->piece_length(); + int file = fs.file_index_at_offset(offset); + int num_blocks = m_picker->blocks_in_piece(i->index); + piece_picker::block_info const* info = m_picker->blocks_for_piece(*i); + for (int k = 0; k < num_blocks; ++k) + { + TORRENT_ASSERT(file < fs.num_files()); + TORRENT_ASSERT(offset == boost::int64_t(i->index) * m_torrent_file->piece_length() + + k * 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.num_files()); + + boost::int64_t block = block_size(); + + if (info[k].state == piece_picker::block_info::state_none) + { + offset += block; + continue; + } + + if (info[k].state == piece_picker::block_info::state_requested) + { + block = 0; + torrent_peer* p = static_cast(info[k].peer); + if (p && p->connection) + { + peer_connection* peer = static_cast(p->connection); + boost::optional pbp + = peer->downloading_piece_progress(); + if (pbp && pbp->piece_index == i->index && pbp->block_index == k) + 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)) + { + int left_over = int(block_size() - block); + // split the block on multiple files + while (block > 0) + { + TORRENT_ASSERT(offset <= fs.file_offset(file) + fs.file_size(file)); + boost::int64_t 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.num_files()) + { + offset += block; + break; + } + } + } + offset += left_over; + TORRENT_ASSERT(offset == boost::int64_t(i->index) * m_torrent_file->piece_length() + + (k+1) * block_size()); + } + else + { + fp[file] += block; + offset += block_size(); + } + TORRENT_ASSERT(file <= m_torrent_file->num_files()); + } + } + } + + void torrent::new_external_ip() + { + if (m_peer_list) m_peer_list->clear_peer_prio(); + } + + namespace + { + bool is_downloading_state(int st) + { + switch (st) + { + case torrent_status::checking_files: + case torrent_status::allocating: + case torrent_status::checking_resume_data: + return false; + case torrent_status::downloading_metadata: + case torrent_status::downloading: + case torrent_status::finished: + case torrent_status::seeding: + return true; + default: + // unexpected state + TORRENT_ASSERT_VAL(false, st); + return false; + } + } + } + + void torrent::stop_when_ready(bool 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 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_want_peers(); + update_state_list(); + update_gauge(); + + state_updated(); + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + TORRENT_TRY { + (*i)->on_state(m_state); + } TORRENT_CATCH (std::exception&) {} + } +#endif + } + +#ifndef TORRENT_DISABLE_EXTENSIONS + void torrent::notify_extension_add_peer(tcp::endpoint const& ip + , int src, int flags) + { + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + TORRENT_TRY { + (*i)->on_add_peer(ip, src, flags); + } TORRENT_CATCH (std::exception&) {} + } + } +#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; + + std::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, boost::uint32_t flags) + { + INVARIANT_CHECK; + + time_point now = aux::time_now(); + + st->handle = get_handle(); + st->info_hash = info_hash(); + st->is_loaded = is_loaded(); + + 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; + +#ifndef TORRENT_NO_DEPRECATE + if (m_error) st->error = convert_from_native(m_error.message()) + + ": " + resolve_filename(m_error_file); +#endif + st->seed_mode = m_seed_mode; + 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; + st->stop_when_ready = m_stop_when_ready; + + st->added_time = m_added_time; + st->completed_time = m_completed_time; + + st->last_scrape = m_last_scrape == (std::numeric_limits::min)() ? -1 + : clamped_subtract(m_ses.session_time(), m_last_scrape); + + st->share_mode = m_share_mode; + st->upload_mode = m_upload_mode; + st->up_bandwidth_queue = 0; + st->down_bandwidth_queue = 0; + int priority = 0; + for (int i = 0; i < num_classes(); ++i) + { + int const* prio = m_ses.peer_classes().at(class_at(i))->priority; + if (priority < prio[peer_connection::upload_channel]) + priority = prio[peer_connection::upload_channel]; + if (priority < prio[peer_connection::download_channel]) + priority = prio[peer_connection::download_channel]; + } + st->priority = priority; + + st->num_peers = int(m_connections.size()) - 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; + st->seed_rank = seed_rank(settings()); + + st->all_time_upload = m_total_uploaded; + st->all_time_download = m_total_downloaded; + + // activity time + st->finished_time = finished_time(); + st->active_time = active_time(); + st->seeding_time = seeding_time(); + st->time_since_upload = m_last_upload == (std::numeric_limits::min)() ? -1 + : clamped_subtract(m_ses.session_time(), m_last_upload); + st->time_since_download = m_last_download == (std::numeric_limits::min)() ? -1 + : clamped_subtract(m_ses.session_time(), 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; + st->paused = is_torrent_paused(); + st->auto_managed = m_auto_managed; + st->sequential_download = m_sequential_download; + st->is_seeding = is_seed(); + st->is_finished = is_finished(); + st->super_seeding = m_super_seeding; + st->has_metadata = valid_metadata(); + bytes_done(*st, (flags & torrent_handle::query_accurate_download_counters) != 0); + 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 (m_waiting_tracker && !is_paused()) + st->next_announce = next_announce() - now; + else + st->next_announce = seconds(0); + + if (st->next_announce.count() < 0) + st->next_announce = seconds(0); + +#ifndef TORRENT_NO_DEPRECATE + st->announce_interval = seconds(0); +#endif + + st->current_tracker.clear(); + if (m_last_working_tracker >= 0) + { + TORRENT_ASSERT(m_last_working_tracker < int(m_trackers.size())); + const int i = m_last_working_tracker; + st->current_tracker = m_trackers[i].url; + } + else + { + std::vector::const_iterator i; + for (i = m_trackers.begin(); i != m_trackers.end(); ++i) + { + if (i->updating) continue; + if (!i->verified) continue; + st->current_tracker = i->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 = int(m_connections.size()); + 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(); + st->ip_filter_applies = m_apply_ip_filter; + + st->state = static_cast(m_state); + +#if defined TORRENT_DEBUG || defined TORRENT_RELEASE_ASSERTS + if (st->state == torrent_status::finished + || st->state == torrent_status::seeding) + { + TORRENT_ASSERT(st->is_finished); + } +#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 = st->total_wanted_done * 1000000 + / st->total_wanted; +#if !TORRENT_NO_FPU + st->progress = st->progress_ppm / 1000000.f; +#endif + } + + int num_pieces = m_torrent_file->num_pieces(); + if (has_picker() && (flags & torrent_handle::query_pieces)) + { + st->pieces.resize(num_pieces, false); + for (int i = 0; i < num_pieces; ++i) + 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()) + { + boost::tie(st->distributed_full_copies, st->distributed_fraction) = + m_picker->distributed_copies(); +#if TORRENT_NO_FPU + st->distributed_copies = -1.f; +#else + st->distributed_copies = st->distributed_full_copies + + float(st->distributed_fraction) / 1000; +#endif + } + else + { + st->distributed_full_copies = -1; + st->distributed_fraction = -1; + st->distributed_copies = -1.f; + } + + st->last_seen_complete = m_swarm_last_seen_complete; + } + + void torrent::add_redundant_bytes(int b, torrent::wasted_reason_t reason) + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(b > 0); + m_total_redundant_bytes += b; + + TORRENT_ASSERT(b > 0); + TORRENT_ASSERT(reason >= 0); + TORRENT_ASSERT(reason < waste_reason_max); + m_stats_counters.inc_stats_counter(counters::recv_redundant_bytes, b); + m_stats_counters.inc_stats_counter(counters::waste_piece_timed_out + reason, b); + } + + void torrent::add_failed_bytes(int b) + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(b > 0); + m_total_failed_bytes += b; + m_stats_counters.inc_stats_counter(counters::recv_failed_bytes, b); + } + + int torrent::num_seeds() const + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + return m_num_seeds; + } + + int torrent::num_downloaders() const + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + return (std::max)(0, int(m_connections.size()) - m_num_seeds - m_num_connecting); + } + + void torrent::tracker_request_error(tracker_request const& r + , int response_code, error_code const& ec, std::string const& msg + , int retry_interval) + { + TORRENT_ASSERT(is_single_thread()); + + INVARIANT_CHECK; + +#ifndef TORRENT_DISABLE_LOGGING + debug_log("*** tracker error: (%d) %s %s", ec.value() + , ec.message().c_str(), msg.c_str()); +#endif + if (0 == (r.kind & tracker_request::scrape_request)) + { + // announce request + announce_entry* ae = find_tracker(r); + if (ae) + { + ae->failed(settings(), retry_interval); + ae->last_error = ec; + ae->message = msg; + int tracker_index = ae - &m_trackers[0]; +#ifndef TORRENT_DISABLE_LOGGING + debug_log("*** increment tracker fail count [%d]", ae->fails); +#endif + // never talk to this tracker again + if (response_code == 410) ae->fail_limit = 1; + + deprioritize_tracker(tracker_index); + } + if (m_ses.alerts().should_post() + || r.triggered_manually) + { + m_ses.alerts().emplace_alert(get_handle() + , ae?ae->fails:0, response_code, r.url, ec, msg); + } + } + else + { + // scrape request + if (response_code == 410) + { + // never talk to this tracker again + announce_entry* ae = find_tracker(r); + if (ae) 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) + { + m_ses.alerts().emplace_alert(get_handle(), r.url, ec); + } + } + // announce to the next working tracker + if ((!m_abort && !is_paused()) || r.event == tracker_request::stopped) + announce_with_tracker(r.event); + update_tracker_timer(aux::time_now()); + } + +#ifndef TORRENT_DISABLE_LOGGING + TORRENT_FORMAT(2,3) + void torrent::debug_log(char const* fmt, ...) const + { + if (!alerts().should_post()) return; + + va_list v; + va_start(v, fmt); + + char buf[400]; + vsnprintf(buf, sizeof(buf), fmt, v); + va_end(v); + + alerts().emplace_alert( + const_cast(this)->get_handle(), buf); + } +#endif + +} diff --git a/src/torrent_handle.cpp b/src/torrent_handle.cpp new file mode 100644 index 0000000..0e72528 --- /dev/null +++ b/src/torrent_handle.cpp @@ -0,0 +1,809 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/peer_id.hpp" +#include "libtorrent/bt_peer_connection.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/aux_/session_impl.hpp" +#include "libtorrent/aux_/session_call.hpp" +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/utf8.hpp" +#include "libtorrent/thread.hpp" +#include "libtorrent/announce_entry.hpp" + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-macros" +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-macros" +#endif + +#define TORRENT_ASYNC_CALL(x) \ + boost::shared_ptr t = m_torrent.lock(); \ + if (!t) return; \ + session_impl& ses = static_cast(t->session()); \ + ses.get_io_service().dispatch(boost::bind(&torrent:: x, t)) + +#define TORRENT_ASYNC_CALL1(x, a1) \ + boost::shared_ptr t = m_torrent.lock(); \ + if (!t) return; \ + session_impl& ses = static_cast(t->session()); \ + ses.get_io_service().dispatch(boost::bind(&torrent:: x, t, a1)) + +#define TORRENT_ASYNC_CALL2(x, a1, a2) \ + boost::shared_ptr t = m_torrent.lock(); \ + if (!t) return; \ + session_impl& ses = static_cast(t->session()); \ + ses.get_io_service().dispatch(boost::bind(&torrent:: x, t, a1, a2)) + +#define TORRENT_ASYNC_CALL3(x, a1, a2, a3) \ + boost::shared_ptr t = m_torrent.lock(); \ + if (!t) return; \ + session_impl& ses = static_cast(t->session()); \ + ses.get_io_service().dispatch(boost::bind(&torrent:: x, t, a1, a2, a3)) + +#define TORRENT_ASYNC_CALL4(x, a1, a2, a3, a4) \ + boost::shared_ptr t = m_torrent.lock(); \ + if (!t) return; \ + session_impl& ses = static_cast(t->session()); \ + ses.get_io_service().dispatch(boost::bind(&torrent:: x, t, a1, a2, a3, a4)) + +#define TORRENT_SYNC_CALL(x) \ + boost::shared_ptr t = m_torrent.lock(); \ + if (t) aux::sync_call_handle(t, boost::bind(&torrent:: x, t)); + +#define TORRENT_SYNC_CALL1(x, a1) \ + boost::shared_ptr t = m_torrent.lock(); \ + if (t) aux::sync_call_handle(t, boost::bind(&torrent:: x, t, a1)); + +#define TORRENT_SYNC_CALL2(x, a1, a2) \ + boost::shared_ptr t = m_torrent.lock(); \ + if (t) aux::sync_call_handle(t, boost::bind(&torrent:: x, t, a1, a2)); + +#define TORRENT_SYNC_CALL3(x, a1, a2, a3) \ + boost::shared_ptr t = m_torrent.lock(); \ + if (t) aux::sync_call_handle(t, boost::bind(&torrent:: x, t, a1, a2, a3)); + +#define TORRENT_SYNC_CALL_RET(type, def, x) \ + boost::shared_ptr t = m_torrent.lock(); \ + type r = def; \ + if (t) aux::sync_call_ret_handle(t, r, boost::function(boost::bind(&torrent:: x, t))); + +#define TORRENT_SYNC_CALL_RET1(type, def, x, a1) \ + boost::shared_ptr t = m_torrent.lock(); \ + type r = def; \ + if (t) aux::sync_call_ret_handle(t, r, boost::function(boost::bind(&torrent:: x, t, a1))); + +#define TORRENT_SYNC_CALL_RET2(type, def, x, a1, a2) \ + boost::shared_ptr t = m_torrent.lock(); \ + type r = def; \ + if (t) aux::sync_call_ret_handle(t, r, boost::function(boost::bind(&torrent:: x, t, a1, a2))); + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +using libtorrent::aux::session_impl; + +namespace libtorrent +{ + +#ifndef BOOST_NO_EXCEPTIONS + void throw_invalid_handle() + { + throw libtorrent_exception(errors::invalid_torrent_handle); + } +#endif + + sha1_hash torrent_handle::info_hash() const + { + boost::shared_ptr t = m_torrent.lock(); + static const sha1_hash empty; + if (!t) return empty; + return t->info_hash(); + } + + int torrent_handle::max_uploads() const + { + TORRENT_SYNC_CALL_RET(int, 0, max_uploads); + return r; + } + + void torrent_handle::set_max_uploads(int max_uploads) const + { + TORRENT_ASSERT_PRECOND(max_uploads >= 2 || max_uploads == -1); + TORRENT_ASYNC_CALL2(set_max_uploads, max_uploads, true); + } + + int torrent_handle::max_connections() const + { + TORRENT_SYNC_CALL_RET(int, 0, max_connections); + return r; + } + + void torrent_handle::set_max_connections(int max_connections) const + { + TORRENT_ASSERT_PRECOND(max_connections >= 2 || max_connections == -1); + TORRENT_ASYNC_CALL2(set_max_connections, max_connections, true); + } + + void torrent_handle::set_upload_limit(int limit) const + { + TORRENT_ASSERT_PRECOND(limit >= -1); + TORRENT_ASYNC_CALL1(set_upload_limit, limit); + } + + int torrent_handle::upload_limit() const + { + TORRENT_SYNC_CALL_RET(int, 0, upload_limit); + return r; + } + + void torrent_handle::set_download_limit(int limit) const + { + TORRENT_ASSERT_PRECOND(limit >= -1); + TORRENT_ASYNC_CALL1(set_download_limit, limit); + } + + int torrent_handle::download_limit() const + { + TORRENT_SYNC_CALL_RET(int, 0, download_limit); + return r; + } + + void torrent_handle::move_storage( + std::string const& save_path, int flags) const + { + TORRENT_ASYNC_CALL2(move_storage, save_path, flags); + } + +#if TORRENT_USE_WSTRING +#ifndef TORRENT_NO_DEPRECATE + void torrent_handle::move_storage( + std::wstring const& save_path, int flags) const + { + std::string utf8; + wchar_utf8(save_path, utf8); + TORRENT_ASYNC_CALL2(move_storage, utf8, flags); + } + + void torrent_handle::rename_file(int index, std::wstring const& new_name) const + { + std::string utf8; + wchar_utf8(new_name, utf8); + TORRENT_ASYNC_CALL2(rename_file, index, utf8); + } +#endif // TORRENT_NO_DEPRECATE +#endif // TORRENT_USE_WSTRING + + void torrent_handle::rename_file(int index, std::string const& new_name) const + { + TORRENT_ASYNC_CALL2(rename_file, index, new_name); + } + + void torrent_handle::add_extension( + boost::function(torrent_handle const&, void*)> const& ext + , void* userdata) + { +#ifndef TORRENT_DISABLE_EXTENSIONS + TORRENT_ASYNC_CALL2(add_extension, ext, userdata); +#else + TORRENT_UNUSED(ext); + TORRENT_UNUSED(userdata); +#endif + } + + bool torrent_handle::set_metadata(char const* metadata, int size) const + { + TORRENT_SYNC_CALL_RET2(bool, false, set_metadata, metadata, size); + return r; + } + + void torrent_handle::pause(int flags) const + { + TORRENT_ASYNC_CALL1(pause, bool(flags & graceful_pause)); + } + + void torrent_handle::stop_when_ready(bool b) const + { + TORRENT_ASYNC_CALL1(stop_when_ready, b); + } + + void torrent_handle::apply_ip_filter(bool b) const + { + TORRENT_ASYNC_CALL1(set_apply_ip_filter, b); + } + + void torrent_handle::set_share_mode(bool b) const + { + TORRENT_ASYNC_CALL1(set_share_mode, b); + } + + void torrent_handle::set_upload_mode(bool b) const + { + TORRENT_ASYNC_CALL1(set_upload_mode, b); + } + + void torrent_handle::flush_cache() const + { + TORRENT_ASYNC_CALL(flush_cache); + } + + void torrent_handle::set_ssl_certificate( + std::string const& certificate + , std::string const& private_key + , std::string const& dh_params + , std::string const& passphrase) + { +#ifdef TORRENT_USE_OPENSSL + TORRENT_ASYNC_CALL4(set_ssl_cert, certificate, private_key, dh_params, passphrase); +#else + TORRENT_UNUSED(certificate); + TORRENT_UNUSED(private_key); + TORRENT_UNUSED(dh_params); + TORRENT_UNUSED(passphrase); +#endif + } + + void torrent_handle::set_ssl_certificate_buffer( + std::string const& certificate + , std::string const& private_key + , std::string const& dh_params) + { +#ifdef TORRENT_USE_OPENSSL + TORRENT_ASYNC_CALL3(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(int f) const + { + TORRENT_ASYNC_CALL1(save_resume_data, f); + } + + bool torrent_handle::need_save_resume_data() const + { + TORRENT_SYNC_CALL_RET(bool, false, need_save_resume_data); + return r; + } + + void torrent_handle::force_recheck() const + { + TORRENT_ASYNC_CALL(force_recheck); + } + + void torrent_handle::resume() const + { + TORRENT_ASYNC_CALL(resume); + } + + void torrent_handle::auto_managed(bool m) const + { + TORRENT_ASYNC_CALL1(auto_managed, m); + } + + void torrent_handle::set_priority(int p) const + { + TORRENT_ASYNC_CALL1(set_priority, p); + } + + int torrent_handle::queue_position() const + { + TORRENT_SYNC_CALL_RET(int, -1, queue_position); + return r; + } + + void torrent_handle::queue_position_up() const + { + TORRENT_ASYNC_CALL(queue_up); + } + + void torrent_handle::queue_position_down() const + { + TORRENT_ASYNC_CALL(queue_down); + } + + void torrent_handle::queue_position_top() const + { + TORRENT_ASYNC_CALL1(set_queue_position, 0); + } + + void torrent_handle::queue_position_bottom() const + { + TORRENT_ASYNC_CALL1(set_queue_position, INT_MAX); + } + + void torrent_handle::clear_error() const + { + TORRENT_ASYNC_CALL(clear_error); + } + +#ifndef TORRENT_NO_DEPRECATE + void torrent_handle::set_tracker_login(std::string const& name + , std::string const& password) const + { + TORRENT_ASYNC_CALL2(set_tracker_login, name, password); + } +#endif + + void torrent_handle::file_progress(std::vector& progress, int flags) const + { + TORRENT_SYNC_CALL2(file_progress, boost::ref(progress), flags); + } + + torrent_status torrent_handle::status(boost::uint32_t flags) const + { + torrent_status st; + TORRENT_SYNC_CALL2(status, &st, flags); + return st; + } + + void torrent_handle::set_pinned(bool p) const + { + TORRENT_ASYNC_CALL1(set_pinned, p); + } + + void torrent_handle::set_sequential_download(bool sd) const + { + TORRENT_ASYNC_CALL1(set_sequential_download, sd); + } + + void torrent_handle::piece_availability(std::vector& avail) const + { + TORRENT_SYNC_CALL1(piece_availability, boost::ref(avail)); + } + + void torrent_handle::piece_priority(int index, int priority) const + { + TORRENT_ASYNC_CALL2(set_piece_priority, index, priority); + } + + int torrent_handle::piece_priority(int index) const + { + TORRENT_SYNC_CALL_RET1(int, 0, piece_priority, index); + return r; + } + + void torrent_handle::prioritize_pieces(std::vector const& pieces) const + { + TORRENT_ASYNC_CALL1(prioritize_pieces, pieces); + } + + void torrent_handle::prioritize_pieces(std::vector > const& pieces) const + { + TORRENT_ASYNC_CALL1(prioritize_piece_list, pieces); + } + + std::vector torrent_handle::piece_priorities() const + { + std::vector ret; + TORRENT_SYNC_CALL1(piece_priorities, &ret); + return ret; + } + + void torrent_handle::file_priority(int index, int priority) const + { + TORRENT_ASYNC_CALL2(set_file_priority, index, priority); + } + + int torrent_handle::file_priority(int index) const + { + TORRENT_SYNC_CALL_RET1(int, 0, file_priority, index); + return r; + } + + void torrent_handle::prioritize_files(std::vector const& files) const + { + TORRENT_ASYNC_CALL1(prioritize_files, files); + } + + std::vector torrent_handle::file_priorities() const + { + std::vector ret; + TORRENT_SYNC_CALL1(file_priorities, &ret); + return ret; + } + +#ifndef TORRENT_NO_DEPRECATE +// ============ start deprecation =============== + + 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 + { + TORRENT_ASYNC_CALL1(use_interface, std::string(net_interface)); + } + +#if !TORRENT_NO_FPU + void torrent_handle::file_progress(std::vector& progress) const + { + TORRENT_SYNC_CALL1(file_progress, boost::ref(progress)); + } +#endif + + bool torrent_handle::is_seed() const + { + TORRENT_SYNC_CALL_RET(bool, false, is_seed); + return r; + } + + bool torrent_handle::is_finished() const + { + TORRENT_SYNC_CALL_RET(bool, false, is_finished); + return r; + } + + bool torrent_handle::is_paused() const + { + TORRENT_SYNC_CALL_RET(bool, false, is_torrent_paused); + return r; + } + + bool torrent_handle::is_sequential_download() const + { + TORRENT_SYNC_CALL_RET(bool, false, is_sequential_download); + return r; + } + + bool torrent_handle::is_auto_managed() const + { + TORRENT_SYNC_CALL_RET(bool, false, is_auto_managed); + return r; + } + + bool torrent_handle::has_metadata() const + { + TORRENT_SYNC_CALL_RET(bool, false, valid_metadata); + return r; + } + + void torrent_handle::filter_piece(int index, bool filter) const + { + TORRENT_ASYNC_CALL2(filter_piece, index, filter); + } + + void torrent_handle::filter_pieces(std::vector const& pieces) const + { + TORRENT_ASYNC_CALL1(filter_pieces, pieces); + } + + bool torrent_handle::is_piece_filtered(int index) const + { + TORRENT_SYNC_CALL_RET1(bool, false, is_piece_filtered, index); + return r; + } + + std::vector torrent_handle::filtered_pieces() const + { + std::vector ret; + TORRENT_SYNC_CALL1(filtered_pieces, ret); + return ret; + } + + void torrent_handle::filter_files(std::vector const& files) const + { + TORRENT_ASYNC_CALL1(filter_files, files); + } + + bool torrent_handle::super_seeding() const + { + TORRENT_SYNC_CALL_RET(bool, false, super_seeding); + return r; + } + +// ============ end deprecation =============== +#endif + + std::vector torrent_handle::trackers() const + { + static const std::vector empty; + TORRENT_SYNC_CALL_RET(std::vector, empty, trackers); + return r; + } + + void torrent_handle::add_url_seed(std::string const& url) const + { + TORRENT_ASYNC_CALL2(add_web_seed, url, web_seed_entry::url_seed); + } + + void torrent_handle::remove_url_seed(std::string const& url) const + { + TORRENT_ASYNC_CALL2(remove_web_seed, url, web_seed_entry::url_seed); + } + + std::set torrent_handle::url_seeds() const + { + static const std::set empty; + TORRENT_SYNC_CALL_RET1(std::set, empty, web_seeds, web_seed_entry::url_seed); + return r; + } + + void torrent_handle::add_http_seed(std::string const& url) const + { + TORRENT_ASYNC_CALL2(add_web_seed, url, web_seed_entry::http_seed); + } + + void torrent_handle::remove_http_seed(std::string const& url) const + { + TORRENT_ASYNC_CALL2(remove_web_seed, url, web_seed_entry::http_seed); + } + + std::set torrent_handle::http_seeds() const + { + static const std::set empty; + TORRENT_SYNC_CALL_RET1(std::set, empty, web_seeds, web_seed_entry::http_seed); + return r; + } + + void torrent_handle::replace_trackers( + std::vector const& urls) const + { + TORRENT_ASYNC_CALL1(replace_trackers, urls); + } + + void torrent_handle::add_tracker(announce_entry const& url) const + { + TORRENT_ASYNC_CALL1(add_tracker, url); + } + + void torrent_handle::add_piece(int piece, char const* data, int flags) const + { + TORRENT_SYNC_CALL3(add_piece, piece, data, flags); + } + + void torrent_handle::read_piece(int piece) const + { + TORRENT_ASYNC_CALL1(read_piece, piece); + } + + bool torrent_handle::have_piece(int piece) const + { + TORRENT_SYNC_CALL_RET1(bool, false, have_piece, piece); + return r; + } + + storage_interface* torrent_handle::get_storage_impl() const + { + TORRENT_SYNC_CALL_RET(storage_interface*, 0, get_storage); + return r; + } + + bool torrent_handle::is_valid() const + { + return !m_torrent.expired(); + } + + boost::shared_ptr torrent_handle::torrent_file() const + { + TORRENT_SYNC_CALL_RET(boost::shared_ptr + , boost::shared_ptr(), get_torrent_copy); + return r; + } + +#ifndef TORRENT_NO_DEPRECATE + // 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 boost::shared_ptr holder[4]; + static int cursor = 0; + static mutex holder_mutex; + + boost::shared_ptr r = torrent_file(); + + mutex::scoped_lock l(holder_mutex); + holder[cursor++] = r; + cursor = cursor % (sizeof(holder) / sizeof(holder[0])); + return *r; + } + + entry torrent_handle::write_resume_data() const + { + entry ret(entry::dictionary_t); + TORRENT_SYNC_CALL1(write_resume_data, boost::ref(ret)); + t = m_torrent.lock(); + if (t) + { + bool done = false; + session_impl& ses = static_cast(t->session()); + storage_error ec; + ses.get_io_service().dispatch(boost::bind(&aux::fun_wrap, boost::ref(done), boost::ref(ses.cond) + , boost::ref(ses.mut), boost::function(boost::bind( + &piece_manager::write_resume_data, &t->storage(), boost::ref(ret), boost::ref(ec))))); + t.reset(); + aux::torrent_wait(done, ses); + } + + return ret; + } + + std::string torrent_handle::save_path() const + { + TORRENT_SYNC_CALL_RET(std::string, "", save_path); + return r; + } + + std::string torrent_handle::name() const + { + TORRENT_SYNC_CALL_RET(std::string, "", name); + return r; + } + +#endif + + void torrent_handle::connect_peer(tcp::endpoint const& adr, int source, int flags) const + { + TORRENT_ASYNC_CALL3(add_peer, adr, source, flags); + } + +#ifndef TORRENT_NO_DEPRECATE + void torrent_handle::force_reannounce( + boost::posix_time::time_duration duration) const + { + TORRENT_ASYNC_CALL2(force_tracker_request, aux::time_now() + + seconds(duration.total_seconds()), -1); + } +#endif + + void torrent_handle::force_dht_announce() const + { +#ifndef TORRENT_DISABLE_DHT + TORRENT_ASYNC_CALL(dht_announce); +#endif + } + + void torrent_handle::force_reannounce(int s, int idx) const + { + TORRENT_ASYNC_CALL2(force_tracker_request, aux::time_now() + seconds(s), idx); + } + + void torrent_handle::file_status(std::vector& status) const + { + status.clear(); + + boost::shared_ptr t = m_torrent.lock(); + if (!t || !t->has_storage()) return; + session_impl& ses = static_cast(t->session()); + ses.disk_thread().files().get_status(&status, &t->storage()); + } + + void torrent_handle::scrape_tracker(int idx) const + { + TORRENT_ASYNC_CALL2(scrape_tracker, idx, true); + } + + void torrent_handle::super_seeding(bool on) const + { + TORRENT_ASYNC_CALL1(super_seeding, on); + } + +#ifndef TORRENT_NO_DEPRECATE + void torrent_handle::resolve_countries(bool r) + { +#ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES + TORRENT_ASYNC_CALL1(resolve_countries, r); +#endif + } + + bool torrent_handle::resolve_countries() const + { +#ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES + TORRENT_SYNC_CALL_RET(bool, false, resolving_countries); + return r; +#else + return false; +#endif + } +#endif // TORRENT_NO_DEPRECATE + + void torrent_handle::get_full_peer_list(std::vector& v) const + { + TORRENT_SYNC_CALL1(get_full_peer_list, boost::ref(v)); + } + + void torrent_handle::get_peer_info(std::vector& v) const + { + TORRENT_SYNC_CALL1(get_peer_info, boost::ref(v)); + } + + void torrent_handle::get_download_queue(std::vector& queue) const + { + TORRENT_SYNC_CALL1(get_download_queue, &queue); + } + + void torrent_handle::set_piece_deadline(int index, int deadline, int flags) const + { + TORRENT_ASYNC_CALL3(set_piece_deadline, index, deadline, flags); + } + + void torrent_handle::reset_piece_deadline(int index) const + { + TORRENT_ASYNC_CALL1(reset_piece_deadline, index); + } + + void torrent_handle::clear_piece_deadlines() const + { + TORRENT_ASYNC_CALL(clear_time_critical); + } + + boost::shared_ptr torrent_handle::native_handle() const + { + return m_torrent.lock(); + } + + std::size_t hash_value(torrent_status const& ts) + { + return hash_value(ts.handle); + } + + 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)); + } + +} + diff --git a/src/torrent_info.cpp b/src/torrent_info.cpp new file mode 100644 index 0000000..140d761 --- /dev/null +++ b/src/torrent_info.cpp @@ -0,0 +1,1849 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/ConvertUTF.h" +#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/file.hpp" +#include "libtorrent/utf8.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/aux_/escape_string.hpp" // maybe_url_encode +#include "libtorrent/aux_/merkle.hpp" // for merkle_* +#include "libtorrent/aux_/time.hpp" +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/magnet_uri.hpp" +#include "libtorrent/announce_entry.hpp" + +#ifndef TORRENT_NO_DEPRECATE +#include "libtorrent/lazy_entry.hpp" +#endif + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include + +#include +#include +#include +#include + +#if !defined TORRENT_NO_DEPRECATE && TORRENT_USE_IOSTREAM +#include +#include +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#if TORRENT_USE_I2P +#include "libtorrent/parse_url.hpp" +#endif + +namespace libtorrent +{ + + namespace { + + bool valid_path_character(char c) + { +#ifdef TORRENT_WINDOWS + static const char invalid_chars[] = "?<>\"|\b*:"; +#else + static const char invalid_chars[] = ""; +#endif + if (c >= 0 && c < 32) return false; + return std::strchr(invalid_chars, c) == 0; + } + + } // anonymous namespace + + // fixes invalid UTF-8 sequences + TORRENT_EXTRA_EXPORT 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; + + UTF8 const* ptr = reinterpret_cast(&target[0]); + UTF8 const* end = ptr + target.size(); + while (ptr < end) + { + UTF32 codepoint; + UTF32* cp = &codepoint; + + // decode a single utf-8 character + ConversionResult res = ConvertUTF8toUTF32(&ptr, end, &cp, cp + 1 + , lenientConversion); + + // this was the last character, and nothing was + // written to the destination buffer (i.e. the source character was + // truncated) + if (res == sourceExhausted + || res == sourceIllegal) + { + if (cp == &codepoint) + { + if (res == sourceExhausted) + ptr = end; + else + ++ptr; + + codepoint = '_'; + valid_encoding = false; + } + } + else if ((res != conversionOK && res != targetExhausted) + || codepoint == UNI_REPLACEMENT_CHAR) + { + // we expect the conversion to fail with targetExhausted, since we + // only pass in a single destination character slot. The last + // character will succeed though. Also, if the character was replaced, + // use our own replacement symbol (underscore). + codepoint = '_'; + valid_encoding = false; + } + + // encode codepoint into utf-8 + cp = &codepoint; + UTF8 sequence[5]; + UTF8* start = sequence; + res = ConvertUTF32toUTF8(const_cast(&cp), cp + 1, &start, start + 5, lenientConversion); + TORRENT_ASSERT(res == conversionOK); + + for (int i = 0; i < start - sequence; ++i) + tmp_path += char(sequence[i]); + } + + // 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 + , char const* element, int element_len) + { + if (element_len == 1 && element[0] == '.') return; + +#ifdef TORRENT_WINDOWS +#define TORRENT_SEPARATOR "\\" +#else +#define TORRENT_SEPARATOR "/" +#endif + path.reserve(path.size() + element_len + 2); + int added_separator = 0; + if (!path.empty()) + { + path += TORRENT_SEPARATOR; + added_separator = 1; + } + + if (element_len == 0) + { + path += "_"; + return; + } + +#if !TORRENT_USE_UNC_PATHS && defined TORRENT_WINDOWS + // 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, element_len); + char const* file_end = strrchr(pe.c_str(), '.'); + std::string name; + if (file_end) name.assign(pe.c_str(), file_end); + else name = 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 + num_names) + { + pe = "_" + pe; + element = pe.c_str(); + element_len = pe.size(); + } +#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; + for (int i = 0; i < element_len; ++i) + { + if (element[i] == '/' + || element[i] == '\\' +#ifdef TORRENT_WINDOWS + || element[i] == ':' +#endif + ) + continue; + + if (element[i] == '.') ++num_dots; + + int last_len = 0; + + if ((element[i] & 0x80) == 0) + { + // 1 byte + if (valid_path_character(element[i])) + { + path += element[i]; + } + else + { + path += '_'; + } + last_len = 1; + } + else if ((element[i] & 0xe0) == 0xc0) + { + // 2 bytes + if (element_len - i < 2 + || (element[i+1] & 0xc0) != 0x80) + { + path += '_'; + last_len = 1; + } + else if ((element[i] & 0x1f) == 0) + { + // overlong sequences are invalid + path += '_'; + last_len = 1; + } + else + { + path += element[i]; + path += element[i+1]; + last_len = 2; + } + i += 1; + } + else if ((element[i] & 0xf0) == 0xe0) + { + // 3 bytes + if (element_len - i < 3 + || (element[i+1] & 0xc0) != 0x80 + || (element[i+2] & 0xc0) != 0x80 + ) + { + path += '_'; + last_len = 1; + } + else if ((element[i] & 0x0f) == 0) + { + // overlong sequences are invalid + path += '_'; + last_len = 1; + } + else + { + path += element[i]; + path += element[i+1]; + path += element[i+2]; + last_len = 3; + } + i += 2; + } + else if ((element[i] & 0xf8) == 0xf0) + { + // 4 bytes + if (element_len - i < 4 + || (element[i+1] & 0xc0) != 0x80 + || (element[i+2] & 0xc0) != 0x80 + || (element[i+3] & 0xc0) != 0x80 + ) + { + path += '_'; + last_len = 1; + } + else if ((element[i] & 0x07) == 0 + && (element[i+1] & 0x3f) == 0) + { + // overlong sequences are invalid + path += '_'; + last_len = 1; + } + else + { + path += element[i]; + path += element[i+1]; + path += element[i+2]; + path += element[i+3]; + last_len = 4; + } + i += 3; + } + else + { + path += '_'; + last_len = 1; + } + + added += last_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 = element_len-1; j > (std::max)(element_len - 10, i); --j) + { + if (element[j] != '.') continue; + dot = j; + break; + } + // there is no extension + if (dot == -1) break; + found_extension = true; + i = dot - 1; + } + } + + 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 = 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 { + + boost::uint32_t get_file_attributes(bdecode_node const& dict) + { + boost::uint32_t file_flags = 0; + bdecode_node attr = dict.dict_find_string("attr"); + if (attr) + { + for (int i = 0; i < attr.string_length(); ++i) + { + switch (attr.string_ptr()[i]) + { + 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 seaprate 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 e = p.list_at(i); + if (e.type() != bdecode_node::string_t) + { + ec = errors::torrent_invalid_name; + return -1; + } + ret += e.string_length(); + } + return ret + len; + } + + // 'top_level' is extracting the file for a single-file torrent. The + // distinction is that the filename is found in "name" rather than + // "path" + // root_dir is the name of the torrent, unless this is a single file + // torrent, in which case it's empty. + bool extract_single_file(bdecode_node const& dict, file_storage& files + , std::string const& root_dir, ptrdiff_t info_ptr_diff, bool top_level + , int& pad_file_cnt, error_code& ec) + { + if (dict.type() != bdecode_node::dict_t) return false; + + boost::uint32_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 + boost::int64_t const file_size = (file_flags & file_storage::flag_symlink) + ? 0 + : dict.dict_find_int_value("length", -1); + if (file_size < 0 ) + { + ec = errors::torrent_invalid_length; + return false; + } + + boost::int64_t const mtime = dict.dict_find_int_value("mtime", 0); + + std::string path = root_dir; + std::string path_element; + char const* filename = NULL; + int filename_len = 0; + + if (top_level) + { + // prefer the name.utf-8 because if it exists, it is more likely to be + // correctly encoded + bdecode_node p = dict.dict_find_string("name.utf-8"); + if (!p) p = dict.dict_find_string("name"); + if (!p || p.string_length() == 0) + { + ec = errors::torrent_missing_name; + return false; + } + + filename = p.string_ptr() + info_ptr_diff; + filename_len = p.string_length(); + sanitize_append_path_element(path, p.string_ptr(), p.string_length()); + } + 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) + { + int const preallocate = path.size() + path_length(p, ec); + if (ec) return false; + path.reserve(preallocate); + + for (int i = 0, end(p.list_size()); i < end; ++i) + { + bdecode_node e = p.list_at(i); + if (i == end - 1) + { + filename = e.string_ptr() + info_ptr_diff; + filename_len = e.string_length(); + } + sanitize_append_path_element(path, e.string_ptr(), e.string_length()); + } + } + 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[10]; + snprintf(cnt, sizeof(cnt), "%d", pad_file_cnt); + path = combine_path(".pad", cnt); + ++pad_file_cnt; + } + else + { + ec = errors::torrent_missing_name; + return false; + } + } + + // bitcomet pad file + if (path.find("_____padding_file_") != std::string::npos) + file_flags = file_storage::flag_pad_file; + + bdecode_node fh = dict.dict_find_string("sha1"); + char const* filehash = NULL; + if (fh && fh.string_length() == 20) + filehash = fh.string_ptr() + info_ptr_diff; + + std::string symlink_path; + if (file_flags & file_storage::flag_symlink) + { + if (bdecode_node s_p = dict.dict_find_list("symlink path")) + { + int const preallocate = 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) + { + bdecode_node const& n = s_p.list_at(i); + sanitize_append_path_element(symlink_path, n.string_ptr() + , n.string_length()); + } + } + } + else + { + file_flags &= ~file_storage::flag_symlink; + } + + if (filename_len > path.length() + || path.compare(path.size() - filename_len, filename_len, filename + , filename_len) != 0) + { + // if the filename was sanitized and differ, clear it to just use path + filename = NULL; + filename_len = 0; + } + + files.add_file_borrow(filename, filename_len, path, file_size, file_flags, filehash + , mtime, symlink_path); + return true; + } + +#if TORRENT_HAS_BOOST_UNORDERED + struct string_hash_no_case + { + size_t operator()(std::string const& s) const + { + char const* s1 = s.c_str(); + size_t ret = 5381; + int c; + + while ((c = *s1++)) + ret = (ret * 33) ^ to_lower(c); + + return ret; + } + }; + + struct string_eq_no_case + { + bool operator()(std::string const& lhs, std::string const& rhs) const + { + char c1, c2; + char const* s1 = lhs.c_str(); + char const* s2 = rhs.c_str(); + + while (*s1 != 0 && *s2 != 0) + { + c1 = to_lower(*s1); + c2 = to_lower(*s2); + if (c1 != c2) return false; + ++s1; + ++s2; + } + return *s1 == *s2; + } + }; + +#else + struct string_less_no_case + { + bool operator()(std::string const& lhs, std::string const& rhs) const + { + char c1, c2; + char const* s1 = lhs.c_str(); + char const* s2 = rhs.c_str(); + + while (*s1 != 0 || *s2 != 0) + { + c1 = to_lower(*s1); + c2 = to_lower(*s2); + if (c1 < c2) return true; + if (c1 > c2) return false; + ++s1; + ++s2; + } + return false; + } + }; +#endif + + // 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, ptrdiff_t info_ptr_diff, error_code& ec) + { + if (list.type() != bdecode_node::list_t) + { + ec = errors::torrent_file_parse_failed; + return false; + } + target.reserve(list.list_size()); + + // this is the counter used to name pad files + int pad_file_cnt = 0; + for (int i = 0, end(list.list_size()); i < end; ++i) + { + if (!extract_single_file(list.list_at(i), target, root_dir + , info_ptr_diff, false, pad_file_cnt, ec)) + return false; + } + return true; + } + + int load_file(std::string const& filename, std::vector& v + , error_code& ec, int limit = 8000000) + { + ec.clear(); + file f; + if (!f.open(filename, file::read_only, ec)) return -1; + boost::int64_t s = f.get_size(ec); + if (ec) return -1; + if (s > limit) + { + ec = errors::metadata_too_large; + return -2; + } + v.resize(std::size_t(s)); + if (s == 0) return 0; + file::iovec_t b = {&v[0], size_t(s) }; + boost::int64_t read = f.readv(0, &b, 1, ec); + if (read != s) return -3; + if (ec) return -3; + return 0; + } + + } // anonymous namespace + + web_seed_entry::web_seed_entry(std::string const& url_, type_t type_ + , std::string const& auth_ + , headers_t const& extra_headers_) + : url(url_) + , auth(auth_) + , extra_headers(extra_headers_) + , type(type_) + { + } + + torrent_info::torrent_info(torrent_info const& t) + : m_files(t.m_files) + , m_orig_files(t.m_orig_files) + , m_urls(t.m_urls) + , m_web_seeds(t.m_web_seeds) + , m_nodes(t.m_nodes) + , m_merkle_tree(t.m_merkle_tree) + , m_piece_hashes(t.m_piece_hashes) + , m_comment(t.m_comment) + , m_created_by(t.m_created_by) + , m_creation_date(t.m_creation_date) + , m_info_hash(t.m_info_hash) + , m_info_section_size(t.m_info_section_size) + , m_merkle_first_leaf(t.m_merkle_first_leaf) + , m_multifile(t.m_multifile) + , m_private(t.m_private) + , m_i2p(t.m_i2p) + { +#if TORRENT_USE_INVARIANT_CHECKS + t.check_invariant(); +#endif + if (m_info_section_size == 0) return; + TORRENT_ASSERT(m_piece_hashes); + + error_code ec; + m_info_section.reset(new char[m_info_section_size]); + memcpy(m_info_section.get(), t.m_info_section.get(), m_info_section_size); + + ptrdiff_t offset = m_info_section.get() - t.m_info_section.get(); + + m_files.apply_pointer_offset(offset); + if (m_orig_files) + const_cast(*m_orig_files).apply_pointer_offset(offset); + +#ifndef TORRENT_DISABLE_MUTABLE_TORRENTS + for (int i = 0; i < m_collections.size(); ++i) + m_collections[i].first += offset; + + for (int i = 0; i < m_similar_torrents.size(); ++i) + m_similar_torrents[i] += offset; +#endif + + if (m_info_dict) + { + // make this decoded object point to our copy of the info section + // buffer + m_info_dict.switch_underlying_buffer(m_info_section.get()); + } + + m_piece_hashes += offset; + TORRENT_ASSERT(m_piece_hashes >= m_info_section.get()); + TORRENT_ASSERT(m_piece_hashes < m_info_section.get() + m_info_section_size); + } + + void torrent_info::resolve_duplicate_filenames() + { + INVARIANT_CHECK; + + boost::unordered_set files; + + std::string empty_str; + + // insert all directories first, to make sure no files + // are allowed to collied with them + m_files.all_path_hashes(files); + for (int i = 0; i < m_files.num_files(); ++i) + { + // as long as this file already exists + // increase the counter + boost::uint32_t 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; + } + } + } + + void torrent_info::resolve_duplicate_filenames_slow() + { + INVARIANT_CHECK; + int cnt = 0; + +#if TORRENT_HAS_BOOST_UNORDERED + boost::unordered_set files; +#else + std::set files; +#endif + + std::vector const& paths = m_files.paths(); + files.reserve(paths.size() + m_files.num_files()); + + // insert all directories first, to make sure no files + // are allowed to collied with them + for (std::vector::const_iterator i = paths.begin() + , end(paths.end()); i != end; ++i) + { + std::string p = combine_path(m_files.name(), *i); + files.insert(p); + while (has_parent_path(p)) + { + p = parent_path(p); + // we don't want trailing slashes here + TORRENT_ASSERT(p[p.size() - 1] == *TORRENT_SEPARATOR); + p.resize(p.size() - 1); + files.insert(p); + } + } + + for (int i = 0; i < m_files.num_files(); ++i) + { + // as long as this file already exists + // increase the counter + std::string filename = m_files.file_path(i); + if (!files.insert(filename).second) + { + std::string base = remove_extension(filename); + std::string ext = extension(filename); + do + { + ++cnt; + char new_ext[50]; + snprintf(new_ext, sizeof(new_ext), ".%d%s", cnt, ext.c_str()); + filename = base + new_ext; + } + while (!files.insert(filename).second); + + copy_on_write(); + m_files.rename_file(i, filename); + } + cnt = 0; + } + } + + 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()); + } + +#ifndef TORRENT_NO_DEPRECATE + torrent_info::torrent_info(lazy_entry const& torrent_file, error_code& ec + , int flags) + : m_piece_hashes(0) + , m_creation_date(0) + , m_info_section_size(0) + , m_merkle_first_leaf(0) + , m_multifile(false) + , m_private(false) + , m_i2p(false) + { + TORRENT_UNUSED(flags); + std::pair buf = torrent_file.data_section(); + bdecode_node e; + if (bdecode(buf.first, buf.first + buf.second, e, ec) != 0) + return; + parse_torrent_file(e, ec, 0); + } + + torrent_info::torrent_info(lazy_entry const& torrent_file, int flags) + : m_piece_hashes(0) + , m_creation_date(0) + , m_info_section_size(0) + , m_merkle_first_leaf(0) + , m_multifile(false) + , m_private(false) + , m_i2p(false) + { + TORRENT_UNUSED(flags); + std::pair buf = torrent_file.data_section(); + bdecode_node e; + error_code ec; + if (bdecode(buf.first, buf.first + buf.second, e, ec) != 0) + { +#ifndef BOOST_NO_EXCEPTIONS + throw invalid_torrent_file(ec); +#else + return; +#endif + } +#ifndef BOOST_NO_EXCEPTIONS + if (!parse_torrent_file(e, ec, 0)) + throw invalid_torrent_file(ec); +#else + parse_torrent_file(e, ec, 0); +#endif + } + + // standard constructor that parses a torrent file + torrent_info::torrent_info(entry const& torrent_file) + : m_piece_hashes(0) + , m_creation_date(0) + , m_info_section_size(0) + , m_merkle_first_leaf(0) + , m_multifile(false) + , m_private(false) + , m_i2p(false) + { + std::vector tmp; + std::back_insert_iterator > out(tmp); + bencode(out, torrent_file); + + bdecode_node e; + error_code ec; + if (tmp.size() == 0 || bdecode(&tmp[0], &tmp[0] + tmp.size(), e, ec) != 0) + { +#ifndef BOOST_NO_EXCEPTIONS + throw invalid_torrent_file(ec); +#else + return; +#endif + } +#ifndef BOOST_NO_EXCEPTIONS + if (!parse_torrent_file(e, ec, 0)) + throw invalid_torrent_file(ec); +#else + parse_torrent_file(e, ec, 0); +#endif + INVARIANT_CHECK; + } +#endif + +#ifndef BOOST_NO_EXCEPTIONS + torrent_info::torrent_info(bdecode_node const& torrent_file, int flags) + : m_piece_hashes(0) + , m_creation_date(0) + , m_info_section_size(0) + , m_merkle_first_leaf(0) + , m_multifile(false) + , m_private(false) + , m_i2p(false) + { + error_code ec; + if (!parse_torrent_file(torrent_file, ec, flags)) + throw invalid_torrent_file(ec); + + INVARIANT_CHECK; + } + + torrent_info::torrent_info(char const* buffer, int size, int flags) + : m_piece_hashes(0) + , m_creation_date(0) + , m_info_section_size(0) + , m_merkle_first_leaf(0) + , m_multifile(false) + , m_private(false) + , m_i2p(false) + { + error_code ec; + bdecode_node e; + if (bdecode(buffer, buffer + size, e, ec) != 0) + throw invalid_torrent_file(ec); + + if (!parse_torrent_file(e, ec, flags)) + throw invalid_torrent_file(ec); + + INVARIANT_CHECK; + } + + torrent_info::torrent_info(std::string const& filename, int flags) + : m_piece_hashes(0) + , m_creation_date(0) + , m_info_section_size(0) + , m_merkle_first_leaf(0) + , m_multifile(false) + , m_private(false) + , m_i2p(false) + { + std::vector buf; + error_code ec; + int ret = load_file(filename, buf, ec); + if (ret < 0) throw invalid_torrent_file(ec); + + bdecode_node e; + if (buf.size() == 0 || bdecode(&buf[0], &buf[0] + buf.size(), e, ec) != 0) + throw invalid_torrent_file(ec); + + if (!parse_torrent_file(e, ec, flags)) + throw invalid_torrent_file(ec); + + INVARIANT_CHECK; + } + +#if TORRENT_USE_WSTRING +#ifndef TORRENT_NO_DEPRECATE + torrent_info::torrent_info(std::wstring const& filename, int flags) + : m_piece_hashes(0) + , m_creation_date(0) + , m_info_section_size(0) + , m_merkle_first_leaf(0) + , m_multifile(false) + , m_private(false) + , m_i2p(false) + { + std::vector buf; + std::string utf8; + wchar_utf8(filename, utf8); + error_code ec; + int ret = load_file(utf8, buf, ec); + if (ret < 0) throw invalid_torrent_file(ec); + + bdecode_node e; + if (buf.size() == 0 || bdecode(&buf[0], &buf[0] + buf.size(), e, ec) != 0) + throw invalid_torrent_file(ec); + + if (!parse_torrent_file(e, ec, flags)) + throw invalid_torrent_file(ec); + + INVARIANT_CHECK; + } + + void torrent_info::rename_file(int index, std::wstring const& new_filename) + { + TORRENT_ASSERT(is_loaded()); + copy_on_write(); + m_files.rename_file_deprecated(index, new_filename); + } +#endif // TORRENT_NO_DEPRECATE +#endif // TORRENT_USE_WSTRING +#endif + + torrent_info::torrent_info(bdecode_node const& torrent_file, error_code& ec + , int flags) + : m_piece_hashes(0) + , m_creation_date(0) + , m_info_section_size(0) + , m_merkle_first_leaf(0) + , m_multifile(false) + , m_private(false) + , m_i2p(false) + { + parse_torrent_file(torrent_file, ec, flags); + + INVARIANT_CHECK; + } + + torrent_info::torrent_info(char const* buffer, int size, error_code& ec, int flags) + : m_piece_hashes(0) + , m_creation_date(0) + , m_info_section_size(0) + , m_merkle_first_leaf(0) + , m_multifile(false) + , m_private(false) + , m_i2p(false) + { + bdecode_node e; + if (bdecode(buffer, buffer + size, e, ec) != 0) + return; + parse_torrent_file(e, ec, flags); + + INVARIANT_CHECK; + } + + torrent_info::torrent_info(std::string const& filename, error_code& ec, int flags) + : m_piece_hashes(0) + , m_creation_date(0) + , m_info_section_size(0) + , m_merkle_first_leaf(0) + , m_multifile(false) + , m_private(false) + , m_i2p(false) + { + std::vector buf; + int ret = load_file(filename, buf, ec); + if (ret < 0) return; + + bdecode_node e; + if (buf.size() == 0 || bdecode(&buf[0], &buf[0] + buf.size(), e, ec) != 0) + return; + parse_torrent_file(e, ec, flags); + + INVARIANT_CHECK; + } + +#if TORRENT_USE_WSTRING +#ifndef TORRENT_NO_DEPRECATE + torrent_info::torrent_info(std::wstring const& filename, error_code& ec + , int flags) + : m_piece_hashes(0) + , m_creation_date(0) + , m_info_section_size(0) + , m_merkle_first_leaf(0) + , m_multifile(false) + , m_private(false) + , m_i2p(false) + { + std::vector buf; + std::string utf8; + wchar_utf8(filename, utf8); + int ret = load_file(utf8, buf, ec); + if (ret < 0) return; + + bdecode_node e; + if (buf.size() == 0 || bdecode(&buf[0], &buf[0] + buf.size(), e, ec) != 0) + return; + parse_torrent_file(e, ec, flags); + + INVARIANT_CHECK; + } +#endif // TORRENT_NO_DEPRECATE +#endif // TORRENT_USE_WSTRING + + // constructor used for creating new torrents + // will not contain any hashes, comments, creation date + // just the necessary to use it with piece manager + // used for torrents with no metadata + torrent_info::torrent_info(sha1_hash const& info_hash, int flags) + : m_piece_hashes(0) + , m_creation_date(time(0)) + , m_info_hash(info_hash) + , m_info_section_size(0) + , m_merkle_first_leaf(0) + , m_multifile(false) + , m_private(false) + , m_i2p(false) + { + TORRENT_UNUSED(flags); + } + + torrent_info::~torrent_info() + {} + + void torrent_info::load(char const* buffer, int size, error_code& ec) + { + bdecode_node e; + if (bdecode(buffer, buffer + size, e, ec) != 0) + return; + + if (!parse_torrent_file(e, ec, 0)) + return; + } + + void torrent_info::unload() + { + TORRENT_ASSERT(m_info_section.unique()); + + m_info_section.reset(); + m_info_section_size = 0; + + // if we have orig_files, we have to keep + // m_files around, since it means we have + // remapped files, and we won't be able to + // restore that from just reloading the + // torrent file + if (m_orig_files) m_orig_files.reset(); + else m_files.unload(); + + m_piece_hashes = 0; + std::vector().swap(m_web_seeds); + + TORRENT_ASSERT(!is_loaded()); + } + + sha1_hash torrent_info::hash_for_piece(int 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)); + } + +#define SWAP(tmp, a, b) \ + tmp = a; \ + a = b; \ + b = tmp; + + void torrent_info::swap(torrent_info& ti) + { + INVARIANT_CHECK; + + using std::swap; + m_urls.swap(ti.m_urls); + m_web_seeds.swap(ti.m_web_seeds); + m_files.swap(ti.m_files); + m_orig_files.swap(ti.m_orig_files); + m_nodes.swap(ti.m_nodes); + swap(m_info_hash, ti.m_info_hash); + swap(m_creation_date, ti.m_creation_date); + m_comment.swap(ti.m_comment); + m_created_by.swap(ti.m_created_by); + swap(m_info_section, ti.m_info_section); + swap(m_piece_hashes, ti.m_piece_hashes); + m_info_dict.swap(ti.m_info_dict); + swap(m_merkle_tree, ti.m_merkle_tree); + std::swap(m_info_section_size, ti.m_info_section_size); + + boost::uint32_t tmp; + SWAP(tmp, m_merkle_first_leaf, ti.m_merkle_first_leaf); + + bool tmp2; + SWAP(tmp2, m_private, ti.m_private); + SWAP(tmp2, m_i2p, ti.m_i2p); + SWAP(tmp2, m_multifile, ti.m_multifile); + } + +#undef SWAP + + std::string torrent_info::ssl_cert() const + { + // 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); + if (ec) return ""; + } + if (m_info_dict.type() != bdecode_node::dict_t) return ""; + return m_info_dict.dict_find_string_value("ssl-cert"); + } + + bool torrent_info::parse_info_section(bdecode_node const& info + , error_code& ec, int flags) + { + TORRENT_UNUSED(flags); + if (info.type() != bdecode_node::dict_t) + { + ec = errors::torrent_info_no_dict; + return false; + } + + // hash the info-field to calculate info-hash + hasher h; + std::pair section = info.data_section(); + h.update(section.first, section.second); + m_info_hash = h.final(); + + if (section.second >= (std::numeric_limits::max)()) + { + ec = errors::metadata_too_large; + return false; + } + + // copy the info section + m_info_section_size = section.second; + m_info_section.reset(new char[m_info_section_size]); + std::memcpy(m_info_section.get(), section.first, m_info_section_size); + TORRENT_ASSERT(section.first[0] == 'd'); + TORRENT_ASSERT(section.first[m_info_section_size-1] == 'e'); + + // when translating a pointer that points into the 'info' tree's + // backing buffer, into a pointer to our copy of the info section, + // this is the pointer offset to use. + ptrdiff_t info_ptr_diff = m_info_section.get() - section.first; + + // extract piece length + int piece_length = info.dict_find_int_value("piece length", -1); + if (piece_length <= 0) + { + ec = errors::torrent_missing_piece_length; + return false; + } + file_storage files; + files.set_piece_length(piece_length); + + // extract file name (or the directory name if it's a multifile 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; + return false; + } + + std::string name; + sanitize_append_path_element(name, name_ent.string_ptr() + , name_ent.string_length()); + if (name.empty()) name = to_hex(m_info_hash.to_string()); + + // extract file list + bdecode_node files_node = info.dict_find_list("files"); + if (!files_node) + { + // if there's no list of files, there has to be a length + // field. + // this is the counter used to name pad files + int pad_file_cnt = 0; + if (!extract_single_file(info, files, "", info_ptr_diff, true, pad_file_cnt, ec)) + return false; + + m_multifile = false; + } + else + { + if (!extract_files(files_node, files, name, info_ptr_diff, ec)) + return false; + m_multifile = true; + } + TORRENT_ASSERT(!files.name().empty()); + + // extract sha-1 hashes for all pieces + // we want this division to round upwards, that's why we have the + // extra addition + + files.set_num_pieces(int((files.total_size() + files.piece_length() - 1) + / files.piece_length())); + + bdecode_node pieces = info.dict_find_string("pieces"); + bdecode_node root_hash = info.dict_find_string("root hash"); + if (!pieces && !root_hash) + { + ec = errors::torrent_missing_pieces; + return false; + } + + if (pieces) + { + if (pieces.string_length() != files.num_pieces() * 20) + { + ec = errors::torrent_invalid_hashes; + return false; + } + + m_piece_hashes = pieces.string_ptr() + info_ptr_diff; + TORRENT_ASSERT(m_piece_hashes >= m_info_section.get()); + TORRENT_ASSERT(m_piece_hashes < m_info_section.get() + m_info_section_size); + } + else + { + TORRENT_ASSERT(root_hash); + if (root_hash.string_length() != 20) + { + ec = errors::torrent_invalid_hashes; + return false; + } + int num_leafs = merkle_num_leafs(files.num_pieces()); + int num_nodes = merkle_num_nodes(num_leafs); + if (num_nodes - num_leafs >= (2<<24)) + { + ec = errors::too_many_pieces_in_torrent; + return false; + } + m_merkle_first_leaf = num_nodes - num_leafs; + m_merkle_tree.resize(num_nodes); + std::memset(&m_merkle_tree[0], 0, num_nodes * 20); + m_merkle_tree[0].assign(root_hash.string_ptr()); + } + + m_private = info.dict_find_int_value("private", 0) != 0; + +#ifndef TORRENT_DISABLE_MUTABLE_TORRENTS + bdecode_node similar = info.dict_find_list("similar"); + if (similar) + { + for (int i = 0; i < similar.list_size(); ++i) + { + if (similar.list_at(i).type() != bdecode_node::string_t) + continue; + + if (similar.list_at(i).string_length() != 20) + continue; + + m_similar_torrents.push_back(similar.list_at(i).string_ptr() + + info_ptr_diff); + } + } + + bdecode_node collections = info.dict_find_list("collections"); + if (collections) + { + for (int i = 0; i < collections.list_size(); ++i) + { + bdecode_node str = collections.list_at(i); + + if (str.type() != bdecode_node::string_t) continue; + + m_collections.push_back(std::make_pair(str.string_ptr() + + info_ptr_diff, str.string_length())); + } + } +#endif // TORRENT_DISABLE_MUTABLE_TORRENTS + + // now, commit the files structure we just parsed out + // into the torrent_info object. + // if we already have an m_files that's populated, it + // indicates that we unloaded this torrent_info ones + // and we had modifications to the files, so we unloaded + // the orig_files. In that case, the orig files is what + // needs to be restored + if (m_files.is_loaded()) { + m_orig_files.reset(new file_storage); + const_cast(*m_orig_files).swap(files); + } + else + { + m_files.swap(files); + } + return true; + } + + bdecode_node torrent_info::info(char const* key) const + { + if (m_info_dict.type() == bdecode_node::none_t) + { + error_code ec; + bdecode(m_info_section.get(), m_info_section.get() + + m_info_section_size, m_info_dict, ec); + if (ec) return bdecode_node(); + } + return m_info_dict.dict_find(key); + } + + + bool torrent_info::add_merkle_nodes(std::map const& subtree + , int piece) + { + INVARIANT_CHECK; + + int n = m_merkle_first_leaf + piece; + typedef std::map::const_iterator iter; + iter it = subtree.find(n); + if (it == subtree.end()) return false; + sha1_hash h = it->second; + + // if the verification passes, these are the + // nodes to add to our tree + std::map to_add; + + while (n > 0) + { + int sibling = merkle_get_sibling(n); + int parent = merkle_get_parent(n); + iter sibling_hash = subtree.find(sibling); + if (sibling_hash == subtree.end()) + return false; + to_add[n] = h; + to_add[sibling] = sibling_hash->second; + hasher hs; + if (sibling < n) + { + hs.update(sibling_hash->second.data(), 20); + hs.update(h.data(), 20); + } + else + { + hs.update(h.data(), 20); + hs.update(sibling_hash->second.data(), 20); + } + h = hs.final(); + n = parent; + } + if (h != m_merkle_tree[0]) return false; + + // the nodes and piece hash matched the root-hash + // insert them into our tree + + for (std::map::iterator i = to_add.begin() + , end(to_add.end()); i != end; ++i) + { + m_merkle_tree[i->first] = i->second; + } + return true; + } + + // builds a list of nodes that are required to verify + // the given piece + std::map torrent_info::build_merkle_list(int piece) const + { + INVARIANT_CHECK; + + std::map ret; + int n = m_merkle_first_leaf + piece; + ret[n] = m_merkle_tree[n]; + ret[0] = m_merkle_tree[0]; + while (n > 0) + { + int sibling = merkle_get_sibling(n); + int parent = merkle_get_parent(n); + ret[sibling] = m_merkle_tree[sibling]; + // we cannot build the tree path if one + // of the nodes in the tree is missing + TORRENT_ASSERT(m_merkle_tree[sibling] != sha1_hash(0)); + n = parent; + } + return ret; + } + + bool torrent_info::parse_torrent_file(bdecode_node const& torrent_file + , error_code& ec, int flags) + { + if (torrent_file.type() != bdecode_node::dict_t) + { + ec = errors::torrent_is_no_dict; + return false; + } + + bdecode_node info = torrent_file.dict_find_dict("info"); + if (info == 0) + { + bdecode_node link = torrent_file.dict_find_string("magnet-uri"); + if (link) + { + std::string uri = link.string_value(); + + add_torrent_params p; + parse_magnet_uri(uri, p, ec); + if (ec) return false; + + m_info_hash = p.info_hash; + for (std::vector::iterator i = p.trackers.begin() + , end(p.trackers.end()); i != end; ++i) + m_urls.push_back(*i); + + return true; + } + + ec = errors::torrent_missing_info; + return false; + } + if (!parse_info_section(info, ec, flags)) return false; + resolve_duplicate_filenames(); + +#ifndef TORRENT_DISABLE_MUTABLE_TORRENTS + bdecode_node 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.push_back( + sha1_hash(similar.list_at(i).string_ptr())); + } + } + + bdecode_node collections = torrent_file.dict_find_list("collections"); + if (collections) + { + for (int i = 0; i < collections.list_size(); ++i) + { + bdecode_node str = collections.list_at(i); + + if (str.type() != bdecode_node::string_t) continue; + + m_owned_collections.push_back(std::string(str.string_ptr() + , str.string_length())); + } + } +#endif // TORRENT_DISABLE_MUTABLE_TORRENTS + + // extract the url of the tracker + bdecode_node 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 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)); + e.trim(); + if (e.url.empty()) continue; + e.tier = j; + e.fail_limit = 0; + e.source = announce_entry::source_torrent; +#if TORRENT_USE_I2P + if (is_i2p_url(e.url)) m_i2p = true; +#endif + m_urls.push_back(e); + } + } + + if (!m_urls.empty()) + { + // shuffle each tier + std::vector::iterator start = m_urls.begin(); + std::vector::iterator stop; + int current_tier = m_urls.front().tier; + for (stop = m_urls.begin(); stop != m_urls.end(); ++stop) + { + if (stop->tier != current_tier) + { + std::random_shuffle(start, stop, randint); + start = stop; + current_tier = stop->tier; + } + } + std::random_shuffle(start, stop, randint); + } + } + + if (m_urls.empty()) + { + announce_entry e(torrent_file.dict_find_string_value("announce")); + e.fail_limit = 0; + e.source = announce_entry::source_torrent; + e.trim(); +#if TORRENT_USE_I2P + if (is_i2p_url(e.url)) m_i2p = true; +#endif + if (!e.url.empty()) m_urls.push_back(e); + } + + bdecode_node nodes = torrent_file.dict_find_list("nodes"); + if (nodes) + { + for (int i = 0, end(nodes.list_size()); i < end; ++i) + { + bdecode_node 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.push_back(std::make_pair( + n.list_at(0).string_value() + , int(n.list_at(1).int_value()))); + } + } + + // extract creation date + boost::int64_t cd = torrent_file.dict_find_int_value("creation date", -1); + if (cd >= 0) + { + m_creation_date = long(cd); + } + + // if there are any url-seeds, extract them + bdecode_node 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()) + , web_seed_entry::url_seed); + if (m_multifile && ent.url[ent.url.size()-1] != '/') ent.url += '/'; + m_web_seeds.push_back(ent); + } + else if (url_seeds && url_seeds.type() == bdecode_node::list_t) + { + // only add a URL once + std::set unique; + for (int i = 0, end(url_seeds.list_size()); i < end; ++i) + { + bdecode_node 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()) + , web_seed_entry::url_seed); + if (m_multifile && ent.url[ent.url.size()-1] != '/') ent.url += '/'; + if (unique.count(ent.url)) continue; + unique.insert(ent.url); + m_web_seeds.push_back(ent); + } + } + + // if there are any http-seeds, extract them + bdecode_node 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.push_back(web_seed_entry(maybe_url_encode(http_seeds.string_value()) + , 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 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()); + if (unique.count(u)) continue; + unique.insert(u); + m_web_seeds.push_back(web_seed_entry(u, web_seed_entry::http_seed)); + } + } + + m_comment = torrent_file.dict_find_string_value("comment.utf-8"); + if (m_comment.empty()) m_comment = torrent_file.dict_find_string_value("comment"); + verify_encoding(m_comment); + + m_created_by = torrent_file.dict_find_string_value("created by.utf-8"); + if (m_created_by.empty()) m_created_by = torrent_file.dict_find_string_value("created by"); + verify_encoding(m_created_by); + + return true; + } + + boost::optional + torrent_info::creation_date() const + { + if (m_creation_date != 0) + { + return boost::optional(m_creation_date); + } + return boost::optional(); + } + + void torrent_info::add_tracker(std::string const& url, int tier) + { + announce_entry e(url); + e.tier = tier; + e.source = announce_entry::source_client; + m_urls.push_back(e); + + std::sort(m_urls.begin(), m_urls.end(), boost::bind(&announce_entry::tier, _1) + < boost::bind(&announce_entry::tier, _2)); + } + +#ifndef TORRENT_NO_DEPRECATE + namespace + { + struct filter_web_seed_type + { + filter_web_seed_type(web_seed_entry::type_t t_) : t(t_) {} + void operator() (web_seed_entry const& w) + { if (w.type == t) urls.push_back(w.url); } + std::vector urls; + web_seed_entry::type_t t; + }; + } + + std::vector torrent_info::url_seeds() const + { + return std::for_each(m_web_seeds.begin(), m_web_seeds.end() + , filter_web_seed_type(web_seed_entry::url_seed)).urls; + } + + std::vector torrent_info::http_seeds() const + { + return std::for_each(m_web_seeds.begin(), m_web_seeds.end() + , filter_web_seed_type(web_seed_entry::http_seed)).urls; + } + + bool torrent_info::parse_info_section(lazy_entry const& le, error_code& ec + , int flags) + { + if (le.type() == lazy_entry::none_t) return false; + std::pair buf = le.data_section(); + bdecode_node e; + if (bdecode(buf.first, buf.first + buf.second, e, ec) != 0) + return false; + + return parse_info_section(e, ec, flags); + } + +#endif // TORRENT_NO_DEPRECATE + + 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.push_back(web_seed_entry(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.push_back(web_seed_entry(url, web_seed_entry::http_seed + , auth, extra_headers)); + } + + void torrent_info::set_web_seeds(std::vector seeds) + { + m_web_seeds = 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 (int i = 0; i < m_similar_torrents.size(); ++i) + ret.push_back(sha1_hash(m_similar_torrents[i])); + + for (int i = 0; i < m_owned_similar_torrents.size(); ++i) + ret.push_back(m_owned_similar_torrents[i]); +#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 (int i = 0; i < m_collections.size(); ++i) + ret.push_back(std::string(m_collections[i].first, m_collections[i].second)); + + for (int i = 0; i < m_owned_collections.size(); ++i) + ret.push_back(m_owned_collections[i]); +#endif // TORRENT_DISABLE_MUTABLE_TORRENTS + + return ret; + } + +#if !defined TORRENT_NO_DEPRECATE && TORRENT_USE_IOSTREAM +// ------- start deprecation ------- + + void torrent_info::print(std::ostream& os) const + { + INVARIANT_CHECK; + + os << "trackers:\n"; + for (std::vector::const_iterator i = trackers().begin(); + i != trackers().end(); ++i) + { + os << i->tier << ": " << i->url << "\n"; + } + if (!m_comment.empty()) + os << "comment: " << m_comment << "\n"; + os << "private: " << (m_private?"yes":"no") << "\n"; + os << "number of pieces: " << num_pieces() << "\n"; + os << "piece length: " << piece_length() << "\n"; + os << "files:\n"; + for (int i = 0; i < m_files.num_files(); ++i) + os << " " << std::setw(11) << m_files.file_size(i) + << " " << m_files.file_path(i) << "\n"; + } + +// ------- end deprecation ------- +#endif + +#if TORRENT_USE_INVARIANT_CHECKS + void torrent_info::check_invariant() const + { + for (int i = 0; i < m_files.num_files(); ++i) + { + TORRENT_ASSERT(m_files.file_name_ptr(i) != 0); + if (m_files.file_name_len(i) != -1) + { + // name needs to point into the allocated info section buffer + TORRENT_ASSERT(m_files.file_name_ptr(i) >= m_info_section.get()); + TORRENT_ASSERT(m_files.file_name_ptr(i) < m_info_section.get() + m_info_section_size); + } + else + { + // name must be a valid string + TORRENT_ASSERT(strlen(m_files.file_name_ptr(i)) < 2048); + } + } + + if (m_piece_hashes != 0) + { + TORRENT_ASSERT(m_piece_hashes >= m_info_section.get()); + TORRENT_ASSERT(m_piece_hashes < m_info_section.get() + m_info_section_size); + } + } +#endif + +} + diff --git a/src/torrent_peer.cpp b/src/torrent_peer.cpp new file mode 100644 index 0000000..338d287 --- /dev/null +++ b/src/torrent_peer.cpp @@ -0,0 +1,304 @@ +/* + +Copyright (c) 2012-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/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 // for BIG_ENDIAN and LITTLE_ENDIAN macros + +namespace libtorrent +{ + namespace { + + void apply_mask(boost::uint8_t* b, boost::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 + boost::uint32_t peer_priority(tcp::endpoint e1, tcp::endpoint e2) + { + TORRENT_ASSERT(e1.address().is_v4() == e2.address().is_v4()); + + using std::swap; + + boost::uint32_t ret; + if (e1.address() == e2.address()) + { + if (e1.port() > e2.port()) + swap(e1, e2); + boost::uint32_t p; +#if defined BOOST_BIG_ENDIAN + p = e1.port() << 16; + p |= e2.port(); +#elif defined BOOST_LITTLE_ENDIAN + p = aux::host_to_network(e2.port()) << 16; + p |= aux::host_to_network(e1.port()); +#else +#error unsupported endianness +#endif + ret = crc32c_32(p); + } +#if TORRENT_USE_IPV6 + else if (e1.address().is_v6()) + { + static const boost::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 mask = memcmp(&b1[0], &b2[0], 4) ? 0 + : memcmp(&b1[0], &b2[0], 6) ? 1 : 2; + apply_mask(&b1[0], v6mask[mask], 8); + apply_mask(&b2[0], v6mask[mask], 8); + boost::uint64_t addrbuf[4]; + memcpy(&addrbuf[0], &b1[0], 16); + memcpy(&addrbuf[2], &b2[0], 16); + ret = crc32c(addrbuf, 4); + } +#endif + else + { + static const boost::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); + boost::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(boost::uint16_t port_, bool conn, int src) + : prev_amount_upload(0) + , prev_amount_download(0) + , connection(0) + , peer_rank(0) + , last_optimistically_unchoked(0) + , last_connected(0) + , port(port_) + , hashfails(0) + , failcount(0) + , connectable(conn) + , optimistically_unchoked(false) + , seed(false) + , fast_reconnects(0) + , trust_points(0) + , source(src) +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + // assume no support in order to + // prefer opening non-encrypted + // connections. If it fails, we'll + // retry with encryption + , pe_support(false) +#endif +#if TORRENT_USE_IPV6 + , is_v6_addr(false) +#endif +#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) +#if TORRENT_USE_ASSERTS + , in_use(false) +#endif + { + TORRENT_ASSERT((src & 0xff) == src); + } + + boost::uint32_t torrent_peer::rank(external_ip const& external, int external_port) const + { +//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()), external_port) + , tcp::endpoint(this->address(), this->port)); + return peer_rank; + } + +#ifndef TORRENT_DISABLE_LOGGING + std::string torrent_peer::to_string() const + { +#if TORRENT_USE_I2P + if (is_i2p_addr) return dest(); +#endif // TORRENT_USE_I2P + error_code ec; + return address().to_string(ec); + } +#endif + + boost::uint64_t torrent_peer::total_download() const + { + if (connection != 0) + { + TORRENT_ASSERT(prev_amount_download == 0); + return connection->statistics().total_payload_download(); + } + else + { + return boost::uint64_t(prev_amount_download) << 10; + } + } + + boost::uint64_t torrent_peer::total_upload() const + { + if (connection != 0) + { + TORRENT_ASSERT(prev_amount_upload == 0); + return connection->statistics().total_payload_upload(); + } + else + { + return boost::uint64_t(prev_amount_upload) << 10; + } + } + + ipv4_peer::ipv4_peer( + tcp::endpoint const& ep, bool c, int src + ) + : torrent_peer(ep.port(), c, src) + , addr(ep.address().to_v4()) + { +#if TORRENT_USE_IPV6 + is_v6_addr = false; +#endif +#if TORRENT_USE_I2P + is_i2p_addr = false; +#endif + } + + ipv4_peer::ipv4_peer(ipv4_peer const& p) + : torrent_peer(p), addr(p.addr) {} + +#if TORRENT_USE_I2P + i2p_peer::i2p_peer(char const* dest, bool connectable, int src) + : torrent_peer(0, connectable, src), destination(allocate_string_copy(dest)) + { +#if TORRENT_USE_IPV6 + is_v6_addr = false; +#endif + is_i2p_addr = true; + } + + i2p_peer::~i2p_peer() + { free(destination); } +#endif // TORRENT_USE_I2P + +#if TORRENT_USE_IPV6 + ipv6_peer::ipv6_peer( + tcp::endpoint const& ep, bool c, int 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 + } + +#endif // TORRENT_USE_IPV6 + +#if TORRENT_USE_I2P + char const* torrent_peer::dest() const + { + if (is_i2p_addr) + return static_cast(this)->destination; + return ""; + } +#endif + + libtorrent::address torrent_peer::address() const + { +#if TORRENT_USE_IPV6 + if (is_v6_addr) + return libtorrent::address_v6( + static_cast(this)->addr); + else +#endif +#if TORRENT_USE_I2P + if (is_i2p_addr) return libtorrent::address(); + else +#endif + return static_cast(this)->addr; + } + +} + diff --git a/src/torrent_peer_allocator.cpp b/src/torrent_peer_allocator.cpp new file mode 100644 index 0000000..98a567e --- /dev/null +++ b/src/torrent_peer_allocator.cpp @@ -0,0 +1,134 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/torrent_peer_allocator.hpp" + +namespace libtorrent +{ + + torrent_peer_allocator::torrent_peer_allocator() + : m_ipv4_peer_pool(sizeof(libtorrent::ipv4_peer), 500) +#if TORRENT_USE_IPV6 + , m_ipv6_peer_pool(sizeof(libtorrent::ipv6_peer), 500) +#endif +#if TORRENT_USE_I2P + , m_i2p_peer_pool(sizeof(libtorrent::i2p_peer), 500) +#endif + , m_total_bytes(0) + , m_total_allocations(0) + , m_live_bytes(0) + , m_live_allocations(0) + { + } + + torrent_peer* torrent_peer_allocator::allocate_peer_entry(int type) + { + torrent_peer* p = NULL; + switch(type) + { + case torrent_peer_allocator_interface::ipv4_peer_type: + p = static_cast(m_ipv4_peer_pool.malloc()); + if (p == NULL) return NULL; + 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; +#if TORRENT_USE_IPV6 + case torrent_peer_allocator_interface::ipv6_peer_type: + p = static_cast(m_ipv6_peer_pool.malloc()); + if (p == NULL) return NULL; + 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; +#endif +#if TORRENT_USE_I2P + case torrent_peer_allocator_interface::i2p_peer_type: + p = static_cast(m_i2p_peer_pool.malloc()); + if (p == NULL) return NULL; + 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) + { +#if TORRENT_USE_IPV6 + 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 >= sizeof(ipv6_peer)); + m_live_bytes -= sizeof(ipv6_peer); + TORRENT_ASSERT(m_live_allocations > 0); + --m_live_allocations; + return; + } +#endif +#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 >= sizeof(i2p_peer)); + m_live_bytes -= 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 >= sizeof(ipv4_peer)); + m_live_bytes -= 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..c04454b --- /dev/null +++ b/src/torrent_status.cpp @@ -0,0 +1,114 @@ +/* + +Copyright (c) 2015-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/torrent_status.hpp" + +namespace libtorrent +{ + torrent_status::torrent_status() + : error_file(torrent_status::error_file_none) + , next_announce(seconds(0)) + , total_download(0) + , total_upload(0) + , total_payload_download(0) + , total_payload_upload(0) + , total_failed_bytes(0) + , total_redundant_bytes(0) + , total_done(0) + , total_wanted_done(0) + , total_wanted(0) + , all_time_upload(0) + , all_time_download(0) + , added_time(0) + , completed_time(0) + , last_seen_complete(0) + , storage_mode(storage_mode_sparse) + , progress(0.f) + , progress_ppm(0) + , queue_position(0) + , download_rate(0) + , upload_rate(0) + , download_payload_rate(0) + , upload_payload_rate(0) + , num_seeds(0) + , num_peers(0) + , num_complete(-1) + , num_incomplete(-1) + , list_seeds(0) + , list_peers(0) + , connect_candidates(0) + , num_pieces(0) + , distributed_full_copies(0) + , distributed_fraction(0) + , distributed_copies(0.f) + , block_size(0) + , num_uploads(0) + , num_connections(0) + , uploads_limit(0) + , connections_limit(0) + , up_bandwidth_queue(0) + , down_bandwidth_queue(0) + , time_since_upload(0) + , time_since_download(0) + , active_time(0) + , finished_time(0) + , seeding_time(0) + , seed_rank(0) + , last_scrape(0) + , priority(0) + , state(checking_resume_data) + , need_save_resume(false) + , ip_filter_applies(true) + , upload_mode(false) + , share_mode(false) + , super_seeding(false) + , paused(false) + , auto_managed(false) + , sequential_download(false) + , is_seeding(false) + , is_finished(false) + , has_metadata(false) + , has_incoming(false) + , seed_mode(false) + , moving_storage(false) + , is_loaded(true) + , announcing_to_trackers(false) + , announcing_to_lsd(false) + , announcing_to_dht(false) + , stop_when_ready(false) + , info_hash(0) + {} + + torrent_status::~torrent_status() {} + +} + diff --git a/src/tracker_manager.cpp b/src/tracker_manager.cpp new file mode 100644 index 0000000..592eed8 --- /dev/null +++ b/src/tracker_manager.cpp @@ -0,0 +1,452 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include + +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/tracker_manager.hpp" +#include "libtorrent/http_tracker_connection.hpp" +#include "libtorrent/udp_tracker_connection.hpp" +#include "libtorrent/aux_/session_impl.hpp" + +using boost::tuples::make_tuple; +using boost::tuples::tuple; + +namespace +{ + enum + { + minimum_tracker_response_length = 3, + http_buffer_size = 2048 + }; + +} + +namespace libtorrent +{ + timeout_handler::timeout_handler(io_service& ios) + : m_completion_timeout(0) + , m_start_time(clock_type::now()) + , m_read_time(m_start_time) + , m_timeout(ios) + , m_read_timeout(0) + , m_abort(false) + {} + + 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); + } + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("timeout_handler::timeout_callback"); +#endif + error_code ec; + m_timeout.expires_at(m_read_time + seconds(timeout), ec); + m_timeout.async_wait(boost::bind( + &timeout_handler::timeout_callback, shared_from_this(), _1)); + } + + void timeout_handler::restart_read_timeout() + { + m_read_time = clock_type::now(); + } + + void timeout_handler::cancel() + { + m_abort = true; + m_completion_timeout = 0; + error_code ec; + m_timeout.cancel(ec); + } + + void timeout_handler::timeout_callback(error_code const& error) + { +#if defined TORRENT_ASIO_DEBUGGING + complete_async("timeout_handler::timeout_callback"); +#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); + } +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("timeout_handler::timeout_callback"); +#endif + error_code ec; + m_timeout.expires_at(m_read_time + seconds(timeout), ec); + m_timeout.async_wait( + boost::bind(&timeout_handler::timeout_callback, shared_from_this(), _1)); + } + + tracker_connection::tracker_connection( + tracker_manager& man + , tracker_request const& req + , io_service& ios + , boost::weak_ptr r) + : timeout_handler(ios) + , m_req(req) + , m_requester(r) + , m_man(man) + {} + + boost::shared_ptr tracker_connection::requester() const + { + return m_requester.lock(); + } + + void tracker_connection::fail(error_code const& ec, int code + , char const* msg, int interval, int min_interval) + { + // we need to post the error to avoid deadlock + get_io_service().post(boost::bind(&tracker_connection::fail_impl + , shared_from_this(), ec, code, std::string(msg), interval, min_interval)); + } + + void tracker_connection::fail_impl(error_code const& ec, int code + , std::string msg, int interval, int min_interval) + { + boost::shared_ptr cb = requester(); + if (cb) cb->tracker_request_error(m_req, code, ec, msg.c_str() + , interval == 0 ? min_interval : interval); + close(); + } + + void tracker_connection::sent_bytes(int bytes) + { + m_man.sent_bytes(bytes); + } + + void tracker_connection::received_bytes(int bytes) + { + m_man.received_bytes(bytes); + } + + void tracker_connection::close() + { + cancel(); + m_man.remove_request(this); + } + + // TODO: 2 some of these arguments could probably be moved to the + // tracker request itself. like the ip_filter and settings + tracker_manager::tracker_manager(class udp_socket& sock + , counters& stats_counters + , resolver_interface& resolver + , aux::session_settings const& sett +#if !defined TORRENT_DISABLE_LOGGING || TORRENT_USE_ASSERTS + , aux::session_logger& ses +#endif + ) + : m_udp_socket(sock) + , m_host_resolver(resolver) + , m_settings(sett) + , m_stats_counters(stats_counters) +#if !defined TORRENT_DISABLE_LOGGING || TORRENT_USE_ASSERTS + , m_ses(ses) +#endif + , m_abort(false) + {} + + tracker_manager::~tracker_manager() + { + TORRENT_ASSERT(m_abort); + 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(tracker_connection const* c) + { + mutex_t::scoped_lock l(m_mutex); + + http_conns_t::iterator i = std::find_if(m_http_conns.begin() + , m_http_conns.end() + , boost::bind(&boost::shared_ptr::get, _1) == c); + if (i != m_http_conns.end()) + { + m_http_conns.erase(i); + return; + } + + udp_conns_t::iterator j = std::find_if(m_udp_conns.begin() + , m_udp_conns.end() + , boost::bind(&boost::shared_ptr::get + , boost::bind(&udp_conns_t::value_type::second, _1)) == c); + if (j != m_udp_conns.end()) + { + m_udp_conns.erase(j); + return; + } + } + + void tracker_manager::update_transaction_id( + boost::shared_ptr c + , boost::uint64_t tid) + { + m_udp_conns.erase(c->transaction_id()); + m_udp_conns[tid] = c; + } + + void tracker_manager::queue_request( + io_service& ios + , tracker_request req + , boost::weak_ptr c) + { + mutex_t::scoped_lock l(m_mutex); + TORRENT_ASSERT(req.num_want >= 0); + TORRENT_ASSERT(!m_abort || req.event == tracker_request::stopped); + if (m_abort && req.event != tracker_request::stopped) return; + if (req.event == tracker_request::stopped) + req.num_want = 0; + + TORRENT_ASSERT(!m_abort || req.event == tracker_request::stopped); + if (m_abort && req.event != tracker_request::stopped) + return; + + std::string protocol = req.url.substr(0, req.url.find(':')); + +#ifdef TORRENT_USE_OPENSSL + if (protocol == "http" || protocol == "https") +#else + if (protocol == "http") +#endif + { + boost::shared_ptr con + = boost::make_shared( + boost::ref(ios), boost::ref(*this), boost::cref(req), c); + m_http_conns.push_back(con); + con->start(); + return; + } + else if (protocol == "udp") + { + boost::shared_ptr con + = boost::make_shared( + boost::ref(ios), boost::ref(*this), boost::cref(req) , c); + m_udp_conns[con->transaction_id()] = con; + con->start(); + return; + } + + // we need to post the error to avoid deadlock + if (boost::shared_ptr r = c.lock()) + ios.post(boost::bind(&request_callback::tracker_request_error, r, req + , -1, error_code(errors::unsupported_url_protocol) + , "", 0)); + } + + bool tracker_manager::incoming_packet(error_code const& e + , udp::endpoint const& ep, char const* buf, int size) + { + // ignore packets smaller than 8 bytes + if (size < 8) + { +#ifndef TORRENT_DISABLE_LOGGING + m_ses.session_log("incoming packet from %s, not a UDP tracker message " + "(%d Bytes)", print_endpoint(ep).c_str(), size); +#endif + return false; + } + + // the first word is the action, if it's not [0, 3] + // it's not a valid udp tracker response + const char* ptr = buf; + boost::uint32_t action = detail::read_uint32(ptr); + if (action > 3) return false; + + boost::uint32_t transaction = detail::read_uint32(ptr); + udp_conns_t::iterator i = m_udp_conns.find(transaction); + + if (i == m_udp_conns.end()) + { +#ifndef TORRENT_DISABLE_LOGGING + m_ses.session_log("incoming UDP tracker packet from %s has invalid " + "transaction ID (%" PRIu32 ")", print_endpoint(ep).c_str() + , transaction); +#endif + return false; + } + + boost::shared_ptr p = i->second; + // on_receive() may remove the tracker connection from the list + return p->on_receive(e, ep, buf, size); + } + + bool tracker_manager::incoming_packet(error_code const& e + , char const* hostname, char const* buf, int size) + { + // ignore packets smaller than 8 bytes + if (size < 16) return false; + + // the first word is the action, if it's not [0, 3] + // it's not a valid udp tracker response + const char* ptr = buf; + boost::uint32_t action = detail::read_uint32(ptr); + if (action > 3) return false; + + boost::uint32_t transaction = detail::read_uint32(ptr); + udp_conns_t::iterator 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; + } + + boost::shared_ptr p = i->second; + // on_receive() may remove the tracker connection from the list + return p->on_receive_hostname(e, hostname, buf, size); + } + + void tracker_manager::abort_all_requests(bool all) + { + // removes all connections except 'event=stopped'-requests + mutex_t::scoped_lock l(m_mutex); + + m_abort = true; + http_conns_t close_http_connections; + std::vector > close_udp_connections; + + for (http_conns_t::iterator i = m_http_conns.begin() + , end(m_http_conns.end()); i != end; ++i) + { + http_tracker_connection* c = i->get(); + tracker_request const& req = c->tracker_req(); + if (req.event == tracker_request::stopped && !all) + continue; + + close_http_connections.push_back(*i); + +#ifndef TORRENT_DISABLE_LOGGING + boost::shared_ptr rc = c->requester(); + if (rc) rc->debug_log("aborting: %s", req.url.c_str()); +#endif + } + for (udp_conns_t::iterator i = m_udp_conns.begin() + , end(m_udp_conns.end()); i != end; ++i) + { + boost::shared_ptr c = i->second; + tracker_request const& req = c->tracker_req(); + if (req.event == tracker_request::stopped && !all) + continue; + + close_udp_connections.push_back(c); + +#ifndef TORRENT_DISABLE_LOGGING + boost::shared_ptr rc = c->requester(); + if (rc) rc->debug_log("aborting: %s", req.url.c_str()); +#endif + } + l.unlock(); + + for (http_conns_t::iterator i = close_http_connections.begin() + , end(close_http_connections.end()); i != end; ++i) + { + (*i)->close(); + } + + for (std::vector >::iterator i + = close_udp_connections.begin() + , end(close_udp_connections.end()); i != end; ++i) + { + (*i)->close(); + } + } + + bool tracker_manager::empty() const + { + mutex_t::scoped_lock l(m_mutex); + return m_http_conns.empty() && m_udp_conns.empty(); + } + + int tracker_manager::num_requests() const + { + mutex_t::scoped_lock l(m_mutex); + return m_http_conns.size() + m_udp_conns.size(); + } +} diff --git a/src/udp_socket.cpp b/src/udp_socket.cpp new file mode 100644 index 0000000..623a576 --- /dev/null +++ b/src/udp_socket.cpp @@ -0,0 +1,1523 @@ +/* + +Copyright (c) 2007-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" // for print_backtrace +#include "libtorrent/socket.hpp" +#include "libtorrent/udp_socket.hpp" +#include "libtorrent/socket_io.hpp" +#include "libtorrent/error.hpp" +#include "libtorrent/string_util.hpp" // for allocate_string_copy +#include "libtorrent/broadcast_socket.hpp" // for is_any +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/error.hpp" +#include "libtorrent/aux_/time.hpp" // for aux::time_now() + +#include +#include +#include +#include +#include +#include +#include + +#if defined TORRENT_ASIO_DEBUGGING +#include "libtorrent/debug.hpp" +#endif + +using namespace libtorrent; + +udp_socket::udp_socket(io_service& ios) + : m_observers_locked(false) + , m_ipv4_sock(ios) + , m_timer(ios) + , m_buf_size(0) + , m_new_buf_size(0) + , m_buf(0) +#if TORRENT_USE_IPV6 + , m_ipv6_sock(ios) +#endif + , m_bind_port(0) + , m_v4_outstanding(0) + , m_restart_v4(0) +#if TORRENT_USE_IPV6 + , m_v6_outstanding(0) + , m_restart_v6(false) +#endif + , m_socks5_sock(ios) + , m_resolver(ios) + , m_queue_packets(false) + , m_tunnel_packets(false) + , m_force_proxy(false) + , m_abort(true) + , m_outstanding_ops(0) +#if TORRENT_USE_IPV6 + , m_v6_write_subscribed(false) +#endif + , m_v4_write_subscribed(false) +{ +#if TORRENT_USE_ASSERTS + m_magic = 0x1337; + m_started = false; + m_outstanding_when_aborted = -1; + m_outstanding_connect = 0; + m_outstanding_timeout = 0; + m_outstanding_resolve = 0; + m_outstanding_socks = 0; +#endif + + m_buf_size = 2048; + m_new_buf_size = m_buf_size; + m_buf = static_cast(malloc(m_buf_size)); +} + +udp_socket::~udp_socket() +{ + free(m_buf); +#if TORRENT_USE_IPV6 + TORRENT_ASSERT_VAL(m_v6_outstanding == 0, m_v6_outstanding); +#endif + TORRENT_ASSERT_VAL(m_v4_outstanding == 0, m_v4_outstanding); + TORRENT_ASSERT(m_magic == 0x1337); + TORRENT_ASSERT(m_observers_locked == false); +#if TORRENT_USE_ASSERTS + m_magic = 0; +#endif + TORRENT_ASSERT(m_outstanding_ops == 0); +} + +#if TORRENT_USE_ASSERTS + #define CHECK_MAGIC check_magic_ cm_(m_magic) + struct check_magic_ + { + check_magic_(int& m_): m(m_) { TORRENT_ASSERT(m == 0x1337); } + ~check_magic_() { TORRENT_ASSERT(m == 0x1337); } + int& m; + }; +#else + #define CHECK_MAGIC do {} TORRENT_WHILE_0 +#endif + +void udp_socket::send_hostname(char const* hostname, int port + , char const* p, int len, error_code& ec, int flags) +{ + CHECK_MAGIC; + + 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; + } + + if (m_tunnel_packets) + { + // send udp packets through SOCKS5 server + wrap(hostname, port, p, len, ec); + return; + } + + // this function is only supported when we're using a proxy + if (!m_queue_packets && !m_force_proxy) + { + address target = address::from_string(hostname, ec); + if (!ec) send(udp::endpoint(target, port), p, len, ec, 0); + return; + } + + if (m_queue.size() > 1000 || (flags & dont_queue)) return; + + m_queue.push_back(queued_packet()); + queued_packet& qp = m_queue.back(); + qp.ep.port(port); + + address target = address::from_string(hostname, ec); + if (ec) qp.ep.address(target); + else qp.hostname = allocate_string_copy(hostname); + qp.buf.insert(qp.buf.begin(), p, p + len); + qp.flags = 0; +} + +void udp_socket::send(udp::endpoint const& ep, char const* p, int len + , error_code& ec, int flags) +{ + CHECK_MAGIC; + + 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; + } + + const bool allow_proxy + = ((flags & peer_connection) && m_proxy_settings.proxy_peer_connections) + || ((flags & tracker_connection) && m_proxy_settings.proxy_tracker_connections) + || (flags & (tracker_connection | peer_connection)) == 0 + ; + + if (allow_proxy) + { + if (m_tunnel_packets) + { + // send udp packets through SOCKS5 server + wrap(ep, p, len, ec); + return; + } + + if (m_queue_packets) + { + if (m_queue.size() > 1000 || (flags & dont_queue)) return; + + m_queue.push_back(queued_packet()); + queued_packet& qp = m_queue.back(); + qp.ep = ep; + qp.hostname = 0; + qp.flags = flags; + qp.buf.insert(qp.buf.begin(), p, p + len); + return; + } + } + + if (m_force_proxy) return; + +#if TORRENT_USE_IPV6 + if (ep.address().is_v6() && m_ipv6_sock.is_open()) + m_ipv6_sock.send_to(boost::asio::buffer(p, len), ep, 0, ec); + else +#endif + m_ipv4_sock.send_to(boost::asio::buffer(p, len), ep, 0, ec); + + if (ec == error::would_block || ec == error::try_again) + { +#if TORRENT_USE_IPV6 + if (ep.address().is_v6() && m_ipv6_sock.is_open()) + { + if (!m_v6_write_subscribed) + { + m_ipv6_sock.async_send(null_buffers() + , boost::bind(&udp_socket::on_writable, this, _1, &m_ipv6_sock)); + m_v6_write_subscribed = true; + } + } + else +#endif + { + if (!m_v4_write_subscribed) + { + m_ipv4_sock.async_send(null_buffers() + , boost::bind(&udp_socket::on_writable, this, _1, &m_ipv4_sock)); + m_v4_write_subscribed = true; + } + } + } +} + +void udp_socket::on_writable(error_code const& ec, udp::socket* s) +{ +#if TORRENT_USE_IPV6 + if (s == &m_ipv6_sock) + m_v6_write_subscribed = false; + else +#else + TORRENT_UNUSED(s); +#endif + m_v4_write_subscribed = false; + + if (ec == boost::asio::error::operation_aborted) return; + + call_writable_handler(); +} + +// called whenever the socket is readable +void udp_socket::on_read(error_code const& ec, udp::socket* s) +{ +#if defined TORRENT_ASIO_DEBUGGING + complete_async("udp_socket::on_read"); +#endif + + TORRENT_ASSERT(m_magic == 0x1337); + TORRENT_ASSERT(is_single_thread()); + +#if TORRENT_USE_IPV6 + if (s == &m_ipv6_sock) + { + TORRENT_ASSERT(m_v6_outstanding > 0); + --m_v6_outstanding; + } + else +#else + TORRENT_UNUSED(s); +#endif + { + TORRENT_ASSERT(m_v4_outstanding > 0); + --m_v4_outstanding; + } + + if (ec == boost::asio::error::operation_aborted) + { +#if TORRENT_USE_IPV6 + if (s == &m_ipv6_sock) + { + if (m_restart_v6) { + --m_restart_v6; + setup_read(s); + } + } + else +#endif + { + if (m_restart_v4) { + --m_restart_v4; + setup_read(s); + } + } + return; + } + if (m_abort) + { + close_impl(); + return; + } + + CHECK_MAGIC; + + for (;;) + { + error_code err; + udp::endpoint ep; + size_t bytes_transferred = s->receive_from(boost::asio::buffer(m_buf, m_buf_size), ep, 0, err); + + // TODO: it would be nice to detect this on posix systems also +#ifdef TORRENT_WINDOWS + if ((err == error_code(ERROR_MORE_DATA, system_category()) + || err == error_code(WSAEMSGSIZE, system_category())) + && m_buf_size < 65536) + { + // if this function fails to allocate memory, m_buf_size + // is set to 0. In that case, don't issue the async_read(). + set_buf_size(m_buf_size * 2); + if (m_buf_size == 0) return; + continue; + } +#endif + + if (err == boost::asio::error::would_block || err == boost::asio::error::try_again) break; + on_read_impl(ep, err, bytes_transferred); + + // found on iOS, socket will be disconnected when app goes backgroud. try to reopen it. + if (err == boost::asio::error::not_connected || err == boost::asio::error::bad_descriptor) + { + ep = s->local_endpoint(err); + if (!err) { + bind(ep, err); + } + return; + } + } + call_drained_handler(); + setup_read(s); +} + +void udp_socket::call_handler(error_code const& ec, udp::endpoint const& ep, char const* buf, int size) +{ + m_observers_locked = true; + for (std::vector::iterator i = m_observers.begin(); + i != m_observers.end();) + { + bool ret = false; + TORRENT_TRY { + ret = (*i)->incoming_packet(ec, ep, buf, size); + } TORRENT_CATCH (std::exception&) {} + if (*i == NULL) i = m_observers.erase(i); + else ++i; + if (ret) break; + } + if (!m_added_observers.empty()) + { + m_observers.insert(m_observers.end(), m_added_observers.begin(), m_added_observers.end()); + m_added_observers.clear(); + } + m_observers_locked = false; + if (m_new_buf_size != m_buf_size) + set_buf_size(m_new_buf_size); +} + +void udp_socket::call_handler(error_code const& ec, const char* host, char const* buf, int size) +{ + m_observers_locked = true; + for (std::vector::iterator i = m_observers.begin(); + i != m_observers.end();) + { + bool ret = false; + TORRENT_TRY { + ret = (*i)->incoming_packet(ec, host, buf, size); + } TORRENT_CATCH (std::exception&) {} + if (*i == NULL) i = m_observers.erase(i); + else ++i; + if (ret) break; + } + if (!m_added_observers.empty()) + { + m_observers.insert(m_observers.end(), m_added_observers.begin(), m_added_observers.end()); + m_added_observers.clear(); + } + m_observers_locked = false; + if (m_new_buf_size != m_buf_size) + set_buf_size(m_new_buf_size); +} + +void udp_socket::call_drained_handler() +{ + m_observers_locked = true; + for (std::vector::iterator i = m_observers.begin(); + i != m_observers.end();) + { + TORRENT_TRY { + (*i)->socket_drained(); + } TORRENT_CATCH (std::exception&) {} + if (*i == NULL) i = m_observers.erase(i); + else ++i; + } + if (!m_added_observers.empty()) + { + m_observers.insert(m_observers.end(), m_added_observers.begin(), m_added_observers.end()); + m_added_observers.clear(); + } + m_observers_locked = false; + if (m_new_buf_size != m_buf_size) + set_buf_size(m_new_buf_size); +} + +void udp_socket::call_writable_handler() +{ + m_observers_locked = true; + for (std::vector::iterator i = m_observers.begin(); + i != m_observers.end();) + { + TORRENT_TRY { + (*i)->writable(); + } TORRENT_CATCH (std::exception&) {} + if (*i == NULL) i = m_observers.erase(i); + else ++i; + } + if (!m_added_observers.empty()) + { + m_observers.insert(m_observers.end(), m_added_observers.begin(), m_added_observers.end()); + m_added_observers.clear(); + } + m_observers_locked = false; + if (m_new_buf_size != m_buf_size) + set_buf_size(m_new_buf_size); +} + +void udp_socket::subscribe(udp_socket_observer* o) +{ + TORRENT_ASSERT(std::find(m_observers.begin(), m_observers.end(), o) == m_observers.end()); + if (m_observers_locked) + m_added_observers.push_back(o); + else + m_observers.push_back(o); +} + +void udp_socket::unsubscribe(udp_socket_observer* o) +{ + std::vector::iterator i = std::find(m_observers.begin(), m_observers.end(), o); + if (i == m_observers.end()) return; + if (m_observers_locked) + *i = NULL; + else + m_observers.erase(i); +} + +void udp_socket::on_read_impl(udp::endpoint const& ep + , error_code const& e, std::size_t bytes_transferred) +{ + TORRENT_ASSERT(m_magic == 0x1337); + TORRENT_ASSERT(is_single_thread()); + + if (e) + { + call_handler(e, ep, 0, 0); + + // don't stop listening on recoverable errors + if (e != boost::asio::error::host_unreachable + && e != boost::asio::error::fault + && e != boost::asio::error::connection_reset + && e != boost::asio::error::connection_refused + && e != boost::asio::error::connection_aborted + && e != boost::asio::error::operation_aborted + && e != boost::asio::error::network_reset + && e != boost::asio::error::network_unreachable +#ifdef _WIN32 + // ERROR_MORE_DATA means the same thing as EMSGSIZE + && e != error_code(ERROR_MORE_DATA, system_category()) + && e != error_code(ERROR_HOST_UNREACHABLE, system_category()) + && e != error_code(ERROR_PORT_UNREACHABLE, system_category()) + && e != error_code(ERROR_RETRY, system_category()) + && e != error_code(ERROR_NETWORK_UNREACHABLE, system_category()) + && e != error_code(ERROR_CONNECTION_REFUSED, system_category()) + && e != error_code(ERROR_CONNECTION_ABORTED, system_category()) +#endif + && e != boost::asio::error::message_size) + { + return; + } + + if (m_abort) + { + close_impl(); + return; + } + + return; + } + + TORRENT_TRY { + + if (m_tunnel_packets) + { + // if the source IP doesn't match the proxy's, ignore the packet + if (ep == m_udp_proxy_addr) + unwrap(e, m_buf, bytes_transferred); + } + else if (!m_force_proxy) // block incoming packets that aren't coming via the proxy + { + call_handler(e, ep, m_buf, bytes_transferred); + } + + } TORRENT_CATCH (std::exception&) {} +} + +void udp_socket::setup_read(udp::socket* s) +{ + if (m_abort) + { + close_impl(); + return; + } + +#if TORRENT_USE_IPV6 + if (s == &m_ipv6_sock) + { + if (m_v6_outstanding) + { + ++m_restart_v6; + m_ipv6_sock.cancel(); + return; + } + ++m_v6_outstanding; + } + else +#endif + { + if (m_v4_outstanding) + { + ++m_restart_v4; + m_ipv4_sock.cancel(); + return; + } + ++m_v4_outstanding; + } + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("udp_socket::on_read"); +#endif + + udp::endpoint ep; + TORRENT_TRY + { +#if TORRENT_USE_IPV6 + if (s == &m_ipv6_sock) + { + s->async_receive_from(null_buffers() + , ep, make_read_handler6(boost::bind(&udp_socket::on_read, this, _1, s))); + } + else +#endif + { + s->async_receive_from(null_buffers() + , ep, make_read_handler4(boost::bind(&udp_socket::on_read, this, _1, s))); + } + } + TORRENT_CATCH(boost::system::system_error& e) + { +#ifdef BOOST_NO_EXCEPTIONS + // dummy + error_code ec; + boost::system::system_error e(ec); +#endif + get_io_service().post(boost::bind(&udp_socket::on_read + , this, e.code(), s)); + } +} + +void udp_socket::wrap(udp::endpoint const& ep, char const* p, int len, error_code& ec) +{ + CHECK_MAGIC; + using namespace libtorrent::detail; + + char header[25]; + char* h = header; + + write_uint16(0, h); // reserved + write_uint8(0, h); // fragment + write_uint8(ep.address().is_v4()?1:4, h); // atyp + write_endpoint(ep, h); + + boost::array iovec; + iovec[0] = boost::asio::const_buffer(header, h - header); + iovec[1] = boost::asio::const_buffer(p, len); + +#if TORRENT_USE_IPV6 + if (m_udp_proxy_addr.address().is_v4() && m_ipv4_sock.is_open()) +#endif + m_ipv4_sock.send_to(iovec, m_udp_proxy_addr, 0, ec); +#if TORRENT_USE_IPV6 + else + m_ipv6_sock.send_to(iovec, m_udp_proxy_addr, 0, ec); +#endif +} + +void udp_socket::wrap(char const* hostname, int port, char const* p, int len, error_code& ec) +{ + CHECK_MAGIC; + using namespace libtorrent::detail; + + char header[270]; + char* h = header; + + write_uint16(0, h); // reserved + write_uint8(0, h); // fragment + write_uint8(3, h); // atyp + int hostlen = (std::min)(strlen(hostname), size_t(255)); + write_uint8(hostlen, h); // hostname len + memcpy(h, hostname, hostlen); + h += hostlen; + write_uint16(port, h); + + boost::array iovec; + iovec[0] = boost::asio::const_buffer(header, h - header); + iovec[1] = boost::asio::const_buffer(p, len); + +#if TORRENT_USE_IPV6 + if (m_udp_proxy_addr.address().is_v6() && m_ipv6_sock.is_open()) + m_ipv6_sock.send_to(iovec, m_udp_proxy_addr, 0, ec); + else +#endif + m_ipv4_sock.send_to(iovec, m_udp_proxy_addr, 0, ec); +} + +// unwrap the UDP packet from the SOCKS5 header +void udp_socket::unwrap(error_code const& e, char const* buf, int size) +{ + CHECK_MAGIC; + using namespace libtorrent::detail; + + // the minimum socks5 header size + if (size <= 10) return; + + char const* p = buf; + p += 2; // reserved + int frag = read_uint8(p); + // fragmentation is not supported + if (frag != 0) return; + + udp::endpoint sender; + + int atyp = read_uint8(p); + if (atyp == 1) + { + // IPv4 + sender = read_v4_endpoint(p); + } +#if TORRENT_USE_IPV6 + else if (atyp == 4) + { + // IPv6 + sender = read_v6_endpoint(p); + } +#endif + else + { + int len = read_uint8(p); + if (len > (buf + size) - p) return; + std::string hostname(p, p + len); + p += len; + call_handler(e, hostname.c_str(), p, size - (p - buf)); + return; + } + + call_handler(e, sender, p, size - (p - buf)); +} + +#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()); + TORRENT_ASSERT(m_magic == 0x1337); + + error_code ec; + m_ipv4_sock.close(ec); + TORRENT_ASSERT_VAL(!ec || ec == error::bad_descriptor, ec); +#if TORRENT_USE_IPV6 + m_ipv6_sock.close(ec); + TORRENT_ASSERT_VAL(!ec || ec == error::bad_descriptor, ec); +#endif + m_socks5_sock.close(ec); + TORRENT_ASSERT_VAL(!ec || ec == error::bad_descriptor, ec); + m_resolver.cancel(); + m_timer.cancel(); + m_abort = true; + +#if TORRENT_USE_ASSERTS + m_outstanding_when_aborted = num_outstanding(); +#endif +} + +void udp_socket::set_buf_size(int s) +{ + TORRENT_ASSERT(is_single_thread()); + + if (m_observers_locked) + { + // we can't actually reallocate the buffer while + // it's being used by the observers, we have to + // do that once we're done iterating over them + m_new_buf_size = s; + return; + } + + if (s == m_buf_size) return; + + bool no_mem = false; + char* tmp = static_cast(realloc(m_buf, s)); + if (tmp != 0) + { + m_buf = tmp; + m_buf_size = s; + m_new_buf_size = s; + } + else + { + no_mem = true; + } + + if (no_mem) + { + free(m_buf); + m_buf = 0; + m_buf_size = 0; + m_new_buf_size = 0; + udp::endpoint ep; + call_handler(error::no_memory, ep, 0, 0); + close(); + } + + int size = m_buf_size; + + // don't shrink the size of the receive buffer + error_code ec; + boost::asio::socket_base::receive_buffer_size recv_size; + m_ipv4_sock.get_option(recv_size, ec); + if (!ec) size = (std::max)(recv_size.value(), size); +#if TORRENT_USE_IPV6 + m_ipv6_sock.get_option(recv_size, ec); + if (!ec) size = (std::max)(recv_size.value(), size); +#endif + + error_code ignore_errors; + // set the internal buffer sizes as well + m_ipv4_sock.set_option(boost::asio::socket_base::receive_buffer_size(size) + , ignore_errors); +#if TORRENT_USE_IPV6 + m_ipv6_sock.set_option(boost::asio::socket_base::receive_buffer_size(size) + , ignore_errors); +#endif +} + +void udp_socket::bind(udp::endpoint const& ep, error_code& ec) +{ + CHECK_MAGIC; + TORRENT_ASSERT(is_single_thread()); + + m_abort = false; + + if (m_ipv4_sock.is_open()) m_ipv4_sock.close(ec); +#if TORRENT_USE_IPV6 + if (m_ipv6_sock.is_open()) m_ipv6_sock.close(ec); +#endif + ec.clear(); + + if (ep.address().is_v4()) + { + m_ipv4_sock.open(udp::v4(), ec); + if (ec) return; + + // this is best-effort. ignore errors + error_code err; +#ifdef TORRENT_WINDOWS + m_ipv4_sock.set_option(exclusive_address_use(true), err); +#endif + m_ipv4_sock.set_option(boost::asio::socket_base::reuse_address(true), err); + + m_ipv4_sock.bind(ep, ec); + if (ec) return; + udp::socket::non_blocking_io ioc(true); + m_ipv4_sock.io_control(ioc, ec); + if (ec) return; + setup_read(&m_ipv4_sock); + } + +#if TORRENT_USE_IPV6 + // TODO: 2 the udp_socket should really just be a single socket, and the + // session should support having more than one, just like with TCP sockets + // for now, just make bind failures non-fatal + if (supports_ipv6() && (ep.address().is_v6() || is_any(ep.address()))) + { + udp::endpoint ep6 = ep; + if (is_any(ep.address())) ep6.address(address_v6::any()); + m_ipv6_sock.open(udp::v6(), ec); + if (ec) return; + + // this is best-effort. ignore errors + error_code err; +#ifdef TORRENT_WINDOWS + m_ipv6_sock.set_option(exclusive_address_use(true), err); +#endif + m_ipv6_sock.set_option(boost::asio::socket_base::reuse_address(true), err); + m_ipv6_sock.set_option(boost::asio::ip::v6_only(true), err); + + m_ipv6_sock.bind(ep6, ec); + if (ec != error_code(boost::system::errc::address_not_available + , boost::system::generic_category())) + { + if (ec) return; + udp::socket::non_blocking_io ioc(true); + m_ipv6_sock.io_control(ioc, ec); + if (ec) return; + setup_read(&m_ipv6_sock); + } + else + { + ec.clear(); + } + } +#endif +#if TORRENT_USE_ASSERTS + m_started = true; +#endif + m_bind_port = ep.port(); +} + +void udp_socket::set_proxy_settings(aux::proxy_settings const& ps) +{ + CHECK_MAGIC; + TORRENT_ASSERT(is_single_thread()); + + error_code ec; + m_socks5_sock.close(ec); + m_tunnel_packets = false; + + m_proxy_settings = ps; + + if (m_abort) + { + close_impl(); + return; + } + + if (ps.type == settings_pack::socks5 + || ps.type == settings_pack::socks5_pw) + { + m_queue_packets = true; + // connect to socks5 server and open up the UDP tunnel + + // TODO: use the system resolver_interface here + tcp::resolver::query q(ps.hostname, to_string(ps.port).elems); + ++m_outstanding_ops; +#if TORRENT_USE_ASSERTS + ++m_outstanding_resolve; +#endif +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("udp_socket::on_name_lookup"); +#endif + m_resolver.async_resolve(q, boost::bind( + &udp_socket::on_name_lookup, this, _1, _2)); + } +} + +void udp_socket::close_impl() +{ + if (m_outstanding_ops == 0) + { + error_code ec; + m_ipv4_sock.close(ec); +#if TORRENT_USE_IPV6 + m_ipv6_sock.close(ec); +#endif + m_socks5_sock.close(ec); + } +} + +void udp_socket::on_name_lookup(error_code const& e, tcp::resolver::iterator i) +{ +#if defined TORRENT_ASIO_DEBUGGING + complete_async("udp_socket::on_name_lookup"); +#endif +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(m_outstanding_resolve > 0); + --m_outstanding_resolve; +#endif + + TORRENT_ASSERT(m_outstanding_ops > 0); + --m_outstanding_ops; + TORRENT_ASSERT(m_outstanding_ops == m_outstanding_connect + + m_outstanding_timeout + + m_outstanding_resolve + + m_outstanding_socks); + + if (m_abort) + { + close_impl(); + return; + } + + CHECK_MAGIC; + + if (e == boost::asio::error::operation_aborted) return; + + TORRENT_ASSERT(is_single_thread()); + + if (e) + { + if (m_force_proxy) + { + call_handler(e, udp::endpoint(), 0, 0); + } + else + { + // if we can't connect to the proxy, and + // we're not in privacy mode, try to just + // not use a proxy + m_proxy_settings = aux::proxy_settings(); + m_tunnel_packets = false; + } + + drain_queue(); + return; + } + + m_proxy_addr.address(i->endpoint().address()); + m_proxy_addr.port(i->endpoint().port()); + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("udp_socket::on_connected"); +#endif + + error_code ec; + m_socks5_sock.open(m_proxy_addr.address().is_v4()?tcp::v4():tcp::v6(), ec); + + // enable keepalives + m_socks5_sock.set_option(boost::asio::socket_base::keep_alive(true), ec); + + ++m_outstanding_ops; +#if TORRENT_USE_ASSERTS + ++m_outstanding_connect; +#endif + m_socks5_sock.async_connect(tcp::endpoint(m_proxy_addr.address(), m_proxy_addr.port()) + , boost::bind(&udp_socket::on_connected, this, _1)); + + ++m_outstanding_ops; +#if TORRENT_USE_ASSERTS + ++m_outstanding_timeout; +#endif + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("udp_socket::on_connect_timeout"); +#endif + m_timer.expires_from_now(seconds(10)); + m_timer.async_wait(boost::bind(&udp_socket::on_connect_timeout + , this, _1)); +} + +void udp_socket::on_connect_timeout(error_code const& ec) +{ +#if defined TORRENT_ASIO_DEBUGGING + complete_async("udp_socket::on_connect_timeout"); +#endif +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(m_outstanding_timeout > 0); + --m_outstanding_timeout; +#endif + TORRENT_ASSERT(m_outstanding_ops > 0); + --m_outstanding_ops; + TORRENT_ASSERT(m_outstanding_ops == m_outstanding_connect + + m_outstanding_timeout + + m_outstanding_resolve + + m_outstanding_socks); + + if (ec == boost::asio::error::operation_aborted) return; + + m_queue_packets = false; + + if (m_abort) + { + close_impl(); + return; + } + + CHECK_MAGIC; + TORRENT_ASSERT(is_single_thread()); + + error_code ignore; + m_socks5_sock.close(ignore); +} + +void udp_socket::on_connected(error_code const& e) +{ +#if defined TORRENT_ASIO_DEBUGGING + complete_async("udp_socket::on_connected"); +#endif + + TORRENT_ASSERT(is_single_thread()); + +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(m_outstanding_connect > 0); + --m_outstanding_connect; +#endif + TORRENT_ASSERT(m_outstanding_ops > 0); + --m_outstanding_ops; + TORRENT_ASSERT(m_outstanding_ops == m_outstanding_connect + + m_outstanding_timeout + + m_outstanding_resolve + + m_outstanding_socks); + CHECK_MAGIC; + + m_timer.cancel(); + + if (e == boost::asio::error::operation_aborted) return; + + if (m_abort) + { + close_impl(); + return; + } + + if (e) + { + // we failed to connect to the proxy, if we don't have force_proxy set, + // drain the queue over the UDP socket + if (!m_force_proxy) + { + drain_queue(); + } + + call_handler(e, udp::endpoint(), 0, 0); + return; + } + + using namespace libtorrent::detail; + + // send SOCKS5 authentication methods + char* p = &m_tmp_buf[0]; + 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 < int(sizeof(m_tmp_buf)), (p - m_tmp_buf)); +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("udp_socket::on_handshake1"); +#endif + ++m_outstanding_ops; +#if TORRENT_USE_ASSERTS + ++m_outstanding_socks; +#endif + boost::asio::async_write(m_socks5_sock, boost::asio::buffer(m_tmp_buf, p - m_tmp_buf) + , boost::bind(&udp_socket::handshake1, this, _1)); +} + +void udp_socket::handshake1(error_code const& e) +{ +#if defined TORRENT_ASIO_DEBUGGING + complete_async("udp_socket::on_handshake1"); +#endif +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(m_outstanding_socks > 0); + --m_outstanding_socks; +#endif + TORRENT_ASSERT(m_outstanding_ops > 0); + --m_outstanding_ops; + TORRENT_ASSERT(m_outstanding_ops == m_outstanding_connect + + m_outstanding_timeout + + m_outstanding_resolve + + m_outstanding_socks); + if (m_abort) + { + close_impl(); + return; + } + CHECK_MAGIC; + if (e) + { + drain_queue(); + return; + } + + TORRENT_ASSERT(is_single_thread()); + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("udp_socket::on_handshake2"); +#endif + ++m_outstanding_ops; +#if TORRENT_USE_ASSERTS + ++m_outstanding_socks; +#endif + boost::asio::async_read(m_socks5_sock, boost::asio::buffer(m_tmp_buf, 2) + , boost::bind(&udp_socket::handshake2, this, _1)); +} + +void udp_socket::handshake2(error_code const& e) +{ +#if defined TORRENT_ASIO_DEBUGGING + complete_async("udp_socket::on_handshake2"); +#endif +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(m_outstanding_socks > 0); + --m_outstanding_socks; +#endif + TORRENT_ASSERT(m_outstanding_ops > 0); + --m_outstanding_ops; + TORRENT_ASSERT(m_outstanding_ops == m_outstanding_connect + + m_outstanding_timeout + + m_outstanding_resolve + + m_outstanding_socks); + if (m_abort) + { + close_impl(); + return; + } + CHECK_MAGIC; + + if (e) + { + drain_queue(); + return; + } + + using namespace libtorrent::detail; + + TORRENT_ASSERT(is_single_thread()); + + char* p = &m_tmp_buf[0]; + int version = read_uint8(p); + int method = read_uint8(p); + + if (version < 5) + { + error_code ec; + m_socks5_sock.close(ec); + drain_queue(); + return; + } + + if (method == 0) + { + socks_forward_udp(/*l*/); + } + else if (method == 2) + { + if (m_proxy_settings.username.empty()) + { + error_code ec; + m_socks5_sock.close(ec); + drain_queue(); + return; + } + + // start sub-negotiation + p = &m_tmp_buf[0]; + write_uint8(1, p); + write_uint8(m_proxy_settings.username.size(), p); + write_string(m_proxy_settings.username, p); + write_uint8(m_proxy_settings.password.size(), p); + write_string(m_proxy_settings.password, p); + TORRENT_ASSERT_VAL(p - m_tmp_buf < int(sizeof(m_tmp_buf)), (p - m_tmp_buf)); +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("udp_socket::on_handshake3"); +#endif + ++m_outstanding_ops; +#if TORRENT_USE_ASSERTS + ++m_outstanding_socks; +#endif + boost::asio::async_write(m_socks5_sock, boost::asio::buffer(m_tmp_buf, p - m_tmp_buf) + , boost::bind(&udp_socket::handshake3, this, _1)); + } + else + { + drain_queue(); + error_code ec; + m_socks5_sock.close(ec); + return; + } +} + +void udp_socket::handshake3(error_code const& e) +{ +#if defined TORRENT_ASIO_DEBUGGING + complete_async("udp_socket::on_handshake3"); +#endif +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(m_outstanding_socks > 0); + --m_outstanding_socks; +#endif + TORRENT_ASSERT(m_outstanding_ops > 0); + --m_outstanding_ops; + TORRENT_ASSERT(m_outstanding_ops == m_outstanding_connect + + m_outstanding_timeout + + m_outstanding_resolve + + m_outstanding_socks); + if (m_abort) + { + close_impl(); + return; + } + CHECK_MAGIC; + if (e) + { + drain_queue(); + return; + } + + TORRENT_ASSERT(is_single_thread()); + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("udp_socket::on_handshake4"); +#endif + ++m_outstanding_ops; +#if TORRENT_USE_ASSERTS + ++m_outstanding_socks; +#endif + boost::asio::async_read(m_socks5_sock, boost::asio::buffer(m_tmp_buf, 2) + , boost::bind(&udp_socket::handshake4, this, _1)); +} + +void udp_socket::handshake4(error_code const& e) +{ +#if defined TORRENT_ASIO_DEBUGGING + complete_async("udp_socket::on_handshake4"); +#endif +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(m_outstanding_socks > 0); + --m_outstanding_socks; +#endif + TORRENT_ASSERT(m_outstanding_ops > 0); + --m_outstanding_ops; + TORRENT_ASSERT(m_outstanding_ops == m_outstanding_connect + + m_outstanding_timeout + + m_outstanding_resolve + + m_outstanding_socks); + if (m_abort) + { + close_impl(); + return; + } + CHECK_MAGIC; + if (e) + { + drain_queue(); + return; + } + + TORRENT_ASSERT(is_single_thread()); + + using namespace libtorrent::detail; + + char* p = &m_tmp_buf[0]; + int version = read_uint8(p); + int status = read_uint8(p); + + if (version != 1 || status != 0) + { + drain_queue(); + return; + } + + socks_forward_udp(/*l*/); +} + +void udp_socket::socks_forward_udp() +{ + CHECK_MAGIC; + using namespace libtorrent::detail; + + // send SOCKS5 UDP command + char* p = &m_tmp_buf[0]; + write_uint8(5, p); // SOCKS VERSION 5 + write_uint8(3, p); // UDP ASSOCIATE command + write_uint8(0, p); // reserved + error_code ec; + 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 < int(sizeof(m_tmp_buf)), (p - m_tmp_buf)); +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("udp_socket::connect1"); +#endif + ++m_outstanding_ops; +#if TORRENT_USE_ASSERTS + ++m_outstanding_socks; +#endif + boost::asio::async_write(m_socks5_sock, boost::asio::buffer(m_tmp_buf, p - m_tmp_buf) + , boost::bind(&udp_socket::connect1, this, _1)); +} + +void udp_socket::connect1(error_code const& e) +{ +#if defined TORRENT_ASIO_DEBUGGING + complete_async("udp_socket::connect1"); +#endif +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(m_outstanding_socks > 0); + --m_outstanding_socks; +#endif + TORRENT_ASSERT(m_outstanding_ops > 0); + --m_outstanding_ops; + TORRENT_ASSERT(m_outstanding_ops == m_outstanding_connect + + m_outstanding_timeout + + m_outstanding_resolve + + m_outstanding_socks); + if (m_abort) + { + close_impl(); + return; + } + CHECK_MAGIC; + if (e) + { + drain_queue(); + return; + } + + TORRENT_ASSERT(is_single_thread()); + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("udp_socket::connect2"); +#endif + ++m_outstanding_ops; +#if TORRENT_USE_ASSERTS + ++m_outstanding_socks; +#endif + boost::asio::async_read(m_socks5_sock, boost::asio::buffer(m_tmp_buf, 10) + , boost::bind(&udp_socket::connect2, this, _1)); +} + +void udp_socket::connect2(error_code const& e) +{ +#if defined TORRENT_ASIO_DEBUGGING + complete_async("udp_socket::connect2"); +#endif +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(m_outstanding_socks > 0); + --m_outstanding_socks; +#endif + TORRENT_ASSERT(m_outstanding_ops > 0); + --m_outstanding_ops; + TORRENT_ASSERT(m_outstanding_ops == m_outstanding_connect + + m_outstanding_timeout + + m_outstanding_resolve + + m_outstanding_socks); + + if (m_abort) + { + m_queue.clear(); + return; + } + CHECK_MAGIC; + if (e) + { + drain_queue(); + return; + } + + TORRENT_ASSERT(is_single_thread()); + + using namespace libtorrent::detail; + + char* p = &m_tmp_buf[0]; + int version = read_uint8(p); // VERSION + int status = read_uint8(p); // STATUS + ++p; // RESERVED + int atyp = read_uint8(p); // address type + + if (version != 5 || status != 0) + { + drain_queue(); + 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 + TORRENT_ASSERT(false); + drain_queue(); + return; + } + + m_tunnel_packets = true; + drain_queue(); + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("udp_socket::hung_up"); +#endif + ++m_outstanding_ops; +#if TORRENT_USE_ASSERTS + ++m_outstanding_socks; +#endif + boost::asio::async_read(m_socks5_sock, boost::asio::buffer(m_tmp_buf, 10) + , boost::bind(&udp_socket::hung_up, this, _1)); +} + +void udp_socket::hung_up(error_code const& e) +{ +#if defined TORRENT_ASIO_DEBUGGING + complete_async("udp_socket::hung_up"); +#endif +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(m_outstanding_socks > 0); + --m_outstanding_socks; +#endif + TORRENT_ASSERT(m_outstanding_ops > 0); + --m_outstanding_ops; + TORRENT_ASSERT(m_outstanding_ops == m_outstanding_connect + + m_outstanding_timeout + + m_outstanding_resolve + + m_outstanding_socks); + if (m_abort) + { + close_impl(); + return; + } + CHECK_MAGIC; + TORRENT_ASSERT(is_single_thread()); + + if (e == boost::asio::error::operation_aborted || m_abort) return; + + // the socks connection was closed, re-open it + set_proxy_settings(m_proxy_settings); +} + +void udp_socket::drain_queue() +{ + m_queue_packets = false; + + // forward all packets that were put in the queue + while (!m_queue.empty()) + { + queued_packet const& p = m_queue.front(); + error_code ec; + if (p.hostname) + { + udp_socket::send_hostname(p.hostname, p.ep.port(), &p.buf[0] + , p.buf.size(), ec, p.flags | dont_queue); + free(p.hostname); + } + else + { + udp_socket::send(p.ep, &p.buf[0], p.buf.size(), ec, p.flags | dont_queue); + } + m_queue.pop_front(); + } +} + +rate_limited_udp_socket::rate_limited_udp_socket(io_service& ios) + : udp_socket(ios) + , m_rate_limit(8000) + , m_quota(8000) + , m_last_tick(aux::time_now()) +{ +} + +bool rate_limited_udp_socket::has_quota() +{ + time_point now = clock_type::now(); + time_duration delta = now - m_last_tick; + m_last_tick = now; + // add any new quota we've accrued since last time + m_quota += boost::uint64_t(m_rate_limit) * total_microseconds(delta) / 1000000; + return m_quota > 0; +} + +bool rate_limited_udp_socket::send(udp::endpoint const& ep, char const* p + , int len, error_code& ec, int flags) +{ + time_point now = clock_type::now(); + time_duration delta = now - m_last_tick; + m_last_tick = now; + + // add any new quota we've accrued since last time + m_quota += boost::uint64_t(m_rate_limit) * total_microseconds(delta) / 1000000; + + // allow 3 seconds worth of burst + if (m_quota > 3 * m_rate_limit) m_quota = 3 * m_rate_limit; + + // if there's no quota, and it's OK to drop, just + // drop the packet + if (m_quota < 0 && (flags & dont_drop) == 0) return false; + + m_quota -= len; + if (m_quota < 0) m_quota = 0; + udp_socket::send(ep, p, len, ec, flags); + return true; +} + diff --git a/src/udp_tracker_connection.cpp b/src/udp_tracker_connection.cpp new file mode 100644 index 0000000..7da664a --- /dev/null +++ b/src/udp_tracker_connection.cpp @@ -0,0 +1,789 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include + +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/tracker_manager.hpp" +#include "libtorrent/parse_url.hpp" +#include "libtorrent/udp_tracker_connection.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/hex.hpp" +#include "libtorrent/broadcast_socket.hpp" // for is_any +#include "libtorrent/random.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/resolver_interface.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/aux_/time.hpp" + +#ifndef TORRENT_DISABLE_LOGGING +#include "libtorrent/socket_io.hpp" +#endif + +namespace libtorrent +{ + + std::map + udp_tracker_connection::m_connection_cache; + + mutex udp_tracker_connection::m_cache_mutex; + + udp_tracker_connection::udp_tracker_connection( + io_service& ios + , tracker_manager& man + , tracker_request const& req + , boost::weak_ptr c) + : tracker_connection(man, req, ios, c) + , m_transaction_id(0) + , m_attempts(0) + , m_state(action_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; + + using boost::tuples::ignore; + boost::tie(protocol, ignore, hostname, port, ignore) + = parse_url_components(tracker_req().url, ec); + if (port == -1) port = protocol == "http" ? 80 : 443; + + if (ec) + { + tracker_connection::fail(ec); + return; + } + + aux::session_settings const& settings = m_man.settings(); + + if (settings.get_bool(settings_pack::proxy_hostnames) + && (settings.get_int(settings_pack::proxy_type) == settings_pack::socks5 + || settings.get_int(settings_pack::proxy_type) == settings_pack::socks5_pw)) + { + m_hostname = hostname; + m_target.port(port); + start_announce(); + } + else + { +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("udp_tracker_connection::name_lookup"); +#endif + // when stopping, pass in the prefer cache flag, because we + // don't want to get stuck on DNS lookups when shutting down + // if we can avoid it + m_man.host_resolver().async_resolve(hostname + , tracker_req().event == tracker_request::stopped + ? resolver_interface::prefer_cache + : resolver_interface::abort_on_shutdown + , boost::bind(&udp_tracker_connection::name_lookup + , shared_from_this(), _1, _2, port)); + +#ifndef TORRENT_DISABLE_LOGGING + boost::shared_ptr cb = requester(); + if (cb) cb->debug_log("*** UDP_TRACKER [ initiating name lookup: \"%s\" ]" + , hostname.c_str()); +#endif + } + + set_timeout(tracker_req().event == tracker_request::stopped + ? settings.get_int(settings_pack::stop_tracker_timeout) + : settings.get_int(settings_pack::tracker_completion_timeout) + , settings.get_int(settings_pack::tracker_receive_timeout)); + } + + void udp_tracker_connection::fail(error_code const& ec, int code + , char const* msg, int interval, int min_interval) + { + // m_target failed. remove it from the endpoint list + std::vector::iterator i = std::find(m_endpoints.begin() + , m_endpoints.end(), tcp::endpoint(m_target.address(), m_target.port())); + + if (i != m_endpoints.end()) m_endpoints.erase(i); + + // if that was the last one, fail the whole announce + if (m_endpoints.empty()) + { + tracker_connection::fail(ec, code, msg, interval, min_interval); + return; + } + +#ifndef TORRENT_DISABLE_LOGGING + boost::shared_ptr cb = requester(); + if (cb) cb->debug_log("*** 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 = pick_target_endpoint(); + +#ifndef TORRENT_DISABLE_LOGGING + if (cb) cb->debug_log("*** UDP_TRACKER trying next IP [ host: \"%s\" ip: \"%s\" ]" + , m_hostname.c_str(), print_endpoint(m_target).c_str()); +#endif + get_io_service().post(boost::bind( + &udp_tracker_connection::start_announce, shared_from_this())); + + aux::session_settings const& settings = m_man.settings(); + set_timeout(tracker_req().event == tracker_request::stopped + ? settings.get_int(settings_pack::stop_tracker_timeout) + : settings.get_int(settings_pack::tracker_completion_timeout) + , settings.get_int(settings_pack::tracker_receive_timeout)); + } + + void udp_tracker_connection::name_lookup(error_code const& error + , std::vector
    const& addresses, int port) + { +#if defined TORRENT_ASIO_DEBUGGING + complete_async("udp_tracker_connection::name_lookup"); +#endif + if (m_abort) return; + if (error == boost::asio::error::operation_aborted) return; + if (error || addresses.empty()) + { + fail(error); + return; + } + + boost::shared_ptr cb = requester(); +#ifndef TORRENT_DISABLE_LOGGING + if (cb) cb->debug_log("*** UDP_TRACKER [ name lookup successful ]"); +#endif + if (cancelled()) + { + fail(error_code(errors::torrent_aborted)); + return; + } + + restart_read_timeout(); + + // look for an address that has the same kind as the one + // we're listening on. To make sure the tracker get our + // correct listening address. + + for (std::vector
    ::const_iterator i = addresses.begin() + , end(addresses.end()); i != end; ++i) + m_endpoints.push_back(tcp::endpoint(*i, port)); + + if (tracker_req().filter) + { + // remove endpoints that are filtered by the IP filter + for (std::vector::iterator 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->debug_log("*** UDP_TRACKER [ IP blocked by filter: %s ]" + , print_address(k->address()).c_str()); +#endif + k = m_endpoints.erase(k); + } + else + ++k; + } + } + + // if all endpoints were filtered by the IP filter, we can't connect + if (m_endpoints.empty()) + { + fail(error_code(errors::banned_by_ip_filter)); + return; + } + + m_target = pick_target_endpoint(); + + start_announce(); + } + + udp::endpoint udp_tracker_connection::pick_target_endpoint() const + { + std::vector::const_iterator iter = m_endpoints.begin(); + udp::endpoint target = udp::endpoint(iter->address(), iter->port()); + + if (bind_interface() != address_v4::any()) + { + // find first endpoint that matches our bind interface type + for (; iter != m_endpoints.end() && iter->address().is_v4() + != bind_interface().is_v4(); ++iter); + + if (iter == m_endpoints.end()) + { + TORRENT_ASSERT(target.address().is_v4() != bind_interface().is_v4()); + boost::shared_ptr cb = requester(); + if (cb) + { + char const* tracker_address_type = target.address().is_v4() ? "IPv4" : "IPv6"; + char const* bind_address_type = bind_interface().is_v4() ? "IPv4" : "IPv6"; + char msg[200]; + snprintf(msg, sizeof(msg) + , "the tracker only resolves to an %s address, and you're " + "listening on an %s socket. This may prevent you from receiving " + "incoming connections." + , tracker_address_type, bind_address_type); + + cb->tracker_warning(tracker_req(), msg); + } + } + else + { + target = udp::endpoint(iter->address(), iter->port()); + } + } + + return target; + } + + void udp_tracker_connection::start_announce() + { + mutex::scoped_lock l(m_cache_mutex); + std::map::iterator 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 (0 == (tracker_req().kind & tracker_request::scrape_request)) + send_udp_announce(); + else if (0 != (tracker_req().kind & tracker_request::scrape_request)) + send_udp_scrape(); + return; + } + // if it expired, remove it from the cache + m_connection_cache.erase(cc); + } + l.unlock(); + + send_udp_connect(); + } + + void udp_tracker_connection::on_timeout(error_code const& ec) + { + if (ec) + { + fail(ec); + return; + } + +#ifndef TORRENT_DISABLE_LOGGING + boost::shared_ptr cb = requester(); + if (cb) cb->debug_log("*** UDP_TRACKER [ timed out url: %s ]", tracker_req().url.c_str()); +#endif + fail(error_code(errors::timed_out)); + } + + void udp_tracker_connection::close() + { + error_code ec; + tracker_connection::close(); + } + + bool udp_tracker_connection::on_receive_hostname(error_code const& e + , char const* hostname, char const* buf, int size) + { + 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(e, m_target, buf, size); + } + + bool udp_tracker_connection::on_receive(error_code const& e + , udp::endpoint const& ep, char const* buf, int size) + { +#ifndef TORRENT_DISABLE_LOGGING + boost::shared_ptr cb = requester(); +#endif + + // ignore resposes before we've sent any requests + if (m_state == action_error) + { +#ifndef TORRENT_DISABLE_LOGGING + if (cb) cb->debug_log("<== UDP_TRACKER [ m_action == error ]"); +#endif + return false; + } + + if (m_abort) + { +#ifndef TORRENT_DISABLE_LOGGING + if (cb) cb->debug_log("<== UDP_TRACKER [ aborted]"); +#endif + return false; + } + + // ignore packet not sent from the tracker + // if m_target is inaddr_any, it suggests that we + // sent the packet through a proxy only knowing + // the hostname, in which case this packet might be for us + if (!is_any(m_target.address()) && m_target != ep) + { +#ifndef TORRENT_DISABLE_LOGGING + if (cb) cb->debug_log("<== UDP_TRACKER [ unexpected source IP: %s " + "expected: %s ]" + , print_endpoint(ep).c_str() + , print_endpoint(m_target).c_str()); +#endif + return false; + } + + if (e) fail(e); + +#ifndef TORRENT_DISABLE_LOGGING + if (cb) cb->debug_log("<== UDP_TRACKER_PACKET [ size: %d ]", size); +#endif + + // ignore packets smaller than 8 bytes + if (size < 8) return false; + + const char* ptr = buf; + int action = detail::read_int32(ptr); + boost::uint32_t transaction = detail::read_uint32(ptr); + +#ifndef TORRENT_DISABLE_LOGGING + if (cb) cb->debug_log("*** UDP_TRACKER_PACKET [ action: %d ]", 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_error) + { + fail(error_code(errors::tracker_failure), -1, std::string(ptr, size - 8).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 ]", action, 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_connect: + return on_connect_response(buf, size); + case action_announce: + return on_announce_response(buf, size); + case action_scrape: + return on_scrape_response(buf, size); + default: break; + } + return false; + } + + void udp_tracker_connection::update_transaction_id() + { + boost::uint32_t new_tid; + + // don't use 0, because that has special meaning (unintialized) + do { + new_tid = random(); + } while (new_tid == 0); + + 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(char const* buf, int size) + { + // ignore packets smaller than 16 bytes + if (size < 16) return false; + + restart_read_timeout(); + buf += 8; // skip header + + // reset transaction + update_transaction_id(); + boost::uint64_t connection_id = detail::read_int64(buf); + + mutex::scoped_lock l(m_cache_mutex); + connection_cache_entry& cce = m_connection_cache[m_target.address()]; + cce.connection_id = connection_id; + cce.expires = aux::time_now() + seconds(m_man.settings().get_int(settings_pack::udp_tracker_token_expiry)); + + if (0 == (tracker_req().kind & tracker_request::scrape_request)) + send_udp_announce(); + else if (0 != (tracker_req().kind & tracker_request::scrape_request)) + send_udp_scrape(); + return true; + } + + void udp_tracker_connection::send_udp_connect() + { +#ifndef TORRENT_DISABLE_LOGGING + boost::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; + } + + char buf[16]; + char* ptr = buf; + + TORRENT_ASSERT(m_transaction_id != 0); + + detail::write_uint32(0x417, ptr); + detail::write_uint32(0x27101980, ptr); // connection_id + detail::write_int32(action_connect, ptr); // action (connect) + detail::write_int32(m_transaction_id, ptr); // transaction_id + TORRENT_ASSERT(ptr - buf == sizeof(buf)); + + error_code ec; + if (!m_hostname.empty()) + { + m_man.get_udp_socket().send_hostname(m_hostname.c_str() + , m_target.port(), buf, 16, ec + , udp_socket::tracker_connection); + } + else + { + m_man.get_udp_socket().send(m_target, buf, 16, ec + , udp_socket::tracker_connection); + } + + ++m_attempts; + if (ec) + { +#ifndef TORRENT_DISABLE_LOGGING + if (cb) cb->debug_log("==> UDP_TRACKER_CONNECT [ failed: %s ]" + , ec.message().c_str()); +#endif + fail(ec); + return; + } + +#ifndef TORRENT_DISABLE_LOGGING + if (cb) + { + char hex_ih[41]; + to_hex(tracker_req().info_hash.data(), 20, hex_ih); + 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()).elems).c_str() + , hex_ih); + } +#endif + + m_state = action_connect; + sent_bytes(16 + 28); // assuming UDP/IP header + } + + void udp_tracker_connection::send_udp_scrape() + { + if (m_abort) return; + + std::map::iterator 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]; + char* out = buf; + + detail::write_int64(i->second.connection_id, out); // connection_id + detail::write_int32(action_scrape, out); // action (scrape) + detail::write_int32(m_transaction_id, out); // transaction_id + // info_hash + std::copy(tracker_req().info_hash.begin(), tracker_req().info_hash.end(), out); +#if defined TORRENT_DEBUG || defined TORRENT_RELEASE_ASSERTS + out += 20; + TORRENT_ASSERT(out - buf == sizeof(buf)); +#endif + + error_code ec; + if (!m_hostname.empty()) + { + m_man.get_udp_socket().send_hostname(m_hostname.c_str(), m_target.port() + , buf, sizeof(buf), ec, udp_socket::tracker_connection); + } + else + { + m_man.get_udp_socket().send(m_target, buf, sizeof(buf), ec + , udp_socket::tracker_connection); + } + m_state = action_scrape; + sent_bytes(sizeof(buf) + 28); // assuming UDP/IP header + ++m_attempts; + if (ec) + { + fail(ec); + return; + } + } + + bool udp_tracker_connection::on_announce_response(char const* buf, int size) + { + if (size < 20) return false; + + buf += 8; // skip header + restart_read_timeout(); + + tracker_response resp; + + resp.interval = detail::read_int32(buf); + resp.min_interval = 60; + resp.incomplete = detail::read_int32(buf); + resp.complete = detail::read_int32(buf); + int num_peers = (size - 20) / 6; + if ((size - 20) % 6 != 0) + { + fail(error_code(errors::invalid_tracker_response_length)); + return false; + } + + boost::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; + } + + std::vector peer_list; + resp.peers4.reserve(num_peers); + for (int i = 0; i < num_peers; ++i) + { + ipv4_peer_entry e; + memcpy(&e.ip[0], buf, 4); + buf += 4; + e.port = detail::read_uint16(buf); + resp.peers4.push_back(e); + } + + std::list
    ip_list; + for (std::vector::const_iterator i = m_endpoints.begin() + , end(m_endpoints.end()); i != end; ++i) + { + ip_list.push_back(i->address()); + } + + cb->tracker_response(tracker_req(), m_target.address(), ip_list + , resp); + + close(); + return true; + } + + bool udp_tracker_connection::on_scrape_response(char const* buf, int size) + { + restart_read_timeout(); + int action = detail::read_int32(buf); + boost::uint32_t transaction = detail::read_uint32(buf); + + if (transaction != m_transaction_id) + { + fail(error_code(errors::invalid_tracker_transaction_id)); + return false; + } + + if (action == action_error) + { + fail(error_code(errors::tracker_failure), -1, std::string(buf, size - 8).c_str()); + return true; + } + + if (action != action_scrape) + { + fail(error_code(errors::invalid_tracker_action)); + return true; + } + + if (size < 20) + { + fail(error_code(errors::invalid_tracker_response_length)); + return true; + } + + int complete = detail::read_int32(buf); + int downloaded = detail::read_int32(buf); + int incomplete = detail::read_int32(buf); + + boost::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]; + char* out = buf; + + tracker_request const& req = tracker_req(); + const bool stats = req.send_stats; + aux::session_settings const& settings = m_man.settings(); + + std::map::iterator 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; + + detail::write_int64(i->second.connection_id, out); // connection_id + detail::write_int32(action_announce, out); // action (announce) + detail::write_int32(m_transaction_id, out); // transaction_id + std::copy(req.info_hash.begin(), req.info_hash.end(), out); // info_hash + out += 20; + std::copy(req.pid.begin(), req.pid.end(), out); // peer_id + out += 20; + detail::write_int64(stats ? req.downloaded : 0, out); // downloaded + detail::write_int64(stats ? req.left : 0, out); // left + detail::write_int64(stats ? req.uploaded : 0, out); // uploaded + detail::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 = address::from_string(settings.get_str(settings_pack::announce_ip).c_str(), ec); + if (!ec && ip.is_v4()) announce_ip = ip.to_v4(); + } + detail::write_uint32(announce_ip.to_ulong(), out); + detail::write_int32(req.key, out); // key + detail::write_int32(req.num_want, out); // num_want + detail::write_uint16(req.listen_port, out); // port + + std::string request_string; + error_code ec; + using boost::tuples::ignore; + boost::tie(ignore, ignore, ignore, ignore, request_string) + = parse_url_components(req.url, ec); + if (ec) request_string.clear(); + + if (!request_string.empty()) + { + int str_len = (std::min)(int(request_string.size()), 255); + request_string.resize(str_len); + + detail::write_uint8(2, out); + detail::write_uint8(str_len, out); + detail::write_string(request_string, out); + } + + TORRENT_ASSERT(out - buf <= int(sizeof(buf))); + +#ifndef TORRENT_DISABLE_LOGGING + boost::shared_ptr cb = requester(); + if (cb) + { + char hex_ih[41]; + to_hex(req.info_hash.data(), 20, hex_ih); + cb->debug_log("==> UDP_TRACKER_ANNOUNCE [%s]", hex_ih); + } +#endif + + if (!m_hostname.empty()) + { + m_man.get_udp_socket().send_hostname(m_hostname.c_str() + , m_target.port(), buf, out - buf, ec + , udp_socket::tracker_connection); + } + else + { + m_man.get_udp_socket().send(m_target, buf, out - buf, ec + , udp_socket::tracker_connection); + } + m_state = action_announce; + sent_bytes(out - buf + 28); // assuming UDP/IP header + ++m_attempts; + if (ec) + { + fail(ec); + return; + } + } + +} + diff --git a/src/upnp.cpp b/src/upnp.cpp new file mode 100644 index 0000000..c2a80e7 --- /dev/null +++ b/src/upnp.cpp @@ -0,0 +1,1567 @@ +/* + +Copyright (c) 2007-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/socket.hpp" +#include "libtorrent/socket_io.hpp" +#include "libtorrent/upnp.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/parse_url.hpp" +#include "libtorrent/xml_parse.hpp" +#include "libtorrent/enum_net.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/aux_/time.hpp" // for aux::time_now() +#include "libtorrent/aux_/escape_string.hpp" // for convert_from_native + +#if defined TORRENT_ASIO_DEBUGGING +#include "libtorrent/debug.hpp" +#endif + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include + +namespace libtorrent { + +namespace upnp_errors +{ + boost::system::error_code make_error_code(error_code_enum e) + { + return error_code(e, get_upnp_category()); + } + +} // upnp_errors namespace + +static error_code ignore_error; + +upnp::upnp(io_service& ios + , address const& listen_interface, std::string const& user_agent + , portmap_callback_t const& cb, log_callback_t const& lcb + , bool ignore_nonrouters) + : m_user_agent(user_agent) + , m_callback(cb) + , m_log_callback(lcb) + , m_retry_count(0) + , m_io_service(ios) + , m_resolver(ios) + , m_socket(udp::endpoint(address_v4::from_string("239.255.255.250" + , ignore_error), 1900)) + , m_broadcast_timer(ios) + , m_refresh_timer(ios) + , m_map_timer(ios) + , m_disabled(false) + , m_closing(false) + , m_ignore_non_routers(ignore_nonrouters) + , m_last_if_update(min_time()) +{ + TORRENT_ASSERT(cb); + +// TODO: 3 listen_interface is not used. It's meant to bind the broadcast +// socket. it would probably have to be changed to a vector of interfaces to +// bind to though, since the broadcast socket opens one socket per local +// interface by default + TORRENT_UNUSED(listen_interface); +} + +void upnp::start() +{ + error_code ec; + m_socket.open(boost::bind(&upnp::on_reply, self(), _1, _2, _3) + , m_refresh_timer.get_io_service(), ec); + + m_mappings.reserve(10); +} + +upnp::~upnp() +{ +} + +void upnp::discover_device() +{ + mutex::scoped_lock l(m_mutex); + if (m_socket.num_send_sockets() == 0) + log("No network interfaces to broadcast to", l); + + discover_device_impl(l); +} + +void upnp::log(char const* msg, mutex::scoped_lock& l) +{ + l.unlock(); + m_log_callback(msg); + l.lock(); +} + +void upnp::discover_device_impl(mutex::scoped_lock& l) +{ + const char msearch[] = + "M-SEARCH * HTTP/1.1\r\n" + "HOST: 239.255.255.250:1900\r\n" + "ST:upnp:rootdevice\r\n" + "MAN:\"ssdp:discover\"\r\n" + "MX:3\r\n" + "\r\n\r\n"; + + error_code ec; +#ifdef TORRENT_DEBUG_UPNP + // simulate packet loss + if (m_retry_count & 1) +#endif + m_socket.send(msearch, sizeof(msearch) - 1, ec); + + if (ec) + { + char msg[500]; + snprintf(msg, sizeof(msg), "broadcast failed: %s. Aborting." + , convert_from_native(ec.message()).c_str()); + log(msg, l); + disable(ec, l); + return; + } + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("upnp::resend_request"); +#endif + ++m_retry_count; + m_broadcast_timer.expires_from_now(seconds(2 * m_retry_count), ec); + m_broadcast_timer.async_wait(boost::bind(&upnp::resend_request + , self(), _1)); + + log("broadcasting search for rootdevice", l); +} + +// returns a reference to a mapping or -1 on failure +int upnp::add_mapping(upnp::protocol_type p, int external_port, int local_port) +{ + // external port 0 means _every_ port + TORRENT_ASSERT(external_port != 0); + + mutex::scoped_lock l(m_mutex); + + char msg[500]; + snprintf(msg, sizeof(msg), "adding port map: [ protocol: %s ext_port: %u " + "local_port: %u ] %s", (p == tcp?"tcp":"udp"), external_port + , local_port, m_disabled ? "DISABLED": ""); + log(msg, l); + if (m_disabled) return -1; + + std::vector::iterator mapping_it = std::find_if( + m_mappings.begin(), m_mappings.end() + , boost::bind(&global_mapping_t::protocol, _1) == int(none)); + + if (mapping_it == m_mappings.end()) + { + 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_port = local_port; + + int mapping_index = mapping_it - m_mappings.begin(); + + for (std::set::iterator i = m_devices.begin() + , end(m_devices.end()); i != end; ++i) + { + rootdevice& d = const_cast(*i); + TORRENT_ASSERT(d.magic == 1337); + + if (int(d.mapping.size()) <= mapping_index) + d.mapping.resize(mapping_index + 1); + mapping_t& m = d.mapping[mapping_index]; + + m.action = mapping_t::action_add; + m.protocol = p; + m.external_port = external_port; + m.local_port = local_port; + + if (d.service_namespace) update_map(d, mapping_index, l); + } + + return mapping_index; +} + +void upnp::delete_mapping(int mapping) +{ + mutex::scoped_lock l(m_mutex); + + if (mapping >= int(m_mappings.size())) return; + + global_mapping_t& m = m_mappings[mapping]; + + char msg[500]; + snprintf(msg, sizeof(msg), "deleting port map: [ protocol: %s ext_port: %u " + "local_port: %u ]", (m.protocol == tcp?"tcp":"udp"), m.external_port + , m.local_port); + log(msg, l); + + if (m.protocol == none) return; + + for (std::set::iterator i = m_devices.begin() + , end(m_devices.end()); i != end; ++i) + { + rootdevice& d = const_cast(*i); + TORRENT_ASSERT(d.magic == 1337); + + TORRENT_ASSERT(mapping < int(d.mapping.size())); + d.mapping[mapping].action = mapping_t::action_delete; + + if (d.service_namespace) update_map(d, mapping, l); + } +} + +bool upnp::get_mapping(int index, int& local_port, int& external_port, int& protocol) const +{ + TORRENT_ASSERT(index < int(m_mappings.size()) && index >= 0); + if (index >= int(m_mappings.size()) || index < 0) return false; + global_mapping_t const& m = m_mappings[index]; + if (m.protocol == none) return false; + local_port = m.local_port; + external_port = m.external_port; + protocol = m.protocol; + return true; +} + +void upnp::resend_request(error_code const& ec) +{ +#if defined TORRENT_ASIO_DEBUGGING + complete_async("upnp::resend_request"); +#endif + if (ec) return; + + boost::shared_ptr me(self()); + + mutex::scoped_lock l(m_mutex); + + if (m_closing) return; + + if (m_retry_count < 12 + && (m_devices.empty() || m_retry_count < 4)) + { + discover_device_impl(l); + return; + } + + if (m_devices.empty()) + { + disable(errors::no_router, l); + return; + } + + for (std::set::iterator 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 + rootdevice& d = const_cast(*i); + TORRENT_ASSERT(d.magic == 1337); + TORRENT_TRY + { + char msg[500]; + snprintf(msg, sizeof(msg), "connecting to: %s", d.url.c_str()); + log(msg, l); + if (d.upnp_connection) d.upnp_connection->close(); + d.upnp_connection.reset(new http_connection(m_io_service + , m_resolver + , boost::bind(&upnp::on_upnp_xml, self(), _1, _2 + , boost::ref(d), _5))); + d.upnp_connection->get(d.url, seconds(30), 1); + } + TORRENT_CATCH (std::exception& exc) + { + TORRENT_DECLARE_DUMMY(std::exception, exc); + char msg[500]; + snprintf(msg, sizeof(msg), "connection failed to: %s %s", d.url.c_str(), exc.what()); + log(msg, l); + d.disabled = true; + } + } + } +} + +void upnp::on_reply(udp::endpoint const& from, char* buffer + , std::size_t bytes_transferred) +{ + boost::shared_ptr me(self()); + + mutex::scoped_lock l(m_mutex); + + using namespace libtorrent::detail; + + // 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 + +*/ + error_code ec; + if (clock_type::now() - seconds(60) > m_last_if_update) + { + m_interfaces = enum_net_interfaces(m_io_service, ec); + if (ec) + { + char msg[500]; + snprintf(msg, sizeof(msg), "when receiving response from: %s: %s" + , print_endpoint(from).c_str(), convert_from_native(ec.message()).c_str()); + log(msg, l); + } + m_last_if_update = aux::time_now(); + } + + if (!ec && !in_local_network(m_interfaces, from.address())) + { + char msg[400]; + int num_chars = snprintf(msg, sizeof(msg) + , "ignoring response from: %s. IP is not on local network. " + , print_endpoint(from).c_str()); + + std::vector net = enum_net_interfaces(m_io_service, ec); + for (std::vector::const_iterator i = net.begin() + , end(net.end()); i != end && num_chars < int(sizeof(msg)); ++i) + { + num_chars += snprintf(msg + num_chars, sizeof(msg) - num_chars, "(%s,%s) " + , print_address(i->interface_address).c_str(), print_address(i->netmask).c_str()); + } + log(msg, l); + return; + } + + bool non_router = false; + if (m_ignore_non_routers) + { + std::vector routes = enum_routes(m_io_service, ec); + if (std::find_if(routes.begin(), routes.end() + , boost::bind(&ip_route::gateway, _1) == from.address()) == routes.end()) + { + // this upnp device is filtered because it's not in the + // list of configured routers + if (ec) + { + char msg[500]; + snprintf(msg, sizeof(msg), "failed to enumerate routes when " + "receiving response from: %s: %s" + , print_endpoint(from).c_str(), convert_from_native(ec.message()).c_str()); + log(msg, l); + } + else + { + char msg[400]; + int num_chars = snprintf(msg, sizeof(msg), "SSDP response from: " + "%s: IP is not a router. " + , print_endpoint(from).c_str()); + for (std::vector::const_iterator i = routes.begin() + , end(routes.end()); i != end && num_chars < int(sizeof(msg)); ++i) + { + num_chars += snprintf(msg + num_chars, sizeof(msg) - num_chars, "(%s,%s) " + , print_address(i->gateway).c_str(), print_address(i->netmask).c_str()); + } + log(msg, l); + non_router = true; + } + } + } + + http_parser p; + bool error = false; + p.incoming(buffer::const_interval(buffer + , buffer + bytes_transferred), error); + if (error) + { + char msg[500]; + snprintf(msg, sizeof(msg), "received malformed HTTP from: %s" + , print_endpoint(from).c_str()); + log(msg, l); + return; + } + + if (p.status_code() != 200 && p.method() != "notify") + { + if (p.method().empty()) + { + char msg[500]; + snprintf(msg, sizeof(msg), "HTTP status %u from %s" + , p.status_code(), print_endpoint(from).c_str()); + log(msg, l); + } + else + { + char msg[500]; + snprintf(msg, sizeof(msg), "HTTP method %s from %s" + , p.method().c_str(), print_endpoint(from).c_str()); + log(msg, l); + } + return; + } + + if (!p.header_finished()) + { + char msg[500]; + snprintf(msg, sizeof(msg), "incomplete HTTP packet from %s" + , print_endpoint(from).c_str()); + log(msg, l); + return; + } + + std::string url = p.header("location"); + if (url.empty()) + { + char msg[500]; + snprintf(msg, sizeof(msg), "missing location header from %s" + , print_endpoint(from).c_str()); + log(msg, l); + return; + } + + rootdevice d; + d.url = url; + + std::set::iterator 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 + boost::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; + + if (ec) + { + char msg[500]; + snprintf(msg, sizeof(msg), "invalid URL %s from %s: %s" + , d.url.c_str(), print_endpoint(from).c_str(), convert_from_native(ec.message()).c_str()); + log(msg, l); + return; + } + + // ignore the auth here. It will be re-parsed + // by the http connection later + + if (protocol != "http") + { + char msg[500]; + snprintf(msg, sizeof(msg), "unsupported protocol %s from %s" + , protocol.c_str(), print_endpoint(from).c_str()); + log(msg, l); + return; + } + + if (d.port == 0) + { + char msg[500]; + snprintf(msg, sizeof(msg), "URL with port 0 from %s" + , print_endpoint(from).c_str()); + log(msg, l); + return; + } + + { + char msg[500]; + snprintf(msg, sizeof(msg), "found rootdevice: %s (%d)" + , d.url.c_str(), int(m_devices.size())); + log(msg, l); + } + + if (m_devices.size() >= 50) + { + char msg[500]; + snprintf(msg, sizeof(msg), "too many rootdevices: (%d). Ignoring %s" + , int(m_devices.size()), d.url.c_str()); + log(msg, l); + return; + } + d.non_router = non_router; + + TORRENT_ASSERT(d.mapping.empty()); + for (std::vector::iterator j = m_mappings.begin() + , end(m_mappings.end()); j != end; ++j) + { + mapping_t m; + m.action = mapping_t::action_add; + m.local_port = j->local_port; + m.external_port = j->external_port; + m.protocol = j->protocol; + d.mapping.push_back(m); + } + boost::tie(i, boost::tuples::ignore) = m_devices.insert(d); + } + + + // iterate over the devices we know and connect and issue the mappings + try_map_upnp(l); + + if (m_ignore_non_routers) + { +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("upnp::map_timer"); +#endif + // check back in in a little bit to see if we have seen any + // devices at one of our default routes. If not, we want to override + // ignoring them and use them instead (better than not working). + m_map_timer.expires_from_now(seconds(1), ec); + m_map_timer.async_wait(boost::bind(&upnp::map_timer + , self(), _1)); + } +} + +void upnp::map_timer(error_code const& ec) +{ +#if defined TORRENT_ASIO_DEBUGGING + complete_async("upnp::map_timer"); +#endif + if (ec) return; + if (m_closing) return; + + mutex::scoped_lock l(m_mutex); + try_map_upnp(l, true); +} + +void upnp::try_map_upnp(mutex::scoped_lock& l, bool timer) +{ + if (m_devices.empty()) return; + + bool override_ignore_non_routers = false; + if (m_ignore_non_routers && timer) + { + // if we don't ave any devices that match our default route, we + // should try to map with the ones we did hear from anyway, + // regardless of if they are not running at our gateway. + override_ignore_non_routers = std::find_if(m_devices.begin() + , m_devices.end(), boost::bind(&rootdevice::non_router, _1) == false) + == m_devices.end(); + if (override_ignore_non_routers) + { + char msg[500]; + snprintf(msg, sizeof(msg), "overriding ignore non-routers"); + log(msg, l); + } + } + + for (std::set::iterator i = m_devices.begin() + , end(m_devices.end()); i != end; ++i) + { + // if we're ignoring non-routers, skip them. If on_timer is + // set, we expect to have received all responses and if we don't + // have any devices at our default route, then issue requests + // to any device we found. + if (m_ignore_non_routers && i->non_router + && !override_ignore_non_routers) + continue; + + 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 + rootdevice& d = const_cast(*i); + TORRENT_ASSERT(d.magic == 1337); + TORRENT_TRY + { + char msg[500]; + snprintf(msg, sizeof(msg), "connecting to: %s" + , d.url.c_str()); + log(msg, l); + + if (d.upnp_connection) d.upnp_connection->close(); + d.upnp_connection.reset(new http_connection(m_io_service + , m_resolver + , boost::bind(&upnp::on_upnp_xml, self(), _1, _2 + , boost::ref(d), _5))); + d.upnp_connection->get(d.url, seconds(30), 1); + } + TORRENT_CATCH (std::exception& exc) + { + TORRENT_DECLARE_DUMMY(std::exception, exc); + char msg[500]; + snprintf(msg, sizeof(msg), "connection failed to: %s %s" + , d.url.c_str(), exc.what()); + log(msg, l); + d.disabled = true; + } + } + } +} + +void upnp::post(upnp::rootdevice const& d, char const* soap + , char const* soap_action, mutex::scoped_lock& l) +{ + TORRENT_ASSERT(d.magic == 1337); + TORRENT_ASSERT(d.upnp_connection); + + char header[2048]; + snprintf(header, sizeof(header), "POST %s HTTP/1.1\r\n" + "Host: %s:%u\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, soap_action + , soap); + + d.upnp_connection->m_sendbuffer = header; + + char msg[1024]; + snprintf(msg, sizeof(msg), "sending: %s", header); + log(msg, l); +} + +void upnp::create_port_mapping(http_connection& c, rootdevice& d, int i) +{ + mutex::scoped_lock l(m_mutex); + + TORRENT_ASSERT(d.magic == 1337); + + if (!d.upnp_connection) + { + TORRENT_ASSERT(d.disabled); + char msg[500]; + snprintf(msg, sizeof(msg), "mapping %u aborted", i); + log(msg, l); + return; + } + + char const* soap_action = "AddPortMapping"; + + error_code ec; + std::string local_endpoint = print_address(c.socket().local_endpoint(ec).address()); + + char soap[2048]; + snprintf(soap, sizeof(soap), "\n" + "" + "" + "" + "%u" + "%s" + "%u" + "%s" + "1" + "%s at %s:%d" + "%u" + "" + , soap_action, d.service_namespace, d.mapping[i].external_port + , (d.mapping[i].protocol == udp ? "UDP" : "TCP") + , d.mapping[i].local_port + , local_endpoint.c_str() + , m_user_agent.c_str(), local_endpoint.c_str(), d.mapping[i].local_port + , d.lease_duration, soap_action); + + post(d, soap, soap_action, l); +} + +void upnp::next(rootdevice& d, int i, mutex::scoped_lock& l) +{ + if (i < num_mappings() - 1) + { + update_map(d, i + 1, l); + } + else + { + std::vector::iterator j + = std::find_if(d.mapping.begin(), d.mapping.end() + , boost::bind(&mapping_t::action, _1) != int(mapping_t::action_none)); + if (j == d.mapping.end()) return; + + update_map(d, j - d.mapping.begin(), l); + } +} + +void upnp::update_map(rootdevice& d, int i, mutex::scoped_lock& l) +{ + TORRENT_ASSERT(d.magic == 1337); + TORRENT_ASSERT(i < int(d.mapping.size())); + TORRENT_ASSERT(d.mapping.size() == m_mappings.size()); + + if (d.upnp_connection) return; + + boost::shared_ptr me(self()); + + mapping_t& m = d.mapping[i]; + + if (m.action == mapping_t::action_none + || m.protocol == none) + { + char msg[500]; + snprintf(msg, sizeof(msg), "mapping %u does not need updating, skipping", i); + log(msg, l); + m.action = mapping_t::action_none; + next(d, i, l); + return; + } + + TORRENT_ASSERT(!d.upnp_connection); + TORRENT_ASSERT(d.service_namespace); + + char msg[500]; + snprintf(msg, sizeof(msg), "connecting to %s", d.hostname.c_str()); + log(msg, l); + if (m.action == mapping_t::action_add) + { + if (m.failcount > 5) + { + m.action = mapping_t::action_none; + // giving up + next(d, i, l); + return; + } + + if (d.upnp_connection) d.upnp_connection->close(); + d.upnp_connection.reset(new http_connection(m_io_service + , m_resolver + , boost::bind(&upnp::on_upnp_map_response, self(), _1, _2 + , boost::ref(d), i, _5), true, default_max_bottled_buffer_size + , boost::bind(&upnp::create_port_mapping, self(), _1, boost::ref(d), i))); + + d.upnp_connection->start(d.hostname, d.port + , seconds(10), 1); + } + else if (m.action == mapping_t::action_delete) + { + if (d.upnp_connection) d.upnp_connection->close(); + d.upnp_connection.reset(new http_connection(m_io_service + , m_resolver + , boost::bind(&upnp::on_upnp_unmap_response, self(), _1, _2 + , boost::ref(d), i, _5), true, default_max_bottled_buffer_size + , boost::bind(&upnp::delete_port_mapping, self(), boost::ref(d), i))); + d.upnp_connection->start(d.hostname, d.port + , seconds(10), 1); + } + + m.action = mapping_t::action_none; +} + +void upnp::delete_port_mapping(rootdevice& d, int i) +{ + mutex::scoped_lock l(m_mutex); + + TORRENT_ASSERT(d.magic == 1337); + + if (!d.upnp_connection) + { + TORRENT_ASSERT(d.disabled); + char msg[500]; + snprintf(msg, sizeof(msg), "unmapping %u aborted", i); + log(msg, l); + return; + } + + char const* soap_action = "DeletePortMapping"; + + char soap[2048]; + error_code ec; + snprintf(soap, sizeof(soap), "\n" + "" + "" + "" + "%u" + "%s" + "" + , soap_action, d.service_namespace + , d.mapping[i].external_port + , (d.mapping[i].protocol == udp ? "UDP" : "TCP") + , soap_action); + + post(d, soap, soap_action, l); +} + +namespace +{ + void copy_tolower(std::string& dst, char const* src, int len) + { + dst.clear(); + dst.reserve(len); + while (len-- > 0) + { + dst.push_back(to_lower(*src++)); + } + } +} + +TORRENT_EXTRA_EXPORT void find_control_url(int type, char const* string + , int str_len, parse_state& state) +{ + if (type == xml_start_tag) + { + std::string tag; + copy_tolower(tag, string, str_len); + state.tag_stack.push_back(tag); +// std::copy(state.tag_stack.begin(), state.tag_stack.end(), std::ostream_iterator(std::cout, " ")); +// std::cout << std::endl; + } + else if (type == xml_end_tag) + { + if (!state.tag_stack.empty()) + { + if (state.in_service && 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; +// std::cout << " " << string << std::endl;} + if (!state.in_service && state.top_tags("service", "servicetype")) + { + std::string name(string, str_len); + if (string_equal_no_case(name.c_str(), "urn:schemas-upnp-org:service:WANIPConnection:1") + || string_equal_no_case(name.c_str(), "urn:schemas-upnp-org:service:WANIPConnection:2") + || string_equal_no_case(name.c_str(), "urn:schemas-upnp-org:service:WANPPPConnection:1")) + { + state.service_type.assign(string, str_len); + state.in_service = true; + } + } + else if (state.control_url.empty() && state.in_service && state.top_tags("service", "controlurl") && strlen(string) > 0) + { + // default to the first (or only) control url in the router's listing + state.control_url.assign(string, str_len); + } + else if (state.model.empty() && state.top_tags("device", "modelname")) + { + state.model.assign(string, str_len); + } + else if (state.tag_stack.back() == "urlbase") + { + state.url_base.assign(string, str_len); + } + } +} + +void upnp::on_upnp_xml(error_code const& e + , libtorrent::http_parser const& p, rootdevice& d + , http_connection& c) +{ + boost::shared_ptr me(self()); + + mutex::scoped_lock l(m_mutex); + + 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) + { + char msg[500]; + snprintf(msg, sizeof(msg), "error while fetching control url from: %s: %s" + , d.url.c_str(), convert_from_native(e.message()).c_str()); + log(msg, l); + d.disabled = true; + return; + } + + if (!p.header_finished()) + { + char msg[500]; + snprintf(msg, sizeof(msg), "error while fetching control url from: %s: incomplete HTTP message" + , d.url.c_str()); + log(msg, l); + d.disabled = true; + return; + } + + if (p.status_code() != 200) + { + char msg[500]; + snprintf(msg, sizeof(msg), "error while fetching control url from: %s: %s" + , d.url.c_str(), convert_from_native(p.message()).c_str()); + log(msg, l); + d.disabled = true; + return; + } + + parse_state s; + xml_parse(p.get_body().begin, p.get_body().end + , boost::bind(&find_control_url, _1, _2, _3, boost::ref(s))); + if (s.control_url.empty()) + { + char msg[500]; + snprintf(msg, sizeof(msg), "could not find a port mapping interface in response from: %s" + , d.url.c_str()); + log(msg, l); + d.disabled = true; + return; + } + static std::string service_type; + service_type.swap(s.service_type); + d.service_namespace = service_type.c_str(); + 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] == '/') + { + boost::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).elems + s.control_url; + } + + { + char msg[500]; + snprintf(msg, sizeof(msg), "found control URL: %s namespace %s " + "urlbase: %s in response from %s" + , d.control_url.c_str(), d.service_namespace + , s.url_base.c_str(), d.url.c_str()); + log(msg, l); + } + + boost::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) + { + char msg[500]; + snprintf(msg, sizeof(msg), "failed to parse URL '%s': %s" + , d.control_url.c_str(), convert_from_native(ec.message()).c_str()); + log(msg, l); + d.disabled = true; + return; + } + + d.upnp_connection.reset(new http_connection(m_io_service + , m_resolver + , boost::bind(&upnp::on_upnp_get_ip_address_response, self(), _1, _2 + , boost::ref(d), _5), true, default_max_bottled_buffer_size + , boost::bind(&upnp::get_ip_address, self(), boost::ref(d)))); + d.upnp_connection->start(d.hostname, d.port + , seconds(10), 1); +} + +void upnp::get_ip_address(rootdevice& d) +{ + mutex::scoped_lock l(m_mutex); + + TORRENT_ASSERT(d.magic == 1337); + + if (!d.upnp_connection) + { + TORRENT_ASSERT(d.disabled); + char msg[500]; + snprintf(msg, sizeof(msg), "getting external IP address"); + log(msg, l); + return; + } + + char const* soap_action = "GetExternalIPAddress"; + + char soap[2048]; + error_code ec; + snprintf(soap, sizeof(soap), "\n" + "" + "" + "" + , soap_action, d.service_namespace + , soap_action); + + post(d, soap, soap_action, l); +} + +void upnp::disable(error_code const& ec, mutex::scoped_lock& l) +{ + m_disabled = true; + + // kill all mappings + for (std::vector::iterator i = m_mappings.begin() + , end(m_mappings.end()); i != end; ++i) + { + if (i->protocol == none) continue; + int const proto = i->protocol; + i->protocol = none; + l.unlock(); + m_callback(i - m_mappings.begin(), address(), 0, proto, ec); + l.lock(); + } + + // we cannot clear the devices since there + // might be outstanding requests relying on + // the device entry being present when they + // complete + error_code e; + m_broadcast_timer.cancel(e); + m_refresh_timer.cancel(e); + m_map_timer.cancel(e); + m_socket.close(); +} + +namespace +{ + struct error_code_parse_state + { + error_code_parse_state(): in_error_code(false), exit(false), error_code(-1) {} + bool in_error_code; + bool exit; + int error_code; + }; + + void find_error_code(int type, char const* string, error_code_parse_state& state) + { + if (state.exit) return; + if (type == xml_start_tag && !std::strcmp("errorCode", string)) + { + state.in_error_code = true; + } + else if (type == xml_string && state.in_error_code) + { + state.error_code = std::atoi(string); + state.exit = true; + } + } + + struct ip_address_parse_state: public error_code_parse_state + { + ip_address_parse_state(): in_ip_address(false) {} + bool in_ip_address; + std::string ip_address; + }; + + void find_ip_address(int type, char const* string, ip_address_parse_state& state) + { + find_error_code(type, string, state); + if (state.exit) return; + + if (type == xml_start_tag && !std::strcmp("NewExternalIPAddress", string)) + { + state.in_ip_address = true; + } + else if (type == xml_string && state.in_ip_address) + { + state.ip_address = string; + state.exit = true; + } + } + + 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 : boost::system::error_category +{ + virtual const char* name() const BOOST_SYSTEM_NOEXCEPT + { + return "UPnP error"; + } + + virtual std::string message(int ev) const BOOST_SYSTEM_NOEXCEPT + { + int num_errors = sizeof(error_codes) / sizeof(error_codes[0]); + error_code_t* end = error_codes + num_errors; + error_code_t tmp = {ev, 0}; + error_code_t* e = std::lower_bound(error_codes, end, tmp + , boost::bind(&error_code_t::code, _1) < boost::bind(&error_code_t::code, _2)); + if (e != end && e->code == ev) + { + return e->msg; + } + char msg[500]; + snprintf(msg, sizeof(msg), "unknown UPnP error (%d)", ev); + return msg; + } + + virtual boost::system::error_condition default_error_condition( + int ev) const BOOST_SYSTEM_NOEXCEPT + { + return boost::system::error_condition(ev, *this); + } +}; + +boost::system::error_category& get_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) +{ + boost::shared_ptr me(self()); + + mutex::scoped_lock l(m_mutex); + + 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) + { + char msg[500]; + snprintf(msg, sizeof(msg), "error while getting external IP address: %s" + , convert_from_native(e.message()).c_str()); + log(msg, l); + if (num_mappings() > 0) update_map(d, 0, l); + return; + } + + if (!p.header_finished()) + { + log("error while getting external IP address: incomplete http message", l); + if (num_mappings() > 0) update_map(d, 0, l); + return; + } + + if (p.status_code() != 200) + { + char msg[500]; + snprintf(msg, sizeof(msg), "error while getting external IP address: %s" + , convert_from_native(p.message()).c_str()); + log(msg, l); + if (num_mappings() > 0) update_map(d, 0, l); + return; + } + + // response may look like + // + // + // + // 192.168.160.19 + // + // + // + + { + char msg[500]; + snprintf(msg, sizeof(msg), "get external IP address response: %s" + , std::string(p.get_body().begin, p.get_body().end).c_str()); + log(msg, l); + } + + ip_address_parse_state s; + xml_parse(const_cast(p.get_body().begin), const_cast(p.get_body().end) + , boost::bind(&find_ip_address, _1, _2, boost::ref(s))); + if (s.error_code != -1) + { + char msg[500]; + snprintf(msg, sizeof(msg), "error while getting external IP address, code: %u" + , s.error_code); + log(msg, l); + } + + if (!s.ip_address.empty()) { + char msg[500]; + snprintf(msg, sizeof(msg), "got router external IP address %s", s.ip_address.c_str()); + log(msg, l); + d.external_ip = address::from_string(s.ip_address.c_str(), ignore_error); + } else { + log("failed to find external IP address in response", l); + } + + if (num_mappings() > 0) update_map(d, 0, l); +} + +void upnp::on_upnp_map_response(error_code const& e + , libtorrent::http_parser const& p, rootdevice& d, int mapping + , http_connection& c) +{ + boost::shared_ptr me(self()); + + mutex::scoped_lock l(m_mutex); + + 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) + { + char msg[500]; + snprintf(msg, sizeof(msg), "error while adding port map: %s" + , convert_from_native(e.message()).c_str()); + log(msg, l); + 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()) + { + log("error while adding port map: incomplete http message", l); + next(d, mapping, l); + return; + } + + std::string 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 + ) + { + char msg[300]; + snprintf(msg, sizeof(msg), "error while adding port map: invalid content-type, \"%s\". Expected text/xml or application/soap+xml" + , ct.c_str()); + log(msg, l); + next(d, mapping, l); + 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; + xml_parse(const_cast(p.get_body().begin) + , const_cast(p.get_body().end) + , boost::bind(&find_error_code, _1, _2, boost::ref(s))); + + if (s.error_code != -1) + { + char msg[500]; + snprintf(msg, sizeof(msg), "error while adding port map, code: %u" + , s.error_code); + log(msg, l); + } + + mapping_t& m = d.mapping[mapping]; + + if (s.error_code == 725) + { + // only permanent leases supported + d.lease_duration = 0; + m.action = mapping_t::action_add; + ++m.failcount; + update_map(d, mapping, l); + return; + } + else if (s.error_code == 727) + { + return_error(mapping, s.error_code, l); + } + 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 + (random() % 10000); + m.action = mapping_t::action_add; + ++m.failcount; + update_map(d, mapping, l); + return; + } + else if (s.error_code != -1) + { + return_error(mapping, s.error_code, l); + } + + char msg[500]; + snprintf(msg, sizeof(msg), "map response: %s" + , std::string(p.get_body().begin, p.get_body().end).c_str()); + log(msg, l); + + if (s.error_code == -1) + { + l.unlock(); + m_callback(mapping, d.external_ip, m.external_port, m.protocol, error_code()); + l.lock(); + if (d.lease_duration > 0) + { + m.expires = aux::time_now() + + seconds(int(d.lease_duration * 0.75f)); + time_point next_expire = m_refresh_timer.expires_at(); + if (next_expire < aux::time_now() + || next_expire > m.expires) + { +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("upnp::on_expire"); +#endif + error_code ec; + m_refresh_timer.expires_at(m.expires, ec); + m_refresh_timer.async_wait(boost::bind(&upnp::on_expire, self(), _1)); + } + } + else + { + m.expires = max_time(); + } + m.failcount = 0; + } + + next(d, mapping, l); +} + +void upnp::return_error(int mapping, int code, mutex::scoped_lock& l) +{ + int num_errors = sizeof(error_codes) / sizeof(error_codes[0]); + error_code_t* end = error_codes + num_errors; + error_code_t tmp = {code, 0}; + error_code_t* e = std::lower_bound(error_codes, end, tmp + , boost::bind(&error_code_t::code, _1) < boost::bind(&error_code_t::code, _2)); + std::string error_string = "UPnP mapping error "; + error_string += to_string(code).elems; + if (e != end && e->code == code) + { + error_string += ": "; + error_string += e->msg; + } + const int proto = m_mappings[mapping].protocol; + l.unlock(); + m_callback(mapping, address(), 0, proto, error_code(code, get_upnp_category())); + l.lock(); +} + +void upnp::on_upnp_unmap_response(error_code const& e + , libtorrent::http_parser const& p, rootdevice& d, int mapping + , http_connection& c) +{ + boost::shared_ptr me(self()); + + mutex::scoped_lock l(m_mutex); + + 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) + { + char msg[500]; + snprintf(msg, sizeof(msg), "error while deleting portmap: %s" + , convert_from_native(e.message()).c_str()); + log(msg, l); + } + else if (!p.header_finished()) + { + log("error while deleting portmap: incomplete http message", l); + } + else if (p.status_code() != 200) + { + char msg[500]; + snprintf(msg, sizeof(msg), "error while deleting portmap: %s" + , convert_from_native(p.message()).c_str()); + log(msg, l); + } + else + { + char msg[500]; + snprintf(msg, sizeof(msg), "unmap response: %s" + , std::string(p.get_body().begin, p.get_body().end).c_str()); + log(msg, l); + } + + error_code_parse_state s; + if (p.header_finished()) + { + xml_parse(const_cast(p.get_body().begin) + , const_cast(p.get_body().end) + , boost::bind(&find_error_code, _1, _2, boost::ref(s))); + } + + int const proto = m_mappings[mapping].protocol; + + l.unlock(); + m_callback(mapping, address(), 0, proto, p.status_code() != 200 + ? error_code(p.status_code(), get_http_category()) + : error_code(s.error_code, get_upnp_category())); + l.lock(); + + d.mapping[mapping].protocol = none; + + next(d, mapping, l); +} + +void upnp::on_expire(error_code const& ec) +{ +#if defined TORRENT_ASIO_DEBUGGING + complete_async("upnp::on_expire"); +#endif + if (ec) return; + + time_point now = aux::time_now(); + time_point next_expire = max_time(); + + mutex::scoped_lock l(m_mutex); + + for (std::set::iterator i = m_devices.begin() + , end(m_devices.end()); i != end; ++i) + { + rootdevice& d = const_cast(*i); + TORRENT_ASSERT(d.magic == 1337); + for (int m = 0; m < num_mappings(); ++m) + { + if (d.mapping[m].expires != max_time()) + continue; + + if (d.mapping[m].expires < now) + { + d.mapping[m].expires = max_time(); + update_map(d, m, l); + } + else if (d.mapping[m].expires < next_expire) + { + next_expire = d.mapping[m].expires; + } + } + } + if (next_expire != max_time()) + { +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("upnp::on_expire"); +#endif + error_code e; + m_refresh_timer.expires_at(next_expire, e); + m_refresh_timer.async_wait(boost::bind(&upnp::on_expire, self(), _1)); + } +} + +void upnp::close() +{ + mutex::scoped_lock l(m_mutex); + + error_code ec; + m_refresh_timer.cancel(ec); + m_broadcast_timer.cancel(ec); + m_map_timer.cancel(ec); + m_closing = true; + m_socket.close(); + + for (std::set::iterator i = m_devices.begin() + , end(m_devices.end()); i != end; ++i) + { + rootdevice& d = const_cast(*i); + TORRENT_ASSERT(d.magic == 1337); + if (d.control_url.empty()) continue; + for (std::vector::iterator j = d.mapping.begin() + , end2(d.mapping.end()); j != end2; ++j) + { + if (j->protocol == none) continue; + if (j->action == mapping_t::action_add) + { + j->action = mapping_t::action_none; + continue; + } + j->action = mapping_t::action_delete; + m_mappings[j - d.mapping.begin()].protocol = none; + } + if (num_mappings() > 0) update_map(d, 0, l); + } +} + +} + diff --git a/src/ut_metadata.cpp b/src/ut_metadata.cpp new file mode 100644 index 0000000..19c66b3 --- /dev/null +++ b/src/ut_metadata.cpp @@ -0,0 +1,680 @@ +/* + +Copyright (c) 2007-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_DISABLE_EXTENSIONS + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include + +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/bt_peer_connection.hpp" +#include "libtorrent/peer_connection_handle.hpp" +#include "libtorrent/hasher.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 + +namespace libtorrent { namespace +{ + enum + { + // this is the max number of bytes we'll + // queue up in the send buffer. If we exceed this, + // we'll wait another second before checking + // the send buffer size again. So, this may limit + // the rate at which we can server metadata to + // 160 kiB/s + send_buffer_limit = 0x4000 * 10, + + // this is the max number of requests we'll queue + // up. If we get more requests tha this, we'll + // start rejecting them, claiming we don't have + // metadata. If the torrent is greater than 16 MiB, + // we may hit this case (and the client requesting + // doesn't throttle its requests) + max_incoming_requests = 1024, + + metadata_req = 0, + metadata_piece = 1, + metadata_dont_have = 2 + }; + + int div_round_up(int numerator, int denominator) + { + return (numerator + denominator - 1) / denominator; + } + + struct ut_metadata_peer_plugin; + + struct ut_metadata_plugin TORRENT_FINAL + : torrent_plugin + { + ut_metadata_plugin(torrent& t) + : m_torrent(t) +// , m_metadata_progress(0) + , m_metadata_size(0) + { + // initialize m_metadata_size + if (m_torrent.valid_metadata()) + metadata(); + } + + bool need_loaded() + { return m_torrent.need_loaded(); } + + virtual void on_unload() TORRENT_OVERRIDE + { + m_metadata.reset(); + } + + virtual void on_load() TORRENT_OVERRIDE + { + // initialize m_metadata_size + TORRENT_ASSERT(m_torrent.is_loaded()); + metadata(); + } + + virtual void on_files_checked() TORRENT_OVERRIDE + { + // TODO: 2 if we were to initialize m_metadata_size lazily instead, + // we would probably be more efficient + // initialize m_metadata_size + metadata(); + } + + virtual boost::shared_ptr new_connection( + peer_connection_handle const& pc) TORRENT_OVERRIDE; + + int get_metadata_size() const + { + TORRENT_ASSERT(m_metadata_size > 0); + return m_metadata_size; + } + + buffer::const_interval metadata() const + { + if (!m_torrent.need_loaded()) return buffer::const_interval(NULL, NULL); + TORRENT_ASSERT(m_torrent.valid_metadata()); + if (!m_metadata) + { + m_metadata = m_torrent.torrent_file().metadata(); + m_metadata_size = m_torrent.torrent_file().metadata_size(); + TORRENT_ASSERT(hasher(m_metadata.get(), m_metadata_size).final() + == m_torrent.torrent_file().info_hash()); + } + return buffer::const_interval(m_metadata.get(), m_metadata.get() + + m_metadata_size); + } + + bool received_metadata(ut_metadata_peer_plugin& source + , char const* buf, int size, 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); +/* + // this is called from the peer_connection for + // each piece of metadata it receives + void metadata_progress(int total_size, int received) + { + m_metadata_progress += received; + m_metadata_size = total_size; + m_torrent.set_progress_ppm(boost::int64_t(m_metadata_progress) * 1000000 / m_metadata_size); + } +*/ + void on_piece_pass(int) TORRENT_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 size) + { + if (m_metadata_size > 0 || size <= 0 || size > 4 * 1024 * 1024) return; + m_metadata_size = size; + m_metadata.reset(new char[size]); + m_requested_metadata.resize(div_round_up(size, 16 * 1024)); + } + + private: + torrent& m_torrent; + + // this buffer is filled with the info-section of + // the metadata file while downloading it from + // peers, and while sending it. + // it is mutable because it's generated lazily + mutable boost::shared_array m_metadata; + +// int m_metadata_progress; + mutable int m_metadata_size; + + struct metadata_piece + { + metadata_piece(): num_requests(0), last_request(min_time()) {} + int num_requests; + time_point last_request; + boost::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 + std::vector m_requested_metadata; + + // explicitly disallow assignment, to silence msvc warning + ut_metadata_plugin& operator=(ut_metadata_plugin const&); + }; + + + struct ut_metadata_peer_plugin TORRENT_FINAL + : peer_plugin, boost::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) + {} + + virtual char const* type() const TORRENT_OVERRIDE { return "ut_metadata"; } + + // can add entries to the extension handshake + virtual void add_handshake(entry& h) TORRENT_OVERRIDE + { + entry& messages = h["m"]; + messages["ut_metadata"] = 2; + if (m_torrent.valid_metadata()) + h["metadata_size"] = m_tp.get_metadata_size(); + } + + // called when the extension handshake from the other end is received + virtual bool on_extension_handshake(bdecode_node const& h) TORRENT_OVERRIDE + { + m_message_index = 0; + if (h.type() != bdecode_node::dict_t) return false; + bdecode_node messages = h.dict_find_dict("m"); + if (!messages) return false; + + int index = messages.dict_find_int_value("ut_metadata", -1); + if (index == -1) return false; + m_message_index = index; + + int metadata_size = h.dict_find_int_value("metadata_size"); + if (metadata_size > 0) + m_tp.metadata_size(metadata_size); + else + m_pc.set_has_metadata(false); + + maybe_send_request(); + return true; + } + + void write_metadata_packet(int type, int piece) + { + TORRENT_ASSERT(type >= 0 && type <= 2); + TORRENT_ASSERT(!m_pc.associated_torrent().expired()); + +#ifndef TORRENT_DISABLE_LOGGING + char const* names[] = {"request", "data", "dont-have"}; + char const* n = ""; + if (type >= 0 && type < 3) n = names[type]; + m_pc.peer_log(peer_log_alert::outgoing_message, "UT_METADATA" + , "type: %d (%s) piece: %d", type, n, piece); +#endif + + // abort if the peer doesn't support the metadata extension + if (m_message_index == 0) return; + + entry e; + e["msg_type"] = type; + e["piece"] = piece; + + char const* metadata = 0; + int metadata_piece_size = 0; + + if (m_torrent.valid_metadata()) + e["total_size"] = m_tp.get_metadata_size(); + + if (type == 1) + { + TORRENT_ASSERT(piece >= 0 && piece < int(m_tp.get_metadata_size() + 16 * 1024 - 1)/(16*1024)); + TORRENT_ASSERT(m_pc.associated_torrent().lock()->valid_metadata()); + TORRENT_ASSERT(m_torrent.valid_metadata()); + + int offset = piece * 16 * 1024; + // unloaded torrents don't have any metadata. Since we're + // about to send the metadata, we need it to be loaded + if (!m_tp.need_loaded()) return; + metadata = m_tp.metadata().begin + offset; + metadata_piece_size = (std::min)( + int(m_tp.get_metadata_size() - offset), 16 * 1024); + TORRENT_ASSERT(metadata_piece_size > 0); + TORRENT_ASSERT(offset >= 0); + TORRENT_ASSERT(offset + metadata_piece_size <= int(m_tp.get_metadata_size())); + } + + char msg[200]; + char* header = msg; + char* p = &msg[6]; + int len = bencode(p, e); + int total_size = 2 + len + metadata_piece_size; + namespace io = detail; + io::write_uint32(total_size, header); + io::write_uint8(bt_peer_connection::msg_extended, header); + io::write_uint8(m_message_index, header); + + m_pc.send_buffer(msg, len + 6); + // TODO: we really need to increment the refcounter on the torrent + // while this buffer is still in the peer's send buffer + if (metadata_piece_size) m_pc.append_const_send_buffer( + metadata, 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); + } + + virtual bool on_extended(int length + , int extended_msg, buffer::const_interval body) TORRENT_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, op_bittorrent, 2); + return true; + } + + if (!m_pc.packet_finished()) return true; + + int len; + entry msg = bdecode(body.begin, body.end, len); + if (msg.type() != entry::dictionary_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, op_bittorrent, 2); + return true; + } + + entry const* type_ent = msg.find_key("msg_type"); + entry const* piece_ent = msg.find_key("piece"); + if (type_ent == 0 || type_ent->type() != entry::int_t + || piece_ent == 0 || piece_ent->type() != entry::int_t) + { +#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, op_bittorrent, 2); + return true; + } + int type = type_ent->integer(); + int piece = piece_ent->integer(); + +#ifndef TORRENT_DISABLE_LOGGING + m_pc.peer_log(peer_log_alert::incoming_message, "UT_METADATA" + , "type: %d piece: %d", type, piece); +#endif + + switch (type) + { + case metadata_req: + { + if (!m_torrent.valid_metadata() + || piece < 0 || piece >= int(m_tp.get_metadata_size() + 16 * 1024 - 1)/(16*1024)) + { +#ifndef TORRENT_DISABLE_LOGGING + 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.get_metadata_size()) : 0); +#endif + write_metadata_packet(metadata_dont_have, piece); + return true; + } + if (m_pc.send_buffer_size() < send_buffer_limit) + write_metadata_packet(metadata_piece, piece); + else if (m_incoming_requests.size() < max_incoming_requests) + m_incoming_requests.push_back(piece); + else + write_metadata_packet(metadata_dont_have, piece); + } + break; + case metadata_piece: + { + std::vector::iterator 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); + entry const* total_size = msg.find_key("total_size"); + m_tp.received_metadata(*this, body.begin + len, body.left() - len, piece + , (total_size && total_size->type() == entry::int_t) ? total_size->integer() : 0); + maybe_send_request(); + } + break; + case metadata_dont_have: + { + m_request_limit = (std::max)(aux::time_now() + minutes(1), m_request_limit); + std::vector::iterator i = std::find(m_sent_requests.begin() + , m_sent_requests.end(), piece); + // unwanted piece? + if (i == m_sent_requests.end()) return true; + m_sent_requests.erase(i); + } + break; + default: + // unknown message, ignore + break; + } + + m_pc.stats_counters().inc_stats_counter(counters::num_incoming_metadata); + + return true; + } + + virtual void tick() TORRENT_OVERRIDE + { + maybe_send_request(); + while (!m_incoming_requests.empty() + && m_pc.send_buffer_size() < send_buffer_limit) + { + int piece = m_incoming_requests.front(); + m_incoming_requests.erase(m_incoming_requests.begin()); + write_metadata_packet(metadata_piece, piece); + } + } + + void maybe_send_request() + { + if (m_pc.is_disconnecting()) return; + + // if we don't have any metadata, and this peer + // supports the request metadata extension + // and we aren't currently waiting for a request + // reply. Then, send a request for some metadata. + if (!m_torrent.valid_metadata() + && m_message_index != 0 + && m_sent_requests.size() < 2 + && has_metadata()) + { + int piece = m_tp.metadata_request(m_pc.has_metadata()); + if (piece == -1) return; + + m_sent_requests.push_back(piece); + write_metadata_packet(metadata_req, piece); + } + } + + bool has_metadata() const + { + return m_pc.has_metadata() || (aux::time_now() > m_request_limit); + } + + void failed_hash_check(time_point const& now) + { + m_request_limit = now + seconds(20 + (boost::int64_t(random()) * 50) / UINT_MAX); + } + + private: + + // this is the message index the remote peer uses + // for metadata extension messages. + int m_message_index; + + // this is set to the next time we can request pieces + // again. It is updated every time we get a + // "I don't have metadata" message, but also when + // we receive metadata that fails the infohash check + time_point m_request_limit; + + // request queues + std::vector m_sent_requests; + std::vector m_incoming_requests; + + torrent& m_torrent; + bt_peer_connection& m_pc; + ut_metadata_plugin& m_tp; + + // explicitly disallow assignment, to silence msvc warning + ut_metadata_peer_plugin& operator=(ut_metadata_peer_plugin const&); + }; + + boost::shared_ptr ut_metadata_plugin::new_connection( + peer_connection_handle const& pc) + { + if (pc.type() != peer_connection::bittorrent_connection) + return boost::shared_ptr(); + + bt_peer_connection* c = static_cast(pc.native_handle().get()); + return boost::shared_ptr(new ut_metadata_peer_plugin(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 has_metadata) + { + std::vector::iterator 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 piece = i - m_requested_metadata.begin(); + + // don't request the same block more than once every 3 seconds + time_point 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 + , char const* buf, int size, int piece, int 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(size, torrent::piece_unknown); + return false; + } + + if (!m_metadata) + { + // verify the total_size + if (total_size <= 0 || total_size > m_torrent.session().settings().get_int(settings_pack::max_metadata_size)) + { +#ifndef TORRENT_DISABLE_LOGGING + source.m_pc.peer_log(peer_log_alert::info, "UT_METADATA" + , "metadata size too big: %d", total_size); +#endif +// #error post alert + return false; + } + + m_metadata.reset(new char[total_size]); + m_requested_metadata.resize(div_round_up(total_size, 16 * 1024)); + m_metadata_size = total_size; + } + + if (piece < 0 || piece >= int(m_requested_metadata.size())) + { +#ifndef TORRENT_DISABLE_LOGGING + source.m_pc.peer_log(peer_log_alert::info, "UT_METADATA" + , "piece: %d INVALID", piece); +#endif + return false; + } + + if (total_size != m_metadata_size) + { +#ifndef TORRENT_DISABLE_LOGGING + source.m_pc.peer_log(peer_log_alert::info, "UT_METADATA" + , "total_size: %d INCONSISTENT WITH: %d" + , total_size, m_metadata_size); +#endif + // they disagree about the size! + return false; + } + + if (piece * 16 * 1024 + size > m_metadata_size) + { + // this piece is invalid + return false; + } + + std::memcpy(&m_metadata[piece * 16 * 1024], 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::count_if(m_requested_metadata.begin() + , m_requested_metadata.end(), boost::bind(&metadata_piece::num_requests, _1) + == (std::numeric_limits::max)()) == int(m_requested_metadata.size()); + + if (!have_all) return false; + + if (!m_torrent.set_metadata(&m_metadata[0], m_metadata_size)) + { + if (!m_torrent.valid_metadata()) + { + time_point 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 (int i = 0; i < int(m_requested_metadata.size()); ++i) + { + m_requested_metadata[i].num_requests = 0; + boost::shared_ptr peer + = m_requested_metadata[i].source.lock(); + if (!peer) continue; + + peer->failed_hash_check(single_peer ? now + minutes(5) : now); + } + } + return false; + } + + // free our copy of the metadata and get a reference + // to the torrent's copy instead. No need to keep two + // identical copies around + m_metadata.reset(); + metadata(); + + // clear the storage for the bitfield + std::vector().swap(m_requested_metadata); + + return true; + } + +} } + +namespace libtorrent +{ + + boost::shared_ptr create_ut_metadata_plugin(torrent_handle const& th, void*) + { + torrent* t = th.native_handle().get(); + // don't add this extension if the torrent is private + if (t->valid_metadata() && t->torrent_file().priv()) return boost::shared_ptr(); + return boost::shared_ptr(new ut_metadata_plugin(*t)); + } + +} + +#endif + diff --git a/src/ut_pex.cpp b/src/ut_pex.cpp new file mode 100644 index 0000000..fb5e95b --- /dev/null +++ b/src/ut_pex.cpp @@ -0,0 +1,707 @@ +/* + +Copyright (c) 2006-2016, MassaRoddel, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/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/broadcast_socket.hpp" +#include "libtorrent/socket_io.hpp" +#include "libtorrent/peer_info.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/socket_type.hpp" // for is_utp +#include "libtorrent/performance_counters.hpp" // for counters + +#include "libtorrent/extensions/ut_pex.hpp" + +#ifndef TORRENT_DISABLE_LOGGING +#include "libtorrent/lazy_entry.hpp" +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +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 TORRENT_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 + ut_pex_plugin(torrent& t) + : m_torrent(t) + , m_last_msg(min_time()) + , m_peers_in_message(0) {} + + virtual boost::shared_ptr new_connection( + peer_connection_handle const& pc) TORRENT_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 + virtual void tick() TORRENT_OVERRIDE + { + time_point now = aux::time_now(); + if (now - seconds(60) < m_last_msg) return; + m_last_msg = now; + + int num_peers = m_torrent.num_peers(); + if (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); +#if TORRENT_USE_IPV6 + 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); +#endif + + std::set dropped; + m_old_peers.swap(dropped); + + m_peers_in_message = 0; + int num_added = 0; + for (torrent::peer_iterator i = m_torrent.begin() + , end(m_torrent.end()); i != end; ++i) + { + peer_connection* peer = *i; + if (!send_peer(*peer)) continue; + + tcp::endpoint remote = peer->remote(); + m_old_peers.insert(remote); + + std::set::iterator 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() != peer_connection::bittorrent_connection) + continue; + + bt_peer_connection* p = static_cast(peer); + + // if the peer has told us which port its listening on, + // use that port. But only if we didn't connect to the peer. + // if we connected to it, use the port we know works + torrent_peer *pi = 0; + if (!p->is_outgoing() && (pi = peer->peer_info_struct()) && 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 holepunching 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) && !defined(TORRENT_DISABLE_EXTENSIONS) + flags |= p->supports_encryption() ? 1 : 0; +#endif + flags |= is_utp(*p->get_socket()) ? 4 : 0; + flags |= p->supports_holepunch() ? 8 : 0; + + // i->first was added since the last time + if (remote.address().is_v4()) + { + detail::write_endpoint(remote, pla_out); + detail::write_uint8(flags, plf_out); + } +#if TORRENT_USE_IPV6 + else + { + detail::write_endpoint(remote, pla6_out); + detail::write_uint8(flags, plf6_out); + } +#endif + ++num_added; + ++m_peers_in_message; + } + else + { + // this was in the previous message + // so, it wasn't dropped + dropped.erase(di); + } + } + + for (std::set::const_iterator i = dropped.begin() + , end(dropped.end()); i != end; ++i) + { + if (i->address().is_v4()) + detail::write_endpoint(*i, pld_out); +#if TORRENT_USE_IPV6 + else + detail::write_endpoint(*i, pld6_out); +#endif + ++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&); + }; + + struct ut_pex_peer_plugin TORRENT_FINAL + : 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(); + } + } + + virtual char const* type() const TORRENT_OVERRIDE { return "ut_pex"; } + + virtual void add_handshake(entry& h) TORRENT_OVERRIDE + { + entry& messages = h["m"]; + messages[extension_name] = extension_index; + } + + virtual bool on_extension_handshake(bdecode_node const& h) TORRENT_OVERRIDE + { + m_message_index = 0; + if (h.type() != bdecode_node::dict_t) return false; + bdecode_node messages = h.dict_find_dict("m"); + if (!messages) return false; + + int index = int(messages.dict_find_int_value(extension_name, -1)); + if (index == -1) return false; + m_message_index = index; + return true; + } + + virtual bool on_extended(int length, int msg, buffer::const_interval body) TORRENT_OVERRIDE + { + if (msg != extension_index) return false; + if (m_message_index == 0) return false; + + if (length > 500 * 1024) + { + m_pc.disconnect(errors::pex_message_too_large, op_bittorrent, 2); + return true; + } + + if (body.left() < length) return true; + + time_point 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, op_bittorrent); + return true; + } + + const int 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 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, op_bittorrent, 2); + 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 num_peers = p.string_length() / 6; + char const* in = p.string_ptr(); + + for (int i = 0; i < num_peers; ++i) + { + tcp::endpoint adr = detail::read_v4_endpoint(in); + peers4_t::value_type v(adr.address().to_v4().to_bytes(), adr.port()); + peers4_t::iterator 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 pf = pex_msg.dict_find_string("added.f"); + +#ifndef TORRENT_DISABLE_LOGGING + if (p) num_added += p.string_length() / 6; +#endif + if (p && pf && pf.string_length() == p.string_length() / 6) + { + int 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 adr = detail::read_v4_endpoint(in); + char flags = *fin++; + + if (int(m_peers.size()) >= m_torrent.settings().get_int(settings_pack::max_pex_peers)) + break; + + // ignore local addresses unless the peer is local to us + if (is_local(adr.address()) && !is_local(m_pc.remote().address())) continue; + + peers4_t::value_type v(adr.address().to_v4().to_bytes(), adr.port()); + peers4_t::iterator 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); + } + } + +#if TORRENT_USE_IPV6 + + bdecode_node p6 = pex_msg.dict_find("dropped6"); +#ifndef TORRENT_DISABLE_LOGGING + if (p6) num_dropped += p6.string_length() / 18; +#endif + if (p6 != 0 && p6.type() == bdecode_node::string_t) + { + int num_peers = p6.string_length() / 18; + char const* in = p6.string_ptr(); + + for (int i = 0; i < num_peers; ++i) + { + tcp::endpoint adr = detail::read_v6_endpoint(in); + peers6_t::value_type v(adr.address().to_v6().to_bytes(), adr.port()); + peers6_t::iterator 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("added6"); +#ifndef TORRENT_DISABLE_LOGGING + if (p6) num_added += p6.string_length() / 18; +#endif + bdecode_node p6f = pex_msg.dict_find("added6.f"); + if (p6 != 0 + && p6f != 0 + && p6.type() == bdecode_node::string_t + && p6f.type() == bdecode_node::string_t + && p6f.string_length() == p6.string_length() / 18) + { + int 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 adr = detail::read_v6_endpoint(in); + char flags = *fin++; + // ignore local addresses unless the peer is local to us + if (is_local(adr.address()) && !is_local(m_pc.remote().address())) continue; + if (int(m_peers6.size()) >= m_torrent.settings().get_int(settings_pack::max_pex_peers)) + break; + + peers6_t::value_type v(adr.address().to_v6().to_bytes(), adr.port()); + peers6_t::iterator 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); + } + } +#endif +#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); + return true; + } + + // the peers second tick + // every minute we send a pex message + virtual void tick() TORRENT_OVERRIDE + { + // no handshake yet + if (!m_message_index) return; + + time_point now = aux::time_now(); + if (now - seconds(60) < m_last_msg) + { +#ifndef TORRENT_DISABLE_LOGGING +// m_pc.peer_log(peer_log_alert::info, "PEX", "waiting: %d seconds to next msg" +// , int(total_seconds(seconds(60) - (now - m_last_msg)))); +#endif + return; + } + static time_point global_last = min_time(); + + int num_peers = m_torrent.num_peers(); + if (num_peers <= 1) return; + + // don't send pex messages more often than 1 every 100 ms, and + // allow pex messages to be sent 5 seconds apart if there isn't + // contention + int delay = (std::min)((std::max)(60000 / num_peers, 100), 3000); + + if (now - milliseconds(delay) < global_last) + { +#ifndef TORRENT_DISABLE_LOGGING +// m_pc.peer_log(peer_log_alert::info, "PEX", "global-wait: %d" +// , int(total_seconds(milliseconds(delay) - (now - global_last)))); +#endif + return; + } + + // this will allow us to catch up, even if our timer + // has lower resolution than delay + if (global_last == min_time()) + global_last = now; + else + global_last += milliseconds(delay); + + m_last_msg = now; + + if (m_first_time) + { + send_ut_peer_list(); + m_first_time = false; + } + else + { + send_ut_peer_diff(); + } + } + + void send_ut_peer_diff() + { + // if there's no change in out peer set, don't send anything + if (m_tp.peers_in_msg() == 0) return; + + std::vector const& pex_msg = m_tp.get_ut_pex_msg(); + + char msg[6]; + char* ptr = msg; + + detail::write_uint32(1 + 1 + pex_msg.size(), ptr); + detail::write_uint8(bt_peer_connection::msg_extended, ptr); + detail::write_uint8(m_message_index, ptr); + m_pc.send_buffer(msg, sizeof(msg)); + m_pc.send_buffer(&pex_msg[0], pex_msg.size()); + + 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 + bdecode_node m; + error_code ec; + int 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() + { + 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); + +#if TORRENT_USE_IPV6 + 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); +#endif + + int num_added = 0; + for (torrent::peer_iterator i = m_torrent.begin() + , end(m_torrent.end()); i != end; ++i) + { + peer_connection* peer = *i; + 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() != peer_connection::bittorrent_connection) + continue; + + bt_peer_connection* p = static_cast(peer); + + // no supported flags to set yet + // 0x01 - peer supports encryption + // 0x02 - peer is a seed + // 0x04 - supports uTP. This is only a positive flags + // passing 0 doesn't mean the peer doesn't + // support uTP + // 0x08 - supports holepunching 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) && !defined(TORRENT_DISABLE_EXTENSIONS) + 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(); + + torrent_peer *pi = 0; + if (!p->is_outgoing() && (pi = peer->peer_info_struct()) && pi->port > 0) + remote.port(pi->port); + + // i->first was added since the last time + if (remote.address().is_v4()) + { + detail::write_endpoint(remote, pla_out); + detail::write_uint8(flags, plf_out); + } +#if TORRENT_USE_IPV6 + else + { + detail::write_endpoint(remote, pla6_out); + detail::write_uint8(flags, plf6_out); + } +#endif + ++num_added; + } + std::vector pex_msg; + bencode(std::back_inserter(pex_msg), pex); + + char msg[6]; + char* ptr = msg; + + detail::write_uint32(1 + 1 + pex_msg.size(), ptr); + detail::write_uint8(bt_peer_connection::msg_extended, ptr); + detail::write_uint8(m_message_index, ptr); + m_pc.send_buffer(msg, sizeof(msg)); + m_pc.send_buffer(&pex_msg[0], pex_msg.size()); + + 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; + // stores all peers this 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 + typedef std::vector > peers4_t; + peers4_t m_peers; +#if TORRENT_USE_IPV6 + typedef std::vector > peers6_t; + peers6_t m_peers6; +#endif + // 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&); + }; + + boost::shared_ptr ut_pex_plugin::new_connection(peer_connection_handle const& pc) + { + if (pc.type() != peer_connection::bittorrent_connection) + return boost::shared_ptr(); + + return boost::shared_ptr(new ut_pex_peer_plugin(m_torrent + , *pc.native_handle(), *this)); + } +} } + +namespace libtorrent +{ + boost::shared_ptr create_ut_pex_plugin(torrent_handle const& th, void*) + { + torrent* t = th.native_handle().get(); + if (t->torrent_file().priv() || (t->torrent_file().is_i2p() + && !t->settings().get_bool(settings_pack::allow_i2p_mixed))) + { + return boost::shared_ptr(); + } + return boost::shared_ptr(new ut_pex_plugin(*t)); + } + + bool was_introduced_by(peer_plugin const* pp, tcp::endpoint const& ep) + { + ut_pex_peer_plugin const* p = static_cast(pp); +#if TORRENT_USE_IPV6 + if (ep.address().is_v4()) + { +#endif + ut_pex_peer_plugin::peers4_t::value_type v(ep.address().to_v4().to_bytes(), ep.port()); + ut_pex_peer_plugin::peers4_t::const_iterator i + = std::lower_bound(p->m_peers.begin(), p->m_peers.end(), v); + return i != p->m_peers.end() && *i == v; +#if TORRENT_USE_IPV6 + } + else + { + ut_pex_peer_plugin::peers6_t::value_type v(ep.address().to_v6().to_bytes(), ep.port()); + ut_pex_peer_plugin::peers6_t::const_iterator i + = std::lower_bound(p->m_peers6.begin(), p->m_peers6.end(), v); + return i != p->m_peers6.end() && *i == v; + } +#endif + } +} + +#endif + diff --git a/src/utf8.cpp b/src/utf8.cpp new file mode 100644 index 0000000..13d717a --- /dev/null +++ b/src/utf8.cpp @@ -0,0 +1,207 @@ +/* + +Copyright (c) 2012-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" + +// on windows we need these functions for +// convert_to_native and convert_from_native +#if TORRENT_USE_WSTRING || defined TORRENT_WINDOWS + +#include +#include "libtorrent/utf8.hpp" +#include "libtorrent/ConvertUTF.h" + + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-member-function" +#endif + +namespace libtorrent +{ + namespace + { + // ==== utf-8 -> wide === + template + struct convert_to_wide + { + static utf8_conv_result_t convert(UTF8 const** src_start + , UTF8 const* src_end + , std::wstring& wide) + { + TORRENT_UNUSED(src_start); + TORRENT_UNUSED(src_end); + TORRENT_UNUSED(wide); + return source_illegal; + } + }; + + // ==== utf-8 -> utf-32 === + template<> + struct convert_to_wide<4> + { + static utf8_conv_result_t convert(char const** src_start + , char const* src_end + , std::wstring& wide) + { + wchar_t* dst_start = &wide[0]; + int ret = ConvertUTF8toUTF32( + reinterpret_cast(src_start) + , reinterpret_cast(src_end) + , reinterpret_cast(&dst_start) + , reinterpret_cast(dst_start + wide.size()) + , lenientConversion); + if (ret == sourceIllegal) + { + // assume Latin-1 + wide.clear(); + std::copy(reinterpret_cast(*src_start) + ,reinterpret_cast(src_end) + , std::back_inserter(wide)); + return static_cast(ret); + } + wide.resize(dst_start - wide.c_str()); + return static_cast(ret); + } + }; + + // ==== utf-8 -> utf-16 === + template<> + struct convert_to_wide<2> + { + static utf8_conv_result_t convert(char const** src_start + , char const* src_end + , std::wstring& wide) + { + wchar_t* dst_start = &wide[0]; + int ret = ConvertUTF8toUTF16( + reinterpret_cast(src_start) + , reinterpret_cast(src_end) + , reinterpret_cast(&dst_start) + , reinterpret_cast(dst_start + wide.size()) + , lenientConversion); + if (ret == sourceIllegal) + { + // assume Latin-1 + wide.clear(); + std::copy(reinterpret_cast(*src_start) + , reinterpret_cast(src_end) + , std::back_inserter(wide)); + return static_cast(ret); + } + wide.resize(dst_start - wide.c_str()); + return static_cast(ret); + } + }; + + // ==== wide -> utf-8 === + template + struct convert_from_wide + { + static utf8_conv_result_t convert(wchar_t const** src_start + , wchar_t const* src_end + , std::string& utf8) + { + TORRENT_UNUSED(src_start); + TORRENT_UNUSED(src_end); + TORRENT_UNUSED(utf8); + return source_illegal; + } + }; + + // ==== utf-32 -> utf-8 === + template<> + struct convert_from_wide<4> + { + static utf8_conv_result_t convert(wchar_t const** src_start + , wchar_t const* src_end + , std::string& utf8) + { + char* dst_start = &utf8[0]; + int ret = ConvertUTF32toUTF8( + reinterpret_cast(src_start) + , reinterpret_cast(src_end) + , reinterpret_cast(&dst_start) + , reinterpret_cast(dst_start + utf8.size()) + , lenientConversion); + utf8.resize(dst_start - &utf8[0]); + return static_cast(ret); + } + }; + + // ==== utf-16 -> utf-8 === + template<> + struct convert_from_wide<2> + { + static utf8_conv_result_t convert(wchar_t const** src_start + , wchar_t const* src_end + , std::string& utf8) + { + char* dst_start = &utf8[0]; + int ret = ConvertUTF16toUTF8( + reinterpret_cast(src_start) + , reinterpret_cast(src_end) + , reinterpret_cast(&dst_start) + , reinterpret_cast(dst_start + utf8.size()) + , lenientConversion); + utf8.resize(dst_start - &utf8[0]); + return static_cast(ret); + } + }; + } // anonymous namespace + + utf8_conv_result_t utf8_wchar(std::string const& utf8, std::wstring &wide) + { + // allocate space for worst-case + wide.resize(utf8.size()); + char const* src_start = utf8.c_str(); + return convert_to_wide::convert( + &src_start, src_start + utf8.size(), wide); + } + + utf8_conv_result_t wchar_utf8(std::wstring const& wide, std::string &utf8) + { + // allocate space for worst-case + utf8.resize(wide.size() * 6); + if (wide.empty()) return conversion_ok; + wchar_t const* src_start = wide.c_str(); + return convert_from_wide::convert( + &src_start, src_start + wide.size(), utf8); + } +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#endif + diff --git a/src/utp_socket_manager.cpp b/src/utp_socket_manager.cpp new file mode 100644 index 0000000..c7b1044 --- /dev/null +++ b/src/utp_socket_manager.cpp @@ -0,0 +1,450 @@ +/* + +Copyright (c) 2009-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/utp_stream.hpp" +#include "libtorrent/udp_socket.hpp" +#include "libtorrent/utp_socket_manager.hpp" +#include "libtorrent/instantiate_connection.hpp" +#include "libtorrent/socket_io.hpp" +#include "libtorrent/socket.hpp" // for TORRENT_HAS_DONT_FRAGMENT +#include "libtorrent/broadcast_socket.hpp" // for is_teredo +#include "libtorrent/random.hpp" +#include "libtorrent/performance_counters.hpp" +#include "libtorrent/aux_/time.hpp" // for aux::time_now() + +// #define TORRENT_DEBUG_MTU 1135 + +namespace libtorrent +{ + + utp_socket_manager::utp_socket_manager(aux::session_settings const& sett + , udp_socket& s + , counters& cnt + , void* ssl_context + , incoming_utp_callback_t cb) + : m_sock(s) + , m_cb(cb) + , m_last_socket(0) + , m_new_connection(-1) + , m_sett(sett) + , m_last_route_update(min_time()) + , m_last_if_update(min_time()) + , m_sock_buf_size(0) + , m_counters(cnt) + , m_mtu_idx(0) + , m_ssl_context(ssl_context) + { + m_restrict_mtu.fill(65536); + } + + utp_socket_manager::~utp_socket_manager() + { + for (socket_map_t::iterator i = m_utp_sockets.begin() + , end(m_utp_sockets.end()); i != end; ++i) + { + delete_utp_impl(i->second); + } + } + + void utp_socket_manager::tick(time_point now) + { + for (socket_map_t::iterator i = m_utp_sockets.begin() + , end(m_utp_sockets.end()); i != end;) + { + if (should_delete(i->second)) + { + delete_utp_impl(i->second); + if (m_last_socket == i->second) m_last_socket = 0; + m_utp_sockets.erase(i++); + continue; + } + tick_utp_impl(i->second, now); + ++i; + } + } + + void utp_socket_manager::mtu_for_dest(address const& addr, int& link_mtu, int& utp_mtu) + { + + int mtu = 0; + if (is_teredo(addr)) mtu = TORRENT_TEREDO_MTU; + else mtu = TORRENT_ETHERNET_MTU; + +#if defined __APPLE__ + // apple has a very strange loopback. It appears you can't + // send messages of the reported MTU size, and you don't get + // EWOULDBLOCK either. + if (is_loopback(addr)) + { + if (is_teredo(addr)) mtu = TORRENT_TEREDO_MTU; + else mtu = TORRENT_ETHERNET_MTU; + } +#endif + + // clamp the MTU within reasonable bounds + if (mtu < TORRENT_INET_MIN_MTU) mtu = TORRENT_INET_MIN_MTU; + else if (mtu > TORRENT_INET_MAX_MTU) mtu = TORRENT_INET_MAX_MTU; + + link_mtu = mtu; + + mtu -= TORRENT_UDP_HEADER; + + if (m_sock.get_proxy_settings().type == settings_pack::socks5 + || m_sock.get_proxy_settings().type == settings_pack::socks5_pw) + { + // this is for the IP layer + address proxy_addr = m_sock.proxy_addr().address(); + if (proxy_addr.is_v4()) mtu -= TORRENT_IPV4_HEADER; + else mtu -= TORRENT_IPV6_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; + } + + utp_mtu = (std::min)(mtu, restrict_mtu()); + } + + void utp_socket_manager::send_packet(udp::endpoint const& ep, char const* p + , int len, error_code& ec, int flags) + { +#if !defined TORRENT_HAS_DONT_FRAGMENT && !defined TORRENT_DEBUG_MTU + TORRENT_UNUSED(flags); +#endif + if (!m_sock.is_open()) + { + ec = boost::asio::error::operation_aborted; + return; + } + +#ifdef TORRENT_DEBUG_MTU + // drop packets that exceed the debug MTU + if ((flags & dont_fragment) && len > TORRENT_DEBUG_MTU) return; +#endif + +#ifdef TORRENT_HAS_DONT_FRAGMENT + error_code tmp; + if (flags & utp_socket_manager::dont_fragment) + { + m_sock.set_option(libtorrent::dont_fragment(true), tmp); + TORRENT_ASSERT_VAL(!tmp, tmp.message()); + } +#endif + m_sock.send(ep, p, len, ec, udp_socket::peer_connection); +#ifdef TORRENT_HAS_DONT_FRAGMENT + if (flags & utp_socket_manager::dont_fragment) + { + m_sock.set_option(libtorrent::dont_fragment(false), tmp); + TORRENT_ASSERT_VAL(!tmp, tmp.message()); + } +#endif + } + + int utp_socket_manager::local_port(error_code& ec) const + { + return m_sock.local_endpoint(ec).port(); + } + + tcp::endpoint utp_socket_manager::local_endpoint(address const& remote, error_code& ec) const + { + tcp::endpoint socket_ep = m_sock.local_endpoint(ec); + + // first enumerate the routes in the routing table + if (aux::time_now() - seconds(60) > m_last_route_update) + { + m_last_route_update = aux::time_now(); + error_code err; + m_routes = enum_routes(m_sock.get_io_service(), err); + if (err) return socket_ep; + } + + if (m_routes.empty()) return socket_ep; + // then find the best match + ip_route* best = &m_routes[0]; + for (std::vector::iterator i = m_routes.begin() + , end(m_routes.end()); i != end; ++i) + { + if (is_any(i->destination) && i->destination.is_v4() == remote.is_v4()) + { + best = &*i; + break; + } + + if (match_addr_mask(remote, i->destination, i->netmask)) + { + best = &*i; + break; + } + } + + // best now tells us which interface we would send over + // for this target. Now figure out what the local address + // is for that interface + + if (aux::time_now() - seconds(60) > m_last_if_update) + { + m_last_if_update = aux::time_now(); + error_code err; + m_interfaces = enum_net_interfaces(m_sock.get_io_service(), err); + if (err) return socket_ep; + } + + for (std::vector::iterator i = m_interfaces.begin() + , end(m_interfaces.end()); i != end; ++i) + { + if (i->interface_address.is_v4() != remote.is_v4()) + continue; + + if (strcmp(best->name, i->name) == 0) + return tcp::endpoint(i->interface_address, socket_ep.port()); + } + return socket_ep; + } + + bool utp_socket_manager::incoming_packet(error_code const& ec, udp::endpoint const& ep + , char const* p, int size) + { + // TODO: 2 we may want to take ec into account here. possibly close + // connections quicker + TORRENT_UNUSED(ec); +// UTP_LOGV("incoming packet size:%d\n", size); + + if (size < int(sizeof(utp_header))) return false; + + utp_header const* ph = reinterpret_cast(p); + +// 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. + boost::uint16_t id = ph->connection_id; + + // first test to see if it's the same socket as last time + // in most cases it is + if (m_last_socket + && utp_match(m_last_socket, ep, id)) + { + return utp_incoming_packet(m_last_socket, p, size, ep, receive_time); + } + + std::pair r = + m_utp_sockets.equal_range(id); + + for (; r.first != r.second; ++r.first) + { + if (!utp_match(r.first->second, ep, id)) continue; + bool ret = utp_incoming_packet(r.first->second, p, size, ep, receive_time); + if (ret) m_last_socket = r.first->second; + return ret; + } + +// UTP_LOGV("incoming packet id:%d source:%s\n", id, print_endpoint(ep).c_str()); + + if (!m_sett.get_bool(settings_pack::enable_incoming_utp)) + return false; + + // if not found, see if it's a SYN packet, if it is, + // create a new utp_stream + if (ph->get_type() == ST_SYN) + { + // possible SYN flood. Just ignore + if (int(m_utp_sockets.size()) > m_sett.get_int(settings_pack::connections_limit) * 2) + return false; + +// UTP_LOGV("not found, new connection id:%d\n", m_new_connection); + + boost::shared_ptr c(new (std::nothrow) socket_type(m_sock.get_io_service())); + if (!c) return false; + + TORRENT_ASSERT(m_new_connection == -1); + // create the new socket with this ID + m_new_connection = id; + + instantiate_connection(m_sock.get_io_service(), aux::proxy_settings(), *c + , m_ssl_context, this, true, false); + + + utp_stream* str = NULL; +#ifdef TORRENT_USE_OPENSSL + if (is_ssl(*c)) + str = &c->get >()->next_layer(); + else +#endif + str = c->get(); + + TORRENT_ASSERT(str); + int link_mtu, utp_mtu; + mtu_for_dest(ep.address(), link_mtu, utp_mtu); + utp_init_mtu(str->get_impl(), link_mtu, utp_mtu); + bool ret = utp_incoming_packet(str->get_impl(), p, size, ep, receive_time); + if (!ret) return false; + m_cb(c); + // the connection most likely changed its connection ID here + // we need to move it to the correct ID + return true; + } + + if (ph->get_type() == ST_RESET) return false; + + // #error send reset + + return false; + } + + void utp_socket_manager::subscribe_writable(utp_socket_impl* s) + { + TORRENT_ASSERT(std::find(m_stalled_sockets.begin(), m_stalled_sockets.end() + , s) == m_stalled_sockets.end()); + m_stalled_sockets.push_back(s); + } + + void utp_socket_manager::writable() + { + std::vector stalled_sockets; + m_stalled_sockets.swap(stalled_sockets); + for (std::vector::iterator i = stalled_sockets.begin() + , end(stalled_sockets.end()); i != end; ++i) + { + utp_socket_impl* s = *i; + utp_writable(s); + } + } + + void utp_socket_manager::socket_drained() + { + // flush all deferred acks + + std::vector deferred_acks; + m_deferred_acks.swap(deferred_acks); + for (std::vector::iterator i = deferred_acks.begin() + , end(deferred_acks.end()); i != end; ++i) + { + utp_socket_impl* s = *i; + utp_send_ack(s); + } + + std::vector drained_event; + m_drained_event.swap(drained_event); + for (std::vector::iterator i = drained_event.begin() + , end(drained_event.end()); i != end; ++i) + { + utp_socket_impl* s = *i; + utp_socket_drained(s); + } + } + + void utp_socket_manager::defer_ack(utp_socket_impl* s) + { + TORRENT_ASSERT(std::find(m_deferred_acks.begin(), m_deferred_acks.end(), s) + == m_deferred_acks.end()); + m_deferred_acks.push_back(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_socket(boost::uint16_t id) + { + socket_map_t::iterator i = m_utp_sockets.find(id); + if (i == m_utp_sockets.end()) return; + delete_utp_impl(i->second); + if (m_last_socket == i->second) m_last_socket = 0; + m_utp_sockets.erase(i); + } + + void utp_socket_manager::set_sock_buf(int size) + { + if (size < m_sock_buf_size) return; + m_sock.set_buf_size(size); + error_code ec; + // add more socket buffer storage on the lower level socket + // to avoid dropping packets because of a full receive buffer + // while processing a packet + + // only update the buffer size if it's bigger than + // what we already have + udp::socket::receive_buffer_size recv_buf_size_opt; + m_sock.get_option(recv_buf_size_opt, ec); + if (recv_buf_size_opt.value() < size * 10) + { + m_sock.set_option(udp::socket::receive_buffer_size(size * 10), ec); + m_sock.set_option(udp::socket::send_buffer_size(size * 3), ec); + } + m_sock_buf_size = size; + } + + 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) + { + boost::uint16_t send_id = 0; + boost::uint16_t recv_id = 0; + if (m_new_connection != -1) + { + send_id = m_new_connection; + recv_id = m_new_connection + 1; + m_new_connection = -1; + } + else + { + send_id = random() & 0xffff; + recv_id = send_id - 1; + } + utp_socket_impl* impl = construct_utp_impl(recv_id, send_id, str, this); + m_utp_sockets.insert(std::make_pair(recv_id, impl)); + return impl; + } +} + diff --git a/src/utp_stream.cpp b/src/utp_stream.cpp new file mode 100644 index 0000000..bf6d791 --- /dev/null +++ b/src/utp_stream.cpp @@ -0,0 +1,3776 @@ +/* + +Copyright (c) 2009-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/utp_stream.hpp" +#include "libtorrent/sliding_average.hpp" +#include "libtorrent/utp_socket_manager.hpp" +#include "libtorrent/alloca.hpp" +#include "libtorrent/timestamp_history.hpp" +#include "libtorrent/error.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/performance_counters.hpp" +#include "libtorrent/io_service.hpp" +#include +#include + +// the behavior of the sequence numbers as implemented by uTorrent is not +// particularly regular. This switch indicates the odd parts. +#define TORRENT_UT_SEQ 1 + +#if TORRENT_UTP_LOG +#include +#include "libtorrent/socket_io.hpp" +#endif + +namespace libtorrent { + +#if TORRENT_UTP_LOG + +char const* packet_type_names[] = { "ST_DATA", "ST_FIN", "ST_STATE", "ST_RESET", "ST_SYN" }; +char const* socket_state_names[] = { "NONE", "SYN_SENT", "CONNECTED", "FIN_SENT", "ERROR", "DELETE" }; + +static struct utp_logger +{ + FILE* utp_log_file; + mutex utp_log_mutex; + + utp_logger() : utp_log_file(NULL) {} + ~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 == NULL) return; + + mutex::scoped_lock lock(log_file_holder.utp_log_mutex); + static time_point start = clock_type::now(); + 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 != NULL; +} + +void set_utp_stream_logging(bool enable) { + if (enable) + { + if (log_file_holder.utp_log_file == NULL) + { + log_file_holder.utp_log_file = fopen("utp.log", "w+"); + } + } + else + { + if (log_file_holder.utp_log_file != NULL) + { + FILE* f = log_file_holder.utp_log_file; + log_file_holder.utp_log_file = NULL; + 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 + +#if __cplusplus >= 201103L || defined __clang__ + +#define UTP_LOG(...) do {} while(false) +#define UTP_LOGV(...) do {} while(false) + +#else + +#define UTP_LOG TORRENT_WHILE_0 printf +#define UTP_LOGV TORRENT_WHILE_0 printf + +#endif // cplusplus + +#endif + +enum +{ + ACK_MASK = 0xffff, + + // the number of packets that'll fit in the reorder buffer + max_packets_reorder = 512, + + // if a packet receives more than this number of + // duplicate acks, we'll trigger a fast re-send + dup_ack_limit = 3, + + // the max number of packets to fast-resend per + // selective ack message + // only re-sending a single packet per sack + // appears to improve performance by making it + // less likely to loose the re-sent packet. Because + // when that happens, we must time-out in order + // to continue, which takes a long time. + sack_resend_limit = 1 +}; + +// 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 +TORRENT_EXTRA_EXPORT bool compare_less_wrap(boost::uint32_t lhs + , boost::uint32_t rhs, boost::uint32_t mask) +{ + // distance walking from lhs to rhs, downwards + boost::uint32_t dist_down = (lhs - rhs) & mask; + // distance walking from lhs to rhs, upwards + boost::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; +} + +// 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' + boost::uint16_t allocated; + + // the size of the buffer 'buf' points to + boost::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 + boost::uint16_t header_size; + + // the number of times this packet has been sent + boost::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; + +#ifdef TORRENT_DEBUG + int num_fast_resend; +#endif + + // the actual packet buffer + boost::uint8_t buf[1]; +}; + +// since the uTP socket state may be needed after the +// utp_stream is closed, it's kept in a separate struct +// whose lifetime is not tied to the lifetime of utp_stream + +// the utp socket is closely modelled after the asio async +// operations and handler model. For writing to the socket, +// the client provides a list of buffers (for gather/writev +// style of I/O) and whenever the socket can write another +// packet to the stream, it picks up data from these buffers. +// When all of the data has been written, or enough time has +// passed since we first started writing, the write handler +// is called and the write buffer is reset. This means that +// we're not writing anything at all while waiting for the +// client to re-issue a write request. + +// reading is a little bit more complicated, since we must +// be able to receive data even when the user doesn't have +// an outstanding read operation on the socket. When the user +// does however, we want to receive data directly into the +// user's buffer instead of first copying it into our receive +// buffer. This is why the receive case is more complicated. +// There are two receive buffers. One provided by the user, +// which when present is always used. The other one is used +// when the user doesn't have an outstanding read request, +// and hence hasn't provided any buffer space to receive into. + +// the user provided read buffer is called "m_read_buffer" and +// its size is "m_read_buffer_size". The buffer we spill over +// into when the user provided buffer is full or when there +// is none, is "m_receive_buffer" and "m_receive_buffer_size" +// respectively. + +// in order to know when to trigger the read and write handlers +// there are two counters, m_read and m_written, which count +// the number of bytes we've stuffed into the user provided +// read buffer or written to the stream from the write buffer. +// These are used to trigger the handlers if we're written a +// large number of bytes. It's also triggered if we're filled +// the whole read buffer, or written the entire write buffer. +// The last way the handlers can be triggered is if we're read +// or written some, and enough time has elapsed since then. + +// when we receive data into m_receive_buffer (i.e. the buffer +// used when there's no user provided one) is stored as a +// number of heap allocated packets. This is just because it's +// simple to reuse the data structured and it provides all the +// functionality needed for this buffer. + +struct utp_socket_impl +{ + utp_socket_impl(boost::uint16_t recv_id, boost::uint16_t send_id + , void* userdata, utp_socket_manager* sm) + : m_sm(sm) + , m_userdata(userdata) + , m_nagle_packet(NULL) + , m_read_handler(false) + , m_write_handler(false) + , m_connect_handler(false) + , m_remote_address() + , m_timeout(clock_type::now() + milliseconds(m_sm->connect_timeout())) + , m_last_history_step(clock_type::now()) + , m_cwnd(TORRENT_ETHERNET_MTU << 16) + , m_ssthres(0) + , m_buffered_incoming_bytes(0) + , m_reply_micro(0) + , m_adv_wnd(TORRENT_ETHERNET_MTU) + , m_bytes_in_flight(0) + , m_read(0) + , m_write_buffer_size(0) + , m_written(0) + , m_receive_buffer_size(0) + , m_read_buffer_size(0) + , m_in_buf_size(1024 * 1024) + , m_in_packets(0) + , m_out_packets(0) + , m_send_delay(0) + , m_recv_delay(0) + , m_close_reason(0) + , m_port(0) + , m_send_id(send_id) + , m_recv_id(recv_id) + , m_ack_nr(0) + , m_seq_nr(0) + , m_acked_seq_nr(0) + , m_fast_resend_seq_nr(0) + , m_eof_seq_nr(0) + , m_loss_seq_nr(0) + , m_mtu(TORRENT_ETHERNET_MTU - TORRENT_IPV4_HEADER - TORRENT_UDP_HEADER - 8 - 24 - 36) + , m_mtu_floor(TORRENT_INET_MIN_MTU - TORRENT_IPV4_HEADER - TORRENT_UDP_HEADER) + , m_mtu_ceiling(TORRENT_ETHERNET_MTU - TORRENT_IPV4_HEADER - TORRENT_UDP_HEADER) + , m_mtu_seq(0) + , m_duplicate_acks(0) + , m_num_timeouts(0) + , m_delay_sample_idx(0) + , m_state(UTP_STATE_NONE) + , m_eof(false) + , m_attached(true) + , m_nagle(true) + , m_slow_start(true) + , m_cwnd_full(false) + , m_null_buffers(false) + , m_deferred_ack(false) + , m_subscribe_drained(false) + , m_stalled(false) + , m_confirmed(false) + { + m_sm->inc_stats_counter(counters::num_utp_idle); + TORRENT_ASSERT(m_userdata); + for (int i = 0; i != num_delay_hist; ++i) + m_delay_sample_hist[i] = (std::numeric_limits::max)(); + } + + ~utp_socket_impl(); + + void tick(time_point now); + void init_mtu(int link_mtu, int utp_mtu); + bool incoming_packet(boost::uint8_t const* buf, int size + , udp::endpoint const& ep, time_point receive_time); + void writable(); + + bool should_delete() const; + tcp::endpoint remote_endpoint(error_code& ec) const + { + if (m_state == UTP_STATE_NONE) + ec = boost::asio::error::not_connected; + else + TORRENT_ASSERT(m_remote_address != address_v4::any()); + return tcp::endpoint(m_remote_address, m_port); + } + std::size_t available() const; + // returns true if there were handlers cancelled + // if it returns false, we can detach immediately + bool destroy(); + void set_close_reason(boost::uint16_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); + void parse_sack(boost::uint16_t packet_ack, boost::uint8_t const* ptr + , int size, int* acked_bytes, time_point const now, boost::uint32_t& min_rtt); + void parse_close_reason(boost::uint8_t const* ptr, int size); + void write_payload(boost::uint8_t* ptr, int size); + void maybe_inc_acked_seq_nr(); + void ack_packet(packet* p, time_point const& receive_time + , boost::uint32_t& min_rtt, boost::uint16_t seq_nr); + void write_sack(boost::uint8_t* buf, int size) const; + void incoming(boost::uint8_t const* buf, int size, packet* 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 kill); + bool consume_incoming_data( + utp_header const* ph, boost::uint8_t const* ptr, int payload_size, time_point now); + void update_mtu_limits(); + void experienced_loss(int seq_nr); + + void set_state(int s); + +private: + + // non-copyable + utp_socket_impl(utp_socket_impl const&); + utp_socket_impl const& operator=(utp_socket_impl const&); + + // TODO: 2 it would be nice if not everything would have to be public here +public: + + void check_receive_buffers() const; + +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const; +#endif + + utp_socket_manager* m_sm; + + // userdata pointer passed along + // with any callback. This is initialized to 0 + // then set to point to the utp_stream when + // hooked up, and then reset to 0 once the utp_stream + // detaches. This is used to know whether or not + // the socket impl is still attached to a utp_stream + // object. When it isn't, we'll never be able to + // signal anything back to the client, and in case + // of errors, we just have to delete ourselves + // i.e. transition to the UTP_STATE_DELETED state + void* m_userdata; + + // This is a platform-independent replacement + // for the regular iovec type in posix. Since + // it's not used in any system call, we might as + // well define our own type instead of wrapping + // the system's type. + struct iovec_t + { + iovec_t(void* b, size_t l): buf(b), len(l) {} + void* buf; + size_t len; + }; + + // if there's currently an async read or write + // operation in progress, these buffers are initialized + // and used, otherwise any bytes received are stuck in + // m_receive_buffer until another read is made + // as we flush from the write buffer, individual iovecs + // are updated to only refer to unflushed portions of the + // buffers. Buffers that empty are erased from the vector. + std::vector m_write_buffer; + + // if this is non NULL, 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* m_nagle_packet; + + // the user provided read buffer. If this has a size greater + // than 0, we'll always prefer using it over putting received + // data in the m_receive_buffer. As data is stored in the + // read buffer, the iovec_t elements are adjusted to only + // refer to the unwritten portions of the buffers, and the + // ones that fill up are erased from the vector + std::vector m_read_buffer; + + // packets we've received without a read operation + // active. Store them here until the client triggers + // an async_read_some + std::vector m_receive_buffer; + + // this is the error on this socket. If m_state is + // set to UTP_STATE_ERROR_WAIT, this error should be + // forwarded to the client as soon as we have a new + // async operation initiated + error_code m_error; + + // these indicate whether or not there is an outstanding read/write or + // connect operation. i.e. is there upper layer subscribed to these events. + bool m_read_handler; + bool m_write_handler; + bool m_connect_handler; + + // the address of the remote endpoint + address m_remote_address; + + // the local address + address m_local_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; + + // 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. + boost::int64_t m_cwnd; + + 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 + boost::int32_t m_ssthres; + + // the number of bytes we have buffered in m_inbuf + boost::int32_t m_buffered_incoming_bytes; + + // the timestamp diff in the last packet received + // this is what we'll send back + boost::uint32_t m_reply_micro; + + // 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 + boost::uint32_t m_adv_wnd; + + // the number of un-acked bytes we have sent + boost::int32_t m_bytes_in_flight; + + // the number of bytes read into the user provided + // buffer. If this grows too big, we'll trigger the + // read handler. + boost::int32_t m_read; + + // the sum of the lengths of all iovec in m_write_buffer + boost::int32_t m_write_buffer_size; + + // the number of bytes already written to packets + // from m_write_buffer + boost::int32_t m_written; + + // the sum of all packets stored in m_receive_buffer + boost::int32_t m_receive_buffer_size; + + // the sum of all buffers in m_read_buffer + boost::int32_t m_read_buffer_size; + + // max number of bytes to allocate for receive buffer + boost::int32_t m_in_buf_size; + + // 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. + enum { num_delay_hist = 3 }; + boost::uint32_t m_delay_sample_hist[num_delay_hist]; + + // counters + boost::uint32_t m_in_packets; + boost::uint32_t m_out_packets; + + // the last send delay sample + boost::int32_t m_send_delay; + // the last receive delay sample + boost::int32_t m_recv_delay; + + // average RTT + sliding_average<16> 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 + boost::uint16_t m_close_reason; + + // port of destination endpoint + boost::uint16_t m_port; + + boost::uint16_t m_send_id; + boost::uint16_t m_recv_id; + + // this is the ack we're sending back. We have + // received all packets up to this sequence number + boost::uint16_t m_ack_nr; + + // the sequence number of the next packet + // we'll send + boost::uint16_t m_seq_nr; + + // 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. + boost::uint16_t m_acked_seq_nr; + + // 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 + boost::uint16_t m_fast_resend_seq_nr; + + // 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 + boost::uint16_t m_eof_seq_nr; + + // this is the lowest sequence number that, when lost, + // will cause the window size to be cut in half + boost::uint16_t m_loss_seq_nr; + + // the max number of bytes we can send in a packet + // including the header + boost::uint16_t m_mtu; + + // the floor is the largest packet that we have + // been able to get through without fragmentation + boost::uint16_t m_mtu_floor; + + // 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 + boost::uint16_t m_mtu_ceiling; + + // the sequence number of the probe in-flight + // this is 0 if there is no probe in flight + boost::uint16_t m_mtu_seq; + + // 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. + boost::uint8_t m_duplicate_acks; + + // the number of packet timeouts we've seen in a row + // this affects the packet timeout time + boost::uint8_t m_num_timeouts; + + // it's important that these match the enums in performance_counters for + // num_utp_idle etc. + enum state_t { + // not yet connected + UTP_STATE_NONE, + // sent a syn packet, not received any acks + UTP_STATE_SYN_SENT, + // syn-ack received and in normal operation + // of sending and receiving data + UTP_STATE_CONNECTED, + // fin sent, but all packets up to the fin packet + // have not yet been acked. We might still be waiting + // for a FIN from the other end + UTP_STATE_FIN_SENT, + + // ====== states beyond this point ===== + // === are considered closing states === + // === and will cause the socket to ==== + // ============ be deleted ============= + + // the socket has been gracefully disconnected + // and is waiting for the client to make a + // socket call so that we can communicate this + // fact and actually delete all the state, or + // there is an error on this socket and we're + // waiting to communicate this to the client in + // a callback. The error in either case is stored + // in m_error. If the socket has gracefully shut + // down, the error is error::eof. + UTP_STATE_ERROR_WAIT, + + // there are no more references to this socket + // and we can delete it + UTP_STATE_DELETE + }; + + // this is the cursor into m_delay_sample_hist + boost::uint8_t m_delay_sample_idx:2; + + // the state the socket is in + boost::uint8_t m_state:3; + + // this is set to true when we receive a fin + bool m_eof:1; + + // is this socket state attached to a user space socket? + bool m_attached:1; + + // this is true if nagle is enabled (which it is by default) + bool m_nagle:1; + + // this is true while the socket is in slow start mode. It's + // only in slow-start during the start-up phase. Slow start + // (contrary to what its name suggest) means that we're growing + // the congestion window (cwnd) exponentially rather than linearly. + // this is done at startup of a socket in order to find its + // link capacity faster. This behaves similar to TCP slow start + bool m_slow_start:1; + + // this is true as long as we have as many packets in + // flight as allowed by the congestion window (cwnd) + bool m_cwnd_full:1; + + // this is set to one if the current read operation + // has a null-buffer. i.e. we're not reading into a user-provided + // buffer, we're just signalling when there's something + // to read from our internal receive buffer + bool m_null_buffers:1; + + // this is set to true when this socket has added itself to + // the utp socket manager's list of deferred acks. Once the + // burst of incoming UDP packets is all drained, the utp socket + // manager will send acks for all sockets on this list. + bool m_deferred_ack:1; + + // this is true if this socket has subscribed to be notified + // when this receive round is done + bool m_subscribe_drained:1; + + // if this socket tries to send a packet via the utp socket + // manager, and it fails with EWOULDBLOCK, the socket + // is stalled and this is set. It's also added to a list + // of sockets in the utp_socket_manager to be notified of + // the socket being writable again + bool m_stalled:1; + + // this is false by default and set to true once we've received a non-SYN + // packet for this connection with a correct ack_nr, confirming that the + // other end is not spoofing its source IP + bool m_confirmed:1; +}; + +utp_socket_impl* construct_utp_impl(boost::uint16_t recv_id + , boost::uint16_t send_id, void* userdata + , utp_socket_manager* sm) +{ + return new utp_socket_impl(recv_id, send_id, userdata, sm); +} + +void detach_utp_impl(utp_socket_impl* s) +{ + s->detach(); +} + +void delete_utp_impl(utp_socket_impl* s) +{ + delete s; +} + +bool should_delete(utp_socket_impl* s) +{ + return s->should_delete(); +} + +void tick_utp_impl(utp_socket_impl* s, time_point now) +{ + s->tick(now); +} + +void utp_init_mtu(utp_socket_impl* s, int link_mtu, int utp_mtu) +{ + s->init_mtu(link_mtu, utp_mtu); +} + +bool utp_incoming_packet(utp_socket_impl* s, char const* p + , int size, udp::endpoint const& ep, time_point receive_time) +{ + return s->incoming_packet(reinterpret_cast(p), size + , ep, receive_time); +} + +bool utp_match(utp_socket_impl* s, udp::endpoint const& ep, boost::uint16_t id) +{ + return s->m_remote_address == ep.address() + && s->m_port == ep.port() + && s->m_recv_id == id; +} + +udp::endpoint utp_remote_endpoint(utp_socket_impl* s) +{ + return udp::endpoint(s->m_remote_address, s->m_port); +} + +boost::uint16_t utp_receive_id(utp_socket_impl* s) +{ + return s->m_recv_id; +} + +void utp_writable(utp_socket_impl* s) +{ + TORRENT_ASSERT(s->m_stalled); + s->m_stalled = false; + s->writable(); +} + +void utp_send_ack(utp_socket_impl* s) +{ + TORRENT_ASSERT(s->m_deferred_ack); + s->m_deferred_ack = false; + s->send_pkt(utp_socket_impl::pkt_ack); +} + +void utp_socket_drained(utp_socket_impl* s) +{ + s->m_subscribe_drained = false; + + // at this point, we know we won't receive any + // more packets this round. So, we may want to + // call the receive callback function to + // let the user consume it + + s->maybe_trigger_receive_callback(); + s->maybe_trigger_send_callback(); +} + +void utp_socket_impl::update_mtu_limits() +{ + INVARIANT_CHECK; + + if (m_mtu_floor > m_mtu_ceiling) m_mtu_floor = m_mtu_ceiling; + + m_mtu = (m_mtu_floor + m_mtu_ceiling) / 2; + + if ((m_cwnd >> 16) < m_mtu) m_cwnd = boost::int64_t(m_mtu) << 16; + + UTP_LOGV("%8p: updating MTU to: %d [%d, %d]\n" + , static_cast(this), m_mtu, m_mtu_floor, m_mtu_ceiling); + + // clear the mtu probe sequence number since + // it was either dropped or acked + m_mtu_seq = 0; +} + +int utp_socket_state(utp_socket_impl const* s) +{ + return s->m_state; +} + +int utp_stream::send_delay() const +{ + return m_impl ? m_impl->m_send_delay : 0; +} + +int utp_stream::recv_delay() const +{ + return m_impl ? m_impl->m_recv_delay : 0; +} + +utp_stream::utp_stream(io_service& io_service) + : m_io_service(io_service) + , m_impl(0) + , m_incoming_close_reason(0) + , m_open(false) +{ +} + +utp_socket_impl* utp_stream::get_impl() +{ + return m_impl; +} + +void utp_stream::set_close_reason(boost::uint16_t code) +{ + if (!m_impl) return; + m_impl->set_close_reason(code); +} + +boost::uint16_t utp_stream::get_close_reason() +{ + return m_incoming_close_reason; +} + +void utp_stream::close() +{ + if (!m_impl) return; + if (!m_impl->destroy()) + { + if (!m_impl) return; + detach_utp_impl(m_impl); + m_impl = 0; + } +} + +std::size_t utp_stream::available() const +{ + return m_impl ? m_impl->available() : 0; +} + +utp_stream::endpoint_type utp_stream::remote_endpoint(error_code& ec) const +{ + if (!m_impl) + { + ec = boost::asio::error::not_connected; + return endpoint_type(); + } + return m_impl->remote_endpoint(ec); +} + +utp_stream::endpoint_type utp_stream::local_endpoint(error_code& ec) const +{ + if (m_impl == 0 || m_impl->m_sm == 0) + { + ec = boost::asio::error::not_connected; + return endpoint_type(); + } + return tcp::endpoint(m_impl->m_local_address, m_impl->m_sm->local_port(ec)); +} + +utp_stream::~utp_stream() +{ + if (m_impl) + { + UTP_LOGV("%8p: utp_stream destructed\n", static_cast(m_impl)); + m_impl->destroy(); + detach_utp_impl(m_impl); + } + + m_impl = 0; +} + +void utp_stream::set_impl(utp_socket_impl* impl) +{ + TORRENT_ASSERT(m_impl == 0); + TORRENT_ASSERT(!m_open); + m_impl = impl; + m_open = true; +} + +int utp_stream::read_buffer_size() const +{ + TORRENT_ASSERT(m_impl); + return m_impl->m_receive_buffer_size; +} + +void utp_stream::on_close_reason(void* self, boost::uint16_t close_reason) +{ + utp_stream* s = static_cast(self); + + // it's possible the socket has been unlinked already, in which case m_impl + // will be NULL + if (s->m_impl) + s->m_incoming_close_reason = close_reason; +} + +void utp_stream::on_read(void* self, size_t bytes_transferred + , error_code const& ec, bool kill) +{ + utp_stream* s = static_cast(self); + + UTP_LOGV("%8p: calling read handler read:%d ec:%s kill:%d\n", static_cast(s->m_impl) + , int(bytes_transferred), ec.message().c_str(), kill); + + TORRENT_ASSERT(s->m_read_handler); + TORRENT_ASSERT(bytes_transferred > 0 || ec || s->m_impl->m_null_buffers); + s->m_io_service.post(boost::bind(s->m_read_handler, ec, bytes_transferred)); + s->m_read_handler.clear(); +// boost::function2 tmp; +// tmp.swap(s->m_read_handler); + if (kill && s->m_impl) + { + TORRENT_ASSERT(ec); + detach_utp_impl(s->m_impl); + s->m_impl = 0; + } +// tmp(ec, bytes_transferred); +} + +void utp_stream::on_write(void* self, size_t bytes_transferred + , error_code const& ec, bool kill) +{ + utp_stream* s = static_cast(self); + + UTP_LOGV("%8p: calling write handler written:%d ec:%s kill:%d\n" + , static_cast(s->m_impl) + , int(bytes_transferred), ec.message().c_str(), kill); + + TORRENT_ASSERT(s->m_write_handler); + TORRENT_ASSERT(bytes_transferred > 0 || ec); + s->m_io_service.post(boost::bind(s->m_write_handler, ec, bytes_transferred)); + s->m_write_handler.clear(); +// boost::function2 tmp; +// tmp.swap(s->m_read_handler); + if (kill && s->m_impl) + { + TORRENT_ASSERT(ec); + detach_utp_impl(s->m_impl); + s->m_impl = 0; + } +// tmp(ec, bytes_transferred); +} + +void utp_stream::on_connect(void* self, error_code const& ec, bool kill) +{ + utp_stream* s = static_cast(self); + TORRENT_ASSERT(s); + + UTP_LOGV("%8p: calling connect handler ec:%s kill:%d\n" + , static_cast(s->m_impl), ec.message().c_str(), kill); + + TORRENT_ASSERT(s->m_connect_handler); + s->m_io_service.post(boost::bind(s->m_connect_handler, ec)); + s->m_connect_handler.clear(); +// boost::function1 tmp; +// s->m_connect_handler.swap(tmp); + if (kill && s->m_impl) + { + TORRENT_ASSERT(ec); + detach_utp_impl(s->m_impl); + s->m_impl = 0; + } +// tmp(ec); +} + +void utp_stream::add_read_buffer(void* buf, size_t len) +{ + TORRENT_ASSERT(m_impl); + TORRENT_ASSERT(len < INT_MAX); + TORRENT_ASSERT(len > 0); + TORRENT_ASSERT(buf); + m_impl->m_read_buffer.push_back(utp_socket_impl::iovec_t(buf, len)); + m_impl->m_read_buffer_size += len; + + UTP_LOGV("%8p: add_read_buffer %d bytes\n", static_cast(m_impl), int(len)); +} + +// this is the wrapper to add a user provided write buffer to the +// utp_socket_impl. It makes sure the m_write_buffer_size is kept +// up to date +void utp_stream::add_write_buffer(void const* buf, size_t len) +{ + TORRENT_ASSERT(m_impl); + TORRENT_ASSERT(len < INT_MAX); + TORRENT_ASSERT(len > 0); + TORRENT_ASSERT(buf); + +#ifdef TORRENT_DEBUG + int write_buffer_size = 0; + for (std::vector::iterator i = m_impl->m_write_buffer.begin() + , end(m_impl->m_write_buffer.end()); i != end; ++i) + { + write_buffer_size += i->len; + } + TORRENT_ASSERT(m_impl->m_write_buffer_size == write_buffer_size); +#endif + + m_impl->m_write_buffer.push_back(utp_socket_impl::iovec_t(const_cast(buf), len)); + m_impl->m_write_buffer_size += len; + +#ifdef TORRENT_DEBUG + write_buffer_size = 0; + for (std::vector::iterator i = m_impl->m_write_buffer.begin() + , end(m_impl->m_write_buffer.end()); i != end; ++i) + { + write_buffer_size += i->len; + } + TORRENT_ASSERT(m_impl->m_write_buffer_size == write_buffer_size); +#endif + + UTP_LOGV("%8p: add_write_buffer %d bytes\n", static_cast(m_impl), int(len)); +} + +// this is called when all user provided read buffers have been added +// and it's time to execute the async operation. The first thing we +// do is to copy any data stored in m_receive_buffer into the user +// provided buffer. This might be enough to in turn trigger the read +// handler immediately. +void utp_stream::issue_read() +{ + TORRENT_ASSERT(m_impl->m_userdata); + TORRENT_ASSERT(!m_impl->m_read_handler); + + m_impl->m_null_buffers = m_impl->m_read_buffer_size == 0; + + m_impl->m_read_handler = true; + if (m_impl->test_socket_state()) return; + + UTP_LOGV("%8p: new read handler. %d bytes in buffer\n" + , static_cast(m_impl), m_impl->m_receive_buffer_size); + + // so, the client wants to read. If we already + // have some data in the read buffer, move it into the + // client's buffer right away + + m_impl->m_read += read_some(false); + m_impl->maybe_trigger_receive_callback(); +} + +size_t utp_stream::read_some(bool clear_buffers) +{ + if (m_impl->m_receive_buffer_size == 0) + { + if (clear_buffers) + { + m_impl->m_read_buffer_size = 0; + m_impl->m_read_buffer.clear(); + } + return 0; + } + + std::vector::iterator target = m_impl->m_read_buffer.begin(); + + size_t ret = 0; + + int pop_packets = 0; + for (std::vector::iterator i = m_impl->m_receive_buffer.begin() + , end(m_impl->m_receive_buffer.end()); i != end;) + { + if (target == m_impl->m_read_buffer.end()) + { + UTP_LOGV(" No more target buffers: %d bytes left in buffer\n" + , m_impl->m_receive_buffer_size); + TORRENT_ASSERT(m_impl->m_read_buffer.empty()); + break; + } + + m_impl->check_receive_buffers(); + + packet* p = *i; + int to_copy = (std::min)(p->size - p->header_size, int(target->len)); + TORRENT_ASSERT(to_copy >= 0); + memcpy(target->buf, p->buf + p->header_size, to_copy); + ret += to_copy; + target->buf = static_cast(target->buf) + to_copy; + TORRENT_ASSERT(int(target->len) >= to_copy); + target->len -= to_copy; + m_impl->m_receive_buffer_size -= to_copy; + TORRENT_ASSERT(m_impl->m_read_buffer_size >= to_copy); + m_impl->m_read_buffer_size -= to_copy; + p->header_size += to_copy; + if (target->len == 0) target = m_impl->m_read_buffer.erase(target); + + m_impl->check_receive_buffers(); + + TORRENT_ASSERT(m_impl->m_receive_buffer_size >= 0); + + // Consumed entire packet + if (p->header_size == p->size) + { + free(p); + ++pop_packets; + *i = 0; + ++i; + } + + if (m_impl->m_receive_buffer_size == 0) + { + UTP_LOGV(" Didn't fill entire target: %d bytes left in buffer\n" + , m_impl->m_receive_buffer_size); + break; + } + } + // remove the packets from the receive_buffer that we already copied over + // and freed + m_impl->m_receive_buffer.erase(m_impl->m_receive_buffer.begin() + , m_impl->m_receive_buffer.begin() + pop_packets); + // we exited either because we ran out of bytes to copy + // or because we ran out of space to copy the bytes to + TORRENT_ASSERT(m_impl->m_receive_buffer_size == 0 + || m_impl->m_read_buffer.empty()); + + UTP_LOGV("%8p: %d packets moved from buffer to user space (%d bytes)\n" + , static_cast(m_impl), pop_packets, int(ret)); + + if (clear_buffers) + { + m_impl->m_read_buffer_size = 0; + m_impl->m_read_buffer.clear(); + } + TORRENT_ASSERT(ret > 0 || m_impl->m_null_buffers); + return ret; +} + +// this is called when all user provided write buffers have been +// added. Start trying to send packets with the payload immediately. +void utp_stream::issue_write() +{ + UTP_LOGV("%8p: new write handler. %d bytes to write\n" + , static_cast(m_impl), m_impl->m_write_buffer_size); + + TORRENT_ASSERT(m_impl->m_write_buffer_size > 0); + TORRENT_ASSERT(m_impl->m_write_handler == false); + TORRENT_ASSERT(m_impl->m_userdata); + + m_impl->m_write_handler = true; + m_impl->m_written = 0; + if (m_impl->test_socket_state()) return; + + // try to write. send_pkt returns false if there's + // no more payload to send or if the congestion window + // is full and we can't send more packets right now + while (m_impl->send_pkt()); + + // if there was an error in send_pkt(), m_impl may be + // 0 at this point + if (m_impl) m_impl->maybe_trigger_send_callback(); +} + +void utp_stream::do_connect(tcp::endpoint const& ep) +{ + int link_mtu, utp_mtu; + m_impl->m_sm->mtu_for_dest(ep.address(), link_mtu, utp_mtu); + m_impl->init_mtu(link_mtu, utp_mtu); + TORRENT_ASSERT(m_impl->m_connect_handler == false); + m_impl->m_remote_address = ep.address(); + m_impl->m_port = ep.port(); + + m_impl->m_connect_handler = true; + + error_code ec; + m_impl->m_local_address = m_impl->m_sm->local_endpoint(m_impl->m_remote_address, ec).address(); + + if (m_impl->test_socket_state()) return; + m_impl->send_syn(); +} + +// =========== utp_socket_impl ============ + +utp_socket_impl::~utp_socket_impl() +{ + INVARIANT_CHECK; + + TORRENT_ASSERT(!m_attached); + TORRENT_ASSERT(!m_deferred_ack); + + m_sm->inc_stats_counter(counters::num_utp_idle + m_state, -1); + + UTP_LOGV("%8p: destroying utp socket state\n", static_cast(this)); + + // free any buffers we're holding + for (boost::uint16_t i = m_inbuf.cursor(), end((m_inbuf.cursor() + + m_inbuf.capacity()) & ACK_MASK); + i != end; i = (i + 1) & ACK_MASK) + { + packet* p = m_inbuf.remove(i); + free(p); + } + for (boost::uint16_t i = m_outbuf.cursor(), end((m_outbuf.cursor() + + m_outbuf.capacity()) & ACK_MASK); + i != end; i = (i + 1) & ACK_MASK) + { + packet* p = m_outbuf.remove(i); + free(p); + } + + for (std::vector::iterator i = m_receive_buffer.begin() + , end = m_receive_buffer.end(); i != end; ++i) + { + free(*i); + } + + free(m_nagle_packet); + m_nagle_packet = NULL; +} + +bool utp_socket_impl::should_delete() const +{ + INVARIANT_CHECK; + + // if the socket state is not attached anymore we're free + // to delete it from the client's point of view. The other + // endpoint however might still need to be told that we're + // closing the socket. Only delete the state if we're not + // attached and we're in a state where the other end doesn't + // expect the socket to still be alive + // when m_stalled is true, it means the socket manager has a + // pointer to this socket, waiting for the UDP socket to + // become writable again. We have to wait for that, so that + // the pointer is removed from that queue. Otherwise we would + // leave a dangling pointer in the socket manager + bool ret = (m_state >= UTP_STATE_ERROR_WAIT || m_state == UTP_STATE_NONE) + && !m_attached && !m_stalled; + + if (ret) + { + UTP_LOGV("%8p: should_delete() = true\n", static_cast(this)); + } + + return ret; +} + +void utp_socket_impl::maybe_trigger_receive_callback() +{ + INVARIANT_CHECK; + + if (m_read_handler == false) return; + + // nothing has been read or there's no outstanding read operation + if (m_null_buffers && m_receive_buffer_size == 0) return; + else if (!m_null_buffers && m_read == 0) return; + + UTP_LOGV("%8p: calling read handler read:%d\n", static_cast(this), m_read); + m_read_handler = false; + utp_stream::on_read(m_userdata, 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, m_written, m_error, false); + m_written = 0; + m_write_buffer_size = 0; + m_write_buffer.clear(); +} + +void utp_socket_impl::set_close_reason(boost::uint16_t code) +{ +#if TORRENT_UTP_LOG + UTP_LOGV("%8p: set_close_reason: %d\n" + , static_cast(this), int(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 == 0) return false; + + if (m_state == UTP_STATE_CONNECTED) + send_fin(); + + bool cancelled = cancel_handlers(boost::asio::error::operation_aborted, true); + + m_userdata = 0; + + m_read_buffer.clear(); + m_read_buffer_size = 0; + + m_write_buffer.clear(); + m_write_buffer_size = 0; + + if ((m_state == UTP_STATE_ERROR_WAIT + || m_state == UTP_STATE_NONE + || m_state == UTP_STATE_SYN_SENT) && cancelled) + { + set_state(UTP_STATE_DELETE); +#if TORRENT_UTP_LOG + UTP_LOGV("%8p: state:%s\n", static_cast(this), socket_state_names[m_state]); +#endif + } + + return cancelled; + + // #error our end is closing. Wait for everything to be acked +} + +void utp_socket_impl::detach() +{ + INVARIANT_CHECK; + + UTP_LOGV("%8p: detach()\n", static_cast(this)); + m_attached = false; +} + +void utp_socket_impl::send_syn() +{ + INVARIANT_CHECK; + + m_seq_nr = 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* p = static_cast(malloc(sizeof(packet) + sizeof(utp_header))); + p->size = sizeof(utp_header); + p->header_size = sizeof(utp_header); + p->num_transmissions = 0; + p->mtu_probe = false; +#ifdef TORRENT_DEBUG + p->num_fast_resend = 0; +#endif + p->need_resend = false; + utp_header* 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 now = clock_type::now(); + p->send_time = now; + h->timestamp_microseconds = boost::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(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) + { + free(p); + m_error = ec; + set_state(UTP_STATE_ERROR_WAIT); + test_socket_state(); + return; + } + + if (!m_stalled) + ++p->num_transmissions; + + TORRENT_ASSERT(!m_outbuf.at(m_seq_nr)); + m_outbuf.insert(m_seq_nr, p); + TORRENT_ASSERT(h->seq_nr == m_seq_nr); + TORRENT_ASSERT(p->buf == reinterpret_cast(h)); + + m_seq_nr = (m_seq_nr + 1) & ACK_MASK; + + TORRENT_ASSERT(!m_error); + set_state(UTP_STATE_SYN_SENT); +#if TORRENT_UTP_LOG + UTP_LOGV("%8p: state:%s\n", static_cast(this), socket_state_names[m_state]); +#endif +} + +// if a send ever failed with EWOULDBLOCK, we +// subscribe to the udp socket and will be +// signalled with this function. +void utp_socket_impl::writable() +{ +#if TORRENT_UTP_LOG + UTP_LOGV("%8p: writable\n", static_cast(this)); +#endif + if (should_delete()) return; + + while(send_pkt()); + + maybe_trigger_send_callback(); +} + +void utp_socket_impl::send_fin() +{ + INVARIANT_CHECK; + + send_pkt(pkt_fin); + // unless there was an error, we're now + // in FIN-SENT state + if (!m_error) + set_state(UTP_STATE_FIN_SENT); + +#if TORRENT_UTP_LOG + UTP_LOGV("%8p: state:%s\n", static_cast(this), socket_state_names[m_state]); +#endif +} + +void utp_socket_impl::send_reset(utp_header const* ph) +{ + INVARIANT_CHECK; + + utp_header h; + h.type_ver = (ST_RESET << 4) | 1; + h.extension = utp_no_extension; + h.connection_id = m_send_id; + h.timestamp_difference_microseconds = m_reply_micro; + h.wnd_size = 0; + h.seq_nr = random() & 0xffff; + h.ack_nr = ph->seq_nr; + time_point now = clock_type::now(); + h.timestamp_microseconds = boost::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(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 m_receive_buffer_size; +} + +void utp_socket_impl::parse_close_reason(boost::uint8_t const* ptr, int size) +{ + if (size != 4) return; + // skip reserved bytes + ptr += 2; + boost::uint16_t incoming_close_reason = detail::read_uint16(ptr); + + UTP_LOGV("%8p: incoming close_reason: %d\n" + , static_cast(this), int(incoming_close_reason)); + + if (m_userdata == 0) return; + + utp_stream::on_close_reason(m_userdata, incoming_close_reason); +} + +void utp_socket_impl::parse_sack(boost::uint16_t packet_ack, boost::uint8_t const* ptr + , int size, int* acked_bytes, time_point const now, boost::uint32_t& min_rtt) +{ + INVARIANT_CHECK; + + if (size == 0) return; + + // this is the sequence number the current bit represents + int ack_nr = (packet_ack + 2) & ACK_MASK; + +#if TORRENT_VERBOSE_UTP_LOG + std::string bitmask; + bitmask.reserve(size); + for (boost::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\n" + , static_cast(this), ack_nr, bitmask.c_str(), m_seq_nr); +#endif + + // 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; + + // the sequence number of the last ACKed packet + int last_ack = packet_ack; + + // for each byte + for (boost::uint8_t const* end = ptr + size; ptr != end; ++ptr) + { + unsigned char bitfield = unsigned(*ptr); + unsigned char mask = 1; + // for each bit + for (int i = 0; i < 8; ++i) + { + if (mask & bitfield) + { + last_ack = ack_nr; + if (m_fast_resend_seq_nr == ack_nr) + m_fast_resend_seq_nr = (m_fast_resend_seq_nr + 1) & ACK_MASK; + + if (compare_less_wrap(m_fast_resend_seq_nr, ack_nr, ACK_MASK)) ++dups; + // this bit was set, ack_nr was received + packet* p = m_outbuf.remove(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); + ack_packet(p, now, min_rtt, ack_nr); + } + else + { + // this packet might have been acked by a previous + // selective ack + maybe_inc_acked_seq_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; + } + + TORRENT_ASSERT(m_outbuf.at((m_acked_seq_nr + 1) & ACK_MASK) || ((m_seq_nr - m_acked_seq_nr) & ACK_MASK) <= 1); + + // we received more than dup_ack_limit ACKs in this SACK message. + // trigger fast re-send + if (dups >= dup_ack_limit && compare_less_wrap(m_fast_resend_seq_nr, last_ack, ACK_MASK)) + { + experienced_loss(m_fast_resend_seq_nr); + int num_resent = 0; + while (m_fast_resend_seq_nr != last_ack) + { + packet* p = m_outbuf.at(m_fast_resend_seq_nr); + m_fast_resend_seq_nr = (m_fast_resend_seq_nr + 1) & ACK_MASK; + if (!p) continue; + ++num_resent; + if (!resend_packet(p, true)) break; + m_duplicate_acks = 0; + if (num_resent >= sack_resend_limit) break; + } + } +} + +// copies data from the write buffer into the packet +// pointed to by ptr +void utp_socket_impl::write_payload(boost::uint8_t* ptr, int size) +{ + INVARIANT_CHECK; + +#ifdef TORRENT_DEBUG + int write_buffer_size = 0; + for (std::vector::iterator i = m_write_buffer.begin() + , end(m_write_buffer.end()); i != end; ++i) + { + write_buffer_size += i->len; + } + TORRENT_ASSERT(m_write_buffer_size == write_buffer_size); +#endif + TORRENT_ASSERT(!m_write_buffer.empty() || size == 0); + TORRENT_ASSERT(m_write_buffer_size >= size); + std::vector::iterator i = m_write_buffer.begin(); + + if (size == 0) return; + + int buffers_to_clear = 0; + while (size > 0) + { + // i points to the iovec we'll start copying from + int to_copy = (std::min)(size, int(i->len)); + TORRENT_ASSERT(to_copy >= 0); + TORRENT_ASSERT(to_copy < INT_MAX / 2 && m_written < INT_MAX / 2); + memcpy(ptr, static_cast(i->buf), to_copy); + size -= to_copy; + m_written += to_copy; + ptr += to_copy; + i->len -= to_copy; + TORRENT_ASSERT(m_write_buffer_size >= to_copy); + m_write_buffer_size -= to_copy; + i->buf = static_cast(i->buf) + to_copy; + if (i->len == 0) ++buffers_to_clear; + ++i; + } + + if (buffers_to_clear) + m_write_buffer.erase(m_write_buffer.begin() + , m_write_buffer.begin() + buffers_to_clear); + +#ifdef TORRENT_DEBUG + write_buffer_size = 0; + for (std::vector::iterator j = m_write_buffer.begin() + , end(m_write_buffer.end()); j != end; ++j) + { + write_buffer_size += j->len; + } + TORRENT_ASSERT(m_write_buffer_size == write_buffer_size); +#endif +} + +void utp_socket_impl::subscribe_drained() +{ + INVARIANT_CHECK; + + if (m_subscribe_drained) return; + + UTP_LOGV("%8p: subscribe drained\n", static_cast(this)); + m_subscribe_drained = true; + m_sm->subscribe_drained(this); +} + +void utp_socket_impl::defer_ack() +{ + INVARIANT_CHECK; + + if (m_deferred_ack) return; + + UTP_LOGV("%8p: defer ack\n", static_cast(this)); + m_deferred_ack = true; + m_sm->defer_ack(this); +} + +void utp_socket_impl::remove_sack_header(packet* p) +{ + INVARIANT_CHECK; + + // remove the sack header + boost::uint8_t* ptr = p->buf + sizeof(utp_header); + utp_header* 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) + sack_size + 2); + memmove(ptr, ptr + sack_size + 2, p->size - p->header_size); + p->header_size -= sack_size + 2; + p->size -= sack_size + 2; +} + +struct holder +{ + holder(char* buf = NULL): m_buf(buf) {} + ~holder() { free(m_buf); } + + void reset(char* buf) + { + free(m_buf); + m_buf = buf; + } + + char* release() + { + char* ret = m_buf; + m_buf = NULL; + return ret; + } + +private: + + // not copyable + holder(holder const&); + holder& operator=(holder const&); + + char* m_buf; +}; + +// sends a packet, pulls data from the write buffer (if there's any) +// if ack is true, we need to send a packet regardless of if there's +// any data. Returns true if we could send more data (i.e. call +// send_pkt() again) +// returns true if there is more space for payload in our +// congestion window, false if there is no more space. +bool utp_socket_impl::send_pkt(int const flags) +{ +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + INVARIANT_CHECK; +#endif + + bool const force = (flags & pkt_ack) || (flags & pkt_fin); + +// TORRENT_ASSERT(m_state != UTP_STATE_FIN_SENT || (flags & pkt_ack)); + + // first see if we need to resend any packets + + // TODO: this loop is not very efficient. It could be fixed by having + // a separate list of sequence numbers that need resending + for (int i = (m_acked_seq_nr + 1) & ACK_MASK; i != m_seq_nr; i = (i + 1) & ACK_MASK) + { + packet* p = m_outbuf.at(i); + if (!p) continue; + if (!p->need_resend) continue; + if (!resend_packet(p)) + { + // we couldn't resend the packet. It probably doesn't + // fit in our cwnd. If force is set, we need to continue + // to send our packet anyway, if we don't have force set, + // we might as well return + if (!force) return false; + // resend_packet might have failed + if (m_state == UTP_STATE_ERROR_WAIT || m_state == UTP_STATE_DELETE) return false; + break; + } + + // don't fast-resend this packet + if (m_fast_resend_seq_nr == i) + m_fast_resend_seq_nr = (m_fast_resend_seq_nr + 1) & ACK_MASK; + } + + int sack = 0; + if (m_inbuf.size()) + { + // the SACK bitfield should ideally fit all + // the pieces we have successfully received + sack = (m_inbuf.span() + 7) / 8; + if (sack > 32) sack = 32; + } + + boost::uint32_t const close_reason = m_close_reason; + + // MTU DISCOVERY + + // under these conditions, the next packet we send should be an MTU probe. + // MTU probes get to use the mid-point packet size, whereas other packets + // use a conservative packet size of the largest known to work. The reason + // for the cwnd condition is to make sure the probe is surrounded by non- + // probes, to be able to distinguish a loss of the probe vs. just loss in + // general. + bool const mtu_probe = (m_mtu_seq == 0 + && m_write_buffer_size >= m_mtu_floor * 3 + && m_seq_nr != 0 + && (m_cwnd >> 16) > m_mtu_floor * 3); + + int const header_size = sizeof(utp_header) + + (sack ? sack + 2 : 0) + + (close_reason ? 6 : 0); + + // for non MTU-probes, use the conservative packet size + int const effective_mtu = mtu_probe ? m_mtu : m_mtu_floor; + int payload_size = (std::min)(m_write_buffer_size + , effective_mtu - header_size); + + // 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 - m_bytes_in_flight))) + { + // 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:%d in-flight:%d mtu:%d\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:%d 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:%d in-flight:%d 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); +#endif + return false; + } + + int packet_size = header_size + payload_size; + + packet* p = NULL; + boost::uint8_t* ptr = NULL; + utp_header* h = NULL; + +#if TORRENT_USE_ASSERTS + bool stack_alloced = false; +#endif + + // used to free the packet buffer in case we exit the + // function early + holder buf_holder; + + // 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)) + { + // we only need a heap allocation if we have payload and + // need to keep the packet around (in the outbuf) + if (payload_size) + { + p = static_cast(malloc(sizeof(packet) + effective_mtu)); + p->allocated = effective_mtu; + buf_holder.reset(reinterpret_cast(p)); + + m_sm->inc_stats_counter(counters::utp_payload_pkts_out); + } + else + { +#if TORRENT_USE_ASSERTS + stack_alloced = true; +#endif + TORRENT_ASSERT(force); + // this alloca() statement won't necessarily produce + // correctly aligned memory. That's why we ask for 7 more bytes + // and adjust our pointer to be aligned later + p = reinterpret_cast(TORRENT_ALLOCA(char, sizeof(packet) + packet_size + + sizeof(packet*) - 1)); + p = reinterpret_cast(align_pointer(p)); + UTP_LOGV("%8p: allocating %d bytes on the stack\n", static_cast(this), packet_size); + p->allocated = packet_size; + } + + p->size = packet_size; + p->header_size = packet_size - payload_size; + p->num_transmissions = 0; +#ifdef TORRENT_DEBUG + p->num_fast_resend = 0; +#endif + p->need_resend = false; + ptr = p->buf; + h = reinterpret_cast(ptr); + ptr += sizeof(utp_header); + + h->extension = 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 = ((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 = m_nagle_packet; + + 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); + sack = 0; + } + } + else + sack = 0; + + boost::int32_t const size_left = (std::min)(p->allocated - p->size + , m_write_buffer_size); + + write_payload(p->buf + p->size, size_left); + p->size += size_left; + + UTP_LOGV("%8p: NAGLE appending %d bytes to nagle packet. new size: %d allocated: %d\n" + , static_cast(this), size_left, p->size, p->allocated); + + // did we fill up the whole mtu? + // if we didn't, we may still send it if there's + // no bytes in flight + if (m_bytes_in_flight > 0 + && p->size < p->allocated + && !force + && m_nagle) + { + return false; + } + + // clear the nagle packet pointer and fall through + // sending p + m_nagle_packet = NULL; + + packet_size = p->size; + payload_size = p->size - p->header_size; + } + + if (sack) + { + *ptr++ = close_reason ? utp_close_reason : utp_no_extension; + *ptr++ = sack; // bytes for SACK bitfield + write_sack(ptr, sack); + ptr += sack; + TORRENT_ASSERT(ptr <= p->buf + p->header_size); + } + + if (close_reason) + { + *ptr++ = utp_no_extension; + *ptr++ = 4; + detail::write_uint32(close_reason, ptr); + } + + if (m_bytes_in_flight > 0 + && p->size < p->allocated + && !force + && m_nagle) + { + // this is nagle. If we don't have a full packet + // worth of payload to send AND we have at least + // one outstanding packet, hold off. Once the + // outstanding packet is acked, we'll send this + // payload + UTP_LOGV("%8p: NAGLE not enough payload send_buffer_size:%d cwnd:%d " + "adv_wnd:%d 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 == NULL); + TORRENT_ASSERT(h->seq_nr == m_seq_nr); + m_nagle_packet = p; + buf_holder.release(); + return false; + } + + // for ST_DATA packets, payload size is 0. Such packets do not have unique + // sequence numbers and should never be used as mtu probes + if ((mtu_probe || p->mtu_probe) && payload_size > m_mtu_floor) + { + p->mtu_probe = true; + m_mtu_seq = m_seq_nr; + } + else + { + p->mtu_probe = false; + } + + h->timestamp_difference_microseconds = m_reply_micro; + h->wnd_size = (std::max)(m_in_buf_size - m_buffered_incoming_bytes + - m_receive_buffer_size, boost::int32_t(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 now = clock_type::now(); + p->send_time = now; + h->timestamp_microseconds = boost::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:%u " + "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, boost::uint32_t(h->timestamp_microseconds) + , boost::uint32_t(h->timestamp_difference_microseconds), int(p->mtu_probe) + , h->extension); +#endif + + error_code ec; + m_sm->send_packet(udp::endpoint(m_remote_address, m_port) + , reinterpret_cast(h), p->size, ec + , p->mtu_probe ? utp_socket_manager::dont_fragment : 0); + + ++m_out_packets; + m_sm->inc_stats_counter(counters::utp_packets_out); + + if (ec == error::message_size) + { +#if TORRENT_UTP_LOG + UTP_LOGV("%8p: error sending packet: %s\n" + , static_cast(this) + , ec.message().c_str()); +#endif + // if we fail even though this is not a probe, we're screwed + // since we'd have to repacketize + TORRENT_ASSERT(p->mtu_probe); + m_mtu_ceiling = p->size - 1; + if (m_mtu_floor > m_mtu_ceiling) m_mtu_floor = m_mtu_ceiling; + update_mtu_limits(); + // resend the packet immediately without + // it being an MTU probe + p->mtu_probe = false; + m_mtu_seq = 0; + ec.clear(); + +#if TORRENT_UTP_LOG + UTP_LOGV("%8p: re-sending\n", static_cast(this)); +#endif + m_sm->send_packet(udp::endpoint(m_remote_address, m_port) + , reinterpret_cast(h), p->size, ec, 0); + } + + 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) + { + TORRENT_ASSERT(stack_alloced != bool(payload_size)); + m_error = ec; + set_state(UTP_STATE_ERROR_WAIT); + test_socket_state(); + 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 == NULL); + +#if !TORRENT_UT_SEQ + // if the other end closed the connection immediately + // our FIN packet will end up having the same sequence + // number as the SYN, so this assert is invalid + TORRENT_ASSERT(!m_outbuf.at(m_seq_nr)); +#endif + 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 + buf_holder.release(); + packet* old = m_outbuf.insert(m_seq_nr, 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; + free(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 += p->size - p->header_size; + } + 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(boost::uint8_t* buf, int size) const +{ + INVARIANT_CHECK; + + TORRENT_ASSERT(m_inbuf.size()); + int ack_nr = (m_ack_nr + 2) & ACK_MASK; + boost::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(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 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); + +#ifdef TORRENT_DEBUG + if (fast_resend) ++p->num_fast_resend; +#endif + p->need_resend = false; + utp_header* h = reinterpret_cast(p->buf); + // update packet header + h->timestamp_difference_microseconds = m_reply_micro; + p->send_time = clock_type::now(); + h->timestamp_microseconds = boost::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) + { + boost::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(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:%u\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, boost::uint32_t(h->timestamp_microseconds) + , boost::uint32_t(h->timestamp_difference_microseconds)); +#endif + + if (ec == error::would_block || ec == error::try_again) + { +#if TORRENT_UTP_LOG + UTP_LOGV("%8p: socket stalled\n", static_cast(this)); +#endif + if (!m_stalled) + { + m_stalled = true; + m_sm->subscribe_writable(this); + } + } + else if (ec) + { + m_error = ec; + set_state(UTP_STATE_ERROR_WAIT); + test_socket_state(); + return false; + } + + if (!m_stalled) + ++p->num_transmissions; + + return !m_stalled; +} + +void utp_socket_impl::experienced_loss(int const seq_nr) +{ + 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; + + // cut window size in 2 + m_cwnd = (std::max)(m_cwnd * m_sm->loss_multiplier() / 100, boost::int64_t(m_mtu << 16)); + m_loss_seq_nr = m_seq_nr; + UTP_LOGV("%8p: Lost packet %d caused cwnd cut\n", static_cast(this), 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 = m_cwnd >> 16; + m_slow_start = false; + UTP_LOGV("%8p: experienced loss, slow_start -> 0\n", static_cast(this)); + } +} + +void utp_socket_impl::set_state(int s) +{ + if (s == m_state) return; + + m_sm->inc_stats_counter(counters::num_utp_idle + m_state, -1); + m_state = 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) == 0) + { + // 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; +} + +void utp_socket_impl::ack_packet(packet* p, time_point const& receive_time + , boost::uint32_t& min_rtt, boost::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); + if (m_mtu_ceiling < m_mtu_floor) m_mtu_ceiling = m_mtu_floor; + update_mtu_limits(); + } + + // increment the acked sequence number counter + maybe_inc_acked_seq_nr(); + + boost::uint32_t rtt = boost::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(false); + } + + 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); + if (rtt < min_rtt) min_rtt = rtt; + free(p); +} + +void utp_socket_impl::incoming(boost::uint8_t const* buf, int size, packet* p + , time_point /* now */) +{ +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + INVARIANT_CHECK; +#endif + + while (!m_read_buffer.empty()) + { + UTP_LOGV("%8p: incoming: have user buffer (%d)\n", static_cast(this), m_read_buffer_size); + if (p) + { + buf = p->buf + p->header_size; + TORRENT_ASSERT(p->size - p->header_size >= size); + } + iovec_t* target = &m_read_buffer.front(); + + int to_copy = (std::min)(size, int(target->len)); + memcpy(target->buf, buf, to_copy); + m_read += to_copy; + target->buf = reinterpret_cast(target->buf) + to_copy; + target->len -= to_copy; + buf += to_copy; + UTP_LOGV("%8p: copied %d bytes into user receive buffer\n", static_cast(this), to_copy); + TORRENT_ASSERT(m_read_buffer_size >= to_copy); + m_read_buffer_size -= to_copy; + size -= to_copy; + if (target->len == 0) m_read_buffer.erase(m_read_buffer.begin()); + if (p) + { + p->header_size += to_copy; + TORRENT_ASSERT(p->header_size <= p->size); + } + + if (size == 0) + { + TORRENT_ASSERT(p == 0 || p->header_size == p->size); + free(p); + return; + } + } + + TORRENT_ASSERT(m_read_buffer_size == 0); + + if (!p) + { + TORRENT_ASSERT(buf); + p = static_cast(malloc(sizeof(packet) + size)); + p->size = size; + p->header_size = 0; + memcpy(p->buf, buf, size); + } + // save this packet until the client issues another read + m_receive_buffer.push_back(p); + m_receive_buffer_size += p->size - p->header_size; + + UTP_LOGV("%8p: incoming: saving packet in receive buffer (%d)\n", static_cast(this), m_receive_buffer_size); + + check_receive_buffers(); +} + +bool utp_socket_impl::cancel_handlers(error_code const& ec, bool kill) +{ + 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 && bool(m_userdata)) || !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, kill); + if (write) utp_stream::on_write(m_userdata, 0, ec, kill); + if (connect) utp_stream::on_connect(m_userdata, ec, kill); + return ret; +} + +bool utp_socket_impl::consume_incoming_data( + utp_header const* ph, boost::uint8_t const* ptr, int payload_size + , time_point 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_in_buf_size - 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_in_buf_size); + return false; + } + + if (ph->seq_nr == ((m_ack_nr + 1) & ACK_MASK)) + { + TORRENT_ASSERT(m_inbuf.at(m_ack_nr) == 0); + + if (m_buffered_incoming_bytes + m_receive_buffer_size + payload_size > m_in_buf_size) + { + 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, 0, 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) == 0); + + 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* p = m_inbuf.remove(next_ack_nr); + + if (!p) break; + + m_buffered_incoming_bytes -= p->size - p->header_size; + incoming(0, p->size - p->header_size, p, now); + + m_ack_nr = 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_in_buf_size) + { + 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* p = static_cast(malloc(sizeof(packet) + payload_size)); + p->size = payload_size; + p->header_size = 0; + p->num_transmissions = 0; +#ifdef TORRENT_DEBUG + p->num_fast_resend = 0; +#endif + p->need_resend = false; + memcpy(p->buf, ptr, payload_size); + m_inbuf.insert(ph->seq_nr, p); + m_buffered_incoming_bytes += p->size; + + UTP_LOGV("%8p: out of order. insert inbuf: %d (%d) m_ack_nr: %d\n" + , static_cast(this), int(ph->seq_nr), int(m_inbuf.size()), m_ack_nr); + } + + return false; +} + +// returns true of the socket was closed +bool utp_socket_impl::test_socket_state() +{ + INVARIANT_CHECK; + + // if the socket is in a state where it's dead, just waiting to + // tell the client that it's closed. Do that and transition into + // the deleted state, where it will be deleted + // it might be possible to get here twice, in which we need to + // cancel any new handlers as well, even though we're already + // in the delete state + if (!m_error) return false; + TORRENT_ASSERT(m_state == UTP_STATE_ERROR_WAIT || m_state == UTP_STATE_DELETE); + +#if TORRENT_UTP_LOG + UTP_LOGV("%8p: state:%s error:%s\n" + , static_cast(this), socket_state_names[m_state], m_error.message().c_str()); +#endif + + if (cancel_handlers(m_error, true)) + { + set_state(UTP_STATE_DELETE); +#if TORRENT_UTP_LOG + UTP_LOGV("%8p: state:%s\n", static_cast(this), socket_state_names[m_state]); +#endif + return true; + } + return false; +} + +void utp_socket_impl::init_mtu(int link_mtu, int utp_mtu) +{ + INVARIANT_CHECK; + + if (link_mtu > TORRENT_ETHERNET_MTU) + { + // we can't use larger packets than this since we're + // not allocating any more memory for socket buffers + int decrease = link_mtu - TORRENT_ETHERNET_MTU; + utp_mtu -= decrease; + link_mtu -= decrease; + } + + // set the ceiling to what we found out from the interface + m_mtu_ceiling = utp_mtu; + + // start in the middle of the PMTU search space + m_mtu = (m_mtu_ceiling + m_mtu_floor) / 2; + if (m_mtu > m_mtu_ceiling) m_mtu = m_mtu_ceiling; + if (m_mtu_floor > utp_mtu) m_mtu_floor = utp_mtu; + + // if the window size is smaller than one packet size + // set it to one + if ((m_cwnd >> 16) < m_mtu) m_cwnd = boost::int64_t(m_mtu) << 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(boost::uint8_t const* buf, int size + , udp::endpoint const& ep, time_point receive_time) +{ + INVARIANT_CHECK; + + utp_header const* ph = reinterpret_cast(buf); + + m_sm->inc_stats_counter(counters::utp_packets_in); + + if (ph->get_version() != 1) + { + UTP_LOG("%8p: ERROR: incoming packet version:%d (ignored)\n" + , static_cast(this), int(ph->get_version())); + m_sm->inc_stats_counter(counters::utp_invalid_pkts_in); + return false; + } + + // SYN packets have special (reverse) connection ids + if (ph->get_type() != ST_SYN && ph->connection_id != m_recv_id) + { + UTP_LOG("%8p: ERROR: incoming packet id:%d expected:%d (ignored)\n" + , static_cast(this), int(ph->connection_id), int(m_recv_id)); + m_sm->inc_stats_counter(counters::utp_invalid_pkts_in); + return false; + } + + if (ph->get_type() >= NUM_TYPES) + { + UTP_LOG("%8p: ERROR: incoming packet type:%d (ignored)\n" + , static_cast(this), int(ph->get_type())); + m_sm->inc_stats_counter(counters::utp_invalid_pkts_in); + return false; + } + + if (m_state == UTP_STATE_NONE && ph->get_type() == ST_SYN) + { + m_remote_address = ep.address(); + m_port = ep.port(); + } + + if (m_state != UTP_STATE_NONE && ph->get_type() == ST_SYN) + { + UTP_LOG("%8p: ERROR: incoming packet type:ST_SYN (ignored)\n" + , static_cast(this)); + m_sm->inc_stats_counter(counters::utp_invalid_pkts_in); + return true; + } + + bool step = false; + if (receive_time - m_last_history_step > minutes(1)) + { + step = true; + m_last_history_step = receive_time; + } + + // this is the difference between their send time and our receive time + // 0 means no sample yet + boost::uint32_t their_delay = 0; + if (ph->timestamp_microseconds != 0) + { + boost::uint32_t timestamp = boost::uint32_t(total_microseconds( + receive_time.time_since_epoch()) & 0xffffffff); + m_reply_micro = timestamp - ph->timestamp_microseconds; + boost::uint32_t 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 base_change = 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 + boost::uint16_t cmp_seq_nr = (m_seq_nr - 1) & ACK_MASK; +#if TORRENT_UT_SEQ + if (m_state == UTP_STATE_SYN_SENT && ph->get_type() == ST_STATE) + cmp_seq_nr = m_seq_nr; +#endif + if ((m_state != UTP_STATE_NONE || ph->get_type() != ST_SYN) + && (compare_less_wrap(cmp_seq_nr, ph->ack_nr, ACK_MASK) + || compare_less_wrap(ph->ack_nr, m_acked_seq_nr + - dup_ack_limit, ACK_MASK))) + { + UTP_LOG("%8p: ERROR: incoming packet ack_nr:%d our seq_nr:%d our " + "acked_seq_nr:%d (ignored)\n" + , static_cast(this), int(ph->ack_nr), m_seq_nr, m_acked_seq_nr); + m_sm->inc_stats_counter(counters::utp_redundant_pkts_in); + return true; + } + + // check to make sure the sequence number of this packet + // is reasonable. If it's a data packet and we've already + // received it, ignore it. This is either a stray old packet + // that finally made it here (after having been re-sent) or + // an attempt to interfere with the connection from a 3rd party + // in both cases, we can safely ignore the timestamp and ACK + // information in this packet +/* + // even if we've already received this packet, we need to + // send another ack to it, since it may be a resend caused by + // our ack getting dropped + if (m_state != UTP_STATE_SYN_SENT + && ph->get_type() == ST_DATA + && !compare_less_wrap(m_ack_nr, ph->seq_nr, ACK_MASK)) + { + // we've already received this packet + UTP_LOGV("%8p: incoming packet seq_nr:%d our ack_nr:%d (ignored)\n" + , static_cast(this), int(ph->seq_nr), m_ack_nr); + m_sm->inc_stats_counter(counters::utp_redundant_pkts_in); + return true; + } +*/ + + // if the socket is closing, always ignore any packet + // with a higher sequence number than the FIN sequence number + if (m_eof && compare_less_wrap(m_eof_seq_nr, ph->seq_nr, ACK_MASK)) + { +#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); + + if (m_state != UTP_STATE_NONE + && m_state != UTP_STATE_SYN_SENT + && compare_less_wrap((m_ack_nr + max_packets_reorder) & ACK_MASK, ph->seq_nr, ACK_MASK)) + { + // this is too far out to fit in our reorder buffer. Drop it + // This is either an attack to try to break the connection + // or a seriously damaged connection that lost a lot of + // packets. Neither is very likely, and it should be OK + // to drop the timestamp information. + UTP_LOG("%8p: ERROR: incoming packet seq_nr:%d our ack_nr:%d (ignored)\n" + , static_cast(this), int(ph->seq_nr), m_ack_nr); + m_sm->inc_stats_counter(counters::utp_redundant_pkts_in); + return true; + } + + if (ph->get_type() == ST_RESET) + { + if (compare_less_wrap(cmp_seq_nr, ph->ack_nr, ACK_MASK)) + { + UTP_LOG("%8p: ERROR: invalid RESET packet, ack_nr:%d our seq_nr:%d (ignored)\n" + , static_cast(this), int(ph->ack_nr), m_seq_nr); + return true; + } + UTP_LOGV("%8p: incoming packet type:RESET\n", static_cast(this)); + m_error = boost::asio::error::connection_reset; + set_state(UTP_STATE_ERROR_WAIT); + test_socket_state(); + return true; + } + + ++m_in_packets; + + // this is a valid incoming packet, update the timeout timer + m_num_timeouts = 0; + m_timeout = receive_time + milliseconds(packet_timeout()); + UTP_LOGV("%8p: updating timeout to: now + %d\n" + , static_cast(this), packet_timeout()); + + // the test for INT_MAX here is a work-around for a bug in uTorrent where + // it's sometimes sent as INT_MAX when it is in fact uninitialized + const boost::uint32_t sample = ph->timestamp_difference_microseconds == INT_MAX + ? 0 : ph->timestamp_difference_microseconds; + + boost::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 >= num_delay_hist) 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; + } + + boost::uint32_t min_rtt = (std::numeric_limits::max)(); + + TORRENT_ASSERT(m_outbuf.at((m_acked_seq_nr + 1) & ACK_MASK) || ((m_seq_nr - m_acked_seq_nr) & ACK_MASK) <= 1); + + // has this packet already been ACKed? + // if the ACK we just got is less than the max ACKed + // sequence number, it doesn't tell us anything. + // So, only act on it if the ACK is greater than the last acked + // sequence number + if (m_state != UTP_STATE_NONE && compare_less_wrap(m_acked_seq_nr, ph->ack_nr, ACK_MASK)) + { + int const next_ack_nr = ph->ack_nr; + + for (int ack_nr = (m_acked_seq_nr + 1) & ACK_MASK; + ack_nr != ((next_ack_nr + 1) & ACK_MASK); + ack_nr = (ack_nr + 1) & ACK_MASK) + { + if (m_fast_resend_seq_nr == ack_nr) + m_fast_resend_seq_nr = (m_fast_resend_seq_nr + 1) & ACK_MASK; + packet* p = m_outbuf.remove(ack_nr); + + if (!p) continue; + + acked_bytes += p->size - p->header_size; + ack_packet(p, receive_time, min_rtt, ack_nr); + } + + maybe_inc_acked_seq_nr(); + } + + // look for extended headers + boost::uint8_t const* ptr = buf; + ptr += sizeof(utp_header); + + unsigned int extension = ph->extension; + while (extension) + { + // invalid packet. It says it has an extension header + // but the packet is too short + if (ptr - buf + 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; + } + int next_extension = *ptr++; + int len = *ptr++; + if (len < 0) + { + UTP_LOGV("%8p: invalid extension length:%d packet:%d\n" + , static_cast(this), len, int(ptr - buf)); + m_sm->inc_stats_counter(counters::utp_invalid_pkts_in); + return true; + } + if (ptr - buf + len > ptrdiff_t(size)) + { + UTP_LOG("%8p: ERROR: invalid extension header size:%d packet:%d\n" + , static_cast(this), len, int(ptr - buf)); + m_sm->inc_stats_counter(counters::utp_invalid_pkts_in); + return true; + } + switch(extension) + { + case utp_sack: // selective ACKs + parse_sack(ph->ack_nr, ptr, len, &acked_bytes, receive_time, min_rtt); + break; + case utp_close_reason: + parse_close_reason(ptr, len); + break; + } + ptr += len; + extension = next_extension; + } + + // the send operation in parse_sack() may have set the socket to an error + // state, in which case we shouldn't continue + if (m_state == UTP_STATE_ERROR_WAIT || m_state == UTP_STATE_DELETE) return true; + + if (m_duplicate_acks >= dup_ack_limit + && ((m_acked_seq_nr + 1) & ACK_MASK) == m_fast_resend_seq_nr) + { + // LOSS + + UTP_LOGV("%8p: Packet %d lost. (%d duplicate acks, trigger fast-resend)\n" + , static_cast(this), m_fast_resend_seq_nr, m_duplicate_acks); + + // resend the lost packet + packet* p = m_outbuf.at(m_fast_resend_seq_nr); + TORRENT_ASSERT(p); + + // don't fast-resend this again + m_fast_resend_seq_nr = (m_fast_resend_seq_nr + 1) & ACK_MASK; + + if (p) + { + experienced_loss(m_fast_resend_seq_nr); + resend_packet(p, true); + if (m_state == UTP_STATE_ERROR_WAIT || m_state == UTP_STATE_DELETE) return true; + } + } + + // ptr points to the payload of the packet + // size is the packet size, payload is the + // number of payload bytes are in this packet + const int header_size = ptr - buf; + 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, boost::uint32_t(ph->timestamp_difference_microseconds) + , boost::uint32_t(ph->timestamp_microseconds), m_ack_nr, m_seq_nr, m_acked_seq_nr, socket_state_names[m_state]); +#endif + + if (ph->get_type() == ST_FIN) + { + // We ignore duplicate FIN packets, but we still need to ACK them. + if (ph->seq_nr == ((m_ack_nr + 1) & ACK_MASK) + || ph->seq_nr == m_ack_nr) + { + UTP_LOGV("%8p: FIN received in order\n", static_cast(this)); + + // The FIN arrived in order, nothing else is in the + // reorder buffer. + +// TORRENT_ASSERT(m_inbuf.size() == 0); + m_ack_nr = ph->seq_nr; + + // Transition to UTP_STATE_FIN_SENT. The sent FIN is also an ack + // to the FIN we received. Once we're in UTP_STATE_FIN_SENT we + // just need to wait for our FIN to be acked. + + if (m_state == UTP_STATE_FIN_SENT) + { + send_pkt(pkt_ack); + if (m_state == UTP_STATE_ERROR_WAIT || m_state == UTP_STATE_DELETE) return true; + } + else + { + send_fin(); + if (m_state == UTP_STATE_ERROR_WAIT || m_state == UTP_STATE_DELETE) return true; + } + } + + if (m_eof) + { + UTP_LOGV("%8p: duplicate FIN packet (ignoring)\n", static_cast(this)); + return true; + } + m_eof = true; + m_eof_seq_nr = ph->seq_nr; + + // we will respond with a fin once we have received everything up to m_eof_seq_nr + } + + switch (m_state) + { + case UTP_STATE_NONE: + { + if (ph->get_type() == ST_SYN) + { + // if we're in state_none, the only thing + // we accept are SYN packets. + set_state(UTP_STATE_CONNECTED); + + m_remote_address = ep.address(); + m_port = ep.port(); + + error_code ec; + m_local_address = m_sm->local_endpoint(m_remote_address, ec).address(); + + m_ack_nr = ph->seq_nr; + m_seq_nr = 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 + TORRENT_ASSERT(m_send_id == ph->connection_id); + TORRENT_ASSERT(m_recv_id == ((m_send_id + 1) & 0xffff)); + + defer_ack(); + } + else + { +#if TORRENT_UTP_LOG + UTP_LOG("%8p: ERROR: type:%s state:%s (ignored)\n" + , static_cast(this), packet_type_names[ph->get_type()], socket_state_names[m_state]); +#endif + } + break; + } + case UTP_STATE_SYN_SENT: + { + // just wait for an ack to our SYN, ignore everything else + if (ph->ack_nr != ((m_seq_nr - 1) & ACK_MASK)) + { +#if TORRENT_UTP_LOG + UTP_LOGV("%8p: incorrect ack_nr (%d) waiting for %d\n" + , static_cast(this), int(ph->ack_nr), (m_seq_nr - 1) & ACK_MASK); +#endif + break; + } + + TORRENT_ASSERT(!m_error); + set_state(UTP_STATE_CONNECTED); +#if TORRENT_UTP_LOG + UTP_LOGV("%8p: state:%s\n", static_cast(this), socket_state_names[m_state]); +#endif + + // only progress our ack_nr on ST_DATA messages + // since our m_ack_nr is uninitialized at this point + // we still need to set it to something regardless + if (ph->get_type() == ST_DATA) + m_ack_nr = ph->seq_nr; + else + m_ack_nr = (ph->seq_nr - 1) & ACK_MASK; + + // notify the client that the socket connected + if (m_connect_handler) + { + UTP_LOGV("%8p: calling connect handler\n", static_cast(this)); + m_connect_handler = false; + utp_stream::on_connect(m_userdata, m_error, false); + } + // fall through + } + case UTP_STATE_CONNECTED: + { + // the lowest seen RTT can be used to clamp the delay + // within reasonable bounds. The one-way delay is never + // higher than the round-trip time. + + if (sample && acked_bytes && prev_bytes_in_flight) + { + // only use the minimum from the last 3 delay measurements + delay = *std::min_element(m_delay_sample_hist, m_delay_sample_hist + num_delay_hist); + + // 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, delay, prev_bytes_in_flight); + m_send_delay = delay; + } + + m_recv_delay = (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; + boost::uint32_t prev_out_packets = m_out_packets; + + // the connection is connected and this packet made it past all the + // checks. We can now assume the other end is not spoofing it's IP. + if (ph->get_type() != ST_SYN) m_confirmed = true; + + // try to send more data as long as we can + // if send_pkt returns true + while (send_pkt()); + + if (has_ack && prev_out_packets == m_out_packets) + { + // we need to ack some data we received, and we didn't + // end up sending any payload packets in the loop + // above (because m_out_packets would have been incremented + // in that case). This means we need to send an ack. + // don't do it right away, because we may still receive + // more packets. defer the ack to send as few acks as possible + defer_ack(); + } + + // we may want to call the user callback function at the end + // of this round. Subscribe to that event + subscribe_drained(); + + if (m_state == UTP_STATE_ERROR_WAIT || m_state == UTP_STATE_DELETE) return true; + + // Everything up to the FIN has been received, respond with a FIN + // from our side. + if (m_eof && m_ack_nr == ((m_eof_seq_nr - 1) & ACK_MASK)) + { + UTP_LOGV("%8p: incoming stream consumed\n", static_cast(this)); + + // This transitions to the UTP_STATE_FIN_SENT state. + send_fin(); + if (m_state == UTP_STATE_ERROR_WAIT || m_state == UTP_STATE_DELETE) return true; + } + +#if TORRENT_UTP_LOG + if (sample && acked_bytes && prev_bytes_in_flight) + { + char their_delay_base[20]; + if (m_their_delay_hist.initialized()) + 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()) + 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 + , float(delay / 1000.f) + , float(their_delay / 1000.f) + , float(int(m_sm->target_delay() - delay)) / 1000.f + , boost::uint32_t(m_cwnd >> 16) + , 0 + , our_delay_base + , float(delay + their_delay) / 1000.f + , m_sm->target_delay() / 1000 + , acked_bytes + , m_bytes_in_flight + , 0.f // 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_seq_nr - m_acked_seq_nr) & ACK_MASK + , m_mtu + , their_delay_base + , boost::uint32_t(m_reply_micro) + , m_seq_nr + , m_acked_seq_nr + , m_reply_micro + , min_rtt / 1000 + , m_write_buffer_size + , m_read_buffer_size + , m_fast_resend_seq_nr + , m_ssthres); + } +#endif + + break; + } + case UTP_STATE_FIN_SENT: + { + // There are two ways we can end up in this state: + // + // 1. If the socket has been explicitly closed on our + // side, in which case m_eof is false. + // + // 2. If we received a FIN from the remote side, in which + // case m_eof is true. If this is the case, we don't + // come here until everything up to the FIN has been + // received. + // + // + // + + // At this point m_seq_nr - 1 is the FIN sequence number. + + // We can receive both ST_DATA and ST_STATE here, because after + // we have closed our end of the socket, the remote end might + // have data in the pipeline. We don't really care about the + // data, but we do have to ack it. Or rather, we have to ack + // the FIN that will come after the data. + + // Case 1: + // --------------------------------------------------------------- + // + // If we are here because the local endpoint was closed, we need + // to first wait for all of our messages to be acked: + // + // if (m_acked_seq_nr == ((m_seq_nr - 1) & ACK_MASK)) + // + // `m_seq_nr - 1` is the ST_FIN message that we sent. + // + // ---------------------- + // + // After that has happened we need to wait for the remote side + // to send its ST_FIN message. When we receive that we send an + // ST_STATE back to ack, and wait for a sufficient period. + // During this wait we keep acking incoming ST_FIN's. This is + // all handled at the top of this function. + // + // Note that the user handlers are all cancelled when the initial + // close() call happens, so nothing will happen on the user side + // after that. + + // Case 2: + // --------------------------------------------------------------- + // + // If we are here because we received a ST_FIN message, and then + // sent our own ST_FIN to ack that, we need to wait for our ST_FIN + // to be acked: + // + // if (m_acked_seq_nr == ((m_seq_nr - 1) & ACK_MASK)) + // + // `m_seq_nr - 1` is the ST_FIN message that we sent. + // + // After that has happened we know the remote side has all our + // data, and we can gracefully shut down. + + if (consume_incoming_data(ph, ptr, payload_size, receive_time)) + { + break; + } + + if (m_acked_seq_nr == ((m_seq_nr - 1) & ACK_MASK)) + { + // When this happens we know that the remote side has + // received all of our packets. + + UTP_LOGV("%8p: FIN acked\n", static_cast(this)); + + if (!m_attached) + { + UTP_LOGV("%8p: close initiated here, delete socket\n" + , static_cast(this)); + m_error = boost::asio::error::eof; + set_state(UTP_STATE_DELETE); + test_socket_state(); + } + else + { + UTP_LOGV("%8p: closing socket\n", static_cast(this)); + m_error = boost::asio::error::eof; + set_state(UTP_STATE_ERROR_WAIT); + test_socket_state(); + } + } + + break; + } + case UTP_STATE_DELETE: + default: + { + // respond with a reset + send_reset(ph); + break; + } + } + return true; +} + +void utp_socket_impl::do_ledbat(const int acked_bytes, const int delay + , const int in_flight) +{ + INVARIANT_CHECK; + + // the portion of the in-flight bytes that were acked. This is used to make + // the gain factor be scaled by the rtt. The formula is applied once per + // rtt, or on every ACK scaled by the number of ACKs per rtt + TORRENT_ASSERT(in_flight > 0); + TORRENT_ASSERT(acked_bytes > 0); + + const int target_delay = (std::max)(1, m_sm->target_delay()); + + // true if the upper layer is pushing enough data down the socket to be + // limited by the cwnd. If this is not the case, we should not adjust cwnd. + const bool cwnd_saturated = (m_bytes_in_flight + acked_bytes + m_mtu > (m_cwnd >> 16)); + + // all of these are fixed points with 16 bits fraction portion + const boost::int64_t window_factor = (boost::int64_t(acked_bytes) << 16) / in_flight; + const boost::int64_t delay_factor = (boost::int64_t(target_delay - delay) << 16) / target_delay; + boost::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 = (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); + } + + boost::int64_t linear_gain = (window_factor * delay_factor) >> 16; + linear_gain *= boost::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) + { + boost::int64_t exponential_gain = boost::int64_t(acked_bytes) << 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 / float(1 << 16) + , delay_factor / float(1 << 16) + , scaled_gain / float(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 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; + UTP_LOGV("%8p: cwnd > advertized wnd (%d) slow_start -> 0\n" + , static_cast(this), m_adv_wnd); + } +} + +void utp_stream::bind(endpoint_type const&, error_code&) { } + +void utp_stream::cancel_handlers(error_code const& ec) +{ + if (!m_impl) return; + m_impl->cancel_handlers(ec, false); +} +// returns the number of milliseconds a packet would have before +// it would time-out if it was sent right now. Takes the RTT estimate +// into account +int utp_socket_impl::packet_timeout() const +{ + INVARIANT_CHECK; + + // SYN packets have a bit longer timeout, since we don't + // have an RTT estimate yet, make a conservative guess + if (m_state == UTP_STATE_NONE) return 3000; + + // avoid overflow by simply capping based on number of timeouts as well + if (m_num_timeouts >= 7) return 60000; + + int timeout = (std::max)(m_sm->min_timeout(), m_rtt.mean() + m_rtt.avg_deviation() * 2); + if (m_num_timeouts > 0) timeout += (1 << (int(m_num_timeouts) - 1)) * 1000; + + // timeouts over 1 minute are capped + if (timeout > 60000) timeout = 60000; + return timeout; +} + +void utp_socket_impl::tick(time_point now) +{ + INVARIANT_CHECK; + +#if TORRENT_UTP_LOG + UTP_LOGV("%8p: tick:%s r: %d (%s) w: %d (%s)\n" + , static_cast(this), socket_state_names[m_state], m_read, m_read_handler ? "handler" : "no handler" + , m_written, m_write_handler ? "handler" : "no handler"); +#endif + + TORRENT_ASSERT(m_outbuf.at((m_acked_seq_nr + 1) & ACK_MASK) || ((m_seq_nr - m_acked_seq_nr) & ACK_MASK) <= 1); + + // if we're already in an error state, we're just waiting for the + // client to perform an operation so that we can communicate the + // error. No need to do anything else with this socket + if (m_state == UTP_STATE_ERROR_WAIT || m_state == UTP_STATE_DELETE) return; + + if (now > m_timeout) + { + // TIMEOUT! + // set cwnd to 1 MSS + + // 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 != 0) + { + ++m_num_timeouts; + m_sm->inc_stats_counter(counters::utp_timeout); + } + + UTP_LOGV("%8p: timeout num-timeouts: %d max-resends: %d confirmed: %d " + " acked-seq-num: %d mtu-seq: %d\n" + , static_cast(this) + , m_num_timeouts + , m_sm->num_resends() + , m_confirmed + , m_acked_seq_nr + , m_mtu_seq); + + // a socket that has not been confirmed to actually have a live remote end + // (the IP may have been spoofed) fail on the first timeout. If we had + // heard anything from this peer, it would have been confirmed. + if (m_num_timeouts > m_sm->num_resends() + || (m_num_timeouts > 0 && !m_confirmed)) + { + // the connection is dead + m_error = boost::asio::error::timed_out; + set_state(UTP_STATE_ERROR_WAIT); + test_socket_state(); + return; + } + + if (((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; + if (m_mtu_floor > m_mtu_ceiling) m_mtu_floor = m_mtu_ceiling; + update_mtu_limits(); + } + + 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, boost::int64_t(m_mtu) << 16); + } + else + { + // we timed out because a packet was not ACKed or because + // the cwnd was made smaller than one packet + m_cwnd = boost::int64_t(m_mtu) << 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)); + + // we dropped all packets, that includes the mtu probe + m_mtu_seq = 0; + + // 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 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(i); + if (!p) continue; + if (p->need_resend) continue; + p->need_resend = true; + TORRENT_ASSERT(m_bytes_in_flight >= p->size - p->header_size); + m_bytes_in_flight -= p->size - p->header_size; + UTP_LOGV("%8p: Packet %d lost (timeout).\n", static_cast(this), i); + } + + TORRENT_ASSERT(m_bytes_in_flight == 0); + + // if we have a packet that needs re-sending, resend it + packet* p = m_outbuf.at((m_acked_seq_nr + 1) & ACK_MASK); + if (p) + { + if (p->num_transmissions >= m_sm->num_resends() + || (m_state == UTP_STATE_SYN_SENT && p->num_transmissions >= m_sm->syn_resends()) + || (m_state == UTP_STATE_FIN_SENT && p->num_transmissions >= m_sm->fin_resends())) + { +#if TORRENT_UTP_LOG + UTP_LOGV("%8p: %d failed sends in a row. Socket timed out. state:%s\n" + , static_cast(this), p->num_transmissions, socket_state_names[m_state]); +#endif + + if (p->size > m_mtu_floor) + { + // the packet that caused the connection to fail was an mtu probe + // (note that the mtu_probe field won't be set at this point because + // it's cleared when the packet is re-sent). This suggests that + // perhaps our network throws away oversized packets without + // fragmenting them. Tell the socket manager to be more conservative + // about mtu ceiling in the future + m_sm->restrict_mtu(m_mtu); + } + // the connection is dead + m_error = boost::asio::error::timed_out; + set_state(UTP_STATE_ERROR_WAIT); + test_socket_state(); + return; + } + + // don't fast-resend this packet + if (m_fast_resend_seq_nr == ((m_acked_seq_nr + 1) & ACK_MASK)) + m_fast_resend_seq_nr = (m_fast_resend_seq_nr + 1) & ACK_MASK; + + // the packet timed out, resend it + resend_packet(p); + if (m_state == UTP_STATE_ERROR_WAIT || m_state == UTP_STATE_DELETE) return; + } + else if (m_state < UTP_STATE_FIN_SENT) + { + send_pkt(); + if (m_state == UTP_STATE_ERROR_WAIT || m_state == UTP_STATE_DELETE) return; + } + else if (m_state == UTP_STATE_FIN_SENT) + { + // the connection is dead + m_error = boost::asio::error::eof; + set_state(UTP_STATE_ERROR_WAIT); + test_socket_state(); + return; + } + } + + switch (m_state) + { + case UTP_STATE_NONE: + case UTP_STATE_DELETE: + return; +// case UTP_STATE_SYN_SENT: +// +// break; + } +} + +void utp_socket_impl::check_receive_buffers() const +{ + INVARIANT_CHECK; + + std::size_t size = 0; + + for (std::vector::const_iterator i = m_receive_buffer.begin() + , end(m_receive_buffer.end()); i != end; ++i) + { + if (packet const* p = *i) + size += p->size - p->header_size; + } + + TORRENT_ASSERT(int(size) == m_receive_buffer_size); +} + +#if TORRENT_USE_INVARIANT_CHECKS +void utp_socket_impl::check_invariant() const +{ + for (int i = m_outbuf.cursor(); + i != int((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..dc4722b --- /dev/null +++ b/src/version.cpp @@ -0,0 +1,43 @@ +/* + +Copyright (c) 2015-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/version.hpp" + +namespace libtorrent { + +char const* version() +{ + return LIBTORRENT_VERSION; +} + +} + diff --git a/src/web_connection_base.cpp b/src/web_connection_base.cpp new file mode 100644 index 0000000..b531cc4 --- /dev/null +++ b/src/web_connection_base.cpp @@ -0,0 +1,216 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/web_connection_base.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/identify_client.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/version.hpp" +#include "libtorrent/parse_url.hpp" +#include "libtorrent/peer_info.hpp" + +using boost::shared_ptr; + +namespace libtorrent +{ + web_connection_base::web_connection_base( + peer_connection_args const& pack + , web_seed_t& web) + : peer_connection(pack) + , m_first_request(true) + , m_ssl(false) + , m_external_auth(web.auth) + , m_extra_headers(web.extra_headers) + , m_parser(http_parser::dont_parse_chunks) + , m_body_start(0) + { + TORRENT_ASSERT(&web.peer_info == pack.peerinfo); + // when going through a proxy, we don't necessarily have an endpoint here, + // since the proxy might be resolving the hostname, not us + TORRENT_ASSERT(web.endpoints.empty() || web.endpoints.front() == pack.endp); + + INVARIANT_CHECK; + + TORRENT_ASSERT(is_outgoing()); + + // 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; + boost::tie(protocol, m_basic_auth, m_host, m_port, m_path) + = parse_url_components(web.url, ec); + TORRENT_ASSERT(!ec); + + if (m_port == -1 && protocol == "http") + m_port = 80; + +#ifdef TORRENT_USE_OPENSSL + if (protocol == "https") + { + m_ssl = true; + if (m_port == -1) m_port = 443; + } +#endif + + if (!m_basic_auth.empty()) + m_basic_auth = base64encode(m_basic_auth); + + m_server_string = "URL seed @ "; + m_server_string += m_host; + } + + int web_connection_base::timeout() const + { + // since this is a web seed, change the timeout + // according to the settings. + return m_settings.get_int(settings_pack::urlseed_timeout); + } + + void web_connection_base::start() + { + set_upload_only(true); + if (is_disconnecting()) return; + peer_connection::start(); + } + + web_connection_base::~web_connection_base() + {} + + void web_connection_base::on_connected() + { + boost::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + + // this is always a seed + incoming_have_all(); + + // it is always possible to request pieces + incoming_unchoke(); + + m_recv_buffer.reset(t->block_size() + 1024); + } + + void web_connection_base::add_headers(std::string& request + , aux::session_settings const& sett, bool using_proxy) const + { + request += "Host: "; + request += m_host; + if (m_first_request || m_settings.get_bool(settings_pack::always_send_user_agent)) { + 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 (web_seed_entry::headers_t::const_iterator it = m_extra_headers.begin(); + it != m_extra_headers.end(); ++it) { + request += "\r\n"; + request += it->first; + request += ": "; + request += it->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, 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..d5b97ab --- /dev/null +++ b/src/web_peer_connection.cpp @@ -0,0 +1,1091 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/web_peer_connection.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/identify_client.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/version.hpp" +#include "libtorrent/parse_url.hpp" +#include "libtorrent/peer_info.hpp" +#include "libtorrent/aux_/session_interface.hpp" +#include "libtorrent/alert_manager.hpp" // for alert_manageralert_manager +#include "libtorrent/aux_/escape_string.hpp" // for escape_path +#include "libtorrent/hex.hpp" // for is_hex + +using boost::shared_ptr; + +namespace libtorrent +{ +enum +{ + request_size_overhead = 5000 +}; + +struct disk_interface; + +web_peer_connection::web_peer_connection(peer_connection_args const& pack + , web_seed_t& web) + : web_connection_base(pack, web) + , m_url(web.url) + , m_web(&web) + , m_received_body(0) + , m_chunk_pos(0) + , m_partial_chunk_header(0) + , m_num_responses(0) +{ + INVARIANT_CHECK; + + if (!m_settings.get_bool(settings_pack::report_web_seed_downloads)) + ignore_stats(true); + + shared_ptr tor = pack.tor.lock(); + TORRENT_ASSERT(tor); + + // we always prefer downloading 1 MiB chunks + // from web seeds, or whole pieces if pieces + // are larger than a MiB + int preferred_size = 1024 * 1024; + + // if the web server is known not to support keep-alive. + // request even larger blocks at a time + if (!web.supports_keepalive) preferred_size *= 4; + + prefer_contiguous_blocks((std::max)(preferred_size / tor->block_size(), 1)); + + boost::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 + if (m_path.empty() || m_path[m_path.size()-1] != '/') m_path += '/'; + if (m_url.empty() || m_url[m_url.size()-1] != '/') 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] == '/') + { + std::string const& name = t->torrent_file().name(); + m_path += escape_string(name.c_str(), name.size()); + } + + if (!m_url.empty() && m_url[m_url.size() - 1] == '/') + { + std::string tmp = t->torrent_file().files().file_path(0); +#ifdef TORRENT_WINDOWS + convert_path_to_posix(tmp); +#endif + tmp = escape_path(tmp.c_str(), tmp.size()); + m_url += tmp; + } + } + + // 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 +} + +void web_peer_connection::on_connected() +{ + incoming_have_all(); + if (m_web->restart_request.piece != -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, int error) +{ + if (is_disconnecting()) return; + + if (op == op_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(); + m_recv_buffer.free_disk_buffer(); + + // 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 == op_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()); + } + + boost::shared_ptr t = associated_torrent().lock(); + + if (!m_requests.empty() && !m_file_requests.empty() + && !m_piece.empty() && m_web) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "SAVE_RESTART_DATA" + , "data: %d req: %d off: %d" + , int(m_piece.size()), int(m_requests.front().piece) + , int(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(m_web->restart_piece.size() + , torrent::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 == 0) + { + // if the web server doesn't support keepalive and we were + // disconnected as a graceful EOF, reconnect right away + if (t) get_io_service().post( + boost::bind(&torrent::maybe_connect_web_seeds, t)); + } + peer_connection::disconnect(ec, op, error); + if (t) t->disconnect_web_seed(this); +} + +boost::optional +web_peer_connection::downloading_piece_progress() const +{ + if (m_requests.empty()) + return boost::optional(); + + boost::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + + piece_block_progress ret; + + ret.piece_index = m_requests.front().piece; + ret.bytes_downloaded = 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.size() ? -1 : 0; + ret.block_index = (m_requests.front().start + m_piece.size() + correction) / t->block_size(); + TORRENT_ASSERT(ret.block_index < int(piece_block::invalid.block_index)); + TORRENT_ASSERT(ret.piece_index < int(piece_block::invalid.piece_index)); + + ret.full_block_bytes = t->block_size(); + const int last_piece = t->torrent_file().num_pieces() - 1; + 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; + + boost::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 = r.piece + request_offset / piece_size; + m_requests.push_back(pr); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "REQUESTING", "piece: %d start: %d len: %d" + , pr.piece, pr.start, pr.length); +#endif + + if (m_web->restart_request == m_requests.front()) + { + m_piece.swap(m_web->restart_piece); + peer_request& 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()), int(front.piece), int(front.start) + , int (front.start + front.length - 1)); +#else + TORRENT_UNUSED(front); +#endif + + req.start += m_piece.size(); + req.length -= 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(m_piece.size()); + m_web->restart_request.piece = -1; + } + +#if 0 + std::cerr << this << " REQ: p: " << pr.piece << " " << pr.start << std::endl; +#endif + size -= pr.length; + } + + 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: 2 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 = 0; + file_req.start = boost::int64_t(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).elems; + request += "-"; + request += to_string(file_req.start + file_req.length - 1).elems; + request += "\r\n\r\n"; + m_first_request = false; + + m_file_requests.push_back(file_req); + } + else + { + if (!t->need_loaded()) + { + disconnect(errors::torrent_aborted, op_bittorrent); + return; + } + + std::vector files = info.orig_files().map_block(req.piece, req.start + , req.length); + + for (std::vector::iterator i = files.begin(); + i != files.end(); ++i) + { + file_slice const& f = *i; + + file_request_t file_req; + file_req.file_index = f.file_index; + file_req.start = f.offset; + file_req.length = 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; + std::string path = info.orig_files().file_path(f.file_index); +#ifdef TORRENT_WINDOWS + convert_path_to_posix(path); +#endif + request += escape_path(path.c_str(), path.length()); + } + else + { + // m_path is already a properly escaped URL + // with the correct slashes. Don't encode it again + request += m_path; + + std::string path = info.orig_files().file_path(f.file_index); +#ifdef TORRENT_WINDOWS + convert_path_to_posix(path); +#endif + request += escape_path(path.c_str(), path.length()); + } + request += " HTTP/1.1\r\n"; + add_headers(request, m_settings, using_proxy); + request += "\r\nRange: bytes="; + request += to_string(f.offset).elems; + request += "-"; + request += to_string(f.offset + f.size - 1).elems; + 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 + TORRENT_ASSERT(f.file_index >= 0); + + m_file_requests.push_back(file_req); + } + } + + if (num_pad_files == int(m_file_requests.size())) + { + get_io_service().post(boost::bind( + &web_peer_connection::on_receive_padfile, + boost::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.c_str(), request.size(), message_type_request); +} + +namespace { + + std::string get_peer_name(http_parser const& p, std::string const& host) + { + std::string ret = "URL seed @ "; + ret += host; + + std::string const& server_version = p.header("server"); + if (!server_version.empty()) + { + ret += " ("; + ret += server_version; + ret += ")"; + } + return ret; + } + + boost::tuple get_range( + http_parser const& parser, error_code& ec) + { + boost::int64_t range_start; + boost::int64_t range_end; + if (parser.status_code() == 206) + { + boost::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 boost::tuple(range_start, range_end); + } +} + +// -------------------------- +// RECEIVE DATA +// -------------------------- + +bool web_peer_connection::received_invalid_data(int 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 + + boost::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. + int fi = files[0].file_index; + int first_piece = fs.file_offset(fi) / fs.piece_length(); + // one past last piece + int end_piece = int((fs.file_offset(fi) + fs.file_size(fi) + 1) / fs.piece_length()); + for (int i = first_piece; i < end_piece; ++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 bytes_left) +{ + boost::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 + int retry_time = atoi(m_parser.header("retry-after").c_str()); + if (retry_time <= 0) retry_time = m_settings.get_int(settings_pack::urlseed_wait_retry); + // temporarily unavailable, retry later + t->retry_web_seed(this, retry_time); + std::string error_msg = to_string(m_parser.status_code()).elems + + (" " + m_parser.message()); + if (t->alerts().should_post()) + { + t->alerts().emplace_alert(t->get_handle(), m_url + , error_msg); + } + received_bytes(0, bytes_left); + disconnect(error_code(m_parser.status_code(), get_http_category()), op_bittorrent, 1); + return; +} + +void web_peer_connection::handle_redirect(int 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); + + boost::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + + if (location.empty()) + { + // we should not try this server again. + t->remove_web_seed(this, errors::missing_location, op_bittorrent, 2); + m_web = NULL; + TORRENT_ASSERT(is_disconnecting()); + return; + } + + bool const single_file_request = !m_path.empty() + && m_path[m_path.size() - 1] != '/'; + + // add the redirected url and remove the current one + if (!single_file_request) + { + TORRENT_ASSERT(!m_file_requests.empty()); + int const file_index = m_file_requests.front().file_index; + + if (!t->need_loaded()) + { + disconnect(errors::torrent_aborted, op_bittorrent); + return; + } + // TODO: 2 create a mapping of file-index to redirection URLs. Use that to form + // URLs instead. Support to reconnect to a new server without destructing this + // peer_connection + torrent_info const& info = t->torrent_file(); + std::string path = info.orig_files().file_path(file_index); +#ifdef TORRENT_WINDOWS + convert_path_to_posix(path); +#endif + path = escape_path(path.c_str(), path.length()); + size_t i = location.rfind(path); + if (i == std::string::npos) + { + t->remove_web_seed(this, errors::invalid_redirection, op_bittorrent, 2); + m_web = NULL; + TORRENT_ASSERT(is_disconnecting()); + return; + } + location.resize(i); + } + 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); + t->remove_web_seed(this, errors::redirecting, op_bittorrent, 2); + m_web = NULL; + TORRENT_ASSERT(is_disconnecting()); + return; +} + +void web_peer_connection::on_receive(error_code const& error + , std::size_t bytes_transferred) +{ + INVARIANT_CHECK; + + if (error) + { + received_bytes(0, bytes_transferred); +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ERROR" + , "web_peer_connection error: %s", error.message().c_str()); +#endif + return; + } + + boost::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 + buffer::const_interval 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; + boost::tie(payload, protocol) = m_parser.incoming(recv_buffer, failed); + received_bytes(0, protocol); + TORRENT_ASSERT(int(recv_buffer.left()) >= protocol); + + if (failed) + { + received_bytes(0, recv_buffer.left()); +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "RECEIVE_BYTES" + , "%s", std::string(recv_buffer.begin, recv_buffer.end).c_str()); +#endif + disconnect(errors::http_parse_error, op_bittorrent, 2); + return; + } + + TORRENT_ASSERT(recv_buffer.left() == 0 || *recv_buffer.begin == 'H'); + TORRENT_ASSERT(recv_buffer.left() <= 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 + peer_log(peer_log_alert::info, "STATUS" + , "%d %s", m_parser.status_code(), m_parser.message().c_str()); + std::multimap const& headers = m_parser.headers(); + for (std::multimap::const_iterator i = headers.begin() + , end(headers.end()); i != end; ++i) + 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())) + { + handle_error(recv_buffer.left()); + return; + } + + if (is_redirect(m_parser.status_code())) + { + handle_redirect(recv_buffer.left()); + return; + } + + m_server_string = get_peer_name(m_parser, m_host); + + recv_buffer.begin += m_body_start; + + m_body_start = m_parser.body_start(); + m_received_body = 0; + } + + // we only received the header, no data + if (recv_buffer.left() == 0) 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 + boost::int64_t range_start; + boost::int64_t range_end; + error_code ec; + boost::tie(range_start, range_end) = get_range(m_parser, ec); + if (ec) + { + received_bytes(0, recv_buffer.left()); + // we should not try this server again. + t->remove_web_seed(this, ec, op_bittorrent, 2); + m_web = NULL; + 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, recv_buffer.left()); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming, "INVALID HTTP RESPONSE" + , "in=(%d, %" PRId64 "-%" PRId64 ") expected=(%d, %" PRId64 "-%" PRId64 ") ]" + , file_req.file_index, range_start, range_end + , file_req.file_index, file_req.start, file_req.start + file_req.length - 1); +#endif + disconnect(errors::invalid_range, op_bittorrent, 2); + return; + } + + if (m_parser.chunked_encoding()) + { + + // ========================= + // === CHUNKED ENCODING === + // ========================= + + while (m_chunk_pos >= 0 && recv_buffer.left() > 0) + { + // 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, recv_buffer.left()); + 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, recv_buffer.left()); + +#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, op_bittorrent, 2); + return; + } + incoming_payload(recv_buffer.begin, copy_size); + + recv_buffer.begin += copy_size; + m_chunk_pos -= copy_size; + + if (recv_buffer.left() == 0) goto done; + } + + TORRENT_ASSERT(m_chunk_pos == 0); + + int header_size = 0; + boost::int64_t chunk_size = 0; + buffer::const_interval chunk_start = recv_buffer; + chunk_start.begin += m_chunk_pos; + TORRENT_ASSERT(chunk_start.begin[0] == '\r' + || detail::is_hex(chunk_start.begin, 1)); + bool ret = m_parser.parse_chunk_header(chunk_start, &chunk_size, &header_size); + if (!ret) + { + received_bytes(0, chunk_start.left() - m_partial_chunk_header); + m_partial_chunk_header = chunk_start.left(); + 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 + || chunk_start.left() <= header_size || chunk_start.begin[header_size] == 'H'); + TORRENT_ASSERT(m_body_start + m_chunk_pos < INT_MAX); + m_chunk_pos += chunk_size; + recv_buffer.begin += 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); + +#ifdef TORRENT_DEBUG + chunk_start = recv_buffer; + chunk_start.begin += m_chunk_pos; + TORRENT_ASSERT(chunk_start.left() == 0 || chunk_start.begin[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, recv_buffer.left()); + +#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, op_bittorrent, 2); + 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.left() == 0) 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 + , recv_buffer.left()); + incoming_payload(recv_buffer.begin, copy_size); + recv_buffer.begin += 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.left() == 0) break; + } +done: + + // now, remove all the bytes we've processed from the receive buffer + m_recv_buffer.cut(recv_buffer.begin - 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[0] + piece_size, buf, 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) + { + boost::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" + , front_request.piece, front_request.start, front_request.length); +#endif + m_requests.pop_front(); + + incoming_piece(front_request, &m_piece[0]); + 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; + + boost::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" + , front_request.piece, front_request.start, front_request.length); +#endif + m_requests.pop_front(); + + incoming_piece(front_request, &m_piece[0]); + 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; + + boost::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 + boost::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(m_piece.size() < front_request.length); + + int pad_size = int((std::min)(file_size + , boost::int64_t(front_request.length - m_piece.size()))); + TORRENT_ASSERT(pad_size > 0); + file_size -= pad_size; + + incoming_zeroes(pad_size); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "HANDLE_PADFILE" + , "file: %d start: %" PRId64 " len: %d" + , 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/xml_parse.cpp b/src/xml_parse.cpp new file mode 100644 index 0000000..d917bf8 --- /dev/null +++ b/src/xml_parse.cpp @@ -0,0 +1,196 @@ +/* + +Copyright (c) 2007-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/xml_parse.hpp" +#include "libtorrent/string_util.hpp" + +namespace libtorrent +{ + + TORRENT_EXTRA_EXPORT void xml_parse(char const* p, char const* end + , boost::function callback) + { + for(;p != end; ++p) + { + char const* start = p; + int token; + // look for tag start + for(; p != end && *p != '<'; ++p); + + if (p != start) + { + token = xml_string; + const int name_len = p - start; + callback(token, start, name_len, NULL, 0); + } + + 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) + { + token = xml_parse_error; + start = "unexpected end of file"; + callback(token, start, strlen(start), NULL, 0); + break; + } + + token = xml_string; + const int name_len = p - start - 2; + callback(token, start, name_len, NULL, 0); + 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) + { + token = xml_parse_error; + start = "unexpected end of file"; + callback(token, start, strlen(start), NULL, 0); + break; + } + + TORRENT_ASSERT(*p == '>'); + + char const* tag_end = p; + if (*start == '/') + { + ++start; + token = xml_end_tag; + const int name_len = tag_name_end - start; + callback(token, start, name_len, NULL, 0); + } + else if (*(p-1) == '/') + { + token = xml_empty_tag; + const int name_len = (std::min)(tag_name_end - start, p - start - 1); + callback(token, start, name_len, NULL, 0); + tag_end = p - 1; + } + else if (*start == '?' && *(p-1) == '?') + { + ++start; + token = xml_declaration_tag; + const int name_len = (std::min)(tag_name_end - start, p - start - 1); + callback(token, start, name_len, NULL, 0); + tag_end = p - 1; + } + else if (start + 5 < p && std::memcmp(start, "!--", 3) == 0 && std::memcmp(p-2, "--", 2) == 0) + { + start += 3; + token = xml_comment; + const int name_len = tag_name_end - start - 2; + callback(token, start, name_len, NULL, 0); + tag_end = p - 2; + continue; + } + else + { + token = xml_start_tag; + const int name_len = tag_name_end - start; + callback(token, start, name_len, NULL, 0); + } + + // parse attributes + for (char const* i = tag_name_end; i < tag_end; ++i) + { + char const* val_start = NULL; + + // find start of attribute name + for (; i != tag_end && is_space(*i); ++i); + if (i == tag_end) break; + start = i; + // find end of attribute name + for (; i != tag_end && *i != '=' && !is_space(*i); ++i); + const int name_len = 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) + { + token = xml_tag_content; + callback(token, start, i - start, NULL, 0); + break; + } + + ++i; + for (; i != tag_end && is_space(*i); ++i); + // check for parse error (values must be quoted) + if (i == tag_end || (*i != '\'' && *i != '\"')) + { + token = xml_parse_error; + start = "unquoted attribute value"; + callback(token, start, strlen(start), NULL, 0); + break; + } + char quote = *i; + ++i; + val_start = i; + for (; i != tag_end && *i != quote; ++i); + // parse error (missing end quote) + if (i == tag_end) + { + token = xml_parse_error; + start = "missing end quote on attribute"; + callback(token, start, strlen(start), NULL, 0); + break; + } + const int val_len = i - val_start; + token = xml_attribute; + callback(token, start, name_len, val_start, val_len); + } + } + } + +} + diff --git a/test/Jamfile b/test/Jamfile new file mode 100644 index 0000000..b5e6933 --- /dev/null +++ b/test/Jamfile @@ -0,0 +1,235 @@ +import testing ; +import feature : feature ; + +use-project /torrent : .. ; + +exe test_natpmp : test_natpmp.cpp /torrent//torrent + : multi on full ; + +exe enum_if : enum_if.cpp /torrent//torrent + : multi on full ; + +exe bdecode_benchmark : bdecode_benchmark.cpp /torrent//torrent + : release ; + +explicit test_natpmp ; +explicit enum_if ; +explicit bdecode_benchmark ; + +lib libtorrent_test + : # sources + main.cpp + test.cpp + setup_transfer.cpp + dht_server.cpp + udp_tracker.cpp + peer_server.cpp + bittorrent_peer.cpp + print_alerts.cpp + web_seed_suite.cpp + swarm_suite.cpp + test_utils.cpp + settings.cpp + make_torrent.cpp + + : # requirements + # this is used to determine whether + # symbols are exported or imported + shared:TORRENT_BUILDING_TEST_SHARED + shared:ED25519_BUILD_DLL + ../ed25519/src + windows:advapi32 + /torrent//torrent + on + darwin:-Wno-unused-command-line-argument + + : # default build + shared + + : # user-requirements + shared:TORRENT_LINK_TEST_SHARED + . +; + +explicit libtorrent_test ; + +lib advapi32 : : Advapi32 ; + +project + : requirements + on + libtorrent_test + /torrent//torrent + darwin:-Wno-unused-command-line-argument + : default-build + multi + full + shared + on + on + on + ; + +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 ; + +test-suite libtorrent : + [ run + test_primitives.cpp + test_create_torrent.cpp + test_packet_buffer.cpp + test_timestamp_history.cpp + test_sha1_hash.cpp + test_bloom_filter.cpp + test_identify_client.cpp + test_merkle.cpp + test_resolve_links.cpp + test_crc32.cpp + test_heterogeneous_queue.cpp + test_ip_voter.cpp + test_sliding_average.cpp + test_socket_io.cpp +# test_random.cpp + test_utf8.cpp + test_bitfield.cpp + test_part_file.cpp + test_peer_list.cpp + test_torrent_info.cpp + test_time.cpp + test_file_storage.cpp + test_peer_priority.cpp + test_threads.cpp + test_tailqueue.cpp + test_bandwidth_limiter.cpp + test_buffer.cpp + test_piece_picker.cpp + test_bencoding.cpp + test_bdecode.cpp + test_http_parser.cpp + test_string.cpp + test_xml.cpp + test_ip_filter.cpp + test_hasher.cpp + test_dht_storage.cpp + test_dht.cpp + test_block_cache.cpp + test_peer_classes.cpp + test_settings_pack.cpp + test_fence.cpp + test_dos_blocker.cpp + test_stat_cache.cpp + test_enum_net.cpp + test_linked_list.cpp + test_file_progress.cpp ] + + [ run test_gzip.cpp ] + [ run test_receive_buffer.cpp ] + [ run test_alert_manager.cpp ] + [ run test_direct_dht.cpp ] + [ run test_magnet.cpp ] + [ run test_storage.cpp ] + [ run test_session.cpp ] + [ run test_read_piece.cpp ] + + [ run test_file.cpp ] + [ run test_fast_extension.cpp ] + [ run test_privacy.cpp ] + [ run test_recheck.cpp ] + [ run test_resume.cpp ] + [ run test_ssl.cpp ] + [ run test_tracker.cpp ] + [ run test_checking.cpp ] + [ run test_url_seed.cpp ] + [ run test_web_seed.cpp ] + [ run test_web_seed_redirect.cpp ] + [ run test_web_seed_socks4.cpp ] + [ run test_web_seed_socks5.cpp ] + [ run test_web_seed_socks5_no_peers.cpp ] + [ run test_web_seed_socks5_pw.cpp ] + [ run test_web_seed_http.cpp ] + [ run test_web_seed_http_pw.cpp ] + [ run test_web_seed_chunked.cpp ] + [ run test_web_seed_ban.cpp ] + [ run test_pe_crypto.cpp ] + + [ run test_remap_files.cpp ] + [ run test_utp.cpp ] + [ run test_auto_unchoke.cpp ] + [ run test_http_connection.cpp ] + [ run test_torrent.cpp ] + [ run test_transfer.cpp ] + [ run test_time_critical.cpp ] + [ run test_pex.cpp ] + [ run test_priority.cpp ] + +# turn these tests into simulations + [ run test_upnp.cpp ] + [ run test_lsd.cpp ] + ; + +# these are the tests run on appveyor, while the flapping ones are being +# transitioned into simulations +alias win-tests : + test_primitives + test_pe_crypto + test_remap_files + test_auto_unchoke + test_torrent + test_transfer + test_time_critical + test_pex + test_priority + test_storage + test_session + test_read_piece + test_file + test_fast_extension + test_recheck + test_resume + test_tracker + test_checking + test_gzip + ; + +# the openssl test hangs on the travis osx machine. difficult to debug +alias osx-tests : + test_primitives + test_alert_manager + test_direct_dht + test_magnet + test_storage + test_session + test_read_piece + test_file + test_fast_extension + test_privacy + test_recheck + test_resume +# test_ssl + test_tracker + test_checking + test_url_seed + test_web_seed + test_web_seed_redirect + test_web_seed_socks4 + test_web_seed_socks5 + test_web_seed_socks5_pw + test_web_seed_http + test_web_seed_http_pw + test_web_seed_chunked + test_web_seed_ban + test_pe_crypto + test_remap_files + test_utp + test_auto_unchoke + test_http_connection + test_torrent + test_transfer + test_time_critical + test_pex + test_priority + test_gzip +; + +explicit win-tests ; + diff --git a/test/Makefile.am b/test/Makefile.am new file mode 100644 index 0000000..f4695c8 --- /dev/null +++ b/test/Makefile.am @@ -0,0 +1,242 @@ +AUTOMAKE_OPTIONS = subdir-objects + +benchmark_programs = \ + bdecode_benchmark + +test_programs = \ + test_primitives \ + test_recheck \ + test_stat_cache \ + test_file \ + test_privacy \ + test_priority \ + test_auto_unchoke \ + test_checking \ + test_fast_extension \ + test_http_connection \ + test_lsd \ + test_pe_crypto \ + test_pex \ + test_read_piece \ + test_receive_buffer \ + test_resume \ + test_ssl \ + test_storage \ + test_time_critical \ + test_torrent \ + test_tracker \ + test_transfer \ + test_create_torrent \ + enum_if \ + test_utp \ + test_session \ + test_web_seed \ + test_web_seed_ban \ + test_web_seed_chunked \ + test_web_seed_http \ + test_web_seed_http_pw \ + test_web_seed_redirect \ + test_web_seed_socks4 \ + test_web_seed_socks5 \ + test_web_seed_socks5_no_peers \ + test_web_seed_socks5_pw \ + test_url_seed \ + test_remap_files \ + test_enum_net \ + test_file_progress \ + test_linked_list \ + test_direct_dht + +if ENABLE_TESTS +check_PROGRAMS = $(test_programs) $(benchmark_programs) +noinst_LTLIBRARIES = libtest.la +endif + +TESTS = $(test_programs) + +EXTRA_DIST = Jamfile \ + test_torrents/backslash_path.torrent \ + test_torrents/base.torrent \ + test_torrents/creation_date.torrent \ + test_torrents/duplicate_files.torrent \ + test_torrents/duplicate_web_seeds.torrent \ + test_torrents/empty_httpseed.torrent \ + test_torrents/empty_path.torrent \ + test_torrents/empty_path_multi.torrent \ + test_torrents/hidden_parent_path.torrent \ + test_torrents/httpseed.torrent \ + test_torrents/invalid_file_size.torrent \ + test_torrents/invalid_info.torrent \ + test_torrents/invalid_name.torrent \ + test_torrents/invalid_name2.torrent \ + test_torrents/invalid_name3.torrent \ + test_torrents/invalid_path_list.torrent \ + test_torrents/invalid_piece_len.torrent \ + test_torrents/invalid_pieces.torrent \ + test_torrents/invalid_root_hash.torrent \ + test_torrents/invalid_root_hash2.torrent \ + test_torrents/invalid_symlink.torrent \ + test_torrents/long_name.torrent \ + test_torrents/missing_path_list.torrent \ + test_torrents/missing_piece_len.torrent \ + test_torrents/negative_file_size.torrent \ + test_torrents/negative_piece_len.torrent \ + test_torrents/negative_size.torrent \ + test_torrents/no_creation_date.torrent \ + test_torrents/no_name.torrent \ + test_torrents/pad_file.torrent \ + test_torrents/pad_file_no_path.torrent \ + test_torrents/parent_path.torrent \ + test_torrents/root_hash.torrent \ + test_torrents/sample.torrent \ + test_torrents/single_multi_file.torrent \ + test_torrents/slash_path.torrent \ + test_torrents/slash_path2.torrent \ + test_torrents/slash_path3.torrent \ + test_torrents/string.torrent \ + test_torrents/symlink1.torrent \ + test_torrents/symlink_zero_size.torrent \ + test_torrents/unaligned_pieces.torrent \ + test_torrents/unordered.torrent \ + test_torrents/url_list.torrent \ + test_torrents/url_list2.torrent \ + test_torrents/url_list3.torrent \ + test_torrents/url_seed.torrent \ + test_torrents/url_seed_multi.torrent \ + test_torrents/url_seed_multi_space.torrent \ + test_torrents/url_seed_multi_space_nolist.torrent \ + test_torrents/whitespace_url.torrent \ + mutable_test_torrents/test1.torrent \ + mutable_test_torrents/test1_pad_files.torrent \ + mutable_test_torrents/test1_single.torrent\ + mutable_test_torrents/test1_single_padded.torrent \ + mutable_test_torrents/test2.torrent \ + mutable_test_torrents/test2_pad_files.torrent \ + mutable_test_torrents/test3.torrent \ + mutable_test_torrents/test3_pad_files.torrent \ + zeroes.gz \ + corrupt.gz \ + utf8_test.txt \ + web_server.py \ + socks.py \ + http.py + +EXTRA_PROGRAMS = $(test_programs) $(benchmark_programs) + +noinst_HEADERS = test.hpp setup_transfer.hpp dht_server.hpp \ + peer_server.hpp udp_tracker.hpp web_seed_suite.hpp swarm_suite.hpp \ + test_utils.hpp settings.hpp make_torrent.hpp bittorrent_peer.hpp \ + print_alerts.hpp + +libtest_la_SOURCES = main.cpp \ + test.cpp \ + setup_transfer.cpp \ + dht_server.cpp \ + udp_tracker.cpp \ + peer_server.cpp \ + bittorrent_peer.cpp \ + make_torrent.cpp \ + web_seed_suite.cpp \ + swarm_suite.cpp \ + test_utils.cpp \ + settings.cpp \ + print_alerts.cpp + +test_primitives_SOURCES = \ + test_primitives.cpp \ + test_packet_buffer.cpp \ + test_timestamp_history.cpp \ + test_sha1_hash.cpp \ + test_bloom_filter.cpp \ + test_identify_client.cpp \ + test_merkle.cpp \ + test_alert_manager.cpp \ + test_resolve_links.cpp \ + test_crc32.cpp \ + test_heterogeneous_queue.cpp \ + test_ip_voter.cpp \ + test_sliding_average.cpp \ + test_socket_io.cpp \ + test_random.cpp \ + test_utf8.cpp \ + test_gzip.cpp \ + test_bitfield.cpp \ + test_part_file.cpp \ + test_peer_list.cpp \ + test_torrent_info.cpp \ + test_time.cpp \ + test_file_storage.cpp \ + test_peer_priority.cpp \ + test_threads.cpp \ + test_tailqueue.cpp \ + test_bandwidth_limiter.cpp \ + test_buffer.cpp \ + test_piece_picker.cpp \ + test_bencoding.cpp \ + test_bdecode.cpp \ + test_http_parser.cpp \ + test_string.cpp \ + test_magnet.cpp \ + test_xml.cpp \ + test_ip_filter.cpp \ + test_hasher.cpp \ + test_dht_storage.cpp \ + test_dht.cpp \ + test_block_cache.cpp \ + test_peer_classes.cpp \ + test_settings_pack.cpp \ + test_fence.cpp \ + test_dos_blocker.cpp \ + test_upnp.cpp + +bdecode_benchmark_SOURCES = bdecode_benchmark.cpp +test_recheck_SOURCES = test_recheck.cpp +test_stat_cache_SOURCES = test_stat_cache.cpp +test_file_SOURCES = test_file.cpp +test_privacy_SOURCES = test_privacy.cpp +test_priority_SOURCES = test_priority.cpp +test_auto_unchoke_SOURCES = test_auto_unchoke.cpp +test_checking_SOURCES = test_checking.cpp +test_enum_net_SOURCES = test_enum_net.cpp +test_fast_extension_SOURCES = test_fast_extension.cpp +test_http_connection_SOURCES = test_http_connection.cpp +test_lsd_SOURCES = test_lsd.cpp +test_pe_crypto_SOURCES = test_pe_crypto.cpp +test_pex_SOURCES = test_pex.cpp +test_read_piece_SOURCES = test_read_piece.cpp +test_receive_buffer_SOURCES = test_receive_buffer.cpp +test_storage_SOURCES = test_storage.cpp +test_time_critical_SOURCES = test_time_critical.cpp +test_resume_SOURCES = test_resume.cpp +test_ssl_SOURCES = test_ssl.cpp +test_torrent_SOURCES = test_torrent.cpp +test_tracker_SOURCES = test_tracker.cpp +test_transfer_SOURCES = test_transfer.cpp +test_create_torrent_SOURCES = test_create_torrent.cpp +enum_if_SOURCES = enum_if.cpp +test_utp_SOURCES = test_utp.cpp +test_session_SOURCES = test_session.cpp +test_web_seed_SOURCES = test_web_seed.cpp +test_web_seed_ban_SOURCES = test_web_seed_ban.cpp +test_web_seed_chunked_SOURCES = test_web_seed_chunked.cpp +test_web_seed_http_SOURCES = test_web_seed_http.cpp +test_web_seed_http_pw_SOURCES = test_web_seed_http_pw.cpp +test_web_seed_redirect_SOURCES = test_web_seed_redirect.cpp +test_web_seed_socks4_SOURCES = test_web_seed_socks4.cpp +test_web_seed_socks5_SOURCES = test_web_seed_socks5.cpp +test_web_seed_socks5_no_peers_SOURCES = test_web_seed_socks5_no_peers.cpp +test_web_seed_socks5_pw_SOURCES = test_web_seed_socks5_pw.cpp +test_url_seed_SOURCES = test_url_seed.cpp +test_remap_files_SOURCES = test_remap_files.cpp +test_file_progress_SOURCES = test_file_progress.cpp +test_linked_list_SOURCES = test_linked_list.cpp +test_direct_dht_SOURCES = test_direct_dht.cpp + +LDADD = libtest.la $(top_builddir)/src/libtorrent-rasterbar.la + +#AM_CXXFLAGS=-ftemplate-depth-50 -I$(top_srcdir)/include -I$(top_srcdir)/include/libtorrent @DEBUGFLAGS@ @PTHREAD_CFLAGS@ +AM_CPPFLAGS=-ftemplate-depth-50 -I$(top_srcdir)/include @DEBUGFLAGS@ + +AM_LDFLAGS=@BOOST_SYSTEM_LIB@ @PTHREAD_LIBS@ @OPENSSL_LDFLAGS@ @OPENSSL_LIBS@ \ + @BOOST_CHRONO_LIB@ + diff --git a/test/Makefile.in b/test/Makefile.in new file mode 100644 index 0000000..6b5f2a9 --- /dev/null +++ b/test/Makefile.in @@ -0,0 +1,2130 @@ +# Makefile.in generated by automake 1.15 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2014 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +target_triplet = @target@ +@ENABLE_TESTS_TRUE@check_PROGRAMS = $(am__EXEEXT_1) $(am__EXEEXT_2) +TESTS = $(am__EXEEXT_1) +EXTRA_PROGRAMS = $(am__EXEEXT_1) $(am__EXEEXT_2) +subdir = test +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_boost_base.m4 \ + $(top_srcdir)/m4/ax_boost_chrono.m4 \ + $(top_srcdir)/m4/ax_boost_python.m4 \ + $(top_srcdir)/m4/ax_boost_random.m4 \ + $(top_srcdir)/m4/ax_boost_system.m4 \ + $(top_srcdir)/m4/ax_check_openssl.m4 \ + $(top_srcdir)/m4/ax_pthread.m4 \ + $(top_srcdir)/m4/ax_python_devel.m4 \ + $(top_srcdir)/m4/gettext-lib.m4 $(top_srcdir)/m4/iconv.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/pkgconfig.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +LTLIBRARIES = $(noinst_LTLIBRARIES) +libtest_la_LIBADD = +am_libtest_la_OBJECTS = main.lo test.lo setup_transfer.lo \ + dht_server.lo udp_tracker.lo peer_server.lo bittorrent_peer.lo \ + make_torrent.lo web_seed_suite.lo swarm_suite.lo test_utils.lo \ + settings.lo print_alerts.lo +libtest_la_OBJECTS = $(am_libtest_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +@ENABLE_TESTS_TRUE@am_libtest_la_rpath = +am__EXEEXT_1 = test_primitives$(EXEEXT) test_recheck$(EXEEXT) \ + test_stat_cache$(EXEEXT) test_file$(EXEEXT) \ + test_privacy$(EXEEXT) test_priority$(EXEEXT) \ + test_auto_unchoke$(EXEEXT) test_checking$(EXEEXT) \ + test_fast_extension$(EXEEXT) test_http_connection$(EXEEXT) \ + test_lsd$(EXEEXT) test_pe_crypto$(EXEEXT) test_pex$(EXEEXT) \ + test_read_piece$(EXEEXT) test_receive_buffer$(EXEEXT) \ + test_resume$(EXEEXT) test_ssl$(EXEEXT) test_storage$(EXEEXT) \ + test_time_critical$(EXEEXT) test_torrent$(EXEEXT) \ + test_tracker$(EXEEXT) test_transfer$(EXEEXT) \ + test_create_torrent$(EXEEXT) enum_if$(EXEEXT) \ + test_utp$(EXEEXT) test_session$(EXEEXT) test_web_seed$(EXEEXT) \ + test_web_seed_ban$(EXEEXT) test_web_seed_chunked$(EXEEXT) \ + test_web_seed_http$(EXEEXT) test_web_seed_http_pw$(EXEEXT) \ + test_web_seed_redirect$(EXEEXT) test_web_seed_socks4$(EXEEXT) \ + test_web_seed_socks5$(EXEEXT) \ + test_web_seed_socks5_no_peers$(EXEEXT) \ + test_web_seed_socks5_pw$(EXEEXT) test_url_seed$(EXEEXT) \ + test_remap_files$(EXEEXT) test_enum_net$(EXEEXT) \ + test_file_progress$(EXEEXT) test_linked_list$(EXEEXT) \ + test_direct_dht$(EXEEXT) +am__EXEEXT_2 = bdecode_benchmark$(EXEEXT) +am_bdecode_benchmark_OBJECTS = bdecode_benchmark.$(OBJEXT) +bdecode_benchmark_OBJECTS = $(am_bdecode_benchmark_OBJECTS) +bdecode_benchmark_LDADD = $(LDADD) +bdecode_benchmark_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_enum_if_OBJECTS = enum_if.$(OBJEXT) +enum_if_OBJECTS = $(am_enum_if_OBJECTS) +enum_if_LDADD = $(LDADD) +enum_if_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_auto_unchoke_OBJECTS = test_auto_unchoke.$(OBJEXT) +test_auto_unchoke_OBJECTS = $(am_test_auto_unchoke_OBJECTS) +test_auto_unchoke_LDADD = $(LDADD) +test_auto_unchoke_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_checking_OBJECTS = test_checking.$(OBJEXT) +test_checking_OBJECTS = $(am_test_checking_OBJECTS) +test_checking_LDADD = $(LDADD) +test_checking_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_create_torrent_OBJECTS = test_create_torrent.$(OBJEXT) +test_create_torrent_OBJECTS = $(am_test_create_torrent_OBJECTS) +test_create_torrent_LDADD = $(LDADD) +test_create_torrent_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_direct_dht_OBJECTS = test_direct_dht.$(OBJEXT) +test_direct_dht_OBJECTS = $(am_test_direct_dht_OBJECTS) +test_direct_dht_LDADD = $(LDADD) +test_direct_dht_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_enum_net_OBJECTS = test_enum_net.$(OBJEXT) +test_enum_net_OBJECTS = $(am_test_enum_net_OBJECTS) +test_enum_net_LDADD = $(LDADD) +test_enum_net_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_fast_extension_OBJECTS = test_fast_extension.$(OBJEXT) +test_fast_extension_OBJECTS = $(am_test_fast_extension_OBJECTS) +test_fast_extension_LDADD = $(LDADD) +test_fast_extension_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_file_OBJECTS = test_file.$(OBJEXT) +test_file_OBJECTS = $(am_test_file_OBJECTS) +test_file_LDADD = $(LDADD) +test_file_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_file_progress_OBJECTS = test_file_progress.$(OBJEXT) +test_file_progress_OBJECTS = $(am_test_file_progress_OBJECTS) +test_file_progress_LDADD = $(LDADD) +test_file_progress_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_http_connection_OBJECTS = test_http_connection.$(OBJEXT) +test_http_connection_OBJECTS = $(am_test_http_connection_OBJECTS) +test_http_connection_LDADD = $(LDADD) +test_http_connection_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_linked_list_OBJECTS = test_linked_list.$(OBJEXT) +test_linked_list_OBJECTS = $(am_test_linked_list_OBJECTS) +test_linked_list_LDADD = $(LDADD) +test_linked_list_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_lsd_OBJECTS = test_lsd.$(OBJEXT) +test_lsd_OBJECTS = $(am_test_lsd_OBJECTS) +test_lsd_LDADD = $(LDADD) +test_lsd_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_pe_crypto_OBJECTS = test_pe_crypto.$(OBJEXT) +test_pe_crypto_OBJECTS = $(am_test_pe_crypto_OBJECTS) +test_pe_crypto_LDADD = $(LDADD) +test_pe_crypto_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_pex_OBJECTS = test_pex.$(OBJEXT) +test_pex_OBJECTS = $(am_test_pex_OBJECTS) +test_pex_LDADD = $(LDADD) +test_pex_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_primitives_OBJECTS = test_primitives.$(OBJEXT) \ + test_packet_buffer.$(OBJEXT) test_timestamp_history.$(OBJEXT) \ + test_sha1_hash.$(OBJEXT) test_bloom_filter.$(OBJEXT) \ + test_identify_client.$(OBJEXT) test_merkle.$(OBJEXT) \ + test_alert_manager.$(OBJEXT) test_resolve_links.$(OBJEXT) \ + test_crc32.$(OBJEXT) test_heterogeneous_queue.$(OBJEXT) \ + test_ip_voter.$(OBJEXT) test_sliding_average.$(OBJEXT) \ + test_socket_io.$(OBJEXT) test_random.$(OBJEXT) \ + test_utf8.$(OBJEXT) test_gzip.$(OBJEXT) \ + test_bitfield.$(OBJEXT) test_part_file.$(OBJEXT) \ + test_peer_list.$(OBJEXT) test_torrent_info.$(OBJEXT) \ + test_time.$(OBJEXT) test_file_storage.$(OBJEXT) \ + test_peer_priority.$(OBJEXT) test_threads.$(OBJEXT) \ + test_tailqueue.$(OBJEXT) test_bandwidth_limiter.$(OBJEXT) \ + test_buffer.$(OBJEXT) test_piece_picker.$(OBJEXT) \ + test_bencoding.$(OBJEXT) test_bdecode.$(OBJEXT) \ + test_http_parser.$(OBJEXT) test_string.$(OBJEXT) \ + test_magnet.$(OBJEXT) test_xml.$(OBJEXT) \ + test_ip_filter.$(OBJEXT) test_hasher.$(OBJEXT) \ + test_dht_storage.$(OBJEXT) test_dht.$(OBJEXT) \ + test_block_cache.$(OBJEXT) test_peer_classes.$(OBJEXT) \ + test_settings_pack.$(OBJEXT) test_fence.$(OBJEXT) \ + test_dos_blocker.$(OBJEXT) test_upnp.$(OBJEXT) +test_primitives_OBJECTS = $(am_test_primitives_OBJECTS) +test_primitives_LDADD = $(LDADD) +test_primitives_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_priority_OBJECTS = test_priority.$(OBJEXT) +test_priority_OBJECTS = $(am_test_priority_OBJECTS) +test_priority_LDADD = $(LDADD) +test_priority_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_privacy_OBJECTS = test_privacy.$(OBJEXT) +test_privacy_OBJECTS = $(am_test_privacy_OBJECTS) +test_privacy_LDADD = $(LDADD) +test_privacy_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_read_piece_OBJECTS = test_read_piece.$(OBJEXT) +test_read_piece_OBJECTS = $(am_test_read_piece_OBJECTS) +test_read_piece_LDADD = $(LDADD) +test_read_piece_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_receive_buffer_OBJECTS = test_receive_buffer.$(OBJEXT) +test_receive_buffer_OBJECTS = $(am_test_receive_buffer_OBJECTS) +test_receive_buffer_LDADD = $(LDADD) +test_receive_buffer_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_recheck_OBJECTS = test_recheck.$(OBJEXT) +test_recheck_OBJECTS = $(am_test_recheck_OBJECTS) +test_recheck_LDADD = $(LDADD) +test_recheck_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_remap_files_OBJECTS = test_remap_files.$(OBJEXT) +test_remap_files_OBJECTS = $(am_test_remap_files_OBJECTS) +test_remap_files_LDADD = $(LDADD) +test_remap_files_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_resume_OBJECTS = test_resume.$(OBJEXT) +test_resume_OBJECTS = $(am_test_resume_OBJECTS) +test_resume_LDADD = $(LDADD) +test_resume_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_session_OBJECTS = test_session.$(OBJEXT) +test_session_OBJECTS = $(am_test_session_OBJECTS) +test_session_LDADD = $(LDADD) +test_session_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_ssl_OBJECTS = test_ssl.$(OBJEXT) +test_ssl_OBJECTS = $(am_test_ssl_OBJECTS) +test_ssl_LDADD = $(LDADD) +test_ssl_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_stat_cache_OBJECTS = test_stat_cache.$(OBJEXT) +test_stat_cache_OBJECTS = $(am_test_stat_cache_OBJECTS) +test_stat_cache_LDADD = $(LDADD) +test_stat_cache_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_storage_OBJECTS = test_storage.$(OBJEXT) +test_storage_OBJECTS = $(am_test_storage_OBJECTS) +test_storage_LDADD = $(LDADD) +test_storage_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_time_critical_OBJECTS = test_time_critical.$(OBJEXT) +test_time_critical_OBJECTS = $(am_test_time_critical_OBJECTS) +test_time_critical_LDADD = $(LDADD) +test_time_critical_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_torrent_OBJECTS = test_torrent.$(OBJEXT) +test_torrent_OBJECTS = $(am_test_torrent_OBJECTS) +test_torrent_LDADD = $(LDADD) +test_torrent_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_tracker_OBJECTS = test_tracker.$(OBJEXT) +test_tracker_OBJECTS = $(am_test_tracker_OBJECTS) +test_tracker_LDADD = $(LDADD) +test_tracker_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_transfer_OBJECTS = test_transfer.$(OBJEXT) +test_transfer_OBJECTS = $(am_test_transfer_OBJECTS) +test_transfer_LDADD = $(LDADD) +test_transfer_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_url_seed_OBJECTS = test_url_seed.$(OBJEXT) +test_url_seed_OBJECTS = $(am_test_url_seed_OBJECTS) +test_url_seed_LDADD = $(LDADD) +test_url_seed_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_utp_OBJECTS = test_utp.$(OBJEXT) +test_utp_OBJECTS = $(am_test_utp_OBJECTS) +test_utp_LDADD = $(LDADD) +test_utp_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_web_seed_OBJECTS = test_web_seed.$(OBJEXT) +test_web_seed_OBJECTS = $(am_test_web_seed_OBJECTS) +test_web_seed_LDADD = $(LDADD) +test_web_seed_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_web_seed_ban_OBJECTS = test_web_seed_ban.$(OBJEXT) +test_web_seed_ban_OBJECTS = $(am_test_web_seed_ban_OBJECTS) +test_web_seed_ban_LDADD = $(LDADD) +test_web_seed_ban_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_web_seed_chunked_OBJECTS = test_web_seed_chunked.$(OBJEXT) +test_web_seed_chunked_OBJECTS = $(am_test_web_seed_chunked_OBJECTS) +test_web_seed_chunked_LDADD = $(LDADD) +test_web_seed_chunked_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_web_seed_http_OBJECTS = test_web_seed_http.$(OBJEXT) +test_web_seed_http_OBJECTS = $(am_test_web_seed_http_OBJECTS) +test_web_seed_http_LDADD = $(LDADD) +test_web_seed_http_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_web_seed_http_pw_OBJECTS = test_web_seed_http_pw.$(OBJEXT) +test_web_seed_http_pw_OBJECTS = $(am_test_web_seed_http_pw_OBJECTS) +test_web_seed_http_pw_LDADD = $(LDADD) +test_web_seed_http_pw_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_web_seed_redirect_OBJECTS = test_web_seed_redirect.$(OBJEXT) +test_web_seed_redirect_OBJECTS = $(am_test_web_seed_redirect_OBJECTS) +test_web_seed_redirect_LDADD = $(LDADD) +test_web_seed_redirect_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_web_seed_socks4_OBJECTS = test_web_seed_socks4.$(OBJEXT) +test_web_seed_socks4_OBJECTS = $(am_test_web_seed_socks4_OBJECTS) +test_web_seed_socks4_LDADD = $(LDADD) +test_web_seed_socks4_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_web_seed_socks5_OBJECTS = test_web_seed_socks5.$(OBJEXT) +test_web_seed_socks5_OBJECTS = $(am_test_web_seed_socks5_OBJECTS) +test_web_seed_socks5_LDADD = $(LDADD) +test_web_seed_socks5_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_web_seed_socks5_no_peers_OBJECTS = \ + test_web_seed_socks5_no_peers.$(OBJEXT) +test_web_seed_socks5_no_peers_OBJECTS = \ + $(am_test_web_seed_socks5_no_peers_OBJECTS) +test_web_seed_socks5_no_peers_LDADD = $(LDADD) +test_web_seed_socks5_no_peers_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_web_seed_socks5_pw_OBJECTS = \ + test_web_seed_socks5_pw.$(OBJEXT) +test_web_seed_socks5_pw_OBJECTS = \ + $(am_test_web_seed_socks5_pw_OBJECTS) +test_web_seed_socks5_pw_LDADD = $(LDADD) +test_web_seed_socks5_pw_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ +depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) +LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CXXFLAGS) $(CXXFLAGS) +AM_V_CXX = $(am__v_CXX_@AM_V@) +am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@) +am__v_CXX_0 = @echo " CXX " $@; +am__v_CXX_1 = +CXXLD = $(CXX) +CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \ + $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CXXLD = $(am__v_CXXLD_@AM_V@) +am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@) +am__v_CXXLD_0 = @echo " CXXLD " $@; +am__v_CXXLD_1 = +SOURCES = $(libtest_la_SOURCES) $(bdecode_benchmark_SOURCES) \ + $(enum_if_SOURCES) $(test_auto_unchoke_SOURCES) \ + $(test_checking_SOURCES) $(test_create_torrent_SOURCES) \ + $(test_direct_dht_SOURCES) $(test_enum_net_SOURCES) \ + $(test_fast_extension_SOURCES) $(test_file_SOURCES) \ + $(test_file_progress_SOURCES) $(test_http_connection_SOURCES) \ + $(test_linked_list_SOURCES) $(test_lsd_SOURCES) \ + $(test_pe_crypto_SOURCES) $(test_pex_SOURCES) \ + $(test_primitives_SOURCES) $(test_priority_SOURCES) \ + $(test_privacy_SOURCES) $(test_read_piece_SOURCES) \ + $(test_receive_buffer_SOURCES) $(test_recheck_SOURCES) \ + $(test_remap_files_SOURCES) $(test_resume_SOURCES) \ + $(test_session_SOURCES) $(test_ssl_SOURCES) \ + $(test_stat_cache_SOURCES) $(test_storage_SOURCES) \ + $(test_time_critical_SOURCES) $(test_torrent_SOURCES) \ + $(test_tracker_SOURCES) $(test_transfer_SOURCES) \ + $(test_url_seed_SOURCES) $(test_utp_SOURCES) \ + $(test_web_seed_SOURCES) $(test_web_seed_ban_SOURCES) \ + $(test_web_seed_chunked_SOURCES) $(test_web_seed_http_SOURCES) \ + $(test_web_seed_http_pw_SOURCES) \ + $(test_web_seed_redirect_SOURCES) \ + $(test_web_seed_socks4_SOURCES) \ + $(test_web_seed_socks5_SOURCES) \ + $(test_web_seed_socks5_no_peers_SOURCES) \ + $(test_web_seed_socks5_pw_SOURCES) +DIST_SOURCES = $(libtest_la_SOURCES) $(bdecode_benchmark_SOURCES) \ + $(enum_if_SOURCES) $(test_auto_unchoke_SOURCES) \ + $(test_checking_SOURCES) $(test_create_torrent_SOURCES) \ + $(test_direct_dht_SOURCES) $(test_enum_net_SOURCES) \ + $(test_fast_extension_SOURCES) $(test_file_SOURCES) \ + $(test_file_progress_SOURCES) $(test_http_connection_SOURCES) \ + $(test_linked_list_SOURCES) $(test_lsd_SOURCES) \ + $(test_pe_crypto_SOURCES) $(test_pex_SOURCES) \ + $(test_primitives_SOURCES) $(test_priority_SOURCES) \ + $(test_privacy_SOURCES) $(test_read_piece_SOURCES) \ + $(test_receive_buffer_SOURCES) $(test_recheck_SOURCES) \ + $(test_remap_files_SOURCES) $(test_resume_SOURCES) \ + $(test_session_SOURCES) $(test_ssl_SOURCES) \ + $(test_stat_cache_SOURCES) $(test_storage_SOURCES) \ + $(test_time_critical_SOURCES) $(test_torrent_SOURCES) \ + $(test_tracker_SOURCES) $(test_transfer_SOURCES) \ + $(test_url_seed_SOURCES) $(test_utp_SOURCES) \ + $(test_web_seed_SOURCES) $(test_web_seed_ban_SOURCES) \ + $(test_web_seed_chunked_SOURCES) $(test_web_seed_http_SOURCES) \ + $(test_web_seed_http_pw_SOURCES) \ + $(test_web_seed_redirect_SOURCES) \ + $(test_web_seed_socks4_SOURCES) \ + $(test_web_seed_socks5_SOURCES) \ + $(test_web_seed_socks5_no_peers_SOURCES) \ + $(test_web_seed_socks5_pw_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(noinst_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__tty_colors_dummy = \ + mgn= red= grn= lgn= blu= brg= std=; \ + am__color_tests=no +am__tty_colors = { \ + $(am__tty_colors_dummy); \ + if test "X$(AM_COLOR_TESTS)" = Xno; then \ + am__color_tests=no; \ + elif test "X$(AM_COLOR_TESTS)" = Xalways; then \ + am__color_tests=yes; \ + elif test "X$$TERM" != Xdumb && { test -t 1; } 2>/dev/null; then \ + am__color_tests=yes; \ + fi; \ + if test $$am__color_tests = yes; then \ + red=''; \ + grn=''; \ + lgn=''; \ + blu=''; \ + mgn=''; \ + brg=''; \ + std=''; \ + fi; \ +} +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__recheck_rx = ^[ ]*:recheck:[ ]* +am__global_test_result_rx = ^[ ]*:global-test-result:[ ]* +am__copy_in_global_log_rx = ^[ ]*:copy-in-global-log:[ ]* +# A command that, given a newline-separated list of test names on the +# standard input, print the name of the tests that are to be re-run +# upon "make recheck". +am__list_recheck_tests = $(AWK) '{ \ + recheck = 1; \ + while ((rc = (getline line < ($$0 ".trs"))) != 0) \ + { \ + if (rc < 0) \ + { \ + if ((getline line2 < ($$0 ".log")) < 0) \ + recheck = 0; \ + break; \ + } \ + else if (line ~ /$(am__recheck_rx)[nN][Oo]/) \ + { \ + recheck = 0; \ + break; \ + } \ + else if (line ~ /$(am__recheck_rx)[yY][eE][sS]/) \ + { \ + break; \ + } \ + }; \ + if (recheck) \ + print $$0; \ + close ($$0 ".trs"); \ + close ($$0 ".log"); \ +}' +# A command that, given a newline-separated list of test names on the +# standard input, create the global log from their .trs and .log files. +am__create_global_log = $(AWK) ' \ +function fatal(msg) \ +{ \ + print "fatal: making $@: " msg | "cat >&2"; \ + exit 1; \ +} \ +function rst_section(header) \ +{ \ + print header; \ + len = length(header); \ + for (i = 1; i <= len; i = i + 1) \ + printf "="; \ + printf "\n\n"; \ +} \ +{ \ + copy_in_global_log = 1; \ + global_test_result = "RUN"; \ + while ((rc = (getline line < ($$0 ".trs"))) != 0) \ + { \ + if (rc < 0) \ + fatal("failed to read from " $$0 ".trs"); \ + if (line ~ /$(am__global_test_result_rx)/) \ + { \ + sub("$(am__global_test_result_rx)", "", line); \ + sub("[ ]*$$", "", line); \ + global_test_result = line; \ + } \ + else if (line ~ /$(am__copy_in_global_log_rx)[nN][oO]/) \ + copy_in_global_log = 0; \ + }; \ + if (copy_in_global_log) \ + { \ + rst_section(global_test_result ": " $$0); \ + while ((rc = (getline line < ($$0 ".log"))) != 0) \ + { \ + if (rc < 0) \ + fatal("failed to read from " $$0 ".log"); \ + print line; \ + }; \ + printf "\n"; \ + }; \ + close ($$0 ".trs"); \ + close ($$0 ".log"); \ +}' +# Restructured Text title. +am__rst_title = { sed 's/.*/ & /;h;s/./=/g;p;x;s/ *$$//;p;g' && echo; } +# Solaris 10 'make', and several other traditional 'make' implementations, +# pass "-e" to $(SHELL), and POSIX 2008 even requires this. Work around it +# by disabling -e (using the XSI extension "set +e") if it's set. +am__sh_e_setup = case $$- in *e*) set +e;; esac +# Default flags passed to test drivers. +am__common_driver_flags = \ + --color-tests "$$am__color_tests" \ + --enable-hard-errors "$$am__enable_hard_errors" \ + --expect-failure "$$am__expect_failure" +# To be inserted before the command running the test. Creates the +# directory for the log if needed. Stores in $dir the directory +# containing $f, in $tst the test, in $log the log. Executes the +# developer- defined test setup AM_TESTS_ENVIRONMENT (if any), and +# passes TESTS_ENVIRONMENT. Set up options for the wrapper that +# will run the test scripts (or their associated LOG_COMPILER, if +# thy have one). +am__check_pre = \ +$(am__sh_e_setup); \ +$(am__vpath_adj_setup) $(am__vpath_adj) \ +$(am__tty_colors); \ +srcdir=$(srcdir); export srcdir; \ +case "$@" in \ + */*) am__odir=`echo "./$@" | sed 's|/[^/]*$$||'`;; \ + *) am__odir=.;; \ +esac; \ +test "x$$am__odir" = x"." || test -d "$$am__odir" \ + || $(MKDIR_P) "$$am__odir" || exit $$?; \ +if test -f "./$$f"; then dir=./; \ +elif test -f "$$f"; then dir=; \ +else dir="$(srcdir)/"; fi; \ +tst=$$dir$$f; log='$@'; \ +if test -n '$(DISABLE_HARD_ERRORS)'; then \ + am__enable_hard_errors=no; \ +else \ + am__enable_hard_errors=yes; \ +fi; \ +case " $(XFAIL_TESTS) " in \ + *[\ \ ]$$f[\ \ ]* | *[\ \ ]$$dir$$f[\ \ ]*) \ + am__expect_failure=yes;; \ + *) \ + am__expect_failure=no;; \ +esac; \ +$(AM_TESTS_ENVIRONMENT) $(TESTS_ENVIRONMENT) +# A shell command to get the names of the tests scripts with any registered +# extension removed (i.e., equivalently, the names of the test logs, with +# the '.log' extension removed). The result is saved in the shell variable +# '$bases'. This honors runtime overriding of TESTS and TEST_LOGS. Sadly, +# we cannot use something simpler, involving e.g., "$(TEST_LOGS:.log=)", +# since that might cause problem with VPATH rewrites for suffix-less tests. +# See also 'test-harness-vpath-rewrite.sh' and 'test-trs-basic.sh'. +am__set_TESTS_bases = \ + bases='$(TEST_LOGS)'; \ + bases=`for i in $$bases; do echo $$i; done | sed 's/\.log$$//'`; \ + bases=`echo $$bases` +RECHECK_LOGS = $(TEST_LOGS) +AM_RECURSIVE_TARGETS = check recheck +TEST_SUITE_LOG = test-suite.log +TEST_EXTENSIONS = @EXEEXT@ .test +LOG_DRIVER = $(SHELL) $(top_srcdir)/build-aux/test-driver +LOG_COMPILE = $(LOG_COMPILER) $(AM_LOG_FLAGS) $(LOG_FLAGS) +am__set_b = \ + case '$@' in \ + */*) \ + case '$*' in \ + */*) b='$*';; \ + *) b=`echo '$@' | sed 's/\.log$$//'`; \ + esac;; \ + *) \ + b='$*';; \ + esac +am__test_logs1 = $(TESTS:=.log) +am__test_logs2 = $(am__test_logs1:@EXEEXT@.log=.log) +TEST_LOGS = $(am__test_logs2:.test.log=.log) +TEST_LOG_DRIVER = $(SHELL) $(top_srcdir)/build-aux/test-driver +TEST_LOG_COMPILE = $(TEST_LOG_COMPILER) $(AM_TEST_LOG_FLAGS) \ + $(TEST_LOG_FLAGS) +am__DIST_COMMON = $(srcdir)/Makefile.in \ + $(top_srcdir)/build-aux/depcomp \ + $(top_srcdir)/build-aux/test-driver +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BOOST_CHRONO_LIB = @BOOST_CHRONO_LIB@ +BOOST_CPPFLAGS = @BOOST_CPPFLAGS@ +BOOST_LDFLAGS = @BOOST_LDFLAGS@ +BOOST_PYTHON_LIB = @BOOST_PYTHON_LIB@ +BOOST_RANDOM_LIB = @BOOST_RANDOM_LIB@ +BOOST_SYSTEM_LIB = @BOOST_SYSTEM_LIB@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +COMPILETIME_OPTIONS = @COMPILETIME_OPTIONS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEBUGFLAGS = @DEBUGFLAGS@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GREP = @GREP@ +ICONV_LIBS = @ICONV_LIBS@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INTERFACE_VERSION_INFO = @INTERFACE_VERSION_INFO@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBICONV = @LIBICONV@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OPENSSL_INCLUDES = @OPENSSL_INCLUDES@ +OPENSSL_LDFLAGS = @OPENSSL_LDFLAGS@ +OPENSSL_LIBS = @OPENSSL_LIBS@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PTHREAD_CC = @PTHREAD_CC@ +PTHREAD_CFLAGS = @PTHREAD_CFLAGS@ +PTHREAD_LIBS = @PTHREAD_LIBS@ +PYTHON = @PYTHON@ +PYTHON_CPPFLAGS = @PYTHON_CPPFLAGS@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_EXTRA_LDFLAGS = @PYTHON_EXTRA_LDFLAGS@ +PYTHON_EXTRA_LIBS = @PYTHON_EXTRA_LIBS@ +PYTHON_INSTALL_PARAMS = @PYTHON_INSTALL_PARAMS@ +PYTHON_LIBS = @PYTHON_LIBS@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_SITE_PKG = @PYTHON_SITE_PKG@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +ax_pthread_config = @ax_pthread_config@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target = @target@ +target_alias = @target_alias@ +target_cpu = @target_cpu@ +target_os = @target_os@ +target_vendor = @target_vendor@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AUTOMAKE_OPTIONS = subdir-objects +benchmark_programs = \ + bdecode_benchmark + +test_programs = \ + test_primitives \ + test_recheck \ + test_stat_cache \ + test_file \ + test_privacy \ + test_priority \ + test_auto_unchoke \ + test_checking \ + test_fast_extension \ + test_http_connection \ + test_lsd \ + test_pe_crypto \ + test_pex \ + test_read_piece \ + test_receive_buffer \ + test_resume \ + test_ssl \ + test_storage \ + test_time_critical \ + test_torrent \ + test_tracker \ + test_transfer \ + test_create_torrent \ + enum_if \ + test_utp \ + test_session \ + test_web_seed \ + test_web_seed_ban \ + test_web_seed_chunked \ + test_web_seed_http \ + test_web_seed_http_pw \ + test_web_seed_redirect \ + test_web_seed_socks4 \ + test_web_seed_socks5 \ + test_web_seed_socks5_no_peers \ + test_web_seed_socks5_pw \ + test_url_seed \ + test_remap_files \ + test_enum_net \ + test_file_progress \ + test_linked_list \ + test_direct_dht + +@ENABLE_TESTS_TRUE@noinst_LTLIBRARIES = libtest.la +EXTRA_DIST = Jamfile \ + test_torrents/backslash_path.torrent \ + test_torrents/base.torrent \ + test_torrents/creation_date.torrent \ + test_torrents/duplicate_files.torrent \ + test_torrents/duplicate_web_seeds.torrent \ + test_torrents/empty_httpseed.torrent \ + test_torrents/empty_path.torrent \ + test_torrents/empty_path_multi.torrent \ + test_torrents/hidden_parent_path.torrent \ + test_torrents/httpseed.torrent \ + test_torrents/invalid_file_size.torrent \ + test_torrents/invalid_info.torrent \ + test_torrents/invalid_name.torrent \ + test_torrents/invalid_name2.torrent \ + test_torrents/invalid_name3.torrent \ + test_torrents/invalid_path_list.torrent \ + test_torrents/invalid_piece_len.torrent \ + test_torrents/invalid_pieces.torrent \ + test_torrents/invalid_root_hash.torrent \ + test_torrents/invalid_root_hash2.torrent \ + test_torrents/invalid_symlink.torrent \ + test_torrents/long_name.torrent \ + test_torrents/missing_path_list.torrent \ + test_torrents/missing_piece_len.torrent \ + test_torrents/negative_file_size.torrent \ + test_torrents/negative_piece_len.torrent \ + test_torrents/negative_size.torrent \ + test_torrents/no_creation_date.torrent \ + test_torrents/no_name.torrent \ + test_torrents/pad_file.torrent \ + test_torrents/pad_file_no_path.torrent \ + test_torrents/parent_path.torrent \ + test_torrents/root_hash.torrent \ + test_torrents/sample.torrent \ + test_torrents/single_multi_file.torrent \ + test_torrents/slash_path.torrent \ + test_torrents/slash_path2.torrent \ + test_torrents/slash_path3.torrent \ + test_torrents/string.torrent \ + test_torrents/symlink1.torrent \ + test_torrents/symlink_zero_size.torrent \ + test_torrents/unaligned_pieces.torrent \ + test_torrents/unordered.torrent \ + test_torrents/url_list.torrent \ + test_torrents/url_list2.torrent \ + test_torrents/url_list3.torrent \ + test_torrents/url_seed.torrent \ + test_torrents/url_seed_multi.torrent \ + test_torrents/url_seed_multi_space.torrent \ + test_torrents/url_seed_multi_space_nolist.torrent \ + test_torrents/whitespace_url.torrent \ + mutable_test_torrents/test1.torrent \ + mutable_test_torrents/test1_pad_files.torrent \ + mutable_test_torrents/test1_single.torrent\ + mutable_test_torrents/test1_single_padded.torrent \ + mutable_test_torrents/test2.torrent \ + mutable_test_torrents/test2_pad_files.torrent \ + mutable_test_torrents/test3.torrent \ + mutable_test_torrents/test3_pad_files.torrent \ + zeroes.gz \ + corrupt.gz \ + utf8_test.txt \ + web_server.py \ + socks.py \ + http.py + +noinst_HEADERS = test.hpp setup_transfer.hpp dht_server.hpp \ + peer_server.hpp udp_tracker.hpp web_seed_suite.hpp swarm_suite.hpp \ + test_utils.hpp settings.hpp make_torrent.hpp bittorrent_peer.hpp \ + print_alerts.hpp + +libtest_la_SOURCES = main.cpp \ + test.cpp \ + setup_transfer.cpp \ + dht_server.cpp \ + udp_tracker.cpp \ + peer_server.cpp \ + bittorrent_peer.cpp \ + make_torrent.cpp \ + web_seed_suite.cpp \ + swarm_suite.cpp \ + test_utils.cpp \ + settings.cpp \ + print_alerts.cpp + +test_primitives_SOURCES = \ + test_primitives.cpp \ + test_packet_buffer.cpp \ + test_timestamp_history.cpp \ + test_sha1_hash.cpp \ + test_bloom_filter.cpp \ + test_identify_client.cpp \ + test_merkle.cpp \ + test_alert_manager.cpp \ + test_resolve_links.cpp \ + test_crc32.cpp \ + test_heterogeneous_queue.cpp \ + test_ip_voter.cpp \ + test_sliding_average.cpp \ + test_socket_io.cpp \ + test_random.cpp \ + test_utf8.cpp \ + test_gzip.cpp \ + test_bitfield.cpp \ + test_part_file.cpp \ + test_peer_list.cpp \ + test_torrent_info.cpp \ + test_time.cpp \ + test_file_storage.cpp \ + test_peer_priority.cpp \ + test_threads.cpp \ + test_tailqueue.cpp \ + test_bandwidth_limiter.cpp \ + test_buffer.cpp \ + test_piece_picker.cpp \ + test_bencoding.cpp \ + test_bdecode.cpp \ + test_http_parser.cpp \ + test_string.cpp \ + test_magnet.cpp \ + test_xml.cpp \ + test_ip_filter.cpp \ + test_hasher.cpp \ + test_dht_storage.cpp \ + test_dht.cpp \ + test_block_cache.cpp \ + test_peer_classes.cpp \ + test_settings_pack.cpp \ + test_fence.cpp \ + test_dos_blocker.cpp \ + test_upnp.cpp + +bdecode_benchmark_SOURCES = bdecode_benchmark.cpp +test_recheck_SOURCES = test_recheck.cpp +test_stat_cache_SOURCES = test_stat_cache.cpp +test_file_SOURCES = test_file.cpp +test_privacy_SOURCES = test_privacy.cpp +test_priority_SOURCES = test_priority.cpp +test_auto_unchoke_SOURCES = test_auto_unchoke.cpp +test_checking_SOURCES = test_checking.cpp +test_enum_net_SOURCES = test_enum_net.cpp +test_fast_extension_SOURCES = test_fast_extension.cpp +test_http_connection_SOURCES = test_http_connection.cpp +test_lsd_SOURCES = test_lsd.cpp +test_pe_crypto_SOURCES = test_pe_crypto.cpp +test_pex_SOURCES = test_pex.cpp +test_read_piece_SOURCES = test_read_piece.cpp +test_receive_buffer_SOURCES = test_receive_buffer.cpp +test_storage_SOURCES = test_storage.cpp +test_time_critical_SOURCES = test_time_critical.cpp +test_resume_SOURCES = test_resume.cpp +test_ssl_SOURCES = test_ssl.cpp +test_torrent_SOURCES = test_torrent.cpp +test_tracker_SOURCES = test_tracker.cpp +test_transfer_SOURCES = test_transfer.cpp +test_create_torrent_SOURCES = test_create_torrent.cpp +enum_if_SOURCES = enum_if.cpp +test_utp_SOURCES = test_utp.cpp +test_session_SOURCES = test_session.cpp +test_web_seed_SOURCES = test_web_seed.cpp +test_web_seed_ban_SOURCES = test_web_seed_ban.cpp +test_web_seed_chunked_SOURCES = test_web_seed_chunked.cpp +test_web_seed_http_SOURCES = test_web_seed_http.cpp +test_web_seed_http_pw_SOURCES = test_web_seed_http_pw.cpp +test_web_seed_redirect_SOURCES = test_web_seed_redirect.cpp +test_web_seed_socks4_SOURCES = test_web_seed_socks4.cpp +test_web_seed_socks5_SOURCES = test_web_seed_socks5.cpp +test_web_seed_socks5_no_peers_SOURCES = test_web_seed_socks5_no_peers.cpp +test_web_seed_socks5_pw_SOURCES = test_web_seed_socks5_pw.cpp +test_url_seed_SOURCES = test_url_seed.cpp +test_remap_files_SOURCES = test_remap_files.cpp +test_file_progress_SOURCES = test_file_progress.cpp +test_linked_list_SOURCES = test_linked_list.cpp +test_direct_dht_SOURCES = test_direct_dht.cpp +LDADD = libtest.la $(top_builddir)/src/libtorrent-rasterbar.la + +#AM_CXXFLAGS=-ftemplate-depth-50 -I$(top_srcdir)/include -I$(top_srcdir)/include/libtorrent @DEBUGFLAGS@ @PTHREAD_CFLAGS@ +AM_CPPFLAGS = -ftemplate-depth-50 -I$(top_srcdir)/include @DEBUGFLAGS@ +AM_LDFLAGS = @BOOST_SYSTEM_LIB@ @PTHREAD_LIBS@ @OPENSSL_LDFLAGS@ @OPENSSL_LIBS@ \ + @BOOST_CHRONO_LIB@ + +all: all-am + +.SUFFIXES: +.SUFFIXES: .cpp .lo .log .o .obj .test .test$(EXEEXT) .trs +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign test/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign test/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstLTLIBRARIES: + -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES) + @list='$(noinst_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +libtest.la: $(libtest_la_OBJECTS) $(libtest_la_DEPENDENCIES) $(EXTRA_libtest_la_DEPENDENCIES) + $(AM_V_CXXLD)$(CXXLINK) $(am_libtest_la_rpath) $(libtest_la_OBJECTS) $(libtest_la_LIBADD) $(LIBS) + +clean-checkPROGRAMS: + @list='$(check_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list + +bdecode_benchmark$(EXEEXT): $(bdecode_benchmark_OBJECTS) $(bdecode_benchmark_DEPENDENCIES) $(EXTRA_bdecode_benchmark_DEPENDENCIES) + @rm -f bdecode_benchmark$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(bdecode_benchmark_OBJECTS) $(bdecode_benchmark_LDADD) $(LIBS) + +enum_if$(EXEEXT): $(enum_if_OBJECTS) $(enum_if_DEPENDENCIES) $(EXTRA_enum_if_DEPENDENCIES) + @rm -f enum_if$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(enum_if_OBJECTS) $(enum_if_LDADD) $(LIBS) + +test_auto_unchoke$(EXEEXT): $(test_auto_unchoke_OBJECTS) $(test_auto_unchoke_DEPENDENCIES) $(EXTRA_test_auto_unchoke_DEPENDENCIES) + @rm -f test_auto_unchoke$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_auto_unchoke_OBJECTS) $(test_auto_unchoke_LDADD) $(LIBS) + +test_checking$(EXEEXT): $(test_checking_OBJECTS) $(test_checking_DEPENDENCIES) $(EXTRA_test_checking_DEPENDENCIES) + @rm -f test_checking$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_checking_OBJECTS) $(test_checking_LDADD) $(LIBS) + +test_create_torrent$(EXEEXT): $(test_create_torrent_OBJECTS) $(test_create_torrent_DEPENDENCIES) $(EXTRA_test_create_torrent_DEPENDENCIES) + @rm -f test_create_torrent$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_create_torrent_OBJECTS) $(test_create_torrent_LDADD) $(LIBS) + +test_direct_dht$(EXEEXT): $(test_direct_dht_OBJECTS) $(test_direct_dht_DEPENDENCIES) $(EXTRA_test_direct_dht_DEPENDENCIES) + @rm -f test_direct_dht$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_direct_dht_OBJECTS) $(test_direct_dht_LDADD) $(LIBS) + +test_enum_net$(EXEEXT): $(test_enum_net_OBJECTS) $(test_enum_net_DEPENDENCIES) $(EXTRA_test_enum_net_DEPENDENCIES) + @rm -f test_enum_net$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_enum_net_OBJECTS) $(test_enum_net_LDADD) $(LIBS) + +test_fast_extension$(EXEEXT): $(test_fast_extension_OBJECTS) $(test_fast_extension_DEPENDENCIES) $(EXTRA_test_fast_extension_DEPENDENCIES) + @rm -f test_fast_extension$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_fast_extension_OBJECTS) $(test_fast_extension_LDADD) $(LIBS) + +test_file$(EXEEXT): $(test_file_OBJECTS) $(test_file_DEPENDENCIES) $(EXTRA_test_file_DEPENDENCIES) + @rm -f test_file$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_file_OBJECTS) $(test_file_LDADD) $(LIBS) + +test_file_progress$(EXEEXT): $(test_file_progress_OBJECTS) $(test_file_progress_DEPENDENCIES) $(EXTRA_test_file_progress_DEPENDENCIES) + @rm -f test_file_progress$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_file_progress_OBJECTS) $(test_file_progress_LDADD) $(LIBS) + +test_http_connection$(EXEEXT): $(test_http_connection_OBJECTS) $(test_http_connection_DEPENDENCIES) $(EXTRA_test_http_connection_DEPENDENCIES) + @rm -f test_http_connection$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_http_connection_OBJECTS) $(test_http_connection_LDADD) $(LIBS) + +test_linked_list$(EXEEXT): $(test_linked_list_OBJECTS) $(test_linked_list_DEPENDENCIES) $(EXTRA_test_linked_list_DEPENDENCIES) + @rm -f test_linked_list$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_linked_list_OBJECTS) $(test_linked_list_LDADD) $(LIBS) + +test_lsd$(EXEEXT): $(test_lsd_OBJECTS) $(test_lsd_DEPENDENCIES) $(EXTRA_test_lsd_DEPENDENCIES) + @rm -f test_lsd$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_lsd_OBJECTS) $(test_lsd_LDADD) $(LIBS) + +test_pe_crypto$(EXEEXT): $(test_pe_crypto_OBJECTS) $(test_pe_crypto_DEPENDENCIES) $(EXTRA_test_pe_crypto_DEPENDENCIES) + @rm -f test_pe_crypto$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_pe_crypto_OBJECTS) $(test_pe_crypto_LDADD) $(LIBS) + +test_pex$(EXEEXT): $(test_pex_OBJECTS) $(test_pex_DEPENDENCIES) $(EXTRA_test_pex_DEPENDENCIES) + @rm -f test_pex$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_pex_OBJECTS) $(test_pex_LDADD) $(LIBS) + +test_primitives$(EXEEXT): $(test_primitives_OBJECTS) $(test_primitives_DEPENDENCIES) $(EXTRA_test_primitives_DEPENDENCIES) + @rm -f test_primitives$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_primitives_OBJECTS) $(test_primitives_LDADD) $(LIBS) + +test_priority$(EXEEXT): $(test_priority_OBJECTS) $(test_priority_DEPENDENCIES) $(EXTRA_test_priority_DEPENDENCIES) + @rm -f test_priority$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_priority_OBJECTS) $(test_priority_LDADD) $(LIBS) + +test_privacy$(EXEEXT): $(test_privacy_OBJECTS) $(test_privacy_DEPENDENCIES) $(EXTRA_test_privacy_DEPENDENCIES) + @rm -f test_privacy$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_privacy_OBJECTS) $(test_privacy_LDADD) $(LIBS) + +test_read_piece$(EXEEXT): $(test_read_piece_OBJECTS) $(test_read_piece_DEPENDENCIES) $(EXTRA_test_read_piece_DEPENDENCIES) + @rm -f test_read_piece$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_read_piece_OBJECTS) $(test_read_piece_LDADD) $(LIBS) + +test_receive_buffer$(EXEEXT): $(test_receive_buffer_OBJECTS) $(test_receive_buffer_DEPENDENCIES) $(EXTRA_test_receive_buffer_DEPENDENCIES) + @rm -f test_receive_buffer$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_receive_buffer_OBJECTS) $(test_receive_buffer_LDADD) $(LIBS) + +test_recheck$(EXEEXT): $(test_recheck_OBJECTS) $(test_recheck_DEPENDENCIES) $(EXTRA_test_recheck_DEPENDENCIES) + @rm -f test_recheck$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_recheck_OBJECTS) $(test_recheck_LDADD) $(LIBS) + +test_remap_files$(EXEEXT): $(test_remap_files_OBJECTS) $(test_remap_files_DEPENDENCIES) $(EXTRA_test_remap_files_DEPENDENCIES) + @rm -f test_remap_files$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_remap_files_OBJECTS) $(test_remap_files_LDADD) $(LIBS) + +test_resume$(EXEEXT): $(test_resume_OBJECTS) $(test_resume_DEPENDENCIES) $(EXTRA_test_resume_DEPENDENCIES) + @rm -f test_resume$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_resume_OBJECTS) $(test_resume_LDADD) $(LIBS) + +test_session$(EXEEXT): $(test_session_OBJECTS) $(test_session_DEPENDENCIES) $(EXTRA_test_session_DEPENDENCIES) + @rm -f test_session$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_session_OBJECTS) $(test_session_LDADD) $(LIBS) + +test_ssl$(EXEEXT): $(test_ssl_OBJECTS) $(test_ssl_DEPENDENCIES) $(EXTRA_test_ssl_DEPENDENCIES) + @rm -f test_ssl$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_ssl_OBJECTS) $(test_ssl_LDADD) $(LIBS) + +test_stat_cache$(EXEEXT): $(test_stat_cache_OBJECTS) $(test_stat_cache_DEPENDENCIES) $(EXTRA_test_stat_cache_DEPENDENCIES) + @rm -f test_stat_cache$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_stat_cache_OBJECTS) $(test_stat_cache_LDADD) $(LIBS) + +test_storage$(EXEEXT): $(test_storage_OBJECTS) $(test_storage_DEPENDENCIES) $(EXTRA_test_storage_DEPENDENCIES) + @rm -f test_storage$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_storage_OBJECTS) $(test_storage_LDADD) $(LIBS) + +test_time_critical$(EXEEXT): $(test_time_critical_OBJECTS) $(test_time_critical_DEPENDENCIES) $(EXTRA_test_time_critical_DEPENDENCIES) + @rm -f test_time_critical$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_time_critical_OBJECTS) $(test_time_critical_LDADD) $(LIBS) + +test_torrent$(EXEEXT): $(test_torrent_OBJECTS) $(test_torrent_DEPENDENCIES) $(EXTRA_test_torrent_DEPENDENCIES) + @rm -f test_torrent$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_torrent_OBJECTS) $(test_torrent_LDADD) $(LIBS) + +test_tracker$(EXEEXT): $(test_tracker_OBJECTS) $(test_tracker_DEPENDENCIES) $(EXTRA_test_tracker_DEPENDENCIES) + @rm -f test_tracker$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_tracker_OBJECTS) $(test_tracker_LDADD) $(LIBS) + +test_transfer$(EXEEXT): $(test_transfer_OBJECTS) $(test_transfer_DEPENDENCIES) $(EXTRA_test_transfer_DEPENDENCIES) + @rm -f test_transfer$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_transfer_OBJECTS) $(test_transfer_LDADD) $(LIBS) + +test_url_seed$(EXEEXT): $(test_url_seed_OBJECTS) $(test_url_seed_DEPENDENCIES) $(EXTRA_test_url_seed_DEPENDENCIES) + @rm -f test_url_seed$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_url_seed_OBJECTS) $(test_url_seed_LDADD) $(LIBS) + +test_utp$(EXEEXT): $(test_utp_OBJECTS) $(test_utp_DEPENDENCIES) $(EXTRA_test_utp_DEPENDENCIES) + @rm -f test_utp$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_utp_OBJECTS) $(test_utp_LDADD) $(LIBS) + +test_web_seed$(EXEEXT): $(test_web_seed_OBJECTS) $(test_web_seed_DEPENDENCIES) $(EXTRA_test_web_seed_DEPENDENCIES) + @rm -f test_web_seed$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_web_seed_OBJECTS) $(test_web_seed_LDADD) $(LIBS) + +test_web_seed_ban$(EXEEXT): $(test_web_seed_ban_OBJECTS) $(test_web_seed_ban_DEPENDENCIES) $(EXTRA_test_web_seed_ban_DEPENDENCIES) + @rm -f test_web_seed_ban$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_web_seed_ban_OBJECTS) $(test_web_seed_ban_LDADD) $(LIBS) + +test_web_seed_chunked$(EXEEXT): $(test_web_seed_chunked_OBJECTS) $(test_web_seed_chunked_DEPENDENCIES) $(EXTRA_test_web_seed_chunked_DEPENDENCIES) + @rm -f test_web_seed_chunked$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_web_seed_chunked_OBJECTS) $(test_web_seed_chunked_LDADD) $(LIBS) + +test_web_seed_http$(EXEEXT): $(test_web_seed_http_OBJECTS) $(test_web_seed_http_DEPENDENCIES) $(EXTRA_test_web_seed_http_DEPENDENCIES) + @rm -f test_web_seed_http$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_web_seed_http_OBJECTS) $(test_web_seed_http_LDADD) $(LIBS) + +test_web_seed_http_pw$(EXEEXT): $(test_web_seed_http_pw_OBJECTS) $(test_web_seed_http_pw_DEPENDENCIES) $(EXTRA_test_web_seed_http_pw_DEPENDENCIES) + @rm -f test_web_seed_http_pw$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_web_seed_http_pw_OBJECTS) $(test_web_seed_http_pw_LDADD) $(LIBS) + +test_web_seed_redirect$(EXEEXT): $(test_web_seed_redirect_OBJECTS) $(test_web_seed_redirect_DEPENDENCIES) $(EXTRA_test_web_seed_redirect_DEPENDENCIES) + @rm -f test_web_seed_redirect$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_web_seed_redirect_OBJECTS) $(test_web_seed_redirect_LDADD) $(LIBS) + +test_web_seed_socks4$(EXEEXT): $(test_web_seed_socks4_OBJECTS) $(test_web_seed_socks4_DEPENDENCIES) $(EXTRA_test_web_seed_socks4_DEPENDENCIES) + @rm -f test_web_seed_socks4$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_web_seed_socks4_OBJECTS) $(test_web_seed_socks4_LDADD) $(LIBS) + +test_web_seed_socks5$(EXEEXT): $(test_web_seed_socks5_OBJECTS) $(test_web_seed_socks5_DEPENDENCIES) $(EXTRA_test_web_seed_socks5_DEPENDENCIES) + @rm -f test_web_seed_socks5$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_web_seed_socks5_OBJECTS) $(test_web_seed_socks5_LDADD) $(LIBS) + +test_web_seed_socks5_no_peers$(EXEEXT): $(test_web_seed_socks5_no_peers_OBJECTS) $(test_web_seed_socks5_no_peers_DEPENDENCIES) $(EXTRA_test_web_seed_socks5_no_peers_DEPENDENCIES) + @rm -f test_web_seed_socks5_no_peers$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_web_seed_socks5_no_peers_OBJECTS) $(test_web_seed_socks5_no_peers_LDADD) $(LIBS) + +test_web_seed_socks5_pw$(EXEEXT): $(test_web_seed_socks5_pw_OBJECTS) $(test_web_seed_socks5_pw_DEPENDENCIES) $(EXTRA_test_web_seed_socks5_pw_DEPENDENCIES) + @rm -f test_web_seed_socks5_pw$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_web_seed_socks5_pw_OBJECTS) $(test_web_seed_socks5_pw_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bdecode_benchmark.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bittorrent_peer.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dht_server.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/enum_if.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/main.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/make_torrent.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/peer_server.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/print_alerts.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/settings.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/setup_transfer.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/swarm_suite.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_alert_manager.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_auto_unchoke.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_bandwidth_limiter.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_bdecode.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_bencoding.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_bitfield.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_block_cache.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_bloom_filter.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_buffer.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_checking.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_crc32.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_create_torrent.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_dht.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_dht_storage.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_direct_dht.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_dos_blocker.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_enum_net.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_fast_extension.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_fence.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_file.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_file_progress.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_file_storage.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_gzip.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_hasher.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_heterogeneous_queue.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_http_connection.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_http_parser.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_identify_client.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_ip_filter.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_ip_voter.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_linked_list.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lsd.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_magnet.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_merkle.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_packet_buffer.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_part_file.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_pe_crypto.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_peer_classes.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_peer_list.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_peer_priority.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_pex.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_piece_picker.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_primitives.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_priority.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_privacy.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_random.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_read_piece.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_receive_buffer.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_recheck.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_remap_files.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_resolve_links.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_resume.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_session.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_settings_pack.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_sha1_hash.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_sliding_average.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_socket_io.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_ssl.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_stat_cache.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_storage.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_string.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_tailqueue.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_threads.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_time.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_time_critical.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_timestamp_history.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_torrent.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_torrent_info.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_tracker.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_transfer.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_upnp.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_url_seed.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_utf8.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_utils.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_utp.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_web_seed.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_web_seed_ban.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_web_seed_chunked.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_web_seed_http.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_web_seed_http_pw.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_web_seed_redirect.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_web_seed_socks4.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_web_seed_socks5.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_web_seed_socks5_no_peers.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_web_seed_socks5_pw.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_xml.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/udp_tracker.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/web_seed_suite.Plo@am__quote@ + +.cpp.o: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ +@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $< + +.cpp.obj: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\ +@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\ +@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.cpp.lo: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\ +@am__fastdepCXX_TRUE@ $(LTCXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +# Recover from deleted '.trs' file; this should ensure that +# "rm -f foo.log; make foo.trs" re-run 'foo.test', and re-create +# both 'foo.log' and 'foo.trs'. Break the recipe in two subshells +# to avoid problems with "make -n". +.log.trs: + rm -f $< $@ + $(MAKE) $(AM_MAKEFLAGS) $< + +# Leading 'am--fnord' is there to ensure the list of targets does not +# expand to empty, as could happen e.g. with make check TESTS=''. +am--fnord $(TEST_LOGS) $(TEST_LOGS:.log=.trs): $(am__force_recheck) +am--force-recheck: + @: + +$(TEST_SUITE_LOG): $(TEST_LOGS) + @$(am__set_TESTS_bases); \ + am__f_ok () { test -f "$$1" && test -r "$$1"; }; \ + redo_bases=`for i in $$bases; do \ + am__f_ok $$i.trs && am__f_ok $$i.log || echo $$i; \ + done`; \ + if test -n "$$redo_bases"; then \ + redo_logs=`for i in $$redo_bases; do echo $$i.log; done`; \ + redo_results=`for i in $$redo_bases; do echo $$i.trs; done`; \ + if $(am__make_dryrun); then :; else \ + rm -f $$redo_logs && rm -f $$redo_results || exit 1; \ + fi; \ + fi; \ + if test -n "$$am__remaking_logs"; then \ + echo "fatal: making $(TEST_SUITE_LOG): possible infinite" \ + "recursion detected" >&2; \ + elif test -n "$$redo_logs"; then \ + am__remaking_logs=yes $(MAKE) $(AM_MAKEFLAGS) $$redo_logs; \ + fi; \ + if $(am__make_dryrun); then :; else \ + st=0; \ + errmsg="fatal: making $(TEST_SUITE_LOG): failed to create"; \ + for i in $$redo_bases; do \ + test -f $$i.trs && test -r $$i.trs \ + || { echo "$$errmsg $$i.trs" >&2; st=1; }; \ + test -f $$i.log && test -r $$i.log \ + || { echo "$$errmsg $$i.log" >&2; st=1; }; \ + done; \ + test $$st -eq 0 || exit 1; \ + fi + @$(am__sh_e_setup); $(am__tty_colors); $(am__set_TESTS_bases); \ + ws='[ ]'; \ + results=`for b in $$bases; do echo $$b.trs; done`; \ + test -n "$$results" || results=/dev/null; \ + all=` grep "^$$ws*:test-result:" $$results | wc -l`; \ + pass=` grep "^$$ws*:test-result:$$ws*PASS" $$results | wc -l`; \ + fail=` grep "^$$ws*:test-result:$$ws*FAIL" $$results | wc -l`; \ + skip=` grep "^$$ws*:test-result:$$ws*SKIP" $$results | wc -l`; \ + xfail=`grep "^$$ws*:test-result:$$ws*XFAIL" $$results | wc -l`; \ + xpass=`grep "^$$ws*:test-result:$$ws*XPASS" $$results | wc -l`; \ + error=`grep "^$$ws*:test-result:$$ws*ERROR" $$results | wc -l`; \ + if test `expr $$fail + $$xpass + $$error` -eq 0; then \ + success=true; \ + else \ + success=false; \ + fi; \ + br='==================='; br=$$br$$br$$br$$br; \ + result_count () \ + { \ + if test x"$$1" = x"--maybe-color"; then \ + maybe_colorize=yes; \ + elif test x"$$1" = x"--no-color"; then \ + maybe_colorize=no; \ + else \ + echo "$@: invalid 'result_count' usage" >&2; exit 4; \ + fi; \ + shift; \ + desc=$$1 count=$$2; \ + if test $$maybe_colorize = yes && test $$count -gt 0; then \ + color_start=$$3 color_end=$$std; \ + else \ + color_start= color_end=; \ + fi; \ + echo "$${color_start}# $$desc $$count$${color_end}"; \ + }; \ + create_testsuite_report () \ + { \ + result_count $$1 "TOTAL:" $$all "$$brg"; \ + result_count $$1 "PASS: " $$pass "$$grn"; \ + result_count $$1 "SKIP: " $$skip "$$blu"; \ + result_count $$1 "XFAIL:" $$xfail "$$lgn"; \ + result_count $$1 "FAIL: " $$fail "$$red"; \ + result_count $$1 "XPASS:" $$xpass "$$red"; \ + result_count $$1 "ERROR:" $$error "$$mgn"; \ + }; \ + { \ + echo "$(PACKAGE_STRING): $(subdir)/$(TEST_SUITE_LOG)" | \ + $(am__rst_title); \ + create_testsuite_report --no-color; \ + echo; \ + echo ".. contents:: :depth: 2"; \ + echo; \ + for b in $$bases; do echo $$b; done \ + | $(am__create_global_log); \ + } >$(TEST_SUITE_LOG).tmp || exit 1; \ + mv $(TEST_SUITE_LOG).tmp $(TEST_SUITE_LOG); \ + if $$success; then \ + col="$$grn"; \ + else \ + col="$$red"; \ + test x"$$VERBOSE" = x || cat $(TEST_SUITE_LOG); \ + fi; \ + echo "$${col}$$br$${std}"; \ + echo "$${col}Testsuite summary for $(PACKAGE_STRING)$${std}"; \ + echo "$${col}$$br$${std}"; \ + create_testsuite_report --maybe-color; \ + echo "$$col$$br$$std"; \ + if $$success; then :; else \ + echo "$${col}See $(subdir)/$(TEST_SUITE_LOG)$${std}"; \ + if test -n "$(PACKAGE_BUGREPORT)"; then \ + echo "$${col}Please report to $(PACKAGE_BUGREPORT)$${std}"; \ + fi; \ + echo "$$col$$br$$std"; \ + fi; \ + $$success || exit 1 + +check-TESTS: + @list='$(RECHECK_LOGS)'; test -z "$$list" || rm -f $$list + @list='$(RECHECK_LOGS:.log=.trs)'; test -z "$$list" || rm -f $$list + @test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG) + @set +e; $(am__set_TESTS_bases); \ + log_list=`for i in $$bases; do echo $$i.log; done`; \ + trs_list=`for i in $$bases; do echo $$i.trs; done`; \ + log_list=`echo $$log_list`; trs_list=`echo $$trs_list`; \ + $(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) TEST_LOGS="$$log_list"; \ + exit $$?; +recheck: all $(check_PROGRAMS) + @test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG) + @set +e; $(am__set_TESTS_bases); \ + bases=`for i in $$bases; do echo $$i; done \ + | $(am__list_recheck_tests)` || exit 1; \ + log_list=`for i in $$bases; do echo $$i.log; done`; \ + log_list=`echo $$log_list`; \ + $(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) \ + am__force_recheck=am--force-recheck \ + TEST_LOGS="$$log_list"; \ + exit $$? +test_primitives.log: test_primitives$(EXEEXT) + @p='test_primitives$(EXEEXT)'; \ + b='test_primitives'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_recheck.log: test_recheck$(EXEEXT) + @p='test_recheck$(EXEEXT)'; \ + b='test_recheck'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_stat_cache.log: test_stat_cache$(EXEEXT) + @p='test_stat_cache$(EXEEXT)'; \ + b='test_stat_cache'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_file.log: test_file$(EXEEXT) + @p='test_file$(EXEEXT)'; \ + b='test_file'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_privacy.log: test_privacy$(EXEEXT) + @p='test_privacy$(EXEEXT)'; \ + b='test_privacy'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_priority.log: test_priority$(EXEEXT) + @p='test_priority$(EXEEXT)'; \ + b='test_priority'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_auto_unchoke.log: test_auto_unchoke$(EXEEXT) + @p='test_auto_unchoke$(EXEEXT)'; \ + b='test_auto_unchoke'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_checking.log: test_checking$(EXEEXT) + @p='test_checking$(EXEEXT)'; \ + b='test_checking'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_fast_extension.log: test_fast_extension$(EXEEXT) + @p='test_fast_extension$(EXEEXT)'; \ + b='test_fast_extension'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_http_connection.log: test_http_connection$(EXEEXT) + @p='test_http_connection$(EXEEXT)'; \ + b='test_http_connection'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_lsd.log: test_lsd$(EXEEXT) + @p='test_lsd$(EXEEXT)'; \ + b='test_lsd'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_pe_crypto.log: test_pe_crypto$(EXEEXT) + @p='test_pe_crypto$(EXEEXT)'; \ + b='test_pe_crypto'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_pex.log: test_pex$(EXEEXT) + @p='test_pex$(EXEEXT)'; \ + b='test_pex'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_read_piece.log: test_read_piece$(EXEEXT) + @p='test_read_piece$(EXEEXT)'; \ + b='test_read_piece'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_receive_buffer.log: test_receive_buffer$(EXEEXT) + @p='test_receive_buffer$(EXEEXT)'; \ + b='test_receive_buffer'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_resume.log: test_resume$(EXEEXT) + @p='test_resume$(EXEEXT)'; \ + b='test_resume'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_ssl.log: test_ssl$(EXEEXT) + @p='test_ssl$(EXEEXT)'; \ + b='test_ssl'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_storage.log: test_storage$(EXEEXT) + @p='test_storage$(EXEEXT)'; \ + b='test_storage'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_time_critical.log: test_time_critical$(EXEEXT) + @p='test_time_critical$(EXEEXT)'; \ + b='test_time_critical'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_torrent.log: test_torrent$(EXEEXT) + @p='test_torrent$(EXEEXT)'; \ + b='test_torrent'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_tracker.log: test_tracker$(EXEEXT) + @p='test_tracker$(EXEEXT)'; \ + b='test_tracker'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_transfer.log: test_transfer$(EXEEXT) + @p='test_transfer$(EXEEXT)'; \ + b='test_transfer'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_create_torrent.log: test_create_torrent$(EXEEXT) + @p='test_create_torrent$(EXEEXT)'; \ + b='test_create_torrent'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +enum_if.log: enum_if$(EXEEXT) + @p='enum_if$(EXEEXT)'; \ + b='enum_if'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_utp.log: test_utp$(EXEEXT) + @p='test_utp$(EXEEXT)'; \ + b='test_utp'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_session.log: test_session$(EXEEXT) + @p='test_session$(EXEEXT)'; \ + b='test_session'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_web_seed.log: test_web_seed$(EXEEXT) + @p='test_web_seed$(EXEEXT)'; \ + b='test_web_seed'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_web_seed_ban.log: test_web_seed_ban$(EXEEXT) + @p='test_web_seed_ban$(EXEEXT)'; \ + b='test_web_seed_ban'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_web_seed_chunked.log: test_web_seed_chunked$(EXEEXT) + @p='test_web_seed_chunked$(EXEEXT)'; \ + b='test_web_seed_chunked'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_web_seed_http.log: test_web_seed_http$(EXEEXT) + @p='test_web_seed_http$(EXEEXT)'; \ + b='test_web_seed_http'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_web_seed_http_pw.log: test_web_seed_http_pw$(EXEEXT) + @p='test_web_seed_http_pw$(EXEEXT)'; \ + b='test_web_seed_http_pw'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_web_seed_redirect.log: test_web_seed_redirect$(EXEEXT) + @p='test_web_seed_redirect$(EXEEXT)'; \ + b='test_web_seed_redirect'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_web_seed_socks4.log: test_web_seed_socks4$(EXEEXT) + @p='test_web_seed_socks4$(EXEEXT)'; \ + b='test_web_seed_socks4'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_web_seed_socks5.log: test_web_seed_socks5$(EXEEXT) + @p='test_web_seed_socks5$(EXEEXT)'; \ + b='test_web_seed_socks5'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_web_seed_socks5_no_peers.log: test_web_seed_socks5_no_peers$(EXEEXT) + @p='test_web_seed_socks5_no_peers$(EXEEXT)'; \ + b='test_web_seed_socks5_no_peers'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_web_seed_socks5_pw.log: test_web_seed_socks5_pw$(EXEEXT) + @p='test_web_seed_socks5_pw$(EXEEXT)'; \ + b='test_web_seed_socks5_pw'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_url_seed.log: test_url_seed$(EXEEXT) + @p='test_url_seed$(EXEEXT)'; \ + b='test_url_seed'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_remap_files.log: test_remap_files$(EXEEXT) + @p='test_remap_files$(EXEEXT)'; \ + b='test_remap_files'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_enum_net.log: test_enum_net$(EXEEXT) + @p='test_enum_net$(EXEEXT)'; \ + b='test_enum_net'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_file_progress.log: test_file_progress$(EXEEXT) + @p='test_file_progress$(EXEEXT)'; \ + b='test_file_progress'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_linked_list.log: test_linked_list$(EXEEXT) + @p='test_linked_list$(EXEEXT)'; \ + b='test_linked_list'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_direct_dht.log: test_direct_dht$(EXEEXT) + @p='test_direct_dht$(EXEEXT)'; \ + b='test_direct_dht'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +.test.log: + @p='$<'; \ + $(am__set_b); \ + $(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +@am__EXEEXT_TRUE@.test$(EXEEXT).log: +@am__EXEEXT_TRUE@ @p='$<'; \ +@am__EXEEXT_TRUE@ $(am__set_b); \ +@am__EXEEXT_TRUE@ $(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \ +@am__EXEEXT_TRUE@ --log-file $$b.log --trs-file $$b.trs \ +@am__EXEEXT_TRUE@ $(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \ +@am__EXEEXT_TRUE@ "$$tst" $(AM_TESTS_FD_REDIRECT) + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am + $(MAKE) $(AM_MAKEFLAGS) $(check_PROGRAMS) + $(MAKE) $(AM_MAKEFLAGS) check-TESTS +check: check-am +all-am: Makefile $(LTLIBRARIES) $(HEADERS) +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + -test -z "$(TEST_LOGS)" || rm -f $(TEST_LOGS) + -test -z "$(TEST_LOGS:.log=.trs)" || rm -f $(TEST_LOGS:.log=.trs) + -test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG) + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-checkPROGRAMS clean-generic clean-libtool \ + clean-noinstLTLIBRARIES mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: + +.MAKE: check-am install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am check check-TESTS check-am clean \ + clean-checkPROGRAMS clean-generic clean-libtool \ + clean-noinstLTLIBRARIES cscopelist-am ctags ctags-am distclean \ + distclean-compile distclean-generic distclean-libtool \ + distclean-tags distdir dvi dvi-am html html-am info info-am \ + install install-am install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ + pdf pdf-am ps ps-am recheck tags tags-am uninstall \ + uninstall-am + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/test/bdecode_benchmark.cpp b/test/bdecode_benchmark.cpp new file mode 100644 index 0000000..f2195e0 --- /dev/null +++ b/test/bdecode_benchmark.cpp @@ -0,0 +1,184 @@ +/* + +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/lazy_entry.hpp" +#include "libtorrent/bdecode.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/sha1_hash.hpp" +#include +#include + +#include "test.hpp" +#include "libtorrent/time.hpp" + +using namespace libtorrent; + +int load_file(std::string const& filename, std::vector& v + , libtorrent::error_code& ec, int limit = 8000000) +{ + ec.clear(); + FILE* f = fopen(filename.c_str(), "rb"); + if (f == NULL) + { + 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(s); + if (s == 0) + { + fclose(f); + return 0; + } + + r = 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; +} + +int main(int argc, char* argv[]) +{ + using namespace libtorrent; + + if (argc != 2) + { + fputs("usage: bdecode_benchmark torrent-file\n", stderr); + return 1; + } + + std::vector buf; + error_code ec; + int ret = load_file(argv[1], buf, ec, 40 * 1000000); + if (ret == -1) + { + fprintf(stderr, "file too big, aborting\n"); + return 1; + } + + if (ret != 0) + { + fprintf(stderr, "failed to load file: %s\n", ec.message().c_str()); + return 1; + } + + { + time_point start(clock_type::now()); + entry e; + for (int i = 0; i < 1000000; ++i) + { + int len; + e = bdecode(&buf[0], &buf[0] + buf.size(), len); +// entry& info = e["info"]; + } + ptime stop(time_now_hires()); + + fprintf(stderr, "(slow) bdecode done in %5d ns per message\n" + , int(total_microseconds(stop - start) / 1000)); + } + + // =============================================== + + { + ptime start(time_now_hires()); + lazy_entry e; + for (int i = 0; i < 1000000; ++i) + { + error_code ec; + lazy_bdecode(&buf[0], &buf[0] + buf.size(), e, ec); +// lazy_entry* info = e.dict_find("info"); + } + time_point stop(clock_type::now()); + + fprintf(stderr, "lazy_bdecode done in %5d ns per message\n" + , int(total_microseconds(stop - start) / 1000)); + } + + // =============================================== + + { + ptime start(time_now_hires()); + bdecode_node e; + e.reserve(100); + for (int i = 0; i < 1000000; ++i) + { + error_code ec; + bdecode(&buf[0], &buf[0] + buf.size(), e, ec); +// bdecode_node info = e.dict_find("info"); + } + ptime stop(time_now_hires()); + + fprintf(stderr, "bdecode done in %5d ns per message\n" + , int(total_microseconds(stop - start) / 1000)); + } + + return 0; +} + diff --git a/test/bittorrent_peer.cpp b/test/bittorrent_peer.cpp new file mode 100644 index 0000000..6d9954c --- /dev/null +++ b/test/bittorrent_peer.cpp @@ -0,0 +1,562 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/socket.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/assert.hpp" +#include "bittorrent_peer.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/io_service.hpp" +#include "libtorrent/io.hpp" +#include +#include + +using namespace libtorrent; + +peer_conn::peer_conn(io_service& ios + , boost::function on_msg + , torrent_info const& ti + , tcp::endpoint const& ep + , peer_mode_t mode) + : s(ios) + , m_mode(mode) + , m_ti(ti) + , read_pos(0) + , m_on_msg(on_msg) + , state(handshaking) + , choked(true) + , current_piece(-1) + , m_current_piece_is_allowed(false) + , block(0) + , m_blocks_per_piece((m_ti.piece_length() + 0x3fff) / 0x4000) + , outstanding_requests(0) + , fast_extension(false) + , blocks_received(0) + , blocks_sent(0) + , start_time(clock_type::now()) + , endpoint(ep) + , restarting(false) +{ + pieces.reserve(m_ti.num_pieces()); + start_conn(); +} + +void peer_conn::start_conn() +{ + restarting = false; + s.async_connect(endpoint, boost::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 = (char*)malloc(sizeof(handshake)); + memcpy(h, handshake, sizeof(handshake)); + std::memcpy(h + 28, m_ti.info_hash().data(), 20); + std::generate(h + 48, h + 68, &rand); + // for seeds, don't send the interested message + boost::asio::async_write(s, boost::asio::buffer(h, (sizeof(handshake) - 1) + - (m_mode == uploader ? 5 : 0)) + , boost::bind(&peer_conn::on_handshake, this, h, _1, _2)); +} + +void peer_conn::on_handshake(char* h, error_code const& ec, size_t bytes_transferred) +{ + free(h); + if (ec) + { + close("ERROR SEND HANDSHAKE: %s", ec); + return; + } + + // read handshake + boost::asio::async_read(s, boost::asio::buffer((char*)buffer, 68) + , boost::bind(&peer_conn::on_handshake2, this, _1, _2)); +} + +void peer_conn::on_handshake2(error_code const& ec, size_t bytes_transferred) +{ + if (ec) + { + close("ERROR READ HANDSHAKE: %s", ec); + return; + } + + // buffer is the full 68 byte handshake + // look at the extension bits + + fast_extension = ((char*)buffer)[27] & 4; + + if (m_mode == uploader) + { + write_have_all(); + } + else + { + work_download(); + } +} + +void peer_conn::write_have_all() +{ + using namespace libtorrent::detail; + + 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); + error_code ec; + boost::asio::async_write(s, boost::asio::buffer(write_buf_proto, ptr - write_buf_proto) + , boost::bind(&peer_conn::on_have_all_sent, this, _1, _2)); + } + else + { + // bitfield + int len = (m_ti.num_pieces() + 7) / 8; + char* ptr = (char*)buffer; + write_uint32(len + 1, ptr); + write_uint8(5, ptr); + memset(ptr, 255, len); + ptr += len; + // unchoke + write_uint32(1, ptr); + write_uint8(1, ptr); + error_code ec; + boost::asio::async_write(s, boost::asio::buffer((char*)buffer, len + 10) + , boost::bind(&peer_conn::on_have_all_sent, this, _1, _2)); + } +} + +void peer_conn::on_have_all_sent(error_code const& ec, size_t bytes_transferred) +{ + if (ec) + { + close("ERROR SEND HAVE ALL: %s", ec); + return; + } + + // read message + boost::asio::async_read(s, boost::asio::buffer((char*)buffer, 4) + , boost::bind(&peer_conn::on_msg_length, this, _1, _2)); +} + +bool peer_conn::write_request() +{ + using namespace libtorrent::detail; + + // if we're choked (and there are no allowed-fast pieces left) + if (choked && allowed_fast.empty() && !m_current_piece_is_allowed) return false; + + // if there are no pieces left to request + if (pieces.empty() && suggested_pieces.empty() && current_piece == -1) return false; + + if (current_piece == -1) + { + // pick a new piece + if (choked && allowed_fast.size() > 0) + { + current_piece = allowed_fast.front(); + allowed_fast.erase(allowed_fast.begin()); + m_current_piece_is_allowed = true; + } + else if (suggested_pieces.size() > 0) + { + current_piece = suggested_pieces.front(); + suggested_pieces.erase(suggested_pieces.begin()); + m_current_piece_is_allowed = false; + } + else if (pieces.size() > 0) + { + current_piece = pieces.front(); + pieces.erase(pieces.begin()); + m_current_piece_is_allowed = false; + } + else + { + TORRENT_ASSERT(false); + } + } + char msg[] = "\0\0\0\xd\x06" + " " // piece + " " // offset + " "; // length + char* m = (char*)malloc(sizeof(msg)); + memcpy(m, msg, sizeof(msg)); + char* ptr = m + 5; + write_uint32(current_piece, ptr); + write_uint32(block * 16 * 1024, ptr); + write_uint32(16 * 1024, ptr); + error_code ec; + boost::asio::async_write(s, boost::asio::buffer(m, sizeof(msg) - 1) + , boost::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 bytes_transferred) +{ + 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]; + snprintf(tmp, sizeof(tmp), fmt, ec.message().c_str()); + int time = total_milliseconds(end_time - start_time); + if (time == 0) time = 1; + float up = (boost::int64_t(blocks_sent) * 0x4000) / time / 1000.f; + float down = (boost::int64_t(blocks_received) * 0x4000) / time / 1000.f; + error_code e; + + char ep_str[200]; + address const& addr = s.local_endpoint(e).address(); +#if TORRENT_USE_IPV6 + if (addr.is_v6()) + snprintf(ep_str, sizeof(ep_str), "[%s]:%d", addr.to_string(e).c_str() + , s.local_endpoint(e).port()); + else +#endif + snprintf(ep_str, sizeof(ep_str), "%s:%d", addr.to_string(e).c_str() + , s.local_endpoint(e).port()); + 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((char*)buffer, 4) + , boost::bind(&peer_conn::on_msg_length, this, _1, _2)); +} + +void peer_conn::on_msg_length(error_code const& ec, size_t bytes_transferred) +{ + using namespace libtorrent::detail; + + if ((ec == boost::asio::error::operation_aborted || ec == boost::asio::error::bad_descriptor) + && restarting) + { + start_conn(); + return; + } + + if (ec) + { + close("ERROR RECEIVE MESSAGE PREFIX: %s", ec); + return; + } + char* ptr = (char*)buffer; + unsigned int length = read_uint32(ptr); + if (length > sizeof(buffer)) + { + fprintf(stderr, "len: %d\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((char*)buffer, 4) + , boost::bind(&peer_conn::on_msg_length, this, _1, _2)); + } + else + { + boost::asio::async_read(s, boost::asio::buffer((char*)buffer, length) + , boost::bind(&peer_conn::on_message, this, _1, _2)); + } +} + +void peer_conn::on_message(error_code const& ec, size_t bytes_transferred) +{ + using namespace libtorrent::detail; + + if ((ec == boost::asio::error::operation_aborted || ec == boost::asio::error::bad_descriptor) + && restarting) + { + start_conn(); + return; + } + + if (ec) + { + close("ERROR RECEIVE MESSAGE: %s", ec); + return; + } + char* ptr = (char*)buffer; + int msg = read_uint8(ptr); + + m_on_msg(msg, ptr, bytes_transferred); + + switch (m_mode) + { + case peer_conn::uploader: + if (msg == 6) + { + if (bytes_transferred != 13) + { + close("REQUEST packet has invalid size", error_code()); + return; + } + int piece = detail::read_int32(ptr); + int start = detail::read_int32(ptr); + int length = detail::read_int32(ptr); + write_piece(piece, start, length); + } + else if (msg == 3) // not-interested + { + close("DONE", error_code()); + return; + } + else + { + // read another message + boost::asio::async_read(s, boost::asio::buffer(buffer, 4) + , boost::bind(&peer_conn::on_msg_length, this, _1, _2)); + } + break; + case peer_conn::downloader: + if (msg == 0xe) // have_all + { + // build a list of all pieces and request them all! + pieces.resize(m_ti.num_pieces()); + for (int i = 0; i < int(pieces.size()); ++i) + pieces[i] = i; + std::random_shuffle(pieces.begin(), pieces.end()); + } + else if (msg == 4) // have + { + int piece = detail::read_int32(ptr); + if (pieces.empty()) pieces.push_back(piece); + else pieces.insert(pieces.begin() + (rand() % pieces.size()), piece); + } + else if (msg == 5) // bitfield + { + pieces.reserve(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; + } + std::random_shuffle(pieces.begin(), pieces.end()); + } + 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 piece = detail::read_int32(ptr); + int start = detail::read_int32(ptr); + + if (int((start + bytes_transferred) / 0x4000) == m_blocks_per_piece) + { + write_have(piece); + return; + } + } + else if (msg == 13) // suggest + { + int piece = detail::read_int32(ptr); + std::vector::iterator i = std::find(pieces.begin(), pieces.end(), piece); + if (i != pieces.end()) + { + pieces.erase(i); + suggested_pieces.push_back(piece); + } + } + else if (msg == 16) // reject request + { + int piece = detail::read_int32(ptr); + int start = detail::read_int32(ptr); + int length = detail::read_int32(ptr); + + // put it back! + if (current_piece != piece) + { + if (pieces.empty() || pieces.back() != piece) + pieces.push_back(piece); + } + else + { + block = (std::min)(start / 0x4000, block); + if (block == 0) + { + pieces.push_back(current_piece); + current_piece = -1; + m_current_piece_is_allowed = false; + } + } + --outstanding_requests; + fprintf(stderr, "REJECT: [ piece: %d start: %d length: %d ]\n", piece, start, length); + } + else if (msg == 0) // choke + { + choked = true; + } + else if (msg == 1) // unchoke + { + choked = false; + } + else if (msg == 17) // allowed_fast + { + int piece = detail::read_int32(ptr); + std::vector::iterator i = std::find(pieces.begin(), pieces.end(), piece); + if (i != pieces.end()) + { + pieces.erase(i); + allowed_fast.push_back(piece); + } + } + work_download(); + break; + case peer_conn::idle: + // read another message + boost::asio::async_read(s, boost::asio::buffer(buffer, 4) + , boost::bind(&peer_conn::on_msg_length, this, _1, _2)); + break; + } +} +/* +bool peer_conn::verify_piece(int piece, int start, char const* ptr, int size) +{ + boost::uint32_t* buf = (boost::uint32_t*)ptr; + boost::uint32_t fill = (piece << 8) | ((start / 0x4000) & 0xff); + for (int i = 0; i < size / 4; ++i) + { + if (buf[i] != fill) + { + fprintf(stderr, "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 libtorrent::detail; + +// generate_block(write_buffer, piece, start, length); + + char* ptr = write_buf_proto; + write_uint32(9 + length, ptr); + TORRENT_ASSERT(length == 0x4000); + write_uint8(7, ptr); + write_uint32(piece, ptr); + write_uint32(start, ptr); + boost::array vec; + vec[0] = boost::asio::buffer(write_buf_proto, ptr - write_buf_proto); + vec[1] = boost::asio::buffer(write_buffer, length); + boost::asio::async_write(s, vec, boost::bind(&peer_conn::on_have_all_sent, this, _1, _2)); + ++blocks_sent; +} + +void peer_conn::write_have(int piece) +{ + using namespace libtorrent::detail; + + char* ptr = write_buf_proto; + write_uint32(5, ptr); + write_uint8(4, ptr); + write_uint32(piece, ptr); + boost::asio::async_write(s, boost::asio::buffer(write_buf_proto, 9), boost::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..ef4076e --- /dev/null +++ b/test/bittorrent_peer.hpp @@ -0,0 +1,118 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef BITTORRENT_PEER_HPP +#define BITTORRENT_PEER_HPP + +#include "libtorrent/socket.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/io_service.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/torrent_info.hpp" +#include "test.hpp" // for EXPORT +#include + +using namespace libtorrent; + +struct EXPORT peer_conn +{ + enum peer_mode_t + { uploader, downloader, idle }; + + peer_conn(io_service& ios + , boost::function on_msg + , libtorrent::torrent_info const& ti + , libtorrent::tcp::endpoint const& ep + , peer_mode_t mode); + + void start_conn(); + + void on_connect(error_code const& ec); + void on_handshake(char* h, error_code const& ec, size_t bytes_transferred); + void on_handshake2(error_code const& ec, size_t bytes_transferred); + void write_have_all(); + void on_have_all_sent(error_code const& ec, size_t bytes_transferred); + bool write_request(); + void on_req_sent(char* m, error_code const& ec, size_t bytes_transferred); + void close(char const* fmt, error_code const& ec); + void work_download(); + void on_msg_length(error_code const& ec, size_t bytes_transferred); + void on_message(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: + + tcp::socket s; + char write_buf_proto[100]; + boost::uint32_t write_buffer[17*1024/4]; + boost::uint32_t buffer[17*1024/4]; + + peer_mode_t m_mode; + torrent_info const& m_ti; + + int read_pos; + + boost::function m_on_msg; + + enum state_t + { + handshaking, + sending_request, + receiving_message + }; + int state; + std::vector pieces; + std::vector suggested_pieces; + std::vector allowed_fast; + bool choked; + int current_piece; // the piece we're currently requesting blocks from + bool m_current_piece_is_allowed; + int block; + int const m_blocks_per_piece; + int outstanding_requests; + // if this is true, this connection is a seed + bool fast_extension; + int blocks_received; + int blocks_sent; + time_point start_time; + time_point end_time; + tcp::endpoint endpoint; + bool restarting; +}; + +#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..84b8a34 --- /dev/null +++ b/test/dht_server.cpp @@ -0,0 +1,177 @@ +/* + +Copyright (c) 2013, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/thread.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/io_service.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/aux_/time.hpp" +#include "dht_server.hpp" +#include "test_utils.hpp" + +#include +#include +#include + +#if defined TORRENT_DEBUG && TORRENT_USE_IOSTREAM +#include +#endif + +using namespace libtorrent; + +struct dht_server +{ + + libtorrent::io_service m_ios; + boost::detail::atomic_count m_dht_requests; + udp::socket m_socket; + int m_port; + + boost::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) + { + fprintf(stderr, "Error opening listen DHT socket: %s\n", ec.message().c_str()); + return; + } + + m_socket.bind(udp::endpoint(address_v4::any(), 0), ec); + if (ec) + { + fprintf(stderr, "Error binding DHT socket to port 0: %s\n", ec.message().c_str()); + return; + } + m_port = m_socket.local_endpoint(ec).port(); + if (ec) + { + fprintf(stderr, "Error getting local endpoint of DHT socket: %s\n", ec.message().c_str()); + return; + } + + fprintf(stderr, "%s: DHT initialized on port %d\n", time_now_string(), m_port); + + m_thread.reset(new libtorrent::thread(boost::bind(&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() + { + char buffer[2000]; + + for (;;) + { + error_code ec; + udp::endpoint from; + size_t bytes_transferred; + bool done = false; + m_socket.async_receive_from( + boost::asio::buffer(buffer, sizeof(buffer)), from, 0 + , boost::bind(&incoming_packet, _1, _2, &bytes_transferred, &ec, &done)); + while (!done) + { + m_ios.poll_one(); + m_ios.reset(); + } + + if (ec == boost::asio::error::operation_aborted + || ec == boost::asio::error::bad_descriptor) return; + + if (ec) + { + fprintf(stderr, "Error receiving on DHT socket: %s\n", ec.message().c_str()); + return; + } + + try + { + entry msg = bdecode(buffer, buffer + bytes_transferred); + +#if defined TORRENT_DEBUG && TORRENT_USE_IOSTREAM + std::cerr << msg << std::endl; +#endif + ++m_dht_requests; + } + catch (std::exception& e) + { + fprintf(stderr, "failed to decode DHT message: %s\n", e.what()); + } + } + } +}; + +boost::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..65263de --- /dev/null +++ b/test/enum_if.cpp @@ -0,0 +1,100 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include + +using namespace libtorrent; + +int main() +{ + io_service ios; + error_code ec; + + address def_gw = get_default_gateway(ios, ec); + if (ec) + { + fprintf(stderr, "%s\n", ec.message().c_str()); + return 1; + } + + printf("Default gateway: %s\n", def_gw.to_string(ec).c_str()); + + printf("=========== Routes ===========\n"); + std::vector routes = enum_routes(ios, ec); + if (ec) + { + printf("%s\n", ec.message().c_str()); + return 1; + } + + printf("%-18s%-18s%-35s%-7sinterface\n", "destination", "network", "gateway", "mtu"); + + for (std::vector::const_iterator i = routes.begin() + , end(routes.end()); i != end; ++i) + { + printf("%-18s%-18s%-35s%-7d%s\n" + , i->destination.to_string(ec).c_str() + , i->netmask.to_string(ec).c_str() + , i->gateway.to_string(ec).c_str() + , i->mtu + , i->name); + } + + printf("========= Interfaces =========\n"); + + std::vector const& net = enum_net_interfaces(ios, ec); + if (ec) + { + printf("%s\n", ec.message().c_str()); + return 1; + } + + printf("%-30s%-45s%-20s%-8sflags\n", "address", "netmask", "name", "mtu"); + + for (std::vector::const_iterator i = net.begin() + , end(net.end()); i != end; ++i) + { + printf("%-30s%-45s%-20s%-8d%s%s%s\n" + , i->interface_address.to_string(ec).c_str() + , i->netmask.to_string(ec).c_str() + , i->name + , i->mtu + , (is_multicast(i->interface_address)?"multicast ":"") + , (is_local(i->interface_address)?"local ":"") + , (is_loopback(i->interface_address)?"loopback ":"") + ); + } +} + diff --git a/test/http.py b/test/http.py new file mode 100755 index 0000000..0e3efe0 --- /dev/null +++ b/test/http.py @@ -0,0 +1,233 @@ +# -*- coding: cp1252 -*- +# +# +#Copyright (c) <2009> +# +#Permission is hereby granted, free of charge, to any person +#obtaining a copy of this software and associated documentation +#files (the "Software"), to deal in the Software without +#restriction, including without limitation the rights to use, +#copy, modify, merge, publish, distribute, sublicense, and/or sell +#copies of the Software, and to permit persons to whom the +#Software is furnished to do so, subject to the following +#conditions: +# +#The above copyright notice and this permission notice shall be +#included in all copies or substantial portions of the Software. +# +#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +#EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +#OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +#NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +#HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +#WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +#FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +#OTHER DEALINGS IN THE SOFTWARE. + +"""\ +Copyright (c) <2009> + + ************************************** + *** Python Proxy - A Fast HTTP proxy *** + ************************************** + +Neste momento este proxy é um Elie Proxy. + +Suporta os métodos HTTP: + - OPTIONS; + - GET; + - HEAD; + - POST; + - PUT; + - DELETE; + - TRACE; + - CONENCT. + +Suporta: + - Conexões dos cliente em IPv4 ou IPv6; + - Conexões ao alvo em IPv4 e IPv6; + - Conexões todo o tipo de transmissão de dados TCP (CONNECT tunneling), + p.e. ligações SSL, como é o caso do HTTPS. + +A fazer: + - Verificar se o input vindo do cliente está correcto; + - Enviar os devidos HTTP erros se não, ou simplesmente quebrar a ligação; + - Criar um gestor de erros; + - Criar ficheiro log de erros; + - Colocar excepções nos sítios onde é previsível a ocorrência de erros, + p.e.sockets e ficheiros; + - Rever tudo e melhorar a estrutura do programar e colocar nomes adequados nas + variáveis e métodos; + - Comentar o programa decentemente; + - Doc Strings. + +Funcionalidades futuras: + - Adiconar a funcionalidade de proxy anónimo e transparente; + - Suportar FTP?. + + +(!) Atenção o que se segue só tem efeito em conexões não CONNECT, para estas o + proxy é sempre Elite. + +Qual a diferença entre um proxy Elite, Anónimo e Transparente? + - Um proxy elite é totalmente anónimo, o servidor que o recebe não consegue ter + conhecimento da existência do proxy e não recebe o endereço IP do cliente; + - Quando é usado um proxy anónimo o servidor sabe que o cliente está a usar um + proxy mas não sabe o endereço IP do cliente; + É enviado o cabeçalho HTTP "Proxy-agent". + - Um proxy transparente fornece ao servidor o IP do cliente e um informação que + se está a usar um proxy. + São enviados os cabeçalhos HTTP "Proxy-agent" e "HTTP_X_FORWARDED_FOR". + +""" + +import socket, thread, select, sys, base64, time, errno + +__version__ = '0.1.0 Draft 1' +BUFLEN = 8192 +VERSION = 'Python Proxy/'+__version__ +HTTPVER = 'HTTP/1.1' + +username = None +password = None + +class ConnectionHandler: + def __init__(self, connection, address, timeout): + self.client = connection + self.client_buffer = '' + self.timeout = timeout + self.method, self.path, self.protocol = self.get_base_header() + global username + global password + if username != None: + auth = base64.b64encode(username + ':' + password) + if not 'Proxy-Authorization: Basic ' + auth in self.client_buffer: + print 'PROXY - failed authentication: %s' % self.client_buffer + self.client.send(HTTPVER+' 401 Authentication Failed\n'+ + 'Proxy-agent: %s\n\n'%VERSION) + self.client.close() + return + try: + if self.method == 'CONNECT': + self.method_CONNECT() + elif self.method in ('OPTIONS', 'GET', 'HEAD', 'POST', 'PUT', + 'DELETE', 'TRACE'): + self.method_others() + except: + try: + self.client.send(HTTPVER+' 502 Connection failed\n'+ + 'Proxy-agent: %s\n\n'%VERSION) + except Exception, e: + print 'PROXY - ', e + self.client.close() + return + + self.client.close() + self.target.close() + + def get_base_header(self): + retries = 0 + while 1: + try: + self.client_buffer += self.client.recv(BUFLEN) + except socket.error, e: + err = e.args[0] + if (err == errno.EAGAIN or err == errno.EWOULDBLOCK) and retries < 20: + time.sleep(0.5) + retries += 1 + continue + raise e + end = self.client_buffer.find('\r\n\r\n') + if end!=-1: + break + line_end = self.client_buffer.find('\n') + print 'PROXY - %s' % self.client_buffer[:line_end]#debug + data = (self.client_buffer[:line_end+1]).split() + self.client_buffer = self.client_buffer[line_end+1:] + return data + + def method_CONNECT(self): + self._connect_target(self.path) + self.client.send(HTTPVER+' 200 Connection established\n'+ + 'Proxy-agent: %s\n\n'%VERSION) + self.client_buffer = '' + self._read_write() + + def method_others(self): + self.path = self.path[7:] + i = self.path.find('/') + host = self.path[:i] + path = self.path[i:] + self._connect_target(host) + self.target.send('%s %s %s\n' % (self.method, path, self.protocol)+ + self.client_buffer) + self.client_buffer = '' + self._read_write() + + def _connect_target(self, host): + i = host.find(':') + if i!=-1: + port = int(host[i+1:]) + host = host[:i] + else: + port = 80 + (soc_family, _, _, _, address) = socket.getaddrinfo(host, port)[0] + self.target = socket.socket(soc_family) + self.target.connect(address) + + def _read_write(self): + time_out_max = self.timeout/3 + socs = [self.client, self.target] + count = 0 + while 1: + count += 1 + (recv, _, error) = select.select(socs, [], socs, 3) + if error: + break + if recv: + for in_ in recv: + data = in_.recv(BUFLEN) + if in_ is self.client: + out = self.target + else: + out = self.client + if data: + out.send(data) + count = 0 + if count == time_out_max: + break + +def start_server(host='localhost', port=8080, IPv6=False, timeout=100, + handler=ConnectionHandler): + if IPv6==True: + soc_type=socket.AF_INET6 + else: + soc_type=socket.AF_INET + soc = socket.socket(soc_type) + soc.settimeout(120) + print "PROXY - Serving on %s:%d."%(host, port)#debug + soc.bind((host, port)) + soc.listen(0) + while 1: + thread.start_new_thread(handler, soc.accept()+(timeout,)) + +if __name__ == '__main__': + listen_port = 8080 + i = 1 + while i < len(sys.argv): + if sys.argv[i] == '--port': + listen_port = int(sys.argv[i+1]) + i += 1 + elif sys.argv[i] == '--username': + username = sys.argv[i+1] + i += 1 + elif sys.argv[i] == '--password': + password = sys.argv[i+1] + i += 1 + else: + if sys.argv[i] != '--help': print('PROXY - unknown option "%s"' % sys.argv[i]) + print('usage: http.py [--port ]') + sys.exit(1) + i += 1 + start_server(port=listen_port) + diff --git a/test/main.cpp b/test/main.cpp new file mode 100644 index 0000000..8061038 --- /dev/null +++ b/test/main.cpp @@ -0,0 +1,474 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include +#include // for exit() +#include "libtorrent/address.hpp" +#include "libtorrent/socket.hpp" +#include "setup_transfer.hpp" // for _g_test_failures +#include "test.hpp" +#include "dht_server.hpp" // for stop_dht +#include "peer_server.hpp" // for stop_peer +#include "udp_tracker.hpp" // for stop_udp_tracker + +#include "libtorrent/assert.hpp" +#include "libtorrent/file.hpp" +#include + +#ifdef WIN32 +#include // fot SetErrorMode +#include // for _dup and _dup2 +#include // for _getpid +#include + +#define dup _dup +#define dup2 _dup2 + +#else + +#include // for getpid() + +#endif + +using namespace libtorrent; + +// these are global so we can restore them on abnormal exits and print stuff +// out, such as the log +int old_stdout = -1; +int old_stderr = -1; +bool redirect_output = true; +bool keep_files = false; + +extern int _g_test_idx; + +// the current tests file descriptor +unit_test_t* current_test = NULL; + +void output_test_log_to_terminal() +{ + if (current_test == NULL || old_stdout == -1 || old_stderr == -1 + || !redirect_output || current_test->output == NULL) + return; + + fflush(stdout); + fflush(stderr); + dup2(old_stdout, fileno(stdout)); + dup2(old_stderr, fileno(stderr)); + + fseek(current_test->output, 0, SEEK_SET); + fprintf(stderr, "\x1b[1m[%s]\x1b[0m\n\n", current_test->name); + char buf[4096]; + int size = 0; + do { + size = fread(buf, 1, sizeof(buf), current_test->output); + if (size > 0) fwrite(buf, 1, size, stderr); + } 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__ + strcat(stack_text, __FUNCTION__); +#else + stack_text[0] = 0; + strcat(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::fprintf(stderr, "exception: (0x%x) %s caught:\n%s\n" + , code, name, stack_text); + + output_test_log_to_terminal(); + + exit(code); +} + +#else + +void sig_handler(int sig) +{ + char stack_text[10000]; + +#if (defined TORRENT_DEBUG && TORRENT_USE_ASSERTS) \ + || defined TORRENT_ASIO_DEBUGGING \ + || defined TORRENT_PROFILE_CALLS \ + || defined TORRENT_RELEASE_ASSERTS \ + || defined TORRENT_DEBUG_BUFFERS + print_backtrace(stack_text, sizeof(stack_text), 30); +#elif defined __FUNCTION__ + strcat(stack_text, __FUNCTION__); +#else + stack_text[0] = 0; + strcat(stack_text, ""); +#endif + char const* sig_name = 0; + switch (sig) + { +#define SIG(x) case x: sig_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 + }; + fprintf(stderr, "signal: %s caught:\n%s\n", sig_name, stack_text); + + output_test_log_to_terminal(); + +#ifdef WIN32 + exit(sig); +#else + exit(128 + sig); +#endif +} + +#endif // _WIN32 + +void print_usage(char const* executable) +{ + 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" + "\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); +} + +EXPORT int 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 (strcmp(argv[0], "-h") == 0 || strcmp(argv[0], "--help") == 0) + { + print_usage(executable); + return 0; + } + + if (strcmp(argv[0], "-l") == 0 || strcmp(argv[0], "--list") == 0) + { + printf("TESTS:\n"); + for (int i = 0; i < _g_num_unit_tests; ++i) + { + printf(" - %s\n", _g_unit_tests[i].name); + } + return 0; + } + + if (strcmp(argv[0], "-n") == 0 || strcmp(argv[0], "--no-redirect") == 0) + { + redirect_output = false; + } + + if (strcmp(argv[0], "-k") == 0 || strcmp(argv[0], "--keep") == 0) + { + 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 + +#else + + 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 + +#endif // WIN32 + + int process_id = -1; +#ifdef _WIN32 + process_id = _getpid(); +#else + process_id = getpid(); +#endif + char dir[40]; + snprintf(dir, sizeof(dir), "test_tmp_%u", process_id); + std::string test_dir = complete(dir); + error_code ec; + create_directory(test_dir, ec); + if (ec) + { + fprintf(stderr, "Failed to create test directory: %s\n", ec.message().c_str()); + return 1; + } +#ifdef TORRENT_WINDOWS + SetCurrentDirectoryA(dir); +#else + chdir(dir); +#endif + fprintf(stderr, "cwd = \"%s\"\n", test_dir.c_str()); + + int total_failures = 0; + + if (_g_num_unit_tests == 0) + { + fprintf(stderr, "\x1b[31mERROR: no unit tests registered\x1b[0m\n"); + return 1; + } + + if (redirect_output) + { + old_stdout = dup(fileno(stdout)); + old_stderr = dup(fileno(stderr)); + } + + int num_run = 0; + for (int i = 0; i < _g_num_unit_tests; ++i) + { + if (filter && tests_to_run.count(_g_unit_tests[i].name) == 0) + continue; + + unit_test_t& t = _g_unit_tests[i]; + + if (redirect_output) + { + // redirect test output to a temporary file + fflush(stdout); + fflush(stderr); + + FILE* f = tmpfile(); + if (f != NULL) + { + int ret1 = dup2(fileno(f), fileno(stdout)); + dup2(fileno(f), fileno(stderr)); + if (ret1 >= 0) + { + t.output = f; + } + else + { + fprintf(stderr, "failed to redirect output: (%d) %s\n" + , errno, strerror(errno)); + } + } + else + { + fprintf(stderr, "failed to create temporary file for redirecting " + "output: (%d) %s\n", errno, strerror(errno)); + } + } + + // get proper interleaving of stderr and stdout + setbuf(stdout, NULL); + setbuf(stderr, NULL); + + _g_test_idx = i; + current_test = &t; + +#ifndef BOOST_NO_EXCEPTIONS + try + { +#endif + + _g_test_failures = 0; + (*t.fun)(); +#ifndef BOOST_NO_EXCEPTIONS + } + catch (std::exception const& e) + { + char buf[200]; + snprintf(buf, sizeof(buf), "Terminated with exception: \"%s\"", e.what()); + report_failure(buf, __FILE__, __LINE__); + } + catch (...) + { + report_failure("Terminated with unknown exception", __FILE__, __LINE__); + } +#endif + + if (!tests_to_run.empty()) tests_to_run.erase(t.name); + + if (_g_test_failures > 0) + { + output_test_log_to_terminal(); + } + + t.num_failures = _g_test_failures; + t.run = true; + total_failures += _g_test_failures; + ++num_run; + + if (redirect_output && t.output) + { + fclose(t.output); + } + } + + if (redirect_output) + { + dup2(old_stdout, fileno(stdout)); + dup2(old_stderr, fileno(stderr)); + } + + if (!tests_to_run.empty()) + { + fprintf(stderr, "\x1b[1mUNKONWN tests:\x1b[0m\n"); + for (std::set::iterator i = tests_to_run.begin() + , end(tests_to_run.end()); i != end; ++i) + { + fprintf(stderr, " %s\n", i->c_str()); + } + } + + if (num_run == 0) + { + fprintf(stderr, "\x1b[31mERROR: no unit tests run\x1b[0m\n"); + 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_output) + { + fflush(stdout); + fflush(stderr); + } + + int ret = print_failures(); +#if !defined TORRENT_LOGGING + if (ret == 0 && !keep_files) + { + remove_all(test_dir, ec); + if (ec) + fprintf(stderr, "failed to remove test dir: %s\n", ec.message().c_str()); + } +#endif + + return total_failures ? 333 : 0; +} + diff --git a/test/make_torrent.cpp b/test/make_torrent.cpp new file mode 100644 index 0000000..0326403 --- /dev/null +++ b/test/make_torrent.cpp @@ -0,0 +1,203 @@ +/* + +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 "make_torrent.hpp" +#include "libtorrent/storage.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/file_pool.hpp" +#include "libtorrent/storage_defs.hpp" + +using namespace libtorrent; + +boost::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) + { + int 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) + { + int file_size = atoi(args.m_files[i].c_str()); + + files.push_back(entry()); + entry::dictionary_type& file_entry = files.back().dict(); + std::string const& ent = args.m_files[i]; + 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]; + snprintf(filename, sizeof(filename), "test_file-%d", i); + + std::string name = filename; + if (ent.find("name=") != std::string::npos) + { + int pos = ent.find("name=") + 5; + name = ent.substr(pos, ent.find(',', pos)); + } + file_entry["path"].list().push_back(name); + file_entry["length"] = file_size; + total_size += file_size; + } + } + + if (!args.m_url_seed.empty()) + { + e["url-list"] = args.m_url_seed; + } + + if (!args.m_http_seed.empty()) + { + e["httpseeds"] = args.m_http_seed; + } + + std::string piece_hashes; + + int num_pieces = (total_size + piece_length - 1) / piece_length; + int torrent_offset = 0; + for (int i = 0; i < num_pieces; ++i) + { + hasher h; + int const piece_size = (i < num_pieces - 1) ? piece_length : total_size - (num_pieces - 1) * piece_length; + + char const data = i; + 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, 1); + } + else + { + h.update(&data, 1); + } + } + piece_hashes += h.final().to_string(); + } + + info["pieces"] = piece_hashes; + + std::vector tmp; + std::back_insert_iterator > out(tmp); + bencode(out, e); + + FILE* f = fopen("test.torrent", "w+"); + fwrite(&tmp[0], 1, tmp.size(), f); + fclose(f); + + return boost::make_shared(&tmp[0], tmp.size()); +} + +void generate_files(libtorrent::torrent_info const& ti, std::string const& path + , bool alternate_data) +{ + file_pool fp; + + storage_params params; + params.files = &ti.files(); + params.path = path; + params.pool = &fp; + + default_storage st(params); + + int const num_pieces = ti.num_pieces(); + + std::vector buffer; + for (int i = 0; i < num_pieces; ++i) + { + int const piece_size = ti.piece_size(i); + buffer.resize(ti.piece_length()); + + boost::uint8_t const data = alternate_data ? 255 - i : i; + for (int o = 0; o < piece_size; ++o) + { + memcpy(&buffer[o], &data, 1); + } + + file::iovec_t b = { &buffer[0], size_t(piece_size) }; + storage_error ec; + int ret = st.writev(&b, 1, i, 0, 0, ec); + if (ret != piece_size || ec) + { + fprintf(stderr, "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..a385170 --- /dev/null +++ b/test/make_torrent.hpp @@ -0,0 +1,69 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef MAKE_TORRENT_HPP +#define MAKE_TORRENT_HPP + +#include "libtorrent/torrent_info.hpp" +#include +#include +#include +#include "test.hpp" + +enum flags_t +{ + private_torrent = 1 +}; + +struct torrent_args +{ + torrent_args() : m_priv(false) {} + torrent_args& name(char const* n) { m_name = n; return *this; } + torrent_args& file(char const* f) { m_files.push_back(f); return *this; } + torrent_args& url_seed(char const* u) { m_url_seed = u; return *this; } + torrent_args& http_seed(char const* u) { m_http_seed = u; return *this; } + torrent_args& priv() { m_priv = true; return *this; } + + bool m_priv; + std::string m_name; + std::vector m_files; + std::string m_url_seed; + std::string m_http_seed; +}; + +EXPORT boost::shared_ptr + make_test_torrent(torrent_args const& args); + +EXPORT void generate_files(libtorrent::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..8297acf --- /dev/null +++ b/test/peer_server.cpp @@ -0,0 +1,166 @@ +/* + +Copyright (c) 2013, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/thread.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/io_service.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/aux_/time.hpp" +#include "libtorrent/io_service.hpp" +#include "peer_server.hpp" +#include "test_utils.hpp" + +#include +#include +#include + +using namespace libtorrent; + +struct peer_server +{ + + libtorrent::io_service m_ios; + boost::detail::atomic_count m_peer_requests; + tcp::acceptor m_acceptor; + int m_port; + + boost::shared_ptr m_thread; + + peer_server() + : m_peer_requests(0) + , m_acceptor(m_ios) + , m_port(0) + { + error_code ec; + m_acceptor.open(tcp::v4(), ec); + if (ec) + { + fprintf(stderr, "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) + { + fprintf(stderr, "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) + { + fprintf(stderr, "PEER Error getting local endpoint of peer socket: %s\n", ec.message().c_str()); + return; + } + m_acceptor.listen(10, ec); + if (ec) + { + fprintf(stderr, "PEER Error listening on peer socket: %s\n", ec.message().c_str()); + return; + } + + fprintf(stderr, "%s: PEER peer initialized on port %d\n", time_now_string(), m_port); + + m_thread.reset(new libtorrent::thread(boost::bind(&peer_server::thread_fun, this))); + } + + ~peer_server() + { + m_acceptor.cancel(); + m_acceptor.close(); + 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); + condition_variable cond; + bool done = false; + m_acceptor.async_accept(socket, from, boost::bind(&new_connection, _1, &ec, &done)); + while (!done) + { + m_ios.poll_one(); + m_ios.reset(); + } + + if (ec == boost::asio::error::operation_aborted + || ec == boost::asio::error::bad_descriptor) return; + + if (ec) + { + fprintf(stderr, "PEER Error accepting connection on peer socket: %s\n", ec.message().c_str()); + return; + } + + fprintf(stderr, "%s: PEER incoming peer connection\n", time_now_string()); + ++m_peer_requests; + socket.close(ec); + } + } +}; + +boost::shared_ptr g_peer; + +int start_peer() +{ + g_peer.reset(new peer_server); + return g_peer->port(); +} + +// the number of DHT messages received +int num_peer_hits() +{ + if (g_peer) return g_peer->num_hits(); + return 0; +} + +void stop_peer() +{ + g_peer.reset(); +} + diff --git a/test/peer_server.hpp b/test/peer_server.hpp new file mode 100644 index 0000000..7070421 --- /dev/null +++ b/test/peer_server.hpp @@ -0,0 +1,47 @@ +/* + +Copyright (c) 2013, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef PEER_SERVER_HPP +#define PEER_SERVER_HPP + +#include "test.hpp" // for EXPORT + +// returns the port the peer is running on +EXPORT int start_peer(); + +// the number of incoming connections to this peer +EXPORT int num_peer_hits(); + +EXPORT void stop_peer(); + +#endif + diff --git a/test/print_alerts.cpp b/test/print_alerts.cpp new file mode 100644 index 0000000..6d183f1 --- /dev/null +++ b/test/print_alerts.cpp @@ -0,0 +1,73 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "print_alerts.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/alert_types.hpp" +#include "print_alerts.hpp" + +void print_alerts(libtorrent::session* ses, libtorrent::time_point start_time) +{ + using namespace libtorrent; + namespace lt = libtorrent; + + if (ses == NULL) 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; + boost::uint32_t millis = lt::duration_cast(d).count(); + 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..e5ecff3 --- /dev/null +++ b/test/print_alerts.hpp @@ -0,0 +1,43 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef PRINT_ALERTS_HPP +#define PRINT_ALERTS_HPP + +#include "libtorrent/time.hpp" +#include "libtorrent/session.hpp" +#include "test.hpp" // for EXPORT + +EXPORT void print_alerts(libtorrent::session* ses, libtorrent::time_point start_time); + +#endif + diff --git a/test/settings.cpp b/test/settings.cpp new file mode 100644 index 0000000..614c732 --- /dev/null +++ b/test/settings.cpp @@ -0,0 +1,75 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/alert.hpp" +#include "settings.hpp" + +using namespace libtorrent; + +libtorrent::settings_pack settings() +{ + const int mask = alert::all_categories + & ~(alert::progress_notification + | alert::performance_warning + | alert::stats_notification + | alert::picker_log_notification); + + 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); + + pack.set_int(settings_pack::alert_mask, mask); + +#ifndef TORRENT_BUILD_SIMULATOR + pack.set_bool(settings_pack::allow_multiple_connections_per_ip, true); +#else + // we use 0 threads (disk I/O operations will be performed in the network + // thread) to be simulator friendly. + pack.set_int(settings_pack::aio_threads, 0); +#endif + +#ifndef TORRENT_NO_DEPRECATE + 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..4390f60 --- /dev/null +++ b/test/settings.hpp @@ -0,0 +1,37 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/settings_pack.hpp" +#include "test.hpp" + +libtorrent::settings_pack EXPORT settings(); + diff --git a/test/setup_transfer.cpp b/test/setup_transfer.cpp new file mode 100644 index 0000000..eff4241 --- /dev/null +++ b/test/setup_transfer.cpp @@ -0,0 +1,935 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include + +#include "libtorrent/session.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/http_parser.hpp" +#include "libtorrent/thread.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/socket_io.hpp" // print_endpoint +#include "libtorrent/socket_type.hpp" +#include "libtorrent/instantiate_connection.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/session_stats.hpp" +#include "libtorrent/thread.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/broadcast_socket.hpp" // for supports_ipv6() + +#include +#include +#include + +#include "test.hpp" +#include "test_utils.hpp" +#include "setup_transfer.hpp" + +#ifdef TORRENT_USE_OPENSSL +#include +#include +#endif + +#ifndef _WIN32 +#include +#include +#endif + +#define DEBUG_WEB_SERVER 0 + +#define DLOG if (DEBUG_WEB_SERVER) fprintf + +using namespace libtorrent; +namespace lt = libtorrent; + +#if defined TORRENT_WINDOWS +#include +#endif + +boost::uint32_t g_addr = 0x92343023; + +void init_rand_address() +{ + g_addr = 0x92343023; +} + +address rand_v4() +{ + address_v4 ret; + do + { + g_addr += 0x3080ca; + ret = address_v4(g_addr); + } while (is_any(ret) || is_local(ret) || is_loopback(ret)); + return ret; +} + +sha1_hash rand_hash() +{ + sha1_hash ret; + for (int i = 0; i < 20; ++i) + ret[i] = lt::random(); + return ret; +} + +#if TORRENT_USE_IPV6 +address rand_v6() +{ + address_v6::bytes_type bytes; + for (int i = 0; i < int(bytes.size()); ++i) bytes[i] = rand(); + return address_v6(bytes); +} +#endif + +static boost::uint16_t g_port = 0; + +tcp::endpoint rand_tcp_ep() +{ + // make sure we don't procude the same "random" port twice + g_port = (g_port + 1) % 14038; + return tcp::endpoint(rand_v4(), g_port + 1024); +} + +udp::endpoint rand_udp_ep() +{ + g_port = (g_port + 1) % 14037; + return udp::endpoint(rand_v4(), g_port + 1024); +} + +std::map get_counters(libtorrent::session& s) +{ + using namespace libtorrent; + 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 (int i = 0; i < int(metrics.size()); ++i) + ret[metrics[i].name] = sa->values[metrics[i].value_index]; + return ret; +} + +alert const* wait_for_alert(lt::session& ses, int type, char const* name) +{ + time_point end = libtorrent::clock_type::now() + seconds(10); + while (true) + { + time_point now = clock_type::now(); + if (now > end) return NULL; + + alert const* ret = NULL; + + ses.wait_for_alert(end - now); + std::vector alerts; + ses.pop_alerts(&alerts); + for (std::vector::iterator i = alerts.begin() + , end(alerts.end()); i != end; ++i) + { + fprintf(stderr, "%s: %s: [%s] %s\n", time_now_string(), name + , (*i)->what(), (*i)->message().c_str()); + if ((*i)->type() == type && !ret) + { + ret = *i; + } + } + if (ret) return ret; + } + return NULL; +} + +int load_file(std::string const& filename, std::vector& v, libtorrent::error_code& ec, int limit) +{ + ec.clear(); + FILE* f = fopen(filename.c_str(), "rb"); + if (f == NULL) + { + 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(s); + if (s == 0) + { + fclose(f); + return 0; + } + + r = 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; +} + +void save_file(char const* filename, char const* data, int size) +{ + error_code ec; + file out(filename, file::write_only, ec); + TEST_CHECK(!ec); + if (ec) + { + fprintf(stderr, "ERROR opening file '%s': %s\n", filename, ec.message().c_str()); + return; + } + file::iovec_t b = { (void*)data, size_t(size) }; + out.writev(0, &b, 1, ec); + TEST_CHECK(!ec); + if (ec) + { + fprintf(stderr, "ERROR writing file '%s': %s\n", filename, ec.message().c_str()); + return; + } + +} + +bool print_alerts(lt::session& ses, char const* name + , bool allow_disconnects, bool allow_no_torrents, bool allow_failed_fastresume + , bool (*predicate)(libtorrent::alert const*), bool no_output) +{ + bool ret = false; + std::vector handles = ses.get_torrents(); + TEST_CHECK(!handles.empty() || allow_no_torrents); + torrent_handle h; + if (!handles.empty()) h = handles[0]; + std::vector alerts; + ses.pop_alerts(&alerts); + for (std::vector::iterator i = alerts.begin(); i != alerts.end(); ++i) + { + if (predicate && predicate(*i)) ret = true; + if (peer_disconnected_alert const* p = alert_cast(*i)) + { + fprintf(stderr, "%s: %s: [%s] (%s): %s\n", time_now_string(), name, (*i)->what(), print_endpoint(p->ip).c_str(), p->message().c_str()); + } + else if ((*i)->message() != "block downloading" + && (*i)->message() != "block finished" + && (*i)->message() != "piece finished" + && !no_output) + { + fprintf(stderr, "%s: %s: [%s] %s\n", time_now_string(), name, (*i)->what(), (*i)->message().c_str()); + } + + TEST_CHECK(alert_cast(*i) == 0 || allow_failed_fastresume); +/* + peer_error_alert const* pea = alert_cast(*i); + if (pea) + { + fprintf(stderr, "%s: peer error: %s\n", time_now_string(), pea->error.message().c_str()); + TEST_CHECK((!handles.empty() && h.status().is_seeding) + || pea->error.message() == "connecting to peer" + || pea->error.message() == "closing connection to ourself" + || pea->error.message() == "duplicate connection" + || pea->error.message() == "duplicate peer-id" + || pea->error.message() == "upload to upload connection" + || pea->error.message() == "stopping torrent" + || (allow_disconnects && pea->error.message() == "Broken pipe") + || (allow_disconnects && pea->error.message() == "Connection reset by peer") + || (allow_disconnects && pea->error.message() == "no shared cipher") + || (allow_disconnects && pea->error.message() == "End of file.")); + } +*/ + + invalid_request_alert const* ira = alert_cast(*i); + if (ira) + { + fprintf(stderr, "peer error: %s\n", ira->message().c_str()); + TEST_CHECK(false); + } + } + return ret; +} + +bool listen_done = false; +bool listen_alert(libtorrent::alert const* a) +{ + if (alert_cast(a) + || alert_cast(a)) + listen_done = true; + return true; +} + +void wait_for_listen(lt::session& ses, char const* name) +{ + listen_done = false; + alert const* a = 0; + do + { + print_alerts(ses, name, true, true, true, &listen_alert, 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); +} + +bool downloading_done = false; +bool downloading_alert(libtorrent::alert const* a) +{ + state_changed_alert const* sc = alert_cast(a); + if (sc && sc->state == torrent_status::downloading) + downloading_done = true; + return true; +} + +void wait_for_downloading(lt::session& ses, char const* name) +{ + time_point start = clock_type::now(); + downloading_done = false; + alert const* a = 0; + do + { + print_alerts(ses, name, true, true, true, &downloading_alert, false); + if (downloading_done) break; + if (total_seconds(clock_type::now() - start) > 10) break; + a = ses.wait_for_alert(seconds(2)); + } while (a); + if (!downloading_done) + { + fprintf(stderr, "%s: did not receive a state_changed_alert indicating " + "the torrent is downloading. waited: %d ms\n" + , name, int(total_milliseconds(clock_type::now() - start))); + } +} + +void print_ses_rate(float time + , libtorrent::torrent_status const* st1 + , libtorrent::torrent_status const* st2 + , libtorrent::torrent_status const* st3) +{ + if (st1) + { + fprintf(stderr, "%3.1fs | %dkB/s %dkB/s %d%% %d cc:%d%s", 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) + fprintf(stderr, " : %3.1fs | %dkB/s %dkB/s %d%% %d cc:%d%s", 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) + fprintf(stderr, " : %3.1fs | %dkB/s %dkB/s %d%% %d cc:%d%s", 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() : ""); + + fprintf(stderr, "\n"); +} + +void test_sleep(int milliseconds) +{ +#if defined TORRENT_WINDOWS || defined TORRENT_CYGWIN + Sleep(milliseconds); +#elif defined TORRENT_BEOS + snooze_until(system_time() + boost::int64_t(milliseconds) * 1000, B_SYSTEM_TIMEBASE); +#else + usleep(milliseconds * 1000); +#endif +} + +#ifdef _WIN32 +typedef DWORD pid_type; +#else +typedef pid_t pid_type; +#endif + +struct proxy_t +{ + pid_type pid; + int type; +}; + +// maps port to proxy type +static std::map running_proxies; + +void stop_proxy(int port) +{ + fprintf(stderr, "stopping proxy on port %d\n", port); + // don't shut down proxies until the test is + // completely done. This saves a lot of time. + // they're closed at the end of main() by + // calling stop_all_proxies(). +} + +// returns 0 on failure, otherwise pid +pid_type async_run(char const* cmdline) +{ +#ifdef _WIN32 + char buf[2048]; + snprintf(buf, sizeof(buf), "%s", cmdline); + + PROCESS_INFORMATION pi; + STARTUPINFOA startup; + memset(&startup, 0, sizeof(startup)); + startup.cb = sizeof(startup); + startup.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + startup.hStdOutput= GetStdHandle(STD_OUTPUT_HANDLE); + startup.hStdError = GetStdHandle(STD_INPUT_HANDLE); + int ret = CreateProcessA(NULL, buf, NULL, NULL, TRUE, CREATE_NEW_PROCESS_GROUP, NULL, NULL, &startup, &pi); + + if (ret == 0) + { + int error = GetLastError(); + fprintf(stderr, "failed (%d) %s\n", error, error_code(error, system_category()).message().c_str()); + return 0; + } + 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(NULL); + + int ret = posix_spawnp(&p, argv[0], NULL, NULL, &argv[0], NULL); + if (ret != 0) + { + fprintf(stderr, "failed (%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); + TerminateProcess(proc, 138); + CloseHandle(proc); +#else + printf("killing pid: %d\n", p); + kill(p, SIGKILL); +#endif +} + +void stop_all_proxies() +{ + std::map proxies = running_proxies; + for (std::map::iterator i = proxies.begin() + , end(proxies.end()); i != end; ++i) + { + stop_process(i->second.pid); + running_proxies.erase(i->second.pid); + } +} + +// returns a port on success and -1 on failure +int start_proxy(int proxy_type) +{ + using namespace libtorrent; + + std::map :: iterator i = running_proxies.begin(); + for (; i != running_proxies.end(); ++i) + { + if (i->second.type == proxy_type) { return i->first; } + } + + int port = 2000 + (lt::random() % 6000); + error_code ec; + io_service ios; + + // make sure the port we pick is free + do { + ++port; + tcp::socket s(ios); + s.open(tcp::v4(), ec); + if (ec) break; + s.bind(tcp::endpoint(address::from_string("127.0.0.1"), port), ec); + } while (ec); + + + char const* type = ""; + char const* auth = ""; + char const* cmd = ""; + + switch (proxy_type) + { + case settings_pack::socks4: + type = "socks4"; + auth = " --allow-v4"; + cmd = "python ../socks.py"; + break; + case settings_pack::socks5: + type = "socks5"; + cmd = "python ../socks.py"; + break; + case settings_pack::socks5_pw: + type = "socks5"; + auth = " --username testuser --password testpass"; + cmd = "python ../socks.py"; + break; + case settings_pack::http: + type = "http"; + cmd = "python ../http.py"; + break; + case settings_pack::http_pw: + type = "http"; + auth = " --username testuser --password testpass"; + cmd = "python ../http.py"; + break; + } + char buf[512]; + snprintf(buf, sizeof(buf), "%s --port %d%s", cmd, port, auth); + + fprintf(stderr, "%s starting proxy on port %d (%s %s)...\n", time_now_string(), port, type, auth); + fprintf(stderr, "%s\n", buf); + pid_type r = async_run(buf); + if (r == 0) abort(); + proxy_t t = { r, proxy_type }; + running_proxies.insert(std::make_pair(port, t)); + fprintf(stderr, "%s launched\n", time_now_string()); + test_sleep(500); + return port; +} + +using namespace libtorrent; + +template +boost::shared_ptr clone_ptr(boost::shared_ptr const& ptr) +{ + return boost::make_shared(*ptr); +} + +unsigned char random_byte() +{ return std::rand() & 0xff; } + +void create_random_files(std::string const& path, const int file_sizes[], int num_files) +{ + error_code ec; + char* random_data = (char*)malloc(300000); + for (int i = 0; i != num_files; ++i) + { + std::generate(random_data, random_data + 300000, random_byte); + char filename[200]; + snprintf(filename, sizeof(filename), "test%d", i); + char dirname[200]; + snprintf(dirname, sizeof(dirname), "test_dir%d", i / 5); + + std::string full_path = combine_path(path, dirname); + error_code ec; + create_directory(full_path, ec); + full_path = combine_path(full_path, filename); + + int to_write = file_sizes[i]; + file f(full_path, file::write_only, ec); + if (ec) fprintf(stderr, "failed to create file \"%s\": (%d) %s\n" + , full_path.c_str(), ec.value(), ec.message().c_str()); + boost::int64_t offset = 0; + while (to_write > 0) + { + int s = (std::min)(to_write, 300000); + file::iovec_t b = { random_data, size_t(s)}; + f.writev(offset, &b, 1, ec); + if (ec) fprintf(stderr, "failed to write file \"%s\": (%d) %s\n" + , full_path.c_str(), ec.value(), ec.message().c_str()); + offset += s; + to_write -= s; + } + } + free(random_data); +} + +boost::shared_ptr create_torrent(std::ostream* file + , char const* name, int piece_size + , int num_pieces, bool add_tracker, std::string ssl_certificate) +{ + // excercise 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); + libtorrent::create_torrent t(fs, piece_size); + if (add_tracker) + { + t.add_tracker(invalid_tracker_url); + t.add_tracker(invalid_tracker_protocol); + } + + if (!ssl_certificate.empty()) + { + std::vector file_buf; + error_code ec; + int res = load_file(ssl_certificate, file_buf, ec); + if (ec || res < 0) + { + fprintf(stderr, "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); + } + } + + std::vector piece(piece_size); + for (int i = 0; i < int(piece.size()); ++i) + piece[i] = (i % 26) + 'A'; + + // calculate the hash for all pieces + int num = t.num_pieces(); + sha1_hash ph = hasher(&piece[0], piece.size()).final(); + for (int i = 0; i < num; ++i) + t.set_hash(i, ph); + + if (file) + { + while (total_size > 0) + { + file->write(&piece[0], (std::min)(int(piece.size()), total_size)); + total_size -= piece.size(); + } + } + + std::vector tmp; + std::back_insert_iterator > out(tmp); + + entry tor = t.generate(); + + bencode(out, tor); + error_code ec; + return boost::make_shared( + &tmp[0], tmp.size(), boost::ref(ec), 0); +} + +boost::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 + , boost::shared_ptr* torrent, bool super_seeding + , add_torrent_params const* p, bool stop_lsd, bool use_ssl_ports + , boost::shared_ptr* torrent2) +{ + TORRENT_ASSERT(ses1); + TORRENT_ASSERT(ses2); + + if (stop_lsd) + { + settings_pack pack; + pack.set_bool(settings_pack::enable_lsd, false); + ses1->apply_settings(pack); + ses2->apply_settings(pack); + if (ses3) ses3->apply_settings(pack); + } + + // This has the effect of applying the global + // rule to all peers, regardless of if they're local or not + ip_filter f; + f.add_rule(address_v4::from_string("0.0.0.0") + , address_v4::from_string("255.255.255.255") + , 1 << 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; + pack.set_int(settings_pack::alert_mask, ~(alert::progress_notification | alert::stats_notification)); + 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); + peer_id pid; + std::generate(&pid[0], &pid[0] + 20, random_byte); + pack.set_str(settings_pack::peer_fingerprint, pid.to_string()); + ses1->apply_settings(pack); + TORRENT_ASSERT(ses1->id() == pid); + + std::generate(&pid[0], &pid[0] + 20, random_byte); + TORRENT_ASSERT(ses1->id() != pid); + pack.set_str(settings_pack::peer_fingerprint, pid.to_string()); + ses2->apply_settings(pack); + TORRENT_ASSERT(ses2->id() == pid); + if (ses3) + { + std::generate(&pid[0], &pid[0] + 20, random_byte); + TORRENT_ASSERT(ses1->id() != pid); + TORRENT_ASSERT(ses2->id() != pid); + pack.set_str(settings_pack::peer_fingerprint, pid.to_string()); + ses3->apply_settings(pack); + TORRENT_ASSERT(ses3->id() == pid); + } + + TORRENT_ASSERT(ses1->id() != ses2->id()); + if (ses3) TORRENT_ASSERT(ses3->id() != ses2->id()); + + boost::shared_ptr t; + if (torrent == 0) + { + error_code ec; + create_directory("tmp1" + suffix, ec); + std::ofstream file(combine_path("tmp1" + suffix, "temporary").c_str()); + t = ::create_torrent(&file, "temporary", piece_size, 9, false); + file.close(); + if (clear_files) + { + remove_all(combine_path("tmp2" + suffix, "temporary"), ec); + remove_all(combine_path("tmp3" + suffix, "temporary"), ec); + } + char ih_hex[41]; + to_hex((char const*)&t->info_hash()[0], 20, ih_hex); + fprintf(stderr, "generated torrent: %s tmp1%s/temporary\n", ih_hex, suffix.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 &= ~add_torrent_params::flag_paused; + param.flags &= ~add_torrent_params::flag_auto_managed; + if (p) param = *p; + param.ti = clone_ptr(t); + param.save_path = "tmp1" + suffix; + param.flags |= add_torrent_params::flag_seed_mode; + error_code ec; + torrent_handle tor1 = ses1->add_torrent(param, ec); + if (ec) + { + fprintf(stderr, "ses1.add_torrent: %s\n", ec.message().c_str()); + return boost::make_tuple(torrent_handle(), torrent_handle(), torrent_handle()); + } + tor1.super_seeding(super_seeding); + + // the downloader cannot use seed_mode + param.flags &= ~add_torrent_params::flag_seed_mode; + + TEST_CHECK(!ses1->get_torrents().empty()); + + torrent_handle tor2; + torrent_handle tor3; + + if (ses3) + { + param.ti = clone_ptr(t); + param.save_path = "tmp3" + suffix; + tor3 = ses3->add_torrent(param, ec); + TEST_CHECK(!ses3->get_torrents().empty()); + } + + if (use_metadata_transfer) + { + param.ti.reset(); + param.info_hash = t->info_hash(); + } + else if (torrent2) + { + param.ti = clone_ptr(*torrent2); + } + else + { + param.ti = clone_ptr(t); + } + param.save_path = "tmp2" + suffix; + + tor2 = ses2->add_torrent(param, ec); + TEST_CHECK(!ses2->get_torrents().empty()); + + TORRENT_ASSERT(ses1->get_torrents().size() == 1); + TORRENT_ASSERT(ses2->get_torrents().size() == 1); + +// test_sleep(100); + + if (connect_peers) + { + wait_for_downloading(*ses2, "ses2"); + + error_code ec; + int port = 0; + if (use_ssl_ports) + { + port = ses2->ssl_listen_port(); + fprintf(stderr, "%s: ses2->ssl_listen_port(): %d\n", time_now_string(), port); + } + + if (port == 0) + { + port = ses2->listen_port(); + fprintf(stderr, "%s: ses2->listen_port(): %d\n", time_now_string(), port); + } + + fprintf(stderr, "%s: ses1: connecting peer port: %d\n" + , time_now_string(), port); + tor1.connect_peer(tcp::endpoint(address::from_string("127.0.0.1", ec) + , 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(); + + fprintf(stderr, "ses3: connecting peer port: %d\n", port); + tor3.connect_peer(tcp::endpoint( + address::from_string("127.0.0.1", ec), port)); + fprintf(stderr, "ses3: connecting peer port: %d\n", port2); + tor3.connect_peer(tcp::endpoint( + address::from_string("127.0.0.1", ec) + , port2)); + } + } + + return boost::make_tuple(tor1, tor2, tor3); +} + +pid_type web_server_pid = 0; + +int start_web_server(bool ssl, bool chunked_encoding, bool keepalive) +{ + int port = 2000 + (lt::random() % 6000); + error_code ec; + io_service ios; + + // make sure the port we pick is free + do { + ++port; + tcp::socket s(ios); + s.open(tcp::v4(), ec); + if (ec) break; + s.bind(tcp::endpoint(address::from_string("127.0.0.1"), port), ec); + } while (ec); + + char buf[200]; + snprintf(buf, sizeof(buf), "python ../web_server.py %d %d %d %d" + , port, chunked_encoding , ssl, keepalive); + + fprintf(stderr, "%s starting web_server on port %d...\n", time_now_string(), port); + + fprintf(stderr, "%s\n", buf); + pid_type r = async_run(buf); + if (r == 0) abort(); + web_server_pid = r; + fprintf(stderr, "%s launched\n", time_now_string()); + test_sleep(500); + return port; +} + +void stop_web_server() +{ + if (web_server_pid == 0) return; + fprintf(stderr, "stopping web server\n"); + stop_process(web_server_pid); + web_server_pid = 0; +} + +tcp::endpoint ep(char const* ip, int port) +{ + error_code ec; + return tcp::endpoint(address::from_string(ip, ec), port); +} diff --git a/test/setup_transfer.hpp b/test/setup_transfer.hpp new file mode 100644 index 0000000..8a23955 --- /dev/null +++ b/test/setup_transfer.hpp @@ -0,0 +1,112 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef SETUP_TRANSFER_HPP +#define SETUP_TRANSFER_HPP + +#include "libtorrent/session.hpp" +#include +#include "test.hpp" + +namespace libtorrent +{ + class alert; + struct add_torrent_params; +} + +EXPORT int print_failures(); +EXPORT unsigned char random_byte(); + +EXPORT int load_file(std::string const& filename, std::vector& v, libtorrent::error_code& ec, int limit = 8000000); +EXPORT void save_file(char const* filename, char const* data, int size); + +EXPORT void report_failure(char const* err, char const* file, int line); + +EXPORT void init_rand_address(); +EXPORT libtorrent::address rand_v4(); +#if TORRENT_USE_IPV6 +EXPORT libtorrent::address rand_v6(); +#endif +EXPORT libtorrent::tcp::endpoint rand_tcp_ep(); +EXPORT libtorrent::udp::endpoint rand_udp_ep(); + +EXPORT libtorrent::sha1_hash rand_hash(); + +EXPORT std::map get_counters(libtorrent::session& s); + +EXPORT libtorrent::alert const* wait_for_alert( + libtorrent::session& ses, int type, char const* name = ""); + +EXPORT void print_ses_rate(float time + , libtorrent::torrent_status const* st1 + , libtorrent::torrent_status const* st2 + , libtorrent::torrent_status const* st3 = NULL); + +EXPORT bool print_alerts(libtorrent::session& ses, char const* name + , bool allow_disconnects = false + , bool allow_no_torrents = false + , bool allow_failed_fastresume = false + , bool (*)(libtorrent::alert const*) = 0 + , bool no_output = false); + +EXPORT void wait_for_listen(libtorrent::session& ses, char const* name); +EXPORT void wait_for_downloading(libtorrent::session& ses, char const* name); +EXPORT void test_sleep(int millisec); + +EXPORT void create_random_files(std::string const& path, const int file_sizes[], int num_files); + +EXPORT boost::shared_ptr create_torrent(std::ostream* file = 0 + , char const* name = "temporary", int piece_size = 16 * 1024, int num_pieces = 13 + , bool add_tracker = true, std::string ssl_certificate = ""); + +EXPORT boost::tuple +setup_transfer(libtorrent::session* ses1, libtorrent::session* ses2 + , libtorrent::session* ses3, bool clear_files, bool use_metadata_transfer = true + , bool connect = true, std::string suffix = "", int piece_size = 16 * 1024 + , boost::shared_ptr* torrent = 0, bool super_seeding = false + , libtorrent::add_torrent_params const* p = 0, bool stop_lsd = true, bool use_ssl_ports = false + , boost::shared_ptr* torrent2 = 0); + +EXPORT int start_web_server(bool ssl = false, bool chunked = false + , bool keepalive = true); + +EXPORT void stop_web_server(); +EXPORT int start_proxy(int type); +EXPORT void stop_proxy(int port); +EXPORT void stop_all_proxies(); + +EXPORT libtorrent::tcp::endpoint ep(char const* ip, int port); + +#endif + diff --git a/test/socks.py b/test/socks.py new file mode 100755 index 0000000..2ce316d --- /dev/null +++ b/test/socks.py @@ -0,0 +1,282 @@ +#!/usr/bin/env python + +"""Minimal non-feature complete socks proxy""" + +import random +import socket +from SocketServer import StreamRequestHandler, ThreadingTCPServer +from struct import pack, unpack +import threading +import sys + +def debug(s): + print >>sys.stderr, 'socks.py: ', s + +def error(s): + print >>sys.stderr, 'socks.py, ERROR: ', s + +class MyTCPServer(ThreadingTCPServer): + allow_reuse_address = True + + def handle_timeout(self): + raise Exception('timeout') + +CLOSE = object() + +VERSION = '\x05' +NOAUTH = '\x00' +USERPASS = '\x02' +CONNECT = '\x01' +UDP_ASSOCIATE = '\x03' +IPV4 = '\x01' +IPV6 = '\x04' +DOMAIN_NAME = '\x03' +SUCCESS = '\x00' + +password = None +username = None +allow_v4 = False + +def send(dest, msg): + if msg == CLOSE: + try: dest.shutdown(socket.SHUT_WR) + except: pass + dest.close() + return 0 + else: + return dest.sendall(msg) + +def recv(source, buffer): + data = source.recv(buffer) + if data == '': + 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 = '' + while len(data) < n: + extra = self.rfile.read(n) + if extra == '': + raise Exception('Connection closed') + data += extra + return data + + def 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 == '\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 = '' + c = self.read(1) + while c != '\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 != '\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 == 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 != '\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 or pwd != password: + 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, cmd, zero, address_type = self.read(4) + if version != '\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 != '\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(map(lambda x: hex(x)[2:],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, e: + print e + 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, e: + print e + 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,e: + print e + + def send_reply_v4(self, (bind_addr, bind_port)): + self.wfile.write('\0\x5a\0\0\0\0\0\0') + self.wfile.flush() + + def send_reply(self, (bind_addr, bind_port)): + 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, '\x00', IPV4, *full_address) + self.wfile.write(msg) + + def send_reply6(self, (bind_addr, bind_port, unused1, unused2)): + bind_tuple = tuple(map(lambda x: int(x,16), bind_addr.split(':'))) + full_address = bind_tuple + (bind_port,) + debug('Setting up forwarding port %r' % (full_address,)) + msg = pack('>cccc8HH', VERSION, SUCCESS, '\x00', IPV6, *full_address) + self.wfile.write(msg) + + def send_no_method(self): + self.wfile.write('\x05\xff') + self.close_request() + + def send_no_auth_method(self): + self.wfile.write('\x05\x00') + self.wfile.flush() + + def send_user_pass_auth_method(self): + self.wfile.write('\x05\x02') + self.wfile.flush() + + def send_authenticated(self): + self.wfile.write('\x01\x00') + self.wfile.flush() + +if __name__ == '__main__': + + 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.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/swarm_suite.cpp b/test/swarm_suite.cpp new file mode 100644 index 0000000..3be77fa --- /dev/null +++ b/test/swarm_suite.cpp @@ -0,0 +1,239 @@ +/* + +Copyright (c) 2014, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/session.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/thread.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/random.hpp" +#include + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "swarm_suite.hpp" + +void test_swarm(int flags) +{ + using namespace libtorrent; + namespace lt = libtorrent; + + fprintf(stderr, "\n\n ==== TEST SWARM === %s%s%s%s%s ===\n\n\n" + , (flags & super_seeding) ? "super-seeding ": "" + , (flags & strict_super_seeding) ? "strict-super-seeding ": "" + , (flags & seed_mode) ? "seed-mode ": "" + , (flags & time_critical) ? "time-critical ": "" + , (flags & suggest) ? "suggest ": "" + ); + + // in case the previous run was terminated + error_code ec; + remove_all("tmp1_swarm", ec); + remove_all("tmp2_swarm", ec); + remove_all("tmp3_swarm", ec); + + // these are declared before the session objects + // so that they are destructed last. This enables + // the sessions to destruct in parallel + session_proxy p1; + session_proxy p2; + session_proxy p3; + + const int mask = alert::all_categories + & ~(alert::progress_notification + | alert::performance_warning + | alert::stats_notification); + + 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_int(settings_pack::alert_mask, mask); + pack.set_bool(settings_pack::allow_multiple_connections_per_ip, true); + + if (flags & strict_super_seeding) + pack.set_bool(settings_pack::strict_super_seeding, true); + + if (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 rate_limit = 100000; + + int port = lt::random() % 100; + char iface[50]; + snprintf(iface, sizeof(iface), "0.0.0.0:480%02d", port); + pack.set_int(settings_pack::upload_rate_limit, rate_limit); + pack.set_str(settings_pack::listen_interfaces, iface); + pack.set_int(settings_pack::max_retry_port_bind, 1000); + + pack.set_int(settings_pack::out_enc_policy, settings_pack::pe_forced); + pack.set_int(settings_pack::in_enc_policy, settings_pack::pe_forced); + + lt::session ses1(pack); + + snprintf(iface, sizeof(iface), "0.0.0.0:490%02d", port); + pack.set_str(settings_pack::listen_interfaces, iface); + pack.set_int(settings_pack::download_rate_limit, rate_limit / 2); + pack.set_int(settings_pack::upload_rate_limit, rate_limit); + lt::session ses2(pack); + + snprintf(iface, sizeof(iface), "0.0.0.0:500%02d", port); + pack.set_str(settings_pack::listen_interfaces, iface); + lt::session ses3(pack); + + torrent_handle tor1; + torrent_handle tor2; + torrent_handle tor3; + + add_torrent_params p; + p.flags &= ~add_torrent_params::flag_paused; + p.flags &= ~add_torrent_params::flag_auto_managed; + if (flags & seed_mode) p.flags |= add_torrent_params::flag_seed_mode; + // test using piece sizes smaller than 16kB + boost::tie(tor1, tor2, tor3) = setup_transfer(&ses1, &ses2, &ses3, true + , false, true, "_swarm", 8 * 1024, 0, flags & super_seeding, &p); + + if (flags & time_critical) + { + tor2.set_piece_deadline(2, 0); + tor2.set_piece_deadline(5, 1000); + tor2.set_piece_deadline(8, 2000); + } + + float sum_dl_rate2 = 0.f; + float sum_dl_rate3 = 0.f; + int count_dl_rates2 = 0; + int count_dl_rates3 = 0; + + for (int i = 0; i < 80; ++i) + { + print_alerts(ses1, "ses1"); + print_alerts(ses2, "ses2"); + print_alerts(ses3, "ses3"); + + torrent_status st1 = tor1.status(); + torrent_status st2 = tor2.status(); + torrent_status st3 = tor3.status(); + + if (flags & super_seeding) + { + TEST_CHECK(st1.is_seeding); + TEST_CHECK(st1.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(i, &st1, &st2, &st3); + + if (st2.is_seeding && st3.is_seeding) break; + test_sleep(1000); + } + + TEST_CHECK(tor2.status().is_seeding); + TEST_CHECK(tor3.status().is_seeding); + + float average2 = sum_dl_rate2 / float(count_dl_rates2); + float average3 = sum_dl_rate3 / float(count_dl_rates3); + + std::cerr << average2 << std::endl; + std::cerr << "average rate: " << (average2 / 1000.f) << "kB/s - " + << (average3 / 1000.f) << "kB/s" << std::endl; + + if (tor2.status().is_seeding && tor3.status().is_seeding) std::cerr << "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) != 0); + + // 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)))) + { + fprintf(stderr, "wait returned: %d ms\n" + , int(total_milliseconds(clock_type::now() - start))); + std::vector alerts; + ses1.pop_alerts(&alerts); + for (std::vector::iterator i = alerts.begin() + , end(alerts.end()); i != end; ++i) + { + fprintf(stderr, "%s\n", ret->message().c_str()); + } + start = clock_type::now(); + } + + fprintf(stderr, "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 end = clock_type::now(); + + fprintf(stderr, "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); +} + + diff --git a/test/swarm_suite.hpp b/test/swarm_suite.hpp new file mode 100644 index 0000000..8356fd8 --- /dev/null +++ b/test/swarm_suite.hpp @@ -0,0 +1,45 @@ +/* + +Copyright (c) 2014, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" + +enum test_flags_t +{ + super_seeding = 1, + strict_super_seeding = 2, + seed_mode = 4, + time_critical = 8, + suggest = 16, +}; + +void EXPORT test_swarm(int flags = 0); + diff --git a/test/test.cpp b/test/test.cpp new file mode 100644 index 0000000..93cacd0 --- /dev/null +++ b/test/test.cpp @@ -0,0 +1,93 @@ +/* + +Copyright (c) 2008-2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include // for tmpfile() +#include "test.hpp" + +unit_test_t _g_unit_tests[1024]; +int _g_num_unit_tests = 0; +int _g_test_failures = 0; +int _g_test_idx = 0; + +static std::vector failure_strings; + +int test_counter() +{ + return _g_test_idx; +} + +void report_failure(char const* err, char const* file, int line) +{ + char buf[500]; + snprintf(buf, sizeof(buf), "\x1b[41m***** %s:%d \"%s\" *****\x1b[0m\n", file, line, err); + fprintf(stderr, "\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 = strlen(_g_unit_tests[i].name); + if (len > longest_name) longest_name = len; + } + + fprintf(stderr, "\n\n"); + + 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) + { + fprintf(stderr, "\x1b[32m[%-*s] ***PASS***\n" + , longest_name, _g_unit_tests[i].name); + } + else + { + fprintf(stderr, "\x1b[31m[%-*s] %d FAILURES\n" + , longest_name + , _g_unit_tests[i].name + , _g_unit_tests[i].num_failures); + } + } + + fprintf(stderr, "\x1b[0m"); + + if (_g_test_failures > 0) + fprintf(stderr, "\n\n\x1b[41m == %d TEST(S) FAILED ==\x1b[0m\n\n\n", _g_test_failures); + return _g_test_failures; +} + diff --git a/test/test.hpp b/test/test.hpp new file mode 100644 index 0000000..f9751e9 --- /dev/null +++ b/test/test.hpp @@ -0,0 +1,165 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TEST_HPP +#define TEST_HPP + +#include "libtorrent/address.hpp" +#include "libtorrent/socket.hpp" + +#include +#include +#include +#include +#include + +#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. + +#if defined __clang__ +#pragma clang diagnostic ignored "-Wdeprecated" +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + +#elif defined __GNUC__ +#pragma GCC diagnostic ignored "-Wdeprecated" +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + +#elif defined _MSC_VER +#pragma warning(disable : 4996) +#endif + +#if defined TORRENT_BUILDING_TEST_SHARED +#define EXPORT BOOST_SYMBOL_EXPORT +#elif defined TORRENT_LINK_TEST_SHARED +#define EXPORT BOOST_SYMBOL_IMPORT +#else +#define EXPORT +#endif + +void EXPORT report_failure(char const* err, char const* file, int line); +int EXPORT print_failures(); +int EXPORT test_counter(); + +typedef void (*unit_test_fun_t)(); + +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; + +#define TORRENT_TEST(test_name) \ + static void BOOST_PP_CAT(unit_test_, test_name)(); \ + static struct BOOST_PP_CAT(register_class_, test_name) { \ + BOOST_PP_CAT(register_class_, test_name) () { \ + unit_test_t& t = _g_unit_tests[_g_num_unit_tests]; \ + t.fun = &BOOST_PP_CAT(unit_test_, test_name); \ + t.name = __FILE__ "." #test_name; \ + t.num_failures = 0; \ + t.run = false; \ + t.output = NULL; \ + _g_num_unit_tests++; \ + } \ + } BOOST_PP_CAT(_static_registrar_, test_name); \ + static void BOOST_PP_CAT(unit_test_, test_name)() + +#define TEST_REPORT_AUX(x, line, file) \ + report_failure(x, line, file) + +#ifdef BOOST_NO_EXCEPTIONS +#define TEST_CHECK(x) \ + if (!(x)) \ + TEST_REPORT_AUX("TEST_CHECK failed: \"" #x "\"", __FILE__, __LINE__); +#define TEST_EQUAL(x, y) \ + if ((x) != (y)) { \ + std::stringstream s__; \ + s__ << "TEST_EQUAL_ERROR:\n" #x ": " << (x) << "\nexpected: " << (y); \ + TEST_REPORT_AUX(s__.str().c_str(), __FILE__, __LINE__); \ + } +#else +#define TEST_CHECK(x) \ + try \ + { \ + if (!(x)) \ + TEST_REPORT_AUX("TEST_CHECK failed: \"" #x "\"", __FILE__, __LINE__); \ + } \ + catch (std::exception& e) \ + { \ + TEST_ERROR("Exception thrown: " #x " :" + std::string(e.what())); \ + } \ + catch (...) \ + { \ + TEST_ERROR("Exception thrown: " #x); \ + } + +#define TEST_EQUAL(x, y) \ + try { \ + if ((x) != (y)) { \ + std::stringstream s__; \ + s__ << "TEST_EQUAL_ERROR: " #x ": " << (x) << " expected: " << (y); \ + TEST_REPORT_AUX(s__.str().c_str(), __FILE__, __LINE__); \ + } \ + } \ + catch (std::exception& e) \ + { \ + TEST_ERROR("Exception thrown: " #x " :" + std::string(e.what())); \ + } \ + catch (...) \ + { \ + TEST_ERROR("Exception thrown: " #x); \ + } +#endif + +#define TEST_ERROR(x) \ + TEST_REPORT_AUX((std::string("ERROR: \"") + (x) + "\"").c_str(), __FILE__, __LINE__) + +#define TEST_NOTHROW(x) \ + try \ + { \ + x; \ + } \ + catch (...) \ + { \ + TEST_ERROR("Exception thrown: " #x); \ + } + +#endif // TEST_HPP + diff --git a/test/test_alert_manager.cpp b/test/test_alert_manager.cpp new file mode 100644 index 0000000..bf0c28f --- /dev/null +++ b/test/test_alert_manager.cpp @@ -0,0 +1,323 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/alert_manager.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/extensions.hpp" +#include "libtorrent/thread.hpp" +#include "setup_transfer.hpp" + +#include +#include +#include + +using namespace libtorrent; + +TORRENT_TEST(limit) +{ + alert_manager mgr(500, 0xffffffff); + + 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 (int i = 0; i < 600; ++i) + mgr.emplace_alert(torrent_handle()); + + TEST_EQUAL(mgr.pending(), true); + + std::vector alerts; + int num_resume; + mgr.get_all(alerts, num_resume); + + // even though we posted 600, the limit was 500 + TEST_EQUAL(alerts.size(), 500); + + TEST_EQUAL(mgr.pending(), false); + + // now, try lowering the limit and do the same thing again + mgr.set_alert_queue_size_limit(200); + + for (int i = 0; i < 600; ++i) + mgr.emplace_alert(torrent_handle()); + + TEST_EQUAL(mgr.pending(), true); + + mgr.get_all(alerts, num_resume); + + // even though we posted 600, the limit was 200 + TEST_EQUAL(alerts.size(), 200); +} + +TORRENT_TEST(priority_limit) +{ + alert_manager mgr(100, 0xffffffff); + + TEST_EQUAL(mgr.alert_queue_size_limit(), 100); + + // this should only add 100 because of the limit + for (int i = 0; i < 200; ++i) + mgr.emplace_alert(torrent_handle()); + + // the limit is twice as high for priority alerts + for (int i = 0; i < 200; ++i) + mgr.emplace_alert(torrent_handle(), i, error_code()); + + std::vector alerts; + int num_resume; + mgr.get_all(alerts, num_resume); + + // even though we posted 400, the limit was 100 for half of them and + // 200 for the other half, meaning we should have 200 alerts now + TEST_EQUAL(alerts.size(), 200); +} + +void test_dispatch_fun(int& cnt, std::auto_ptr const& a) +{ + ++cnt; +} + +TORRENT_TEST(dispatch_function) +{ +#ifndef TORRENT_NO_DEPRECATE + int cnt = 0; + alert_manager mgr(100, 0xffffffff); + + 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()); + + TEST_EQUAL(mgr.pending(), true); + + mgr.set_dispatch_function(boost::bind(&test_dispatch_fun, boost::ref(cnt), _1)); + + TEST_EQUAL(mgr.pending(), false); + + TEST_EQUAL(cnt, 20); + + for (int i = 0; i < 200; ++i) + mgr.emplace_alert(torrent_handle()); + + TEST_EQUAL(mgr.pending(), false); + TEST_EQUAL(cnt, 220); +#endif +} + +void test_notify_fun(int& cnt) +{ + ++cnt; +} + +TORRENT_TEST(notify_function) +{ + int cnt = 0; + alert_manager mgr(100, 0xffffffff); + + 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()); + + 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(boost::bind(&test_notify_fun, boost::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()); + + 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; + int num_resume; + mgr.get_all(alerts, num_resume); + + TEST_EQUAL(mgr.pending(), false); + + for (int i = 0; i < 20; ++i) + mgr.emplace_alert(torrent_handle()); + + TEST_EQUAL(mgr.pending(), true); + TEST_EQUAL(cnt, 2); +} + +#ifndef TORRENT_DISABLE_EXTENSIONS +int plugin_alerts[3] = { 0, 0, 0 }; + +struct test_plugin : libtorrent::plugin +{ + test_plugin(int index) : m_index(index) {} + virtual void on_alert(alert const* a) + { + ++plugin_alerts[m_index]; + } + int m_index; +}; + +#endif + +TORRENT_TEST(extensions) +{ +#ifndef TORRENT_DISABLE_EXTENSIONS + memset(plugin_alerts, 0, sizeof(plugin_alerts)); + alert_manager mgr(100, 0xffffffff); + + mgr.add_extension(boost::make_shared(0)); + mgr.add_extension(boost::make_shared(1)); + mgr.add_extension(boost::make_shared(2)); + + for (int i = 0; i < 53; ++i) + mgr.emplace_alert(torrent_handle()); + + 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()); + + TEST_EQUAL(plugin_alerts[0], 70); + TEST_EQUAL(plugin_alerts[1], 70); + TEST_EQUAL(plugin_alerts[2], 70); +#endif +} + +void post_torrent_added(alert_manager* mgr) +{ + test_sleep(10); + mgr->emplace_alert(torrent_handle()); +} + +TORRENT_TEST(wait_for_alert) +{ + alert_manager mgr(100, 0xffffffff); + + 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(0)); + fprintf(stderr, "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()); + + start = clock_type::now(); + a = mgr.wait_for_alert(seconds(1)); + end = clock_type::now(); + + fprintf(stderr, "delay: %d ms\n", int(total_milliseconds(end - start))); + TEST_CHECK(end - start < milliseconds(1)); + TEST_CHECK(a->type() == torrent_added_alert::alert_type); + + std::vector alerts; + int num_resume = 0; + mgr.get_all(alerts, num_resume); + + start = clock_type::now(); + libtorrent::thread posting_thread(boost::bind(&post_torrent_added, &mgr)); + + a = mgr.wait_for_alert(seconds(10)); + end = clock_type::now(); + + fprintf(stderr, "delay: %d ms\n", int(total_milliseconds(end - start))); + TEST_CHECK(end - start < milliseconds(500)); + TEST_CHECK(a->type() == torrent_added_alert::alert_type); + + posting_thread.join(); +} + +TORRENT_TEST(queued_resume) +{ + alert_manager mgr(100, 0xffffffff); + + TEST_EQUAL(mgr.num_queued_resume(), 0); + + for (int i = 0; i < 17; ++i) + mgr.emplace_alert(torrent_handle()); + + TEST_EQUAL(mgr.num_queued_resume(), 0); + + std::vector alerts; + int num_resume = 0; + mgr.get_all(alerts, num_resume); + TEST_EQUAL(num_resume, 0); + TEST_EQUAL(alerts.size(), 17); + + TEST_EQUAL(mgr.num_queued_resume(), 0); + + error_code ec(boost::system::errc::no_such_file_or_directory + , generic_category()); + + for (int i = 0; i < 2; ++i) + mgr.emplace_alert(torrent_handle(), ec); + + TEST_EQUAL(mgr.num_queued_resume(), 2); + + mgr.get_all(alerts, num_resume); + TEST_EQUAL(num_resume, 2); + TEST_EQUAL(alerts.size(), 2); + + TEST_EQUAL(mgr.num_queued_resume(), 0); +} + +TORRENT_TEST(alert_mask) +{ + alert_manager mgr(100, 0xffffffff); + + TEST_CHECK(mgr.should_post()); + TEST_CHECK(mgr.should_post()); + + mgr.set_alert_mask(0); + + TEST_CHECK(!mgr.should_post()); + TEST_CHECK(!mgr.should_post()); +} + diff --git a/test/test_auto_unchoke.cpp b/test/test_auto_unchoke.cpp new file mode 100644 index 0000000..0077f19 --- /dev/null +++ b/test/test_auto_unchoke.cpp @@ -0,0 +1,157 @@ +/* + +Copyright (c) 2011, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/session.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/thread.hpp" +#include +#include + +#include "test.hpp" +#include "setup_transfer.hpp" + +void test_swarm() +{ + using namespace libtorrent; + namespace lt = libtorrent; + + // 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; + // 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_int(settings_pack::alert_mask, alert::all_categories); + 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, rate_limit); + pack.set_int(settings_pack::unchoke_slots_limit, 1); + pack.set_int(settings_pack::max_retry_port_bind, 900); + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:48010"); + pack.set_bool(settings_pack::enable_natpmp, false); + pack.set_bool(settings_pack::enable_upnp, false); + pack.set_bool(settings_pack::enable_dht, false); + + 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, rate_limit / 10); + pack.set_int(settings_pack::download_rate_limit, rate_limit / 5); + pack.set_int(settings_pack::unchoke_slots_limit, 0); + pack.set_int(settings_pack::choking_algorithm, settings_pack::fixed_slots_choker); + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:49010"); + + lt::session ses2(pack); + + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:49010"); + + lt::session ses3(pack); + + torrent_handle tor1; + torrent_handle tor2; + torrent_handle tor3; + + boost::tie(tor1, tor2, tor3) = setup_transfer(&ses1, &ses2, &ses3, true, false, true, "_unchoke"); + + std::map cnt = get_counters(ses1); + + fprintf(stderr, "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); + fprintf(stderr, "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); + + test_sleep(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(); +} + +TORRENT_TEST(auto_unchoke) +{ + using namespace libtorrent; + + // in case the previous run was t r catch (std::exception&) {}erminated + 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..091600b --- /dev/null +++ b/test/test_bandwidth_limiter.cpp @@ -0,0 +1,517 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" + +#include "libtorrent/bandwidth_manager.hpp" +#include "libtorrent/bandwidth_queue_entry.hpp" +#include "libtorrent/bandwidth_limit.hpp" +#include "libtorrent/bandwidth_socket.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/stat.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/aux_/session_settings.hpp" + +#include +#include +#include +#include +#include +#include + +struct torrent; +struct peer_connection; + +using namespace libtorrent; + +const float sample_time = 20.f; // seconds + +//#define VERBOSE_LOGGING + +bandwidth_channel global_bwc; + +struct peer_connection: bandwidth_socket, boost::enable_shared_from_this +{ + peer_connection(bandwidth_manager& bwm + , bandwidth_channel& torrent_bwc, int prio, bool ignore_limits, std::string name) + : m_bwm(bwm) + , m_torrent_bandwidth_channel(torrent_bwc) + , m_priority(prio) + , m_ignore_limits(ignore_limits) + , m_name(name) + , m_quota(0) + {} + + bool is_disconnecting() const { return false; } + bool ignore_bandwidth_limits() { return m_ignore_limits; } + void assign_bandwidth(int channel, int amount); + + void throttle(int limit) { m_bandwidth_channel.throttle(limit); } + + void start(); + + bandwidth_manager& m_bwm; + + bandwidth_channel m_bandwidth_channel; + bandwidth_channel& m_torrent_bandwidth_channel; + + int m_priority; + bool m_ignore_limits; + std::string m_name; + int m_quota; +}; + +void peer_connection::assign_bandwidth(int channel, int amount) +{ + m_quota += amount; +#ifdef VERBOSE_LOGGING + std::cerr << " [" << m_name + << "] assign bandwidth, " << amount << std::endl; +#endif + TEST_CHECK(amount > 0); + start(); +} + +void peer_connection::start() +{ + bandwidth_channel* channels[] = { + &m_bandwidth_channel + , &m_torrent_bandwidth_channel + , &global_bwc + }; + + m_bwm.request_bandwidth(shared_from_this(), 400000000, m_priority, channels, 3); +} + + +typedef std::vector > connections_t; + +void do_change_rate(bandwidth_channel& t1, bandwidth_channel& t2, int limit) +{ + static int counter = 10; + --counter; + if (counter == 0) + { + t1.throttle(limit); + t2.throttle(limit); + return; + } + + t1.throttle(limit + limit / 2 * ((counter & 1)?-1:1)); + t2.throttle(limit + limit / 2 * ((counter & 1)?1:-1)); +} + +void do_change_peer_rate(connections_t& v, int limit) +{ + static int count = 10; + --count; + if (count == 0) + { + std::for_each(v.begin(), v.end() + , boost::bind(&peer_connection::throttle, _1, limit)); + return; + } + + int c = count; + for (connections_t::iterator i = v.begin(); i != v.end(); ++i, ++c) + i->get()->throttle(limit + limit / 2 * ((c & 1)?-1:1)); +} + +static void nop() {} + +void run_test(connections_t& v + , bandwidth_manager& manager + , boost::function f = &nop) +{ + std::cerr << "-------------" << std::endl; + + std::for_each(v.begin(), v.end() + , boost::bind(&peer_connection::start, _1)); + + libtorrent::aux::session_settings s; + initialize_default_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 val, float comp, float err) +{ + return fabs(val - comp) <= err; +} + +void spawn_connections(connections_t& v, bandwidth_manager& bwm + , bandwidth_channel& bwc, int num, char const* prefix) +{ + for (int i = 0; i < num; ++i) + { + char name[200]; + snprintf(name, sizeof(name), "%s%d", prefix, i); + v.push_back(boost::shared_ptr(new peer_connection(bwm, bwc, 200, false, name))); + } +} + +void test_equal_connections(int num, int limit) +{ + std::cerr << "\ntest equal connections " << num << " " << limit << std::endl; + bandwidth_manager manager(0); + global_bwc.throttle(limit); + + bandwidth_channel t1; + + connections_t v; + spawn_connections(v, manager, t1, num, "p"); + run_test(v, manager); + + float sum = 0.f; + float 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::cerr << (*i)->m_quota / sample_time + << " target: " << (limit / num) << " eps: " << err << std::endl; + TEST_CHECK(close_to((*i)->m_quota / sample_time, limit / num, err)); + } + sum /= sample_time; + std::cerr << "sum: " << sum << " target: " << limit << std::endl; + TEST_CHECK(sum > 0); + TEST_CHECK(close_to(sum, limit, 50)); +} + +void test_connections_variable_rate(int num, int limit, int torrent_limit) +{ + std::cerr << "\ntest connections variable rate" << num + << " l: " << limit + << " t: " << torrent_limit + << std::endl; + bandwidth_manager manager(0); + global_bwc.throttle(0); + + bandwidth_channel t1; + if (torrent_limit) + t1.throttle(torrent_limit); + + connections_t v; + spawn_connections(v, manager, t1, num, "p"); + std::for_each(v.begin(), v.end() + , boost::bind(&peer_connection::throttle, _1, limit)); + + run_test(v, manager, boost::bind(&do_change_peer_rate + , boost::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::cerr << (*i)->m_quota / sample_time + << " target: " << limit << " eps: " << err << std::endl; + TEST_CHECK(close_to((*i)->m_quota / sample_time, limit, err)); + } + sum /= sample_time; + std::cerr << "sum: " << sum << " target: " << (limit * num) << std::endl; + TEST_CHECK(sum > 0); + TEST_CHECK(close_to(sum, limit * num, limit * 0.3f * num)); +} + +void test_single_peer(int limit, bool torrent_limit) +{ + std::cerr << "\ntest single peer " << limit << " " << torrent_limit << std::endl; + bandwidth_manager manager(0); + bandwidth_channel t1; + global_bwc.throttle(0); + + if (torrent_limit) + t1.throttle(limit); + else + global_bwc.throttle(limit); + + connections_t v; + spawn_connections(v, manager, t1, 1, "p"); + run_test(v, manager); + + float sum = 0.f; + for (connections_t::iterator i = v.begin() + , end(v.end()); i != end; ++i) + { + sum += (*i)->m_quota; + } + sum /= sample_time; + std::cerr << sum << " target: " << limit << std::endl; + TEST_CHECK(sum > 0); + TEST_CHECK(close_to(sum, limit, 1000)); +} + +void test_torrents(int num, int limit1, int limit2, int global_limit) +{ + std::cerr << "\ntest equal torrents " << num + << " l1: " << limit1 + << " l2: " << limit2 + << " g: " << global_limit << std::endl; + bandwidth_manager manager(0); + global_bwc.throttle(global_limit); + + bandwidth_channel t1; + bandwidth_channel t2; + + t1.throttle(limit1); + t2.throttle(limit2); + + connections_t v1; + spawn_connections(v1, manager, t1, num, "t1p"); + connections_t v2; + spawn_connections(v2, manager, t2, num, "t2p"); + connections_t v; + std::copy(v1.begin(), v1.end(), std::back_inserter(v)); + std::copy(v2.begin(), v2.end(), std::back_inserter(v)); + run_test(v, manager); + + if (global_limit > 0 && global_limit < limit1 + limit2) + { + limit1 = (std::min)(limit1, global_limit / 2); + limit2 = global_limit - limit1; + } + float sum = 0.f; + for (connections_t::iterator i = v1.begin() + , end(v1.end()); i != end; ++i) + { + sum += (*i)->m_quota; + } + sum /= sample_time; + std::cerr << sum << " target: " << limit1 << std::endl; + TEST_CHECK(sum > 0); + TEST_CHECK(close_to(sum, 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::cerr << sum << " target: " << limit2 << std::endl; + TEST_CHECK(sum > 0); + TEST_CHECK(close_to(sum, limit2, 1000)); +} + +void test_torrents_variable_rate(int num, int limit, int global_limit) +{ + std::cerr << "\ntest torrents variable rate" << num + << " l: " << limit + << " g: " << global_limit << std::endl; + bandwidth_manager manager(0); + global_bwc.throttle(global_limit); + + bandwidth_channel t1; + bandwidth_channel t2; + + t1.throttle(limit); + t2.throttle(limit); + + connections_t v1; + spawn_connections(v1, manager, t1, num, "t1p"); + connections_t v2; + spawn_connections(v2, manager, t2, num, "t2p"); + connections_t v; + std::copy(v1.begin(), v1.end(), std::back_inserter(v)); + std::copy(v2.begin(), v2.end(), std::back_inserter(v)); + + run_test(v, manager, boost::bind(&do_change_rate, boost::ref(t1), boost::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::cerr << sum << " target: " << limit << std::endl; + TEST_CHECK(sum > 0); + TEST_CHECK(close_to(sum, 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::cerr << sum << " target: " << limit << std::endl; + TEST_CHECK(sum > 0); + TEST_CHECK(close_to(sum, limit, 1000)); +} + +void test_peer_priority(int limit, bool torrent_limit) +{ + std::cerr << "\ntest peer priority " << limit << " " << torrent_limit << std::endl; + bandwidth_manager manager(0); + bandwidth_channel t1; + global_bwc.throttle(0); + + if (torrent_limit) + t1.throttle(limit); + else + global_bwc.throttle(limit); + + connections_t v1; + spawn_connections(v1, manager, t1, 10, "p"); + connections_t v; + std::copy(v1.begin(), v1.end(), std::back_inserter(v)); + boost::shared_ptr p( + new peer_connection(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::cerr << sum << " target: " << limit << std::endl; + TEST_CHECK(sum > 0); + TEST_CHECK(close_to(sum, limit, 50)); + + std::cerr << "non-prioritized rate: " << p->m_quota / sample_time + << " target: " << (limit / 200 / 10) << std::endl; + TEST_CHECK(close_to(p->m_quota / sample_time, limit / 200 / 10, 5)); +} + +void test_no_starvation(int limit) +{ + std::cerr << "\ntest no starvation " << limit << std::endl; + bandwidth_manager manager(0); + bandwidth_channel t1; + bandwidth_channel t2; + + global_bwc.throttle(limit); + + const int num_peers = 20; + + connections_t v1; + spawn_connections(v1, manager, t1, num_peers, "p"); + connections_t v; + std::copy(v1.begin(), v1.end(), std::back_inserter(v)); + boost::shared_ptr p( + new peer_connection(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::cerr << sum << " target: " << limit << std::endl; + TEST_CHECK(sum > 0); + TEST_CHECK(close_to(sum, limit, 50)); + + std::cerr << "non-prioritized rate: " << p->m_quota / sample_time + << " target: " << (limit / 200 / num_peers) << std::endl; + TEST_CHECK(close_to(p->m_quota / sample_time, limit / 200 / num_peers, 5)); +} + +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, 100000000); +} + +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..fd5f0a5 --- /dev/null +++ b/test/test_bdecode.cpp @@ -0,0 +1,1280 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/bdecode.hpp" + +using namespace libtorrent; + +// test integer +TORRENT_TEST(integer) +{ + char b[] = "i12453e"; + bdecode_node e; + error_code ec; + int ret = bdecode(b, b + sizeof(b)-1, e, ec); + TEST_CHECK(ret == 0); + printf("%s\n", print_entry(e).c_str()); + std::pair section = e.data_section(); + TEST_CHECK(std::memcmp(b, section.first, section.second) == 0); + TEST_CHECK(section.second == sizeof(b) - 1); + TEST_CHECK(e.type() == bdecode_node::int_t); + TEST_CHECK(e.int_value() == 12453); +} + +// test string +TORRENT_TEST(string) +{ + char b[] = "26:abcdefghijklmnopqrstuvwxyz"; + bdecode_node e; + error_code ec; + int ret = bdecode(b, b + sizeof(b)-1, e, ec); + TEST_CHECK(ret == 0); + printf("%s\n", print_entry(e).c_str()); + std::pair section = e.data_section(); + TEST_CHECK(std::memcmp(b, section.first, section.second) == 0); + TEST_EQUAL(section.second, sizeof(b) - 1); + 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 + bdecode_node e; + error_code ec; + int ret = bdecode(test.c_str(), test.c_str() + test.size(), e, ec); + TEST_CHECK(ret == 0); + printf("%d bytes string\n", e.string_length()); + std::pair section = e.data_section(); + TEST_CHECK(std::memcmp(test.c_str(), section.first, section.second) == 0); + TEST_EQUAL(section.second, int(test.size())); + 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"; + bdecode_node e; + error_code ec; + int ret = bdecode(b, b + sizeof(b)-1, e, ec); + TEST_CHECK(ret == 0); + printf("%s\n", print_entry(e).c_str()); + std::pair section = e.data_section(); + TEST_CHECK(std::memcmp(b, section.first, section.second) == 0); + TEST_CHECK(section.second == sizeof(b) - 1); + TEST_CHECK(e.type() == bdecode_node::list_t); + TEST_CHECK(e.list_size() == 2); + TEST_CHECK(e.list_at(0).type() == bdecode_node::int_t); + TEST_CHECK(e.list_at(1).type() == bdecode_node::string_t); + TEST_CHECK(e.list_at(0).int_value() == 12453); + TEST_CHECK(e.list_at(1).string_value() == std::string("aaa")); + TEST_CHECK(e.list_at(1).string_length() == 3); + section = e.list_at(1).data_section(); + TEST_CHECK(std::memcmp("3:aaa", section.first, section.second) == 0); + TEST_CHECK(section.second == 5); +} + +// test dict +TORRENT_TEST(dict) +{ + char b[] = "d1:ai12453e1:b3:aaa1:c3:bbb1:X10:0123456789e"; + bdecode_node e; + error_code ec; + int ret = bdecode(b, b + sizeof(b)-1, e, ec); + TEST_EQUAL(ret, 0); + printf("%s\n", print_entry(e).c_str()); + std::pair section = e.data_section(); + TEST_CHECK(std::memcmp(b, section.first, section.second) == 0); + TEST_CHECK(section.second == sizeof(b) - 1); + TEST_CHECK(e.type() == bdecode_node::dict_t); + TEST_CHECK(e.dict_size() == 4); + TEST_CHECK(e.dict_find("a").type() == bdecode_node::int_t); + TEST_CHECK(e.dict_find("a").int_value() == 12453); + TEST_CHECK(e.dict_find("b").type() == bdecode_node::string_t); + TEST_CHECK(e.dict_find("b").string_value() == std::string("aaa")); + TEST_CHECK(e.dict_find("b").string_length() == 3); + TEST_CHECK(e.dict_find("c").type() == bdecode_node::string_t); + TEST_CHECK(e.dict_find("c").string_value() == std::string("bbb")); + TEST_CHECK(e.dict_find("c").string_length() == 3); + TEST_CHECK(e.dict_find_string_value("X") == "0123456789"); +} + +// test dictionary with a key without a value +TORRENT_TEST(dict_key_novalue) +{ + char b[] = "d1:ai1e1:be"; + bdecode_node e; + error_code ec; + int pos; + int ret = bdecode(b, b + sizeof(b)-1, e, ec, &pos); + TEST_EQUAL(ret, -1); + TEST_EQUAL(pos, 10); + TEST_EQUAL(ec, error_code(bdecode_errors::expected_value)); + 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"; + bdecode_node e; + error_code ec; + int pos; + int ret = bdecode(b, b + sizeof(b)-1, e, ec, &pos); + TEST_EQUAL(ret, -1); + TEST_EQUAL(pos, 1); + TEST_EQUAL(ec, error_code(bdecode_errors::expected_digit)); + printf("%s\n", print_entry(e).c_str()); +} + +// dictionary key with \0 +TORRENT_TEST(dict_null_key) +{ + char b[] = "d3:a\0bi1ee"; + bdecode_node e; + error_code ec; + int ret = bdecode(b, b + sizeof(b)-1, e, ec); + TEST_CHECK(ret == 0); + 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); +} + +// premature e +TORRENT_TEST(premature_e) +{ + char b[] = "e"; + bdecode_node e; + error_code ec; + int ret = bdecode(b, b + sizeof(b)-1, e, ec); + TEST_EQUAL(ret, -1); + TEST_EQUAL(ec, error_code(bdecode_errors::unexpected_eof)); + printf("%s\n", print_entry(e).c_str()); +} + +// test strings with negative length-prefix +TORRENT_TEST(negative_length_prefix) +{ + char b[] = "-10:foobar"; + bdecode_node e; + error_code ec; + int pos; + int ret = bdecode(b, b + sizeof(b)-1, e, ec, &pos); + TEST_EQUAL(ret, -1); + TEST_EQUAL(pos, 0); + TEST_EQUAL(ec, error_code(bdecode_errors::expected_value)); + printf("%s\n", print_entry(e).c_str()); +} + +// test strings with overflow length-prefix +TORRENT_TEST(overflow_length_prefix) +{ + char b[] = "18446744073709551615:foobar"; + bdecode_node e; + error_code ec; + int pos; + int ret = bdecode(b, b + sizeof(b)-1, e, ec, &pos); + TEST_EQUAL(ret, -1); + TEST_EQUAL(pos, 19); + TEST_EQUAL(ec, error_code(bdecode_errors::overflow)); + 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"; + bdecode_node e; + error_code ec; + int pos; + int ret = bdecode(b, b + sizeof(b)-1, e, ec, &pos); + TEST_EQUAL(ret, -1); + TEST_EQUAL(pos, 8); + TEST_EQUAL(ec, error_code(bdecode_errors::unexpected_eof)); + 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"; + bdecode_node e; + error_code ec; + int pos; + // pretend that we have a large buffer like that + int ret = bdecode(b, b + 999999999, e, ec, &pos); + TEST_EQUAL(ret, -1); + TEST_EQUAL(pos, 0); + TEST_EQUAL(ec, error_code(bdecode_errors::limit_exceeded)); + printf("%s\n", print_entry(e).c_str()); +} + +// test integer without any digits +TORRENT_TEST(nodigit_int) +{ + char b[] = "ie"; + bdecode_node e; + error_code ec; + int pos; + int ret = bdecode(b, b + sizeof(b)-1, e, ec, &pos); + TEST_EQUAL(ret, -1); + TEST_EQUAL(pos, 1); + TEST_EQUAL(ec, error_code(bdecode_errors::expected_digit)); + printf("%s\n", print_entry(e).c_str()); +} + +// test integer with just a minus +TORRENT_TEST(minus_int) +{ + char b[] = "i-e"; + bdecode_node e; + error_code ec; + int pos; + int ret = bdecode(b, b + sizeof(b)-1, e, ec, &pos); + TEST_EQUAL(ret, -1); + TEST_EQUAL(pos, 2); + TEST_EQUAL(ec, error_code(bdecode_errors::expected_digit)); + 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"; + bdecode_node e; + error_code ec; + int pos; + int ret = bdecode(b, b + sizeof(b)-1, e, ec, &pos); + TEST_EQUAL(ret, -1); + TEST_EQUAL(pos, 6); + TEST_EQUAL(ec, error_code(bdecode_errors::expected_digit)); + printf("%s\n", print_entry(e).c_str()); +} + +// test integers that don't fit in 64 bits +TORRENT_TEST(int_overflow) +{ + char b[] = "i18446744073709551615e"; + bdecode_node e; + error_code ec; + int ret = bdecode(b, b + sizeof(b)-1, e, ec); + TEST_EQUAL(ret, 0); + 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"; + bdecode_node e; + error_code ec; + int pos; + int ret = bdecode(b, b + sizeof(b)-1, e, ec, &pos); + TEST_EQUAL(ret, -1); + TEST_EQUAL(pos, 22); + TEST_EQUAL(ec, error_code(bdecode_errors::overflow)); + printf("%s\n", print_entry(e).c_str()); +} + +// test truncated negative integer +TORRENT_TEST(int_truncated) +{ + char b[] = "i-"; + bdecode_node e; + error_code ec; + int pos; + int ret = bdecode(b, b + sizeof(b)-1, e, ec, &pos); + TEST_EQUAL(ret, -1); + TEST_EQUAL(pos, 2); + TEST_EQUAL(ec, error_code(bdecode_errors::unexpected_eof)); + 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 error")); + ec.assign(5434, get_bdecode_category()); + TEST_EQUAL(ec.message(), "Unknown error"); +} + +// test integers that just exactly fit in 64 bits +TORRENT_TEST(64bit_int) +{ + char b[] = "i9223372036854775807e"; + bdecode_node e; + error_code ec; + int ret = bdecode(b, b + sizeof(b)-1, e, ec); + TEST_CHECK(ret == 0); + 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"; + bdecode_node e; + error_code ec; + int ret = bdecode(b, b + sizeof(b)-1, e, ec); + TEST_CHECK(ret == 0); + 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"; + bdecode_node e; + error_code ec; + int pos = 0; + int ret = bdecode(b, b + sizeof(b)-1, e, ec, &pos); + TEST_EQUAL(ret, -1); + TEST_EQUAL(pos, 9); + TEST_EQUAL(ec, error_code(bdecode_errors::expected_digit)); + 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}; + + printf("%s\n", buf); + bdecode_node e; + error_code ec; + int ret = bdecode((char*)buf, (char*)buf + sizeof(buf), e, ec); + TEST_CHECK(ret == -1); +} + +// 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 + + bdecode_node e; + error_code ec; + int ret = bdecode(b, b + sizeof(b), e, ec, NULL, 100); + TEST_CHECK(ret != 0); + TEST_EQUAL(ec, error_code(bdecode_errors::depth_exceeded + , get_bdecode_category())); +} + +// test the item limit +TORRENT_TEST(item_limit) +{ + char b[10240]; + b[0] = 'l'; + int i = 1; + for (i = 1; i < 10239; i += 2) + memcpy(&b[i], "0:", 2); + b[i] = 'e'; + + bdecode_node e; + error_code ec; + int ret = bdecode(b, b + i + 1, e, ec, NULL, 1000, 1000); + TEST_CHECK(ret != 0); + TEST_EQUAL(ec, error_code(bdecode_errors::limit_exceeded + , get_bdecode_category())); +} + +// test unexpected EOF +TORRENT_TEST(unepected_eof) +{ + char b[] = "l2:.."; // expected terminating 'e' + + bdecode_node e; + error_code ec; + int pos; + int ret = bdecode(b, b + sizeof(b)-1, e, ec, &pos); + TEST_EQUAL(ret, -1); + TEST_EQUAL(pos, 5); + TEST_EQUAL(ec, error_code(bdecode_errors::unexpected_eof)); + printf("%s\n", print_entry(e).c_str()); +} + +// test unexpected EOF (really expected terminator) +TORRENT_TEST(unepected_eof2) +{ + char b[] = "l2:..0"; // expected terminating 'e' instead of '0' + + bdecode_node e; + error_code ec; + int pos; + int ret = bdecode(b, b + sizeof(b)-1, e, ec, &pos); + TEST_EQUAL(ret, -1); + TEST_EQUAL(pos, 6); + TEST_EQUAL(ec, error_code(bdecode_errors::expected_colon)); + 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) + + bdecode_node e; + error_code ec; + int pos; + int ret = bdecode(b, b + sizeof(b)-1, e, ec, &pos); + TEST_EQUAL(ret, -1); + TEST_EQUAL(pos, 1); + TEST_EQUAL(ec, error_code(bdecode_errors::expected_digit)); + 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"; + + bdecode_node e; + error_code ec; + int pos; + int ret = bdecode(b, b + sizeof(b)-1, e, ec, &pos); + TEST_EQUAL(ret, -1); + TEST_EQUAL(pos, 5); + TEST_EQUAL(ec, error_code(bdecode_errors::unexpected_eof)); + printf("%s\n", print_entry(e).c_str()); +} + +// test unexpected EOF while parsing dict key +TORRENT_TEST(unexpected_eof_dict_key2) +{ + char b[] = "d1000:"; + + bdecode_node e; + error_code ec; + int pos; + int ret = bdecode(b, b + sizeof(b)-1, e, ec, &pos); + TEST_EQUAL(ret, -1); + TEST_EQUAL(pos, 5); + TEST_EQUAL(ec, error_code(bdecode_errors::unexpected_eof)); + printf("%s\n", print_entry(e).c_str()); +} + +// test expected string while parsing dict key +TORRENT_TEST(expected_string_dict_key2) +{ + char b[] = "df00:"; + + bdecode_node e; + error_code ec; + int pos; + int ret = bdecode(b, b + sizeof(b)-1, e, ec, &pos); + TEST_EQUAL(ret, -1); + TEST_EQUAL(pos, 1); + TEST_EQUAL(ec, error_code(bdecode_errors::expected_digit)); + printf("%s\n", print_entry(e).c_str()); +} + +// test unexpected EOF while parsing int +TORRENT_TEST(unexpected_eof_int) +{ + char b[] = "i"; + + bdecode_node e; + error_code ec; + int pos; + int ret = bdecode(b, b + sizeof(b)-1, e, ec, &pos); + TEST_EQUAL(ret, -1); + TEST_EQUAL(pos, 1); + TEST_EQUAL(ec, error_code(bdecode_errors::unexpected_eof)); + printf("%s\n", print_entry(e).c_str()); +} + +// test unexpected EOF while parsing int +TORRENT_TEST(unexpected_eof_int2) +{ + char b[] = "i10"; + + bdecode_node e; + error_code ec; + int pos; + int ret = bdecode(b, b + sizeof(b)-1, e, ec, &pos); + TEST_EQUAL(ret, -1); + TEST_EQUAL(pos, 3); + TEST_EQUAL(ec, error_code(bdecode_errors::unexpected_eof)); + printf("%s\n", print_entry(e).c_str()); +} + + +// test expected colon +TORRENT_TEST(expected_colon_dict) +{ + char b[] = "d1000"; + + bdecode_node e; + error_code ec; + int pos; + int ret = bdecode(b, b + sizeof(b)-1, e, ec, &pos); + TEST_EQUAL(ret, -1); + TEST_EQUAL(pos, 5); + TEST_EQUAL(ec, error_code(bdecode_errors::expected_colon)); + printf("%s\n", print_entry(e).c_str()); +} + +// test empty string +TORRENT_TEST(empty_string) +{ + char b[] = ""; + + bdecode_node e; + error_code ec; + int ret = bdecode(b, b + sizeof(b)-1, e, ec, NULL); + TEST_EQUAL(ret, -1); + TEST_EQUAL(ec, error_code(bdecode_errors::unexpected_eof)); + printf("%s\n", print_entry(e).c_str()); +} + +// test partial string +TORRENT_TEST(partial_string) +{ + char b[] = "100:.."; + + bdecode_node e; + error_code ec; + int pos; + int ret = bdecode(b, b + sizeof(b)-1, e, ec, &pos); + TEST_EQUAL(ret, -1); + TEST_EQUAL(pos, 3); + TEST_EQUAL(ec, error_code(bdecode_errors::unexpected_eof)); + 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]; + snprintf(tmp, sizeof(tmp), "i%de", i); + buf += tmp; + } + buf += "e"; + + bdecode_node e; + error_code ec; + int ret = bdecode((char*)&buf[0], (char*)&buf[0] + buf.size(), e, ec); + TEST_EQUAL(ret, 0); + 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]; + snprintf(tmp, sizeof(tmp), "4:%04di%de", i, i); + buf += tmp; + } + buf += "e"; + + printf("%s\n", buf.c_str()); + bdecode_node e; + error_code ec; + int ret = bdecode((char*)&buf[0], (char*)&buf[0] + buf.size(), e, ec); + TEST_EQUAL(ret, 0); + TEST_EQUAL(e.type(), bdecode_node::dict_t); + TEST_EQUAL(e.dict_size(), 1000); + for (int i = 0; i < 1000; ++i) + { + char tmp[30]; + 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"; + + bdecode_node e; + error_code ec; + int ret = bdecode(b, b + sizeof(b)-1, e, ec); + TEST_EQUAL(ret, 0); + + 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"; + + bdecode_node e; + error_code ec; + int ret = bdecode(b, b + sizeof(b)-1, e, ec); + TEST_EQUAL(ret, 0); + + 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"; + + bdecode_node e; + error_code ec; + int ret = bdecode(b, b + 0x3fffffff, e, ec); + TEST_EQUAL(ret, -1); + TEST_EQUAL(ec, error_code(bdecode_errors::limit_exceeded)); + printf("%s\n", print_entry(e).c_str()); +} + +// test parse_int +TORRENT_TEST(parse_int) +{ + char b[] = "1234567890e"; + boost::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(val, 1234567890); + TEST_EQUAL(e, b + sizeof(b) - 2); +} + +// test invalid digit +TORRENT_TEST(invalid_digit) +{ + char b[] = "0o"; + boost::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:"; + boost::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) +{ + char const* b[] = { + "d1:a1919191010:11111", + "d2143289344:a4:aaaae", + "d214328934114:a4:aaaae", + "d9205357638345293824:a4:aaaae", + "d1:a9205357638345293824:11111", + }; + + for (int i = 0; i < int(sizeof(b)/sizeof(b[0])); ++i) + { + error_code ec; + bdecode_node e; + int ret = bdecode(b[i], b[i] + strlen(b[i]), e, ec); + TEST_EQUAL(ret, -1); + TEST_CHECK(ec == error_code(bdecode_errors::unexpected_eof)); + } +} + + +TORRENT_TEST(expected_colon_string) +{ + char b[] = "928"; + boost::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::expected_colon); + 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"; + bdecode_node e; + error_code ec; + int ret = bdecode(b, b + sizeof(b)-1, e, ec); + TEST_EQUAL(ret, 0); + 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"; + bdecode_node e; + error_code ec; + int ret = bdecode(b, b + sizeof(b)-1, e, ec); + TEST_EQUAL(ret, 0); + 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"; + bdecode_node e; + error_code ec; + int ret = bdecode(b, b + sizeof(b)-1, e, ec); + TEST_EQUAL(ret, 0); + 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"; + bdecode_node e; + error_code ec; + int ret = bdecode(b, b + sizeof(b)-1, e, ec); + TEST_EQUAL(ret, 0); + 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"; + bdecode_node e; + error_code ec; + int ret = bdecode(b, b + sizeof(b)-1, e, ec); + TEST_EQUAL(ret, 0); + 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"; + bdecode_node e; + error_code ec; + int ret = bdecode(b, b + sizeof(b)-1, e, ec); + TEST_EQUAL(ret, 0); + 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"; + + bdecode_node e1; + bdecode_node e2; + + error_code ec; + + int ret = bdecode(b1, b1 + sizeof(b1)-1, e1, ec); + TEST_EQUAL(ret, 0); + ret = bdecode(b2, b2 + sizeof(b2)-1, e2, ec); + TEST_EQUAL(ret, 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); + 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); + 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); + 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"; + + bdecode_node e1; + bdecode_node e2; + + error_code ec; + + int ret = bdecode(b1, b1 + sizeof(b1)-1, e1, ec); + TEST_EQUAL(ret, 0); + + 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); + 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); + 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); + 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"; + + bdecode_node e1_root; + bdecode_node e2_root; + + error_code ec; + + int ret = bdecode(b1, b1 + sizeof(b1)-1, e1_root, ec); + TEST_EQUAL(ret, 0); + ret = bdecode(b2, b2 + sizeof(b2)-1, e2_root, ec); + TEST_EQUAL(ret, 0); + + 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"; + + bdecode_node e1_root; + bdecode_node e2; + + error_code ec; + + int ret = bdecode(b1, b1 + sizeof(b1)-1, e1_root, ec); + TEST_EQUAL(ret, 0); + ret = bdecode(b2, b2 + sizeof(b2)-1, e2, ec); + TEST_EQUAL(ret, 0); + + 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"; + + bdecode_node e; + error_code ec; + int ret = bdecode(b1, b1 + sizeof(b1)-1, e, ec); + printf("%s\n", print_entry(e).c_str()); + TEST_EQUAL(ret, 0); + TEST_EQUAL(e.type(), bdecode_node::dict_t); + TEST_EQUAL(e.dict_size(), 4); + TEST_EQUAL(e.dict_at(1).first, "b"); + + ret = bdecode(b2, b2 + sizeof(b2)-1, e, ec); + printf("%s\n", print_entry(e).c_str()); + TEST_EQUAL(ret, 0); + 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"; + + bdecode_node e1; + error_code ec; + int ret = bdecode(b1, b1 + sizeof(b1)-1, e1, ec); + TEST_EQUAL(ret, 0); + TEST_EQUAL(e1.type(), bdecode_node::dict_t); + 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"; + + bdecode_node e1; + error_code ec; + int ret = bdecode(b1, b1 + sizeof(b1)-1, e1, ec); + + TEST_EQUAL(ret, 0); + TEST_EQUAL(e1.type(), bdecode_node::dict_t); + 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"; + + bdecode_node e; + error_code ec; + int pos; + int ret = bdecode(b, b + sizeof(b)-1, e, ec, &pos); + TEST_EQUAL(ret, -1); + TEST_EQUAL(pos, 35); + TEST_EQUAL(e.type(), bdecode_node::dict_t); + + 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"; + + bdecode_node e; + error_code ec; + int pos; + int ret = bdecode(b, b + sizeof(b)-1, e, ec, &pos); + TEST_EQUAL(ret, -1); + TEST_EQUAL(pos, 29); + TEST_EQUAL(e.type(), bdecode_node::dict_t); + + 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"; + + bdecode_node e; + error_code ec; + int pos; + int ret = bdecode(b, b + sizeof(b)-1, e, ec, &pos); + TEST_EQUAL(ret, -1); + TEST_EQUAL(pos, 26); + TEST_EQUAL(e.type(), bdecode_node::dict_t); + + 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"; + + bdecode_node e; + error_code ec; + int pos; + int ret = bdecode(b, b + sizeof(b)-1, e, ec, &pos); + TEST_EQUAL(ret, -1); + TEST_EQUAL(pos, 22); + TEST_EQUAL(e.type(), bdecode_node::dict_t); + + printf("%s\n", print_entry(e).c_str()); + + TEST_EQUAL(print_entry(e), "{ 'a': 1, 'b': 'foo', 'c': [ 1 ] }"); +} + +// 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"; + + bdecode_node e; + error_code ec; + int pos; + int ret = bdecode(b1, b1 + sizeof(b1)-1, e, ec, &pos); + TEST_EQUAL(ret, -1); + TEST_EQUAL(pos, 22); + TEST_EQUAL(e.type(), bdecode_node::dict_t); + + std::string string1 = print_entry(e); + printf("%s\n", string1.c_str()); + + e.switch_underlying_buffer(b2); + + std::string string2 = print_entry(e); + printf("%s\n", string2.c_str()); + + TEST_EQUAL(string1, string2); +} + diff --git a/test/test_bencoding.cpp b/test/test_bencoding.cpp new file mode 100644 index 0000000..25661d4 --- /dev/null +++ b/test/test_bencoding.cpp @@ -0,0 +1,645 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/bencode.hpp" + +#include +#include + +#ifndef TORRENT_NO_DEPRECATE +#include "libtorrent/lazy_entry.hpp" +#endif + +#include "test.hpp" + +using namespace libtorrent; + +// test vectors from bittorrent protocol description +// http://www.bittorrent.com/protocol.html + +std::string encode(entry const& e) +{ + std::string ret; + bencode(std::back_inserter(ret), e); + return ret; +} + +entry decode(std::string const& str) +{ + return bdecode(str.begin(), str.end()); +} + +TORRENT_TEST(strings) +{ + entry e("spam"); + TEST_CHECK(encode(e) == "4:spam"); + TEST_CHECK(decode(encode(e)) == e); +} + +TORRENT_TEST(integers) +{ + entry e(3); + TEST_CHECK(encode(e) == "i3e"); + TEST_CHECK(decode(encode(e)) == e); +} + +TORRENT_TEST(integers2) +{ + entry e(-3); + TEST_CHECK(encode(e) == "i-3e"); + TEST_CHECK(decode(encode(e)) == e); +} + +TORRENT_TEST(integers3) +{ + entry e(int(0)); + TEST_CHECK(encode(e) == "i0e"); + TEST_CHECK(decode(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(decode(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(decode(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"); +} + +#ifndef TORRENT_NO_DEPRECATE +TORRENT_TEST(lazy_entry) +{ + { + char b[] = "i12453e"; + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec); + TEST_CHECK(ret == 0); + printf("%s\n", print_entry(e).c_str()); + std::pair section = e.data_section(); + TEST_CHECK(std::memcmp(b, section.first, section.second) == 0); + TEST_CHECK(section.second == sizeof(b) - 1); + TEST_CHECK(e.type() == lazy_entry::int_t); + TEST_CHECK(e.int_value() == 12453); + } + + { + char b[] = "26:abcdefghijklmnopqrstuvwxyz"; + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec); + TEST_CHECK(ret == 0); + printf("%s\n", print_entry(e).c_str()); + std::pair section = e.data_section(); + TEST_CHECK(std::memcmp(b, section.first, section.second) == 0); + TEST_CHECK(section.second == sizeof(b) - 1); + TEST_CHECK(e.type() == lazy_entry::string_t); + TEST_CHECK(e.string_value() == std::string("abcdefghijklmnopqrstuvwxyz")); + TEST_CHECK(e.string_length() == 26); + } + + { + char b[] = "li12453e3:aaae"; + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec); + TEST_CHECK(ret == 0); + printf("%s\n", print_entry(e).c_str()); + std::pair section = e.data_section(); + TEST_CHECK(std::memcmp(b, section.first, section.second) == 0); + TEST_CHECK(section.second == sizeof(b) - 1); + TEST_CHECK(e.type() == lazy_entry::list_t); + TEST_CHECK(e.list_size() == 2); + TEST_CHECK(e.list_at(0)->type() == lazy_entry::int_t); + TEST_CHECK(e.list_at(1)->type() == lazy_entry::string_t); + TEST_CHECK(e.list_at(0)->int_value() == 12453); + TEST_CHECK(e.list_at(1)->string_value() == std::string("aaa")); + TEST_CHECK(e.list_at(1)->string_length() == 3); + section = e.list_at(1)->data_section(); + TEST_CHECK(std::memcmp("3:aaa", section.first, section.second) == 0); + TEST_CHECK(section.second == 5); + } + + { + char b[] = "d1:ai12453e1:b3:aaa1:c3:bbb1:X10:0123456789e"; + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec); + TEST_CHECK(ret == 0); + printf("%s\n", print_entry(e).c_str()); + std::pair section = e.data_section(); + TEST_CHECK(std::memcmp(b, section.first, section.second) == 0); + TEST_CHECK(section.second == sizeof(b) - 1); + TEST_CHECK(e.type() == lazy_entry::dict_t); + TEST_CHECK(e.dict_size() == 4); + TEST_CHECK(e.dict_find("a")->type() == lazy_entry::int_t); + TEST_CHECK(e.dict_find("a")->int_value() == 12453); + TEST_CHECK(e.dict_find("b")->type() == lazy_entry::string_t); + TEST_CHECK(e.dict_find("b")->string_value() == std::string("aaa")); + TEST_CHECK(e.dict_find("b")->string_length() == 3); + TEST_CHECK(e.dict_find("c")->type() == lazy_entry::string_t); + TEST_CHECK(e.dict_find("c")->string_value() == std::string("bbb")); + TEST_CHECK(e.dict_find("c")->string_length() == 3); + TEST_CHECK(e.dict_find_string_value("X") == "0123456789"); + } + + // dictionary key with \0 + { + char b[] = "d3:a\0bi1ee"; + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec); + TEST_CHECK(ret == 0); + TEST_CHECK(e.dict_size() == 1); + lazy_entry* d = e.dict_find(std::string("a\0b", 3)); + TEST_CHECK(d); + TEST_EQUAL(d->type(), lazy_entry::int_t); + TEST_EQUAL(d->int_value(), 1); + } + + // test strings with negative length-prefix + { + char b[] = "-10:foobar"; + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec); + TEST_CHECK(ret != 0); + printf("%s\n", print_entry(e).c_str()); + TEST_CHECK(ec == error_code(bdecode_errors::expected_value + , get_bdecode_category())); + } + + // test strings with overflow length-prefix + { + char b[] = "18446744073709551615:foobar"; + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec); + TEST_CHECK(ret != 0); + printf("%s\n", print_entry(e).c_str()); + TEST_CHECK(ec == error_code(bdecode_errors::overflow + , get_bdecode_category())); + } + + // test integers that don't fit in 64 bits + { + char b[] = "i18446744073709551615e"; + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec); + TEST_CHECK(ret == 0); + printf("%s\n", print_entry(e).c_str()); + // the lazy aspect makes this overflow when asking for + // the value. turning it to zero. + TEST_CHECK(e.int_value() == 0); + } + + // test integers that just exactly fit in 64 bits + { + char b[] = "i9223372036854775807e"; + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec); + TEST_CHECK(ret == 0); + printf("%s\n", print_entry(e).c_str()); + TEST_CHECK(e.int_value() == 9223372036854775807LL); + } + + // test integers that just exactly fit in 64 bits + { + char b[] = "i-9223372036854775807e"; + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec); + TEST_CHECK(ret == 0); + printf("%s\n", print_entry(e).c_str()); + TEST_CHECK(e.int_value() == -9223372036854775807LL); + } + + // test invalid encoding + { + unsigned char buf[] = + { 0x64, 0x31, 0x3a, 0x61, 0x64, 0x32, 0x3a, 0x69 + , 0x64, 0x32, 0x30, 0x3a, 0x2a, 0x21, 0x19, 0x89 + , 0x9f, 0xcd, 0x5f, 0xc9, 0xbc, 0x80, 0xc1, 0x76 + , 0xfe, 0xe0, 0xc6, 0x84, 0x2d, 0xf6, 0xfc, 0xb8 + , 0x39, 0x3a, 0x69, 0x6e, 0x66, 0x6f, 0x5f, 0x68 + , 0x61, 0xae, 0x68, 0x32, 0x30, 0x3a, 0x14, 0x78 + , 0xd5, 0xb0, 0xdc, 0xf6, 0x82, 0x42, 0x32, 0xa0 + , 0xd6, 0x88, 0xeb, 0x48, 0x57, 0x01, 0x89, 0x40 + , 0x4e, 0xbc, 0x65, 0x31, 0x3a, 0x71, 0x39, 0x3a + , 0x67, 0x65, 0x74, 0x5f, 0x70, 0x65, 0x65, 0x72 + , 0x78, 0xff, 0x3a, 0x74, 0x38, 0x3a, 0xaa, 0xd4 + , 0xa1, 0x88, 0x7a, 0x8d, 0xc3, 0xd6, 0x31, 0x3a + , 0x79, 0x31, 0xae, 0x71, 0x65, 0}; + + printf("%s\n", buf); + lazy_entry e; + error_code ec; + int ret = lazy_bdecode((char*)buf, (char*)buf + sizeof(buf), e, ec); + TEST_CHECK(ret == -1); + } + + // test the depth limit + { + char b[2048]; + for (int i = 0; i < 1024; ++i) + b[i]= 'l'; + + for (int i = 1024; i < 2048; ++i) + b[i]= 'e'; + + // 1024 levels nested lists + + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b), e, ec); + TEST_CHECK(ret != 0); + TEST_EQUAL(ec, error_code(bdecode_errors::depth_exceeded + , get_bdecode_category())); + } + + // test the item limit + { + char b[10240]; + b[0] = 'l'; + int i = 1; + for (i = 1; i < 10239; i += 2) + memcpy(&b[i], "0:", 2); + b[i] = 'e'; + + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + i + 1, e, ec, NULL, 1000, 1000); + TEST_CHECK(ret != 0); + TEST_EQUAL(ec, error_code(bdecode_errors::limit_exceeded + , get_bdecode_category())); + } + + // test unexpected EOF + { + char b[] = "l2:.."; // expected terminating 'e' + + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec, NULL); + TEST_CHECK(ret != 0); + printf("%s\n", print_entry(e).c_str()); + TEST_EQUAL(ec, error_code(bdecode_errors::unexpected_eof + , get_bdecode_category())); + } + + // test unexpected EOF (really expected terminator) + { + char b[] = "l2:..0"; // expected terminating 'e' instead of '0' + + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec, NULL); + TEST_CHECK(ret != 0); + printf("%s\n", print_entry(e).c_str()); + TEST_EQUAL(ec, error_code(bdecode_errors::unexpected_eof + , get_bdecode_category())); + } + + // test expected string + { + char b[] = "di2ei0ee"; + // expected string (dict keys must be strings) + + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec, NULL); + TEST_CHECK(ret != 0); + printf("%s\n", print_entry(e).c_str()); + TEST_EQUAL(ec, error_code(bdecode_errors::expected_digit + , get_bdecode_category())); + } + + // test unexpected EOF while parsing dict key + { + char b[] = "d1000:..e"; + + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec, NULL); + TEST_CHECK(ret != 0); + printf("%s\n", print_entry(e).c_str()); + TEST_EQUAL(ec, error_code(bdecode_errors::unexpected_eof + , get_bdecode_category())); + } + + // test unexpected EOF while parsing dict key + { + char b[] = "d1000:"; + + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec, NULL); + TEST_CHECK(ret != 0); + printf("%s\n", print_entry(e).c_str()); + TEST_EQUAL(ec, error_code(bdecode_errors::unexpected_eof + , get_bdecode_category())); + } + + // test expected string while parsing dict key + { + char b[] = "df00:"; + + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec, NULL); + TEST_CHECK(ret != 0); + printf("%s\n", print_entry(e).c_str()); + TEST_EQUAL(ec, error_code(bdecode_errors::expected_digit + , get_bdecode_category())); + } + + // test unexpected EOF while parsing int + { + char b[] = "i"; + + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec, NULL); + TEST_CHECK(ret != 0); + printf("%s\n", print_entry(e).c_str()); + TEST_EQUAL(ec, error_code(bdecode_errors::unexpected_eof + , get_bdecode_category())); + } + + // test unexpected EOF while parsing int + { + char b[] = "i10"; + + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec, NULL); + TEST_CHECK(ret != 0); + printf("%s\n", print_entry(e).c_str()); + TEST_EQUAL(ec, error_code(bdecode_errors::unexpected_eof + , get_bdecode_category())); + } + + + // test expected colon + { + char b[] = "d1000"; + + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec, NULL); + TEST_CHECK(ret != 0); + printf("%s\n", print_entry(e).c_str()); + TEST_EQUAL(ec, error_code(bdecode_errors::expected_colon + , get_bdecode_category())); + } + + // test empty string + { + char b[] = ""; + + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec, NULL); + TEST_EQUAL(ret, -1); + TEST_EQUAL(ec, error_code(bdecode_errors::unexpected_eof + , get_bdecode_category())); + printf("%s\n", print_entry(e).c_str()); + } + + // test partial string + { + char b[] = "100:.."; + + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec, NULL); + TEST_CHECK(ret != 0); + printf("%s\n", print_entry(e).c_str()); + TEST_EQUAL(ec, error_code(bdecode_errors::unexpected_eof + , get_bdecode_category())); + } + + // test pascal string dict + { + char b[] = "d6:foobar6:barfooe"; + + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec, NULL); + TEST_EQUAL(ret, 0); + printf("%s\n", print_entry(e).c_str()); + + pascal_string ps = e.dict_find_pstr("foobar"); + TEST_EQUAL(memcmp(ps.ptr, "barfoo", ps.len), 0); + TEST_EQUAL(ps.len, 6); + + ps = e.dict_find_pstr("foobar2"); + TEST_EQUAL(ps.ptr, static_cast(0)); + TEST_EQUAL(ps.len, 0); + } + + // test pascal string in list + { + char b[] = "l6:foobari4ee"; + + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec, NULL); + TEST_EQUAL(ret, 0); + printf("%s\n", print_entry(e).c_str()); + + TEST_EQUAL(e.list_size(), 2); + pascal_string ps = e.list_pstr_at(0); + TEST_EQUAL(memcmp(ps.ptr, "foobar", ps.len), 0); + TEST_EQUAL(ps.len, 6); + + ps = e.list_pstr_at(1); + TEST_EQUAL(ps.ptr, static_cast(0)); + TEST_EQUAL(ps.len, 0); + } + + { + unsigned char buf[] = { 0x44, 0x91, 0x3a }; + entry ent = bdecode(buf, buf + sizeof(buf)); + TEST_CHECK(ent == entry()); + } + + { + std::string buf; + buf += "l"; + for (int i = 0; i < 1000; ++i) + { + char tmp[20]; + snprintf(tmp, sizeof(tmp), "i%de", i); + buf += tmp; + } + buf += "e"; + + lazy_entry e; + error_code ec; + int ret = lazy_bdecode((char*)&buf[0], (char*)&buf[0] + buf.size(), e, ec); + TEST_EQUAL(ret, 0); + TEST_EQUAL(e.type(), lazy_entry::list_t); + TEST_EQUAL(e.list_size(), 1000); + for (int i = 0; i < 1000; ++i) + { + TEST_EQUAL(e.list_int_value_at(i), i); + } + } + + { + std::string buf; + buf += "d"; + for (int i = 0; i < 1000; ++i) + { + char tmp[30]; + snprintf(tmp, sizeof(tmp), "4:%04di%de", i, i); + buf += tmp; + } + buf += "e"; + + printf("%s\n", buf.c_str()); + lazy_entry e; + error_code ec; + int ret = lazy_bdecode((char*)&buf[0], (char*)&buf[0] + buf.size(), e, ec); + TEST_EQUAL(ret, 0); + TEST_EQUAL(e.type(), lazy_entry::dict_t); + TEST_EQUAL(e.dict_size(), 1000); + for (int i = 0; i < 1000; ++i) + { + char tmp[30]; + snprintf(tmp, sizeof(tmp), "%04d", i); + TEST_EQUAL(e.dict_find_int_value(tmp), i); + } + } + + // test parse_int + { + char b[] = "1234567890e"; + boost::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(val, 1234567890); + TEST_EQUAL(e, b + sizeof(b) - 2); + } + + // test invalid digit + { + char b[] = "0o"; + boost::int64_t val = 0; + bdecode_errors::error_code_enum ec; + char const* e = parse_int(b, b + sizeof(b)-1, 'e', val, ec); + TEST_EQUAL(ec, bdecode_errors::expected_digit); + TEST_EQUAL(e, b + 1); + } + + { + char b[] = "9223372036854775808:"; + boost::int64_t val = 0; + bdecode_errors::error_code_enum ec; + char const* e = parse_int(b, b + sizeof(b)-1, ':', val, ec); + TEST_CHECK(ec == bdecode_errors::overflow); + TEST_EQUAL(e, b + 18); + } + + { + char b[] = "928"; + boost::int64_t val = 0; + bdecode_errors::error_code_enum ec; + char const* e = parse_int(b, b + sizeof(b)-1, ':', val, ec); + TEST_CHECK(ec == bdecode_errors::expected_colon); + TEST_EQUAL(e, b + 3); + } + + { + char const* b[] = { + "d1:a1919191010:11111", + "d2143289344:a4:aaaae", + "d214328934114:a4:aaaae", + "d9205357638345293824:a4:aaaae", + "d1:a9205357638345293824:11111", + }; + + for (int i = 0; i < int(sizeof(b)/sizeof(b[0])); ++i) + { + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b[i], b[i] + strlen(b[i]), e, ec, NULL); + TEST_EQUAL(ret, -1); + TEST_CHECK(ec == error_code(bdecode_errors::unexpected_eof)); + printf("%s\n", print_entry(e).c_str()); + } + } +} +#endif // TORRENT_NO_DEPRECATE + diff --git a/test/test_bitfield.cpp b/test/test_bitfield.cpp new file mode 100644 index 0000000..f2a548e --- /dev/null +++ b/test/test_bitfield.cpp @@ -0,0 +1,190 @@ +/* + +Copyright (c) 2008-2013, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/bitfield.hpp" +#include + +using namespace libtorrent; + +void print_bitfield(bitfield const& b) +{ + std::string out; + for (int i = 0; i < b.size(); ++i) + { + out += b.get_bit(i) ? "1" : "0"; + } + printf("%s\n", out.c_str()); +} + +void test_iterators(bitfield& test1) +{ + test1.set_all(); + int num = 0; + + printf("expecting %d ones\n", test1.size()); + for (bitfield::const_iterator i = test1.begin(); i != test1.end(); ++i) + { + printf("%d", *i); + TEST_EQUAL(*i, true); + num += *i; + } + printf("\n"); + TEST_EQUAL(num, test1.size()); + TEST_EQUAL(num, test1.count()); +} + +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 = std::distance(test1.begin(), test1.end()); + 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_CHECK(test1.count() == 100); + test1.resize(200, false); + TEST_CHECK(test1.all_set() == false); + TEST_CHECK(test1.count() == 100); + test1.resize(50, false); + TEST_CHECK(test1.all_set() == true); + TEST_CHECK(test1.count() == 50); + test1.resize(101, true); + TEST_CHECK(test1.all_set() == true); + TEST_CHECK(test1.count() == 101); + + boost::uint8_t b1[] = { 0x08, 0x10 }; + test1.assign((char*)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); + + boost::uint8_t b2[] = { 0x08, 0x10, 0xff, 0xff, 0xff, 0xff, 0xf, 0xc, 0x7f }; + test1.assign((char*)b2, 72); + print_bitfield(test1); + TEST_EQUAL(test1.count(), 47); + + boost::uint8_t b3[] = { 0x08, 0x10, 0xff, 0xff, 0xff, 0xff, 0xf, 0xc }; + test1.assign((char*)b3, 64); + print_bitfield(test1); + TEST_EQUAL(test1.count(), 40); + + for (int i = 0; i < 100; ++i) + { + test1.resize(i, false); + test_iterators(test1); + } + + // test alignment + boost::uint32_t buffer[4]; + char* b = (char*)buffer; + + for (int i = 0; i < 4; ++i) + { + b[i] = 0xc0; + test1.assign(b + i, 2); + print_bitfield(test1); + TEST_EQUAL(test1.count(), 2); + TEST_EQUAL(test1.all_set(), true); + } + + for (int i = 0; i < 4; ++i) + { + memset(b + i, 0xff, 5); + b[i + 5] = 0xc0; + test1.assign(b + i, 32 + 8 + 2); + print_bitfield(test1); + TEST_EQUAL(test1.count(), 32 + 8 + 2); + TEST_EQUAL(test1.all_set(), true); + } +} diff --git a/test/test_block_cache.cpp b/test/test_block_cache.cpp new file mode 100644 index 0000000..c64cda2 --- /dev/null +++ b/test/test_block_cache.cpp @@ -0,0 +1,476 @@ +/* + +Copyright (c) 2012, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/block_cache.hpp" +#include "libtorrent/io_service.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/disk_io_thread.hpp" +#include "libtorrent/storage.hpp" +#include "libtorrent/session.hpp" + +#include +#include +#include + +using namespace libtorrent; + +struct test_storage_impl : storage_interface +{ + virtual void initialize(storage_error& ec) {} + + virtual int readv(file::iovec_t const* bufs, int num_bufs + , int piece, int offset, int flags, storage_error& ec) + { + return bufs_size(bufs, num_bufs); + } + virtual int writev(file::iovec_t const* bufs, int num_bufs + , int piece, int offset, int flags, storage_error& ec) + { + return bufs_size(bufs, num_bufs); + } + + virtual bool has_any_file(storage_error& ec) { return false; } + virtual void set_file_priority(std::vector const& prio + , storage_error& ec) {} + virtual int move_storage(std::string const& save_path, int flags + , storage_error& ec) { return 0; } + virtual bool verify_resume_data(bdecode_node const& rd + , std::vector const* links + , storage_error& ec) { return true; } + virtual void write_resume_data(entry& rd, storage_error& ec) const {} + virtual void release_files(storage_error& ec) {} + virtual void rename_file(int index, std::string const& new_filenamem + , storage_error& ec) {} + virtual void delete_files(int, storage_error& ec) {} + virtual void finalize_file(int, storage_error&) {} +}; + +static void nop() {} + +#if defined TORRENT_DEBUG || TORRENT_RELEASE_ASSERTS +#define INITIALIZE_JOB(j) j.in_use = true; +#else +#define INITIALIZE_JOB(j) +#endif + +#define TEST_SETUP \ + io_service ios; \ + block_cache bc(0x4000, ios, boost::bind(&nop)); \ + aux::session_settings sett; \ + file_storage fs; \ + fs.add_file("a/test0", 0x4000); \ + fs.add_file("a/test1", 0x4000); \ + fs.add_file("a/test2", 0x4000); \ + fs.add_file("a/test3", 0x4000); \ + fs.add_file("a/test4", 0x4000); \ + fs.add_file("a/test5", 0x4000); \ + fs.add_file("a/test6", 0x4000); \ + fs.add_file("a/test7", 0x4000); \ + fs.set_piece_length(0x8000); \ + fs.set_num_pieces(5); \ + test_storage_impl* st = new test_storage_impl; \ + boost::shared_ptr pm(boost::make_shared(st, boost::shared_ptr(new int), &fs)); \ + error_code ec; \ + bc.set_settings(sett, ec); \ + st->m_settings = &sett; \ + disk_io_job rj; \ + disk_io_job wj; \ + INITIALIZE_JOB(rj) \ + INITIALIZE_JOB(wj) \ + rj.storage = pm; \ + wj.storage = pm; \ + cached_piece_entry* pe = NULL; \ + int ret = 0; \ + file::iovec_t iov[1]; \ + (void)iov[0]; \ + (void)ret; \ + (void)pe + +#define WRITE_BLOCK(p, b) \ + wj.flags = disk_io_job::in_progress; \ + wj.action = disk_io_job::write; \ + wj.d.io.offset = b * 0x4000; \ + wj.d.io.buffer_size = 0x4000; \ + wj.piece = p; \ + wj.buffer.disk_block = bc.allocate_buffer("write-test"); \ + pe = bc.add_dirty_block(&wj) + +#define READ_BLOCK(p, b, r) \ + rj.action = disk_io_job::read; \ + rj.d.io.offset = b * 0x4000; \ + rj.d.io.buffer_size = 0x4000; \ + rj.piece = p; \ + rj.storage = pm; \ + rj.requester = (void*)r; \ + rj.buffer.disk_block = 0; \ + ret = bc.try_read(&rj) + +#define RETURN_BUFFER \ + if (rj.d.io.ref.storage) bc.reclaim_block(rj.d.io.ref); \ + else if (rj.buffer.disk_block) bc.free_buffer(rj.buffer.disk_block); \ + rj.d.io.ref.storage = 0 + +#define FLUSH(flushing) \ + for (int i = 0; i < int(sizeof(flushing)/sizeof(flushing[0])); ++i) \ + { \ + pe->blocks[flushing[i]].pending = true; \ + bc.inc_block_refcount(pe, 0, block_cache::ref_flushing); \ + } \ + bc.blocks_flushed(pe, flushing, sizeof(flushing)/sizeof(flushing[0])) + +#define INSERT(p, b) \ + wj.piece = p; \ + wj.requester = (void*)1; \ + pe = bc.allocate_piece(&wj, cached_piece_entry::read_lru1); \ + ret = bc.allocate_iovec(iov, 1); \ + TEST_EQUAL(ret, 0); \ + bc.insert_blocks(pe, b, iov, 1, &wj) + +void test_write() +{ + TEST_SETUP; + + // write block (0,0) + WRITE_BLOCK(0, 0); + + counters c; + bc.update_stats_counters(c); + TEST_EQUAL(c[counters::write_cache_blocks], 1); + TEST_EQUAL(c[counters::read_cache_blocks], 0); + TEST_EQUAL(c[counters::pinned_blocks], 0); + TEST_EQUAL(c[counters::arc_mru_size], 0); + TEST_EQUAL(c[counters::arc_mru_ghost_size], 0); + TEST_EQUAL(c[counters::arc_mfu_size], 0); + TEST_EQUAL(c[counters::arc_mfu_ghost_size], 0); + TEST_EQUAL(c[counters::arc_write_size], 1); + TEST_EQUAL(c[counters::arc_volatile_size], 0); + + // try to read it back + READ_BLOCK(0, 0, 1); + TEST_EQUAL(bc.pinned_blocks(), 1); + bc.update_stats_counters(c); + TEST_EQUAL(c[counters::pinned_blocks], 1); + + // it's supposed to be a cache hit + TEST_CHECK(ret >= 0); + + // return the reference to the buffer we just read + RETURN_BUFFER; + TEST_EQUAL(bc.pinned_blocks(), 0); + bc.update_stats_counters(c); + TEST_EQUAL(c[counters::pinned_blocks], 0); + + // try to read block (1, 0) + READ_BLOCK(1, 0, 1); + + // that's supposed to be a cache miss + TEST_CHECK(ret < 0); + TEST_EQUAL(bc.pinned_blocks(), 0); + bc.update_stats_counters(c); + TEST_EQUAL(c[counters::pinned_blocks], 0); + + // just in case it wasn't we're supposed to return the reference + // to the buffer + RETURN_BUFFER; + + tailqueue jobs; + bc.clear(jobs); +} + +void test_flush() +{ + TEST_SETUP; + + // write block (0,0) + WRITE_BLOCK(0, 0); + + // pretend to flush to disk + int flushing[1] = {0}; + FLUSH(flushing); + + tailqueue jobs; + bc.clear(jobs); +} + +void test_insert() +{ + TEST_SETUP; + + INSERT(0, 0); + + counters c; + bc.update_stats_counters(c); + TEST_EQUAL(c[counters::write_cache_blocks], 0); + TEST_EQUAL(c[counters::read_cache_blocks], 1); + TEST_EQUAL(c[counters::pinned_blocks], 0); + TEST_EQUAL(c[counters::arc_mru_size], 1); + TEST_EQUAL(c[counters::arc_mru_ghost_size], 0); + TEST_EQUAL(c[counters::arc_mfu_size], 0); + TEST_EQUAL(c[counters::arc_mfu_ghost_size], 0); + TEST_EQUAL(c[counters::arc_write_size], 0); + TEST_EQUAL(c[counters::arc_volatile_size], 0); + + tailqueue jobs; + bc.clear(jobs); +} + +void test_evict() +{ + TEST_SETUP; + + INSERT(0, 0); + + counters c; + bc.update_stats_counters(c); + TEST_EQUAL(c[counters::write_cache_blocks], 0); + TEST_EQUAL(c[counters::read_cache_blocks], 1); + TEST_EQUAL(c[counters::pinned_blocks], 0); + TEST_EQUAL(c[counters::arc_mru_size], 1); + TEST_EQUAL(c[counters::arc_mru_ghost_size], 0); + TEST_EQUAL(c[counters::arc_mfu_size], 0); + TEST_EQUAL(c[counters::arc_mfu_ghost_size], 0); + TEST_EQUAL(c[counters::arc_write_size], 0); + TEST_EQUAL(c[counters::arc_volatile_size], 0); + + tailqueue jobs; + // this should make it not be evicted + // just free the buffers + ++pe->piece_refcount; + bc.evict_piece(pe, jobs); + + bc.update_stats_counters(c); + TEST_EQUAL(c[counters::write_cache_blocks], 0); + TEST_EQUAL(c[counters::read_cache_blocks], 0); + TEST_EQUAL(c[counters::pinned_blocks], 0); + TEST_EQUAL(c[counters::arc_mru_size], 1); + TEST_EQUAL(c[counters::arc_mru_ghost_size], 0); + TEST_EQUAL(c[counters::arc_mfu_size], 0); + TEST_EQUAL(c[counters::arc_mfu_ghost_size], 0); + TEST_EQUAL(c[counters::arc_write_size], 0); + TEST_EQUAL(c[counters::arc_volatile_size], 0); + + --pe->piece_refcount; + bc.evict_piece(pe, jobs); + + bc.update_stats_counters(c); + TEST_EQUAL(c[counters::write_cache_blocks], 0); + TEST_EQUAL(c[counters::read_cache_blocks], 0); + TEST_EQUAL(c[counters::pinned_blocks], 0); + TEST_EQUAL(c[counters::arc_mru_size], 0); + TEST_EQUAL(c[counters::arc_mru_ghost_size], 1); + TEST_EQUAL(c[counters::arc_mfu_size], 0); + TEST_EQUAL(c[counters::arc_mfu_ghost_size], 0); + TEST_EQUAL(c[counters::arc_write_size], 0); + TEST_EQUAL(c[counters::arc_volatile_size], 0); + + bc.clear(jobs); +} + +// test to have two different requestors read a block and +// make sure it moves into the MFU list +void test_arc_promote() +{ + TEST_SETUP; + + INSERT(0, 0); + + counters c; + bc.update_stats_counters(c); + TEST_EQUAL(c[counters::write_cache_blocks], 0); + TEST_EQUAL(c[counters::read_cache_blocks], 1); + TEST_EQUAL(c[counters::pinned_blocks], 0); + TEST_EQUAL(c[counters::arc_mru_size], 1); + TEST_EQUAL(c[counters::arc_mru_ghost_size], 0); + TEST_EQUAL(c[counters::arc_mfu_size], 0); + TEST_EQUAL(c[counters::arc_mfu_ghost_size], 0); + TEST_EQUAL(c[counters::arc_write_size], 0); + TEST_EQUAL(c[counters::arc_volatile_size], 0); + + READ_BLOCK(0, 0, 1); + TEST_EQUAL(bc.pinned_blocks(), 1); + bc.update_stats_counters(c); + TEST_EQUAL(c[counters::pinned_blocks], 1); + + // it's supposed to be a cache hit + TEST_CHECK(ret >= 0); + // return the reference to the buffer we just read + RETURN_BUFFER; + + bc.update_stats_counters(c); + TEST_EQUAL(c[counters::write_cache_blocks], 0); + TEST_EQUAL(c[counters::read_cache_blocks], 1); + TEST_EQUAL(c[counters::pinned_blocks], 0); + TEST_EQUAL(c[counters::arc_mru_size], 1); + TEST_EQUAL(c[counters::arc_mru_ghost_size], 0); + TEST_EQUAL(c[counters::arc_mfu_size], 0); + TEST_EQUAL(c[counters::arc_mfu_ghost_size], 0); + TEST_EQUAL(c[counters::arc_write_size], 0); + TEST_EQUAL(c[counters::arc_volatile_size], 0); + + READ_BLOCK(0, 0, 2); + TEST_EQUAL(bc.pinned_blocks(), 1); + bc.update_stats_counters(c); + TEST_EQUAL(c[counters::pinned_blocks], 1); + + // it's supposed to be a cache hit + TEST_CHECK(ret >= 0); + // return the reference to the buffer we just read + RETURN_BUFFER; + + bc.update_stats_counters(c); + TEST_EQUAL(c[counters::write_cache_blocks], 0); + TEST_EQUAL(c[counters::read_cache_blocks], 1); + TEST_EQUAL(c[counters::pinned_blocks], 0); + TEST_EQUAL(c[counters::arc_mru_size], 0); + TEST_EQUAL(c[counters::arc_mru_ghost_size], 0); + TEST_EQUAL(c[counters::arc_mfu_size], 1); + TEST_EQUAL(c[counters::arc_mfu_ghost_size], 0); + TEST_EQUAL(c[counters::arc_write_size], 0); + TEST_EQUAL(c[counters::arc_volatile_size], 0); + + tailqueue jobs; + bc.clear(jobs); +} + +void test_arc_unghost() +{ + TEST_SETUP; + + INSERT(0, 0); + + counters c; + bc.update_stats_counters(c); + TEST_EQUAL(c[counters::write_cache_blocks], 0); + TEST_EQUAL(c[counters::read_cache_blocks], 1); + TEST_EQUAL(c[counters::pinned_blocks], 0); + TEST_EQUAL(c[counters::arc_mru_size], 1); + TEST_EQUAL(c[counters::arc_mru_ghost_size], 0); + TEST_EQUAL(c[counters::arc_mfu_size], 0); + TEST_EQUAL(c[counters::arc_mfu_ghost_size], 0); + TEST_EQUAL(c[counters::arc_write_size], 0); + TEST_EQUAL(c[counters::arc_volatile_size], 0); + + tailqueue jobs; + bc.evict_piece(pe, jobs); + + bc.update_stats_counters(c); + TEST_EQUAL(c[counters::write_cache_blocks], 0); + TEST_EQUAL(c[counters::read_cache_blocks], 0); + TEST_EQUAL(c[counters::pinned_blocks], 0); + TEST_EQUAL(c[counters::arc_mru_size], 0); + TEST_EQUAL(c[counters::arc_mru_ghost_size], 1); + TEST_EQUAL(c[counters::arc_mfu_size], 0); + TEST_EQUAL(c[counters::arc_mfu_ghost_size], 0); + TEST_EQUAL(c[counters::arc_write_size], 0); + TEST_EQUAL(c[counters::arc_volatile_size], 0); + + // the block is now a ghost. If we cache-hit it, + // it should be promoted back to the main list + bc.cache_hit(pe, (void*)1, false); + + bc.update_stats_counters(c); + TEST_EQUAL(c[counters::write_cache_blocks], 0); + // we didn't actually read in any blocks, so the cache size + // is still 0 + TEST_EQUAL(c[counters::read_cache_blocks], 0); + TEST_EQUAL(c[counters::pinned_blocks], 0); + TEST_EQUAL(c[counters::arc_mru_size], 1); + TEST_EQUAL(c[counters::arc_mru_ghost_size], 0); + TEST_EQUAL(c[counters::arc_mfu_size], 0); + TEST_EQUAL(c[counters::arc_mfu_ghost_size], 0); + TEST_EQUAL(c[counters::arc_write_size], 0); + TEST_EQUAL(c[counters::arc_volatile_size], 0); + + bc.clear(jobs); +} + +void test_iovec() +{ + TEST_SETUP; + + ret = bc.allocate_iovec(iov, 1); + bc.free_iovec(iov, 1); +} + +void test_unaligned_read() +{ + TEST_SETUP; + + INSERT(0, 0); + INSERT(0, 1); + + rj.action = disk_io_job::read; + rj.d.io.offset = 0x2000; + rj.d.io.buffer_size = 0x4000; + rj.piece = 0; + rj.storage = pm; + rj.requester = (void*)1; + rj.buffer.disk_block = 0; + ret = bc.try_read(&rj); + + // unaligned reads copies the data into a new buffer + // rather than + TEST_EQUAL(bc.pinned_blocks(), 0); + counters c; + bc.update_stats_counters(c); + TEST_EQUAL(c[counters::pinned_blocks], 0); + + // it's supposed to be a cache hit + TEST_CHECK(ret >= 0); + // return the reference to the buffer we just read + RETURN_BUFFER; + + tailqueue jobs; + bc.clear(jobs); +} + +TORRENT_TEST(block_cache) +{ + test_write(); + test_flush(); + test_insert(); + test_evict(); + test_arc_promote(); + test_arc_unghost(); + test_iovec(); + test_unaligned_read(); + + // TODO: test try_evict_blocks + // TODO: test evicting volatile pieces, to see them be removed + // TODO: test evicting dirty pieces + // TODO: test free_piece + // TODO: test abort_dirty + // TODO: test unaligned reads +} + diff --git a/test/test_bloom_filter.cpp b/test/test_bloom_filter.cpp new file mode 100644 index 0000000..21afb2a --- /dev/null +++ b/test/test_bloom_filter.cpp @@ -0,0 +1,133 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/bloom_filter.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/sha1_hash.hpp" +#include + +using namespace libtorrent; + +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() +{ + boost::uint8_t bits[4] = {0x00, 0x00, 0x00, 0x00}; + + for (int i = 0; i < 4 * 8; ++i) + { + boost::uint8_t t[4] = { boost::uint8_t(i & 0xff), 0, boost::uint8_t(i & 0xff), 0 }; + TEST_CHECK(!has_bits(t, bits, 6)); + } + + for (int i = 0; i < 4 * 8; i += 2) + { + boost::uint8_t t[4] = { boost::uint8_t(i & 0xff), 0, boost::uint8_t(i & 0xff), 0 }; + TEST_CHECK(!has_bits(t, bits, 4)); + set_bits(t, bits, 4); + TEST_CHECK(has_bits(t, bits, 4)); + } + + boost::uint8_t compare[4] = { 0x55, 0x55, 0x55, 0x55}; + TEST_EQUAL(memcmp(compare, bits, 4), 0); +} + +void test_count_zeroes() +{ + boost::uint8_t bits[4] = {0x00, 0xff, 0x55, 0xaa}; + + TEST_EQUAL(count_zero_bits(bits, 4), 16); + + boost::uint8_t t[4] = { 4, 0, 4, 0 }; + set_bits(t, bits, 4); + TEST_EQUAL(count_zero_bits(bits, 4), 15); + + boost::uint8_t compare[4] = { 0x10, 0xff, 0x55, 0xaa}; + TEST_EQUAL(memcmp(compare, bits, 4), 0); +} + +void test_to_from_string() +{ + boost::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)); + + boost::uint8_t compare[4] = { 0x16, 0xff, 0x55, 0xaa}; + + bits_out = filter.to_string(); + TEST_EQUAL(memcmp(compare, bits_out.c_str(), 4), 0); +} + +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..d7a9db5 --- /dev/null +++ b/test/test_buffer.cpp @@ -0,0 +1,321 @@ +/* + Copyright (c) 2003 - 2005, Arvid Norberg, Daniel Wallin + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of Rasterbar Software nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include +#include + +#include "libtorrent/buffer.hpp" +#include "libtorrent/chained_buffer.hpp" +#include "libtorrent/socket.hpp" + +#include "test.hpp" + +using namespace libtorrent; + +/* +template +T const& min_(T const& x, T const& y) +{ + return x < y ? x : y; +} + +void test_speed() +{ + buffer b; + + char data[32]; + + srand(0); + + boost::timer t; + + int const iterations = 5000000; + int const step = iterations / 20; + + for (int i = 0; i < iterations; ++i) + { + int x = rand(); + + if (i % step == 0) std::cerr << "."; + + std::size_t n = rand() % 32; + n = 32; + + if (x % 2) + { + b.insert(data, data + n); + } + else + { + b.erase(min_(b.size(), n)); + } + } + + float t1 = t.elapsed(); + std::cerr << "buffer elapsed: " << t.elapsed() << "\n"; + + std::vector v; + + srand(0); + t.restart(); + + for (int i = 0; i < iterations; ++i) + { + int x = rand(); + + if (i % step == 0) std::cerr << "."; + + std::size_t n = rand() % 32; + n = 32; + + if (x % 2) + { + v.insert(v.end(), data, data + n); + } + else + { + v.erase(v.begin(), v.begin() + min_(v.size(), n)); + } + } + + float t2 = t.elapsed(); + std::cerr << "std::vector elapsed: " << t.elapsed() << "\n"; + + assert(t1 < t2); +} +*/ + +// -- test buffer -- + +TORRENT_TEST(buffer) +{ + char data[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + + buffer b; + + TEST_CHECK(b.size() == 0); + TEST_CHECK(b.capacity() == 0); + TEST_CHECK(b.empty()); + + b.resize(10); + TEST_CHECK(b.size() == 10); + TEST_CHECK(b.capacity() == 10); + + std::memcpy(b.begin(), data, 10); + b.reserve(50); + TEST_CHECK(std::memcmp(b.begin(), data, 10) == 0); + TEST_CHECK(b.capacity() == 50); + + b.erase(b.begin() + 6, b.end()); + TEST_CHECK(std::memcmp(b.begin(), data, 6) == 0); + TEST_CHECK(b.capacity() == 50); + TEST_CHECK(b.size() == 6); + + b.insert(b.begin(), data + 5, data + 10); + TEST_CHECK(b.capacity() == 50); + TEST_CHECK(b.size() == 11); + TEST_CHECK(std::memcmp(b.begin(), data + 5, 5) == 0); + + b.clear(); + TEST_CHECK(b.size() == 0); + TEST_CHECK(b.capacity() == 50); + + b.insert(b.end(), data, data + 10); + TEST_CHECK(b.size() == 10); + TEST_CHECK(std::memcmp(b.begin(), data, 10) == 0); + + b.erase(b.begin(), b.end()); + TEST_CHECK(b.capacity() == 50); + TEST_CHECK(b.size() == 0); + + buffer().swap(b); + TEST_CHECK(b.capacity() == 0); + +} + +// -- test chained buffer -- + +std::set buffer_list; + +void free_buffer(char* m, void* userdata, block_cache_reference ref) +{ + TEST_CHECK(userdata == (void*)0x1337); + std::set::iterator 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 = (char*)std::malloc(size); + buffer_list.insert(mem); + return mem; +} + +template +int copy_buffers(T const& b, char* target) +{ + int copied = 0; + for (typename T::const_iterator i = b.begin() + , end(b.end()); i != end; ++i) + { + memcpy(target, boost::asio::buffer_cast(*i), boost::asio::buffer_size(*i)); + target += boost::asio::buffer_size(*i); + copied += boost::asio::buffer_size(*i); + } + return copied; +} + +bool compare_chained_buffer(chained_buffer& b, char const* mem, int size) +{ + if (size == 0) return true; + std::vector flat(size); + std::vector const& iovec2 = b.build_iovec(size); + int copied = copy_buffers(iovec2, &flat[0]); + TEST_CHECK(copied == size); + return std::memcmp(&flat[0], mem, size) == 0; +} + +TORRENT_TEST(chained_buffer) +{ + char data[] = "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), 0); + + char* b1 = allocate_buffer(512); + std::memcpy(b1, data, 6); + b.append_buffer(b1, 512, 6, &free_buffer, (void*)0x1337); + TEST_EQUAL(buffer_list.size(), 1); + + TEST_CHECK(b.capacity() == 512); + TEST_CHECK(b.size() == 6); + TEST_CHECK(!b.empty()); + TEST_CHECK(b.space_in_last_buffer() == 512 - 6); + + b.pop_front(3); + + TEST_CHECK(b.capacity() == 512); + TEST_CHECK(b.size() == 3); + TEST_CHECK(!b.empty()); + TEST_CHECK(b.space_in_last_buffer() == 512 - 6); + + bool ret = b.append(data, 6); + + TEST_CHECK(ret == true); + TEST_CHECK(b.capacity() == 512); + TEST_CHECK(b.size() == 9); + TEST_CHECK(!b.empty()); + TEST_CHECK(b.space_in_last_buffer() == 512 - 12); + + char data2[1024]; + ret = b.append(data2, 1024); + + TEST_CHECK(ret == false); + + char* b2 = allocate_buffer(512); + std::memcpy(b2, data, 6); + b.append_buffer(b2, 512, 6, free_buffer, (void*)0x1337); + TEST_CHECK(buffer_list.size() == 2); + + char* b3 = allocate_buffer(512); + std::memcpy(b3, data, 6); + b.append_buffer(b3, 512, 6, &free_buffer, (void*)0x1337); + TEST_CHECK(buffer_list.size() == 3); + + TEST_CHECK(b.capacity() == 512 * 3); + TEST_CHECK(b.size() == 21); + TEST_CHECK(!b.empty()); + TEST_CHECK(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_CHECK(buffer_list.size() == 2); + TEST_CHECK(b.capacity() == 512 * 2); + TEST_CHECK(b.size() == 10); + TEST_CHECK(!b.empty()); + TEST_CHECK(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_CHECK(b.size() == 9 - i); + } + + char* b4 = allocate_buffer(20); + std::memcpy(b4, data, 6); + std::memcpy(b4 + 6, data, 6); + b.append_buffer(b4, 20, 12, &free_buffer, (void*)0x1337); + TEST_CHECK(b.space_in_last_buffer() == 8); + + ret = b.append(data, 6); + TEST_CHECK(ret == true); + TEST_CHECK(b.space_in_last_buffer() == 2); + std::cout << b.space_in_last_buffer() << std::endl; + ret = b.append(data, 2); + TEST_CHECK(ret == true); + TEST_CHECK(b.space_in_last_buffer() == 0); + std::cout << b.space_in_last_buffer() << std::endl; + + char* b5 = allocate_buffer(20); + std::memcpy(b4, data, 6); + b.append_buffer(b5, 20, 6, &free_buffer, (void*)0x1337); + + b.pop_front(22); + TEST_CHECK(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..f489c05 --- /dev/null +++ b/test/test_checking.cpp @@ -0,0 +1,314 @@ +/* + +Copyright (c) 2013, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/session.hpp" +#include "test.hpp" +#include "setup_transfer.hpp" +#include "libtorrent/create_torrent.hpp" +#include // for chmod +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/torrent_status.hpp" + +static const int file_sizes[] = +{ 5, 16 - 5, 16000, 17, 10, 8000, 8000, 1,1,1,1,1,100,1,1,1,1,100,1,1,1,1,1,1 + ,1,1,1,1,1,1,13,65000,34,75,2,30,400,500,23000,900,43000,400,4300,6, 4}; +const int num_files = sizeof(file_sizes)/sizeof(file_sizes[0]); + +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, +}; + +void test_checking(int flags = read_only_files) +{ + using namespace libtorrent; + namespace lt = libtorrent; + + fprintf(stderr, "\n==== TEST CHECKING %s%s%s=====\n\n" + , (flags & read_only_files) ? "read-only-files ":"" + , (flags & corrupt_files) ? "corrupt ":"" + , (flags & incomplete_files) ? "incomplete ":""); + + // make the files writable again + for (int i = 0; i < num_files; ++i) + { + char name[1024]; + snprintf(name, sizeof(name), "test%d", i); + char dirname[200]; + snprintf(dirname, sizeof(dirname), "test_dir%d", i / 5); + std::string path = combine_path(combine_path("tmp1_checking", "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 + } + + // in case the previous run was terminated + error_code ec; + remove_all("tmp1_checking", ec); + if (ec) fprintf(stderr, "ERROR: removing tmp1_checking: (%d) %s\n" + , ec.value(), ec.message().c_str()); + + create_directory("tmp1_checking", ec); + if (ec) fprintf(stderr, "ERROR: creating directory tmp1_checking: (%d) %s\n" + , ec.value(), ec.message().c_str()); + create_directory(combine_path("tmp1_checking", "test_torrent_dir"), ec); + if (ec) fprintf(stderr, "ERROR: creating directory test_torrent_dir: (%d) %s\n" + , ec.value(), ec.message().c_str()); + + file_storage fs; + std::srand(10); + int piece_size = 0x4000; + + create_random_files(combine_path("tmp1_checking", "test_torrent_dir") + , file_sizes, num_files); + + add_files(fs, combine_path("tmp1_checking", "test_torrent_dir")); + libtorrent::create_torrent t(fs, piece_size, 0x4000 + , libtorrent::create_torrent::optimize_alignment); + + // calculate the hash for all pieces + set_piece_hashes(t, "tmp1_checking", ec); + if (ec) fprintf(stderr, "ERROR: set_piece_hashes: (%d) %s\n" + , ec.value(), ec.message().c_str()); + + std::vector buf; + bencode(std::back_inserter(buf), t.generate()); + boost::shared_ptr ti(new torrent_info(&buf[0], buf.size(), ec)); + + fprintf(stderr, "generated torrent: %s tmp1_checking/test_torrent_dir\n" + , to_hex(ti->info_hash().to_string()).c_str()); + + // truncate every file in half + if (flags & incomplete_files) + { + for (int i = 0; i < num_files; ++i) + { + char name[1024]; + snprintf(name, sizeof(name), "test%d", i); + char dirname[200]; + snprintf(dirname, sizeof(dirname), "test_dir%d", i / 5); + std::string path = combine_path(combine_path("tmp1_checking", "test_torrent_dir"), dirname); + path = combine_path(path, name); + + error_code ec; + file f(path, file::read_write, ec); + if (ec) fprintf(stderr, "ERROR: opening file \"%s\": (%d) %s\n" + , path.c_str(), ec.value(), ec.message().c_str()); + f.set_size(file_sizes[i] / 2, ec); + if (ec) fprintf(stderr, "ERROR: truncating file \"%s\": (%d) %s\n" + , path.c_str(), ec.value(), ec.message().c_str()); + } + } + + // overwrite the files with new random data + if (flags & corrupt_files) + { + fprintf(stderr, "corrupt file test. overwriting files\n"); + // increase the size of some files. When they're read only that forces + // the checker to open them in write-mode to truncate them + static const int file_sizes2[] = + { 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(combine_path("tmp1_checking", "test_torrent_dir"), file_sizes2, num_files); + } + + // make the files read only + if (flags & read_only_files) + { + fprintf(stderr, "making files read-only\n"); + for (int i = 0; i < num_files; ++i) + { + char name[1024]; + snprintf(name, sizeof(name), "test%d", i); + char dirname[200]; + snprintf(dirname, sizeof(dirname), "test_dir%d", i / 5); + + std::string path = combine_path(combine_path("tmp1_checking", "test_torrent_dir"), dirname); + path = combine_path(path, name); + fprintf(stderr, " %s\n", path.c_str()); + +#ifdef TORRENT_WINDOWS + SetFileAttributesA(path.c_str(), FILE_ATTRIBUTE_READONLY); +#else + chmod(path.c_str(), S_IRUSR); +#endif + } + } + + int mask = alert::all_categories + & ~(alert::progress_notification + | alert::performance_warning + | alert::stats_notification); + + 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_int(settings_pack::alert_mask, mask); + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:48000"); + pack.set_int(settings_pack::max_retry_port_bind, 1000); + lt::session ses1(pack); + + add_torrent_params p; + p.save_path = "tmp1_checking"; + p.ti = ti; + torrent_handle tor1 = ses1.add_torrent(p, ec); + TEST_CHECK(!ec); + + torrent_status st; + for (int i = 0; i < 20; ++i) + { + print_alerts(ses1, "ses1"); + + st = tor1.status(); + + printf("%d %f %s\n", st.state, st.progress_ppm / 10000.f, st.errc.message().c_str()); + + if ( +#ifndef TORRENT_NO_DEPRECATE + st.state != torrent_status::queued_for_checking && +#endif + st.state != torrent_status::checking_files + && st.state != torrent_status::checking_resume_data) + break; + + if (st.errc) break; + test_sleep(500); + } + if (flags & incomplete_files) + { + TEST_CHECK(!st.is_seeding); + + test_sleep(500); + st = tor1.status(); + TEST_CHECK(!st.is_seeding); + } + + if (flags & corrupt_files) + { + TEST_CHECK(!st.is_seeding); + + if (flags & read_only_files) + { + // we expect our checking of the files to trigger + // attempts to truncate them, since the files are + // read-only here, we expect the checking to fail. + TEST_CHECK(st.errc); + if (st.errc) + fprintf(stderr, "error: %s\n", st.errc.message().c_str()); + + // wait a while to make sure libtorrent survived the error + test_sleep(1000); + + st = tor1.status(); + TEST_CHECK(!st.is_seeding); + TEST_CHECK(st.errc); + if (st.errc) + fprintf(stderr, "error: %s\n", st.errc.message().c_str()); + } + else + { + TEST_CHECK(!st.errc); + if (st.errc) + fprintf(stderr, "error: %s\n", st.errc.message().c_str()); + } + } + + if ((flags & (incomplete_files | corrupt_files)) == 0) + { + TEST_CHECK(st.is_seeding); + if (st.errc) + fprintf(stderr, "error: %s\n", st.errc.message().c_str()); + } + + // make the files writable again + if (flags & read_only_files) + { + for (int i = 0; i < num_files; ++i) + { + char name[1024]; + snprintf(name, sizeof(name), "test%d", i); + char dirname[200]; + snprintf(dirname, sizeof(dirname), "test_dir%d", i / 5); + + std::string path = combine_path(combine_path("tmp1_checking", "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("tmp1_checking", ec); + if (ec) fprintf(stderr, "ERROR: removing tmp1_checking: (%d) %s\n" + , ec.value(), ec.message().c_str()); +} + +TORRENT_TEST(checking) +{ + test_checking(); +} + +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(corrupt) +{ + test_checking(corrupt_files); +} + diff --git a/test/test_crc32.cpp b/test/test_crc32.cpp new file mode 100644 index 0000000..4e7cf02 --- /dev/null +++ b/test/test_crc32.cpp @@ -0,0 +1,64 @@ +/* + +Copyright (c) 2014, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/crc32c.hpp" +#include "test.hpp" + +TORRENT_TEST(crc32) +{ + using namespace libtorrent; + + boost::uint32_t out; + + boost::uint32_t in1 = 0x5aa5feef; + out = crc32c_32(in1); + + TEST_EQUAL(out, htonl(0xd5b9e35e)); + + boost::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); + + out = crc32c(buf, 4); + TEST_EQUAL(out, htonl(0xaa36918a)); + + 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, htonl(0x43aba862)); + + 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, htonl(0x4e79dd46)); +} + diff --git a/test/test_create_torrent.cpp b/test/test_create_torrent.cpp new file mode 100644 index 0000000..3fb1062 --- /dev/null +++ b/test/test_create_torrent.cpp @@ -0,0 +1,67 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" + +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/aux_/escape_string.hpp" // for convert_path_to_posix +#include +#include + +namespace lt = libtorrent; + +// 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:lengthi12345e12:piece lengthi65536e6:pieces20:ababababababababababee"; + + lt::torrent_info info(test_torrent, sizeof(test_torrent) - 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 != NULL); + + // +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); +} + diff --git a/test/test_dht.cpp b/test/test_dht.cpp new file mode 100644 index 0000000..f7afa97 --- /dev/null +++ b/test/test_dht.cpp @@ -0,0 +1,2651 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DISABLE_DHT + +#include "libtorrent/config.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/kademlia/node.hpp" // for verify_message +#include "libtorrent/bencode.hpp" +#include "libtorrent/socket_io.hpp" // for hash_address +#include "libtorrent/broadcast_socket.hpp" // for supports_ipv6 +#include "libtorrent/performance_counters.hpp" // for counters +#include "libtorrent/random.hpp" +#include "libtorrent/ed25519.hpp" + +#include "libtorrent/kademlia/node_id.hpp" +#include "libtorrent/kademlia/routing_table.hpp" +#include "libtorrent/kademlia/item.hpp" +#include "libtorrent/kademlia/dht_observer.hpp" +#include "libtorrent/ed25519.hpp" +#include +#include + +#include "test.hpp" +#include "setup_transfer.hpp" + +#ifdef TORRENT_USE_VALGRIND +#include +#endif + +#if TORRENT_USE_IOSTREAM +#include +#endif + +using namespace libtorrent; +using namespace libtorrent::dht; + +void nop() {} + +static sha1_hash to_hash(char const* s) +{ + sha1_hash ret; + from_hex(s, 40, (char*)&ret[0]); + return ret; +} + +void add_and_replace(libtorrent::dht::node_id& dst, libtorrent::dht::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(void* userdata, libtorrent::dht::node_entry const& n) +{ + using namespace libtorrent::dht; + std::vector* nv = (std::vector*)userdata; + nv->push_back(n); +} + +static void nop(void* userdata, libtorrent::dht::node_entry const& n) {} + +std::list > g_sent_packets; + +struct mock_socket : udp_socket_interface +{ + bool has_quota() { return true; } + bool send_packet(entry& msg, udp::endpoint const& ep, int flags) + { + // TODO: 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; + } +}; + +sha1_hash generate_next() +{ + sha1_hash ret; + for (int i = 0; i < 20; ++i) ret[i] = rand() & 0xff; + return ret; +} + +boost::array generate_key() +{ + boost::array ret; + for (int i = 0; i < 64; ++i) ret[i] = rand() & 0xff; + return ret; +} + +static const std::string no; + +std::list >::iterator +find_packet(udp::endpoint ep) +{ + return std::find_if(g_sent_packets.begin(), g_sent_packets.end() + , boost::bind(&std::pair::first, _1) == ep); +} + +void lazy_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); +} + +void write_peers(entry::dictionary_type& r, std::set const& peers) +{ + entry::list_type& pe = r["values"].list(); + for (std::set::const_iterator it = peers.begin() + ; it != peers.end(); ++it) + { + std::string endpoint(18, '\0'); + std::string::iterator out = endpoint.begin(); + libtorrent::detail::write_endpoint(*it, out); + endpoint.resize(out - endpoint.begin()); + pe.push_back(entry(endpoint)); + } +} + +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(char const* t) + { if (t) a["target"] = std::string(t, 20); 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(std::string k) + { a["k"] = k; return *this; } + + msg_args& sig(std::string s) + { a["sig"] = s; return *this; } + + msg_args& seq(int s) + { a["seq"] = s; return *this; } + + msg_args& cas(boost::int64_t c) + { a["cas"] = c; return *this; } + + msg_args& nid(sha1_hash const& n) + { a["id"] = n.to_string(); return *this; } + + msg_args& salt(char const* s) + { if (s) a["salt"] = s; return *this; } + + msg_args& want(std::string w) + { a["want"].list().push_back(w); return *this; } + + msg_args& nodes(nodes_t const& n) + { if (!n.empty()) dht::write_nodes_entry(a, n); return *this; } + + msg_args& peers(std::set const& p) + { if (!p.empty()) write_peers(a.dict(), p); 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 lazy_entry, 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); +#if defined TORRENT_DEBUG && TORRENT_USE_IOSTREAM +// this yields a lot of output. too much +// std::cerr << "sending: " << e << "\n"; +#endif + +#ifdef TORRENT_USE_VALGRIND + VALGRIND_CHECK_MEM_IS_DEFINED(msg_buf, size); +#endif + + bdecode_node decoded; + error_code ec; + bdecode(msg_buf, msg_buf + size, decoded, ec); + if (ec) fprintf(stderr, "bdecode failed: %s\n", ec.message().c_str()); + + dht::msg m(decoded, ep); + node.incoming(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 + std::list >::iterator i = find_packet(ep); + if (has_response) + { + if (i == g_sent_packets.end()) + { + TEST_ERROR("not response from DHT node"); + return; + } + + lazy_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"); +// 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 size = bencode(msg_buf, e); + +#ifdef TORRENT_USE_VALGRIND + VALGRIND_CHECK_MEM_IS_DEFINED(msg_buf, size); +#endif + + bdecode_node decoded; + error_code ec; + bdecode(msg_buf, msg_buf + size, decoded, ec); + if (ec) fprintf(stderr, "bdecode failed: %s\n", ec.message().c_str()); + + dht::msg m(decoded, ep); + node.incoming(m); +} + +struct announce_item +{ + sha1_hash next; + int num_peers; + entry ent; + sha1_hash target; + void gen() + { + num_peers = (rand() % 5) + 2; + ent["next"] = next.to_string(); + ent["A"] = "a"; + ent["B"] = "b"; + ent["num_peers"] = num_peers; + + char buf[512]; + char* ptr = buf; + int len = bencode(ptr, ent); + target = hasher(buf, len).final(); + } +}; + +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((char const*)&items[j].target[0])); + + key_desc_t 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]; + +// fprintf(stderr, "msg: %s\n", print_entry(response).c_str()); + int ret = verify_message(response, desc, parsed, error_string + , sizeof(error_string)); + if (ret) + { + TEST_EQUAL(parsed[4].string_value(), "r"); + token = parsed[2].string_value(); +// fprintf(stderr, "got token: %s\n", token.c_str()); + } + else + { + fprintf(stderr, "msg: %s\n", print_entry(response).c_str()); + fprintf(stderr, " 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((char const*)&items[j].target[0]) + .value(items[j].ent)); + + key_desc_t desc2[] = + { + { "y", bdecode_node::string_t, 1, 0 } + }; + + bdecode_node parsed2[1]; + ret = verify_message(response, desc2, parsed2, error_string + , sizeof(error_string)); + if (ret) + { + if (parsed2[0].string_value() != "r") + fprintf(stderr, "msg: %s\n", print_entry(response).c_str()); + + TEST_EQUAL(parsed2[0].string_value(), "r"); + } + else + { + fprintf(stderr, "msg: %s\n", print_entry(response).c_str()); + fprintf(stderr, " 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((char const*)&items[j].target[0])); + + key_desc_t 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 + , sizeof(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 +{ + virtual void set_external_address(address const& addr + , address const& source) TORRENT_OVERRIDE + {} + + virtual address external_address() TORRENT_OVERRIDE + { + return address_v4::from_string("236.0.0.1"); + } + virtual void get_peers(sha1_hash const& ih) TORRENT_OVERRIDE {} + virtual void outgoing_get_peers(sha1_hash const& target + , sha1_hash const& sent_target, udp::endpoint const& ep) TORRENT_OVERRIDE {} + virtual void announce(sha1_hash const& ih, address const& addr, int port) TORRENT_OVERRIDE {} + virtual void log(dht_logger::module_t l, char const* fmt, ...) TORRENT_OVERRIDE + { + va_list v; + va_start(v, fmt); + char buf[1024]; + vsnprintf(buf, sizeof(buf), fmt, v); + va_end(v); + m_log.push_back(buf); + } + virtual void log_packet(message_direction_t dir, char const* pkt, int len + , udp::endpoint node) TORRENT_OVERRIDE {} + virtual bool on_dht_request(char const* query, int query_len + , dht::msg const& request, entry& response) TORRENT_OVERRIDE { return false; } + + std::vector m_log; +}; + +dht_settings test_settings() +{ + dht_settings sett; + sett.max_torrents = 4; + sett.max_dht_items = 4; + sett.enforce_node_id = false; + return sett; +} + +// TODO: test obfuscated_get_peers +// TODO: 2 split this test up into smaller test cases +TORRENT_TEST(dht) +{ + dht_settings sett = test_settings(); + mock_socket s; + obs observer; + counters cnt; + dht::node node(&s, sett, node_id(0), &observer, cnt); + + // DHT should be running on port 48199 now + bdecode_node response; + char error_string[200]; + bool ret; + + // ====== ping ====== + udp::endpoint source(address::from_string("10.0.0.1"), 20); + send_dht_request(node, "ping", source, &response); + + dht::key_desc_t 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]; + + fprintf(stderr, "msg: %s\n", print_entry(response).c_str()); + ret = dht::verify_message(response, pong_desc, pong_keys, error_string + , sizeof(error_string)); + TEST_CHECK(ret); + if (ret) + { + TEST_CHECK(pong_keys[0].string_value() == "r"); + TEST_CHECK(pong_keys[1].string_value() == "10"); + } + else + { + fprintf(stderr, " invalid ping response: %s\n", error_string); + } + + // ====== invalid message ====== + + send_dht_request(node, "find_node", source, &response); + + dht::key_desc_t err_desc[] = { + {"y", bdecode_node::string_t, 1, 0}, + {"e", bdecode_node::list_t, 2, 0} + }; + + bdecode_node err_keys[2]; + + fprintf(stderr, "msg: %s\n", print_entry(response).c_str()); + ret = dht::verify_message(response, err_desc, err_keys, error_string + , sizeof(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 + { + fprintf(stderr, " invalid error response: %s\n", error_string); + } + + // ====== get_peers ====== + + send_dht_request(node, "get_peers", source, &response + , msg_args().info_hash("01010101010101010101")); + + dht::key_desc_t 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}, + }; + + bdecode_node peer1_keys[4]; + + std::string token; + fprintf(stderr, "msg: %s\n", print_entry(response).c_str()); + ret = dht::verify_message(response, peer1_desc, peer1_keys, error_string + , sizeof(error_string)); + TEST_CHECK(ret); + if (ret) + { + TEST_CHECK(peer1_keys[0].string_value() == "r"); + token = peer1_keys[2].string_value(); +// fprintf(stderr, "got token: %s\n", token.c_str()); + } + else + { + fprintf(stderr, "msg: %s\n", print_entry(response).c_str()); + fprintf(stderr, " invalid get_peers response: %s\n", error_string); + } + + // ====== announce ====== + + send_dht_request(node, "announce_peer", source, &response + , msg_args() + .info_hash("01010101010101010101") + .name("test") + .token(token) + .port(8080)); + + + dht::key_desc_t 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]; + + fprintf(stderr, "msg: %s\n", print_entry(response).c_str()); + ret = dht::verify_message(response, ann_desc, ann_keys, error_string + , sizeof(error_string)); + TEST_CHECK(ret); + if (ret) + { + TEST_CHECK(ann_keys[0].string_value() == "r"); + } + else + { + fprintf(stderr, " invalid announce response: %s\n", error_string); + } + + 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) + { + source = udp::endpoint(rand_v4(), 6000); + send_dht_request(node, "get_peers", source, &response + , msg_args().info_hash("01010101010101010101")); + + ret = dht::verify_message(response, peer1_desc, peer1_keys, error_string + , sizeof(error_string)); + + if (ret) + { + TEST_CHECK(peer1_keys[0].string_value() == "r"); + token = peer1_keys[2].string_value(); +// fprintf(stderr, "got token: %s\n", token.c_str()); + } + else + { + fprintf(stderr, "msg: %s\n", print_entry(response).c_str()); + fprintf(stderr, " invalid get_peers response: %s\n", error_string); + } + response.clear(); + send_dht_request(node, "announce_peer", source, &response + , msg_args() + .info_hash("01010101010101010101") + .name("test") + .token(token) + .port(8080) + .seed(i >= 50)); + + response.clear(); + } + + // ====== get_peers ====== + + send_dht_request(node, "get_peers", source, &response + , msg_args().info_hash("01010101010101010101").scrape(true)); + + dht::key_desc_t 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]; + + fprintf(stderr, "msg: %s\n", print_entry(response).c_str()); + ret = dht::verify_message(response, peer2_desc, peer2_keys, error_string + , sizeof(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()); + + fprintf(stderr, "seeds: %f\n", seeds.size()); + fprintf(stderr, "downloaders: %f\n", downloaders.size()); + + TEST_CHECK(fabs(seeds.size() - 50.f) <= 3.f); + TEST_CHECK(fabs(downloaders.size() - 50.f) <= 3.f); + } + else + { + fprintf(stderr, " invalid get_peers response: %s\n", error_string); + } + + // ====== test node ID testing ===== + + { + 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)); + } + + // ====== test node ID enforcement ====== + + // enable node_id enforcement + sett.enforce_node_id = true; + + // this is one of the test vectors from: + // http://libtorrent.org/dht_sec.html + source = udp::endpoint(address::from_string("124.31.75.21"), 1); + node_id nid = to_hash("5fbfbff10c5d6a4ec8a88e4c6ab4c28b95eee401"); + + // verify that we reject invalid node IDs + // this is now an invalid node-id for 'source' + nid[0] = 0x18; + int nodes_num = node.size().get<0>(); + send_dht_request(node, "find_node", source, &response + , msg_args().target("0101010101010101010101010101010101010101").nid(nid)); + + ret = dht::verify_message(response, err_desc, err_keys, error_string + , sizeof(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 + { + fprintf(stderr, "msg: %s\n", print_entry(response).c_str()); + TEST_ERROR("invalid error response"); + } + } + else + { + fprintf(stderr, "msg: %s\n", print_entry(response).c_str()); + fprintf(stderr, " invalid error response: %s\n", error_string); + } + + // a node with invalid node-id shouldn't be added to routing table. + TEST_EQUAL(node.size().get<0>(), nodes_num); + + // now the node-id is valid. + nid[0] = 0x5f; + send_dht_request(node, "find_node", source, &response + , msg_args().target("0101010101010101010101010101010101010101").nid(nid)); + + dht::key_desc_t 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]; + + fprintf(stderr, "msg: %s\n", print_entry(response).c_str()); + ret = dht::verify_message(response, nodes_desc, nodes_keys, error_string + , sizeof(error_string)); + TEST_CHECK(ret); + if (ret) + { + TEST_CHECK(nodes_keys[0].string_value() == "r"); + } + else + { + fprintf(stderr, "msg: %s\n", print_entry(response).c_str()); + fprintf(stderr, " invalid error response: %s\n", error_string); + } + // node with valid node-id should be added to routing table. + TEST_EQUAL(node.size().get<0>(), nodes_num + 1); + + sett.enforce_node_id = false; + +// =========================== + + bloom_filter<256> test; + for (int i = 0; i < 256; ++i) + { + char adr[50]; + snprintf(adr, 50, "192.0.2.%d", i); + address a = address::from_string(adr); + sha1_hash iphash; + hash_address(a, iphash); + test.set(iphash); + } + + if (supports_ipv6()) + { + for (int i = 0; i < 0x3E8; ++i) + { + char adr[50]; + snprintf(adr, 50, "2001:db8::%x", i); + address a = address::from_string(adr); + sha1_hash iphash; + hash_address(a, iphash); + test.set(iphash); + } + } + + // these are test vectors from BEP 33 + // http://www.bittorrent.org/beps/bep_0033.html + fprintf(stderr, "test.size: %f\n", test.size()); + fprintf(stderr, "%s\n", to_hex(test.to_string()).c_str()); + if (supports_ipv6()) + { + TEST_CHECK(fabs(test.size() - 1224.93f) < 0.001); + TEST_CHECK(to_hex(test.to_string()) == "f6c3f5eaa07ffd91bde89f777f26fb2bff37bdb8fb2bbaa2fd3ddde7bacfff75ee7ccbaefe5eedb1fbfaff67f6abff5e43ddbca3fd9b9ffdf4ffd3e9dff12d1bdf59db53dbe9fa5b7ff3b8fdfcde1afb8bedd7be2f3ee71ebbbfe93bcdeefe148246c2bc5dbff7e7efdcf24fd8dc7adffd8fffdfddfff7a4bbeedf5cb95ce81fc7fcff1ff4ffffdfe5f7fdcbb7fd79b3fa1fc77bfe07fff905b7b7ffc7fefeffe0b8370bb0cd3f5b7f2bd93feb4386cfdd6f7fd5bfaf2e9ebffffeecd67adbf7c67f17efd5d75eba6ffeba7fff47a91eb1bfbb53e8abfb5762abe8ff237279bfefbfeef5ffc5febfdfe5adffadfee1fb737ffffbfd9f6aeffeee76b6fd8f72ef"); + } + else + { + TEST_CHECK(fabs(test.size() - 257.854f) < 0.001); + TEST_CHECK(to_hex(test.to_string()) == "24c0004020043000102012743e00480037110820422110008000c0e302854835a05401a4045021302a306c060001881002d8a0a3a8001901b40a800900310008d2108110c2496a0028700010d804188b01415200082004088026411104a804048002002000080680828c400080cc40020c042c0494447280928041402104080d4240040414a41f0205654800b0811830d2020042b002c5800004a71d0204804a0028120a004c10017801490b834004044106005421000c86900a0020500203510060144e900100924a1018141a028012913f0041802250042280481200002004430804210101c08111c10801001080002038008211004266848606b035001048"); + } + + response.clear(); + + // ====== put ====== + + init_rand_address(); + + udp::endpoint eps[1000]; + + for (int i = 0; i < 1000; ++i) + eps[i] = udp::endpoint(rand_v4(), (rand() % 16534) + 1); + + announce_item 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 } + }; + + for (int i = 0; i < int(sizeof(items)/sizeof(items[0])); ++i) + items[i].gen(); + + announce_immutable_items(node, eps, items, sizeof(items)/sizeof(items[0])); + + key_desc_t desc2[] = + { + { "y", bdecode_node::string_t, 1, 0 } + }; + + bdecode_node desc2_keys[1]; + + key_desc_t 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 === + + std::pair itemv; + std::pair empty_salt; + empty_salt.first = NULL; + empty_salt.second = 0; + + char signature[item_sig_len]; + char buffer[1200]; + int seq = 4; + char private_key[item_sk_len]; + char public_key[item_pk_len]; + for (int with_salt = 0; with_salt < 2; ++with_salt) + { + seq = 4; + fprintf(stderr, "\nTEST GET/PUT%s \ngenerating ed25519 keys\n\n" + , with_salt ? " with-salt" : " no-salt"); + unsigned char seed[32]; + ed25519_create_seed(seed); + + ed25519_create_keypair((unsigned char*)public_key, (unsigned char*)private_key, seed); + fprintf(stderr, "pub: %s priv: %s\n" + , to_hex(std::string(public_key, item_pk_len)).c_str() + , to_hex(std::string(private_key, item_sk_len)).c_str()); + + TEST_CHECK(ret); + + std::pair salt((char*)0, 0); + if (with_salt) + salt = std::pair("foobar", 6); + + hasher h(public_key, 32); + if (with_salt) h.update(salt.first, salt.second); + sha1_hash target_id = h.final(); + + fprintf(stderr, "target_id: %s\n" + , to_hex(target_id.to_string()).c_str()); + + send_dht_request(node, "get", source, &response + , msg_args().target((char*)&target_id[0])); + + key_desc_t 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, error_string + , sizeof(error_string)); + if (ret) + { + TEST_EQUAL(desc_keys[4].string_value(), "r"); + token = desc_keys[2].string_value(); + fprintf(stderr, "get response: %s\n" + , print_entry(response).c_str()); + fprintf(stderr, "got token: %s\n", to_hex(token).c_str()); + } + else + { + fprintf(stderr, "msg: %s\n", print_entry(response).c_str()); + fprintf(stderr, " invalid get response: %s\n%s\n" + , error_string, print_entry(response).c_str()); + TEST_ERROR(error_string); + } + + itemv = std::pair(buffer, bencode(buffer, items[0].ent)); + sign_mutable_item(itemv, salt, seq, public_key, private_key, signature); + TEST_EQUAL(verify_mutable_item(itemv, salt, seq, public_key, signature), true); +#ifdef TORRENT_USE_VALGRIND + VALGRIND_CHECK_MEM_IS_DEFINED(signature, item_sig_len); +#endif + + send_dht_request(node, "put", source, &response + , msg_args() + .token(token) + .value(items[0].ent) + .key(std::string(public_key, item_pk_len)) + .sig(std::string(signature, item_sig_len)) + .seq(seq) + .salt(salt.first)); + + ret = verify_message(response, desc2, desc2_keys, error_string + , sizeof(error_string)); + if (ret) + { + fprintf(stderr, "put response: %s\n" + , print_entry(response).c_str()); + TEST_EQUAL(desc2_keys[0].string_value(), "r"); + } + else + { + fprintf(stderr, " invalid put response: %s\n%s\n" + , error_string, print_entry(response).c_str()); + TEST_ERROR(error_string); + } + + send_dht_request(node, "get", source, &response + , msg_args().target((char*)&target_id[0])); + + fprintf(stderr, "target_id: %s\n" + , to_hex(target_id.to_string()).c_str()); + + key_desc_t 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, error_string + , sizeof(error_string)); + if (ret == 0) + { + fprintf(stderr, "msg: %s\n", print_entry(response).c_str()); + fprintf(stderr, " invalid get response: %s\n%s\n" + , error_string, print_entry(response).c_str()); + TEST_ERROR(error_string); + } + else + { + fprintf(stderr, "get response: %s\n" + , print_entry(response).c_str()); + char value[1020]; + char* ptr = value; + int value_len = bencode(ptr, items[0].ent); + TEST_EQUAL(value_len, desc3_keys[2].data_section().second); + TEST_CHECK(memcmp(desc3_keys[2].data_section().first, value, value_len) == 0); + + TEST_EQUAL(seq, desc3_keys[3].int_value()); + } + + // also test that invalid signatures fail! + + itemv.second = bencode(buffer, items[0].ent); + sign_mutable_item(itemv, salt, seq, public_key, private_key, signature); + TEST_EQUAL(verify_mutable_item(itemv, salt, seq, public_key, signature), 1); +#ifdef TORRENT_USE_VALGRIND + VALGRIND_CHECK_MEM_IS_DEFINED(signature, item_sig_len); +#endif + // break the signature + signature[2] ^= 0xaa; + + fprintf(stderr, "PUT broken signature\n"); + + TEST_CHECK(verify_mutable_item(itemv, salt, seq, public_key, signature) != 1); + + send_dht_request(node, "put", source, &response + , msg_args() + .token(token) + .value(items[0].ent) + .key(std::string(public_key, item_pk_len)) + .sig(std::string(signature, item_sig_len)) + .seq(seq) + .salt(salt.first)); + + ret = verify_message(response, desc_error, desc_error_keys, error_string + , sizeof(error_string)); + if (ret) + { + fprintf(stderr, "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 + { + fprintf(stderr, " invalid put response: %s\n%s\n" + , error_string, print_entry(response).c_str()); + TEST_ERROR(error_string); + } + + // === test conditional get === + + send_dht_request(node, "get", source, &response + , msg_args().target((char*)&target_id[0]).seq(seq - 1)); + + { + 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")); + } + + send_dht_request(node, "get", source, &response + , msg_args().target((char*)&target_id[0]).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 + boost::uint64_t cas = seq; + + // increment sequence number + ++seq; + // put item 1 + itemv.second = bencode(buffer, items[1].ent); + sign_mutable_item(itemv, salt, seq, public_key, private_key, signature); + TEST_EQUAL(verify_mutable_item(itemv, salt, seq, public_key, signature), 1); +#ifdef TORRENT_USE_VALGRIND + VALGRIND_CHECK_MEM_IS_DEFINED(signature, item_sig_len); +#endif + + TEST_CHECK(item_target_id(salt, public_key) == target_id); + + fprintf(stderr, "PUT CAS 1\n"); + + send_dht_request(node, "put", source, &response + , msg_args() + .token(token) + .value(items[1].ent) + .key(std::string(public_key, item_pk_len)) + .sig(std::string(signature, item_sig_len)) + .seq(seq) + .cas(cas) + .salt(salt.first)); + + ret = verify_message(response, desc2, desc2_keys, error_string + , sizeof(error_string)); + if (ret) + { + fprintf(stderr, "put response: %s\n" + , print_entry(response).c_str()); + TEST_EQUAL(desc2_keys[0].string_value(), "r"); + } + else + { + fprintf(stderr, " invalid put response: %s\n%s\n" + , error_string, print_entry(response).c_str()); + TEST_ERROR(error_string); + } + + fprintf(stderr, "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(node, "put", source, &response + , msg_args() + .token(token) + .value(items[1].ent) + .key(std::string(public_key, item_pk_len)) + .sig(std::string(signature, item_sig_len)) + .seq(seq) + .cas(cas) + .salt(salt.first)); + + ret = verify_message(response, desc_error, desc_error_keys, error_string + , sizeof(error_string)); + if (ret) + { + fprintf(stderr, "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 + { + fprintf(stderr, " invalid put response: %s\n%s\nExpected failure 301 (CAS hash mismatch)\n" + , error_string, print_entry(response).c_str()); + TEST_ERROR(error_string); + } + + } + + // test node-id functions + using namespace libtorrent::dht; + + TEST_EQUAL(generate_prefix_mask(0), to_hash("0000000000000000000000000000000000000000")); + TEST_EQUAL(generate_prefix_mask(1), to_hash("8000000000000000000000000000000000000000")); + TEST_EQUAL(generate_prefix_mask(2), to_hash("c000000000000000000000000000000000000000")); + TEST_EQUAL(generate_prefix_mask(11), to_hash("ffe0000000000000000000000000000000000000")); + TEST_EQUAL(generate_prefix_mask(17), to_hash("ffff800000000000000000000000000000000000")); + TEST_EQUAL(generate_prefix_mask(160), to_hash("ffffffffffffffffffffffffffffffffffffffff")); + + // test kademlia functions + + // distance_exp + + TEST_EQUAL(distance_exp( + to_hash("ffffffffffffffffffffffffffffffffffffffff"), + to_hash("0000000000000000000000000000000000000000")) + , 159); + + TEST_EQUAL(distance_exp( + to_hash("ffffffffffffffffffffffffffffffffffffffff"), + to_hash("7fffffffffffffffffffffffffffffffffffffff")) + , 159); + + TEST_EQUAL(distance_exp( + to_hash("ffffffffffffffffffffffffffffffffffffffff"), + to_hash("ffffffffffffffffffffffffffffffffffffffff")) + , 0); + + TEST_EQUAL(distance_exp( + to_hash("ffffffffffffffffffffffffffffffffffffffff"), + to_hash("fffffffffffffffffffffffffffffffffffffffe")) + , 0); + + TEST_EQUAL(distance_exp( + to_hash("8000000000000000000000000000000000000000"), + to_hash("fffffffffffffffffffffffffffffffffffffffe")) + , 158); + + TEST_EQUAL(distance_exp( + to_hash("c000000000000000000000000000000000000000"), + to_hash("fffffffffffffffffffffffffffffffffffffffe")) + , 157); + + TEST_EQUAL(distance_exp( + to_hash("e000000000000000000000000000000000000000"), + to_hash("fffffffffffffffffffffffffffffffffffffffe")) + , 156); + + TEST_EQUAL(distance_exp( + to_hash("f000000000000000000000000000000000000000"), + to_hash("fffffffffffffffffffffffffffffffffffffffe")) + , 155); + + TEST_EQUAL(distance_exp( + to_hash("f8f2340985723049587230495872304958703294"), + to_hash("f743589043r890f023980f90e203980d090c3840")) + , 155); + + TEST_EQUAL(distance_exp( + to_hash("ffff740985723049587230495872304958703294"), + to_hash("ffff889043r890f023980f90e203980d090c3840")) + , 159 - 16); + + { + // test kademlia routing table + dht_settings s; + s.extended_routing_table = false; + // s.restrict_routing_ips = false; + node_id id = to_hash("3123456789abcdef01232456789abcdef0123456"); + const int bucket_size = 10; + dht::routing_table table(id, bucket_size, s, &observer); + std::vector nodes; + TEST_EQUAL(table.size().get<0>(), 0); + + node_id tmp = id; + node_id diff = to_hash("15764f7459456a9453f8719b09547c11d5f34061"); + + // test a node with the same IP:port changing ID + add_and_replace(tmp, diff); + table.node_seen(tmp, udp::endpoint(address::from_string("4.4.4.4"), 4), 10); + table.find_node(id, nodes, 0, 10); + TEST_EQUAL(table.bucket_size(0), 1); + TEST_EQUAL(table.size().get<0>(), 1); + TEST_EQUAL(nodes.size(), 1); + if (!nodes.empty()) + { + TEST_EQUAL(nodes[0].id, tmp); + TEST_EQUAL(nodes[0].addr(), address_v4::from_string("4.4.4.4")); + TEST_EQUAL(nodes[0].port(), 4); + TEST_EQUAL(nodes[0].timeout_count, 0); + } + + // set timeout_count to 1 + table.node_failed(tmp, udp::endpoint(address_v4::from_string("4.4.4.4"), 4)); + + nodes.clear(); + table.for_each_node(node_push_back, nop, &nodes); + TEST_EQUAL(nodes.size(), 1); + if (!nodes.empty()) + { + TEST_EQUAL(nodes[0].id, tmp); + TEST_EQUAL(nodes[0].addr(), address_v4::from_string("4.4.4.4")); + 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(address::from_string("4.4.4.4"), 4), 10); + nodes.clear(); + table.for_each_node(node_push_back, nop, &nodes); + TEST_EQUAL(nodes.size(), 1); + if (!nodes.empty()) + { + TEST_EQUAL(nodes[0].id, tmp); + TEST_EQUAL(nodes[0].addr(), address_v4::from_string("4.4.4.4")); + TEST_EQUAL(nodes[0].port(), 4); + TEST_EQUAL(nodes[0].timeout_count, 0); + } + + // test adding the same IP:port again with a new node ID (should replace the old one) + add_and_replace(tmp, diff); + table.node_seen(tmp, udp::endpoint(address::from_string("4.4.4.4"), 4), 10); + table.find_node(id, nodes, 0, 10); + TEST_EQUAL(table.bucket_size(0), 1); + TEST_EQUAL(nodes.size(), 1); + if (!nodes.empty()) + { + TEST_EQUAL(nodes[0].id, tmp); + TEST_EQUAL(nodes[0].addr(), address_v4::from_string("4.4.4.4")); + TEST_EQUAL(nodes[0].port(), 4); + } + + // test adding the same node ID again with a different IP (should be ignored) + table.node_seen(tmp, udp::endpoint(address::from_string("4.4.4.4"), 5), 10); + table.find_node(id, nodes, 0, 10); + TEST_EQUAL(table.bucket_size(0), 1); + if (!nodes.empty()) + { + TEST_EQUAL(nodes[0].id, tmp); + TEST_EQUAL(nodes[0].addr(), address_v4::from_string("4.4.4.4")); + 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(address::from_string("4.4.4.5"), 5), 10); + table.find_node(id, nodes, 0, 10); + TEST_EQUAL(table.bucket_size(0), 1); + if (!nodes.empty()) + { + TEST_EQUAL(nodes[0].id, tmp); + TEST_EQUAL(nodes[0].addr(), address_v4::from_string("4.4.4.4")); + TEST_EQUAL(nodes[0].port(), 4); + } + + s.restrict_routing_ips = false; + + init_rand_address(); + + add_and_replace(tmp, diff); + table.node_seen(id, udp::endpoint(rand_v4(), rand()), 10); + + nodes.clear(); + for (int i = 0; i < 7000; ++i) + { + table.node_seen(tmp, udp::endpoint(rand_v4(), rand()), 20 + (tmp[19] & 0xff)); + add_and_replace(tmp, diff); + } + printf("active buckets: %d\n", table.num_active_buckets()); + TEST_EQUAL(table.num_active_buckets(), 10); + TEST_CHECK(table.size().get<0>() >= 10 * 10); + //#error test num_global_nodes + //#error test need_refresh + +#if defined TORRENT_DEBUG + table.print_state(std::cerr); +#endif + + table.for_each_node(node_push_back, nop, &nodes); + + printf("nodes: %d\n", int(nodes.size())); + + std::vector temp; + + std::generate(tmp.begin(), tmp.end(), random_byte); + table.find_node(tmp, temp, 0, nodes.size() * 2); + 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; + +#ifdef TORRENT_USE_VALGRIND + const int reps = 3; +#else + const int reps = 50; +#endif + + for (int r = 0; r < reps; ++r) + { + std::generate(tmp.begin(), tmp.end(), random_byte); + table.find_node(tmp, temp, 0, bucket_size * 2); + printf("returned: %d\n", int(temp.size())); + TEST_EQUAL(int(temp.size()), (std::min)(bucket_size * 2, int(nodes.size()))); + + std::sort(nodes.begin(), nodes.end(), boost::bind(&compare_ref + , boost::bind(&node_entry::id, _1) + , boost::bind(&node_entry::id, _2), tmp)); + + int expected = std::accumulate(nodes.begin(), nodes.begin() + (bucket_size * 2) + , 0, boost::bind(&sum_distance_exp, _1, _2, tmp)); + int sum_hits = std::accumulate(temp.begin(), temp.end() + , 0, boost::bind(&sum_distance_exp, _1, _2, tmp)); + TEST_EQUAL(bucket_size * 2, int(temp.size())); + printf("expected: %d actual: %d\n", expected, sum_hits); + TEST_EQUAL(expected, sum_hits); + + duplicates.clear(); + // This makes sure enough of the nodes returned are actually + // part of the closest nodes + for (std::vector::iterator i = temp.begin() + , end(temp.end()); i != end; ++i) + { + TEST_CHECK(duplicates.count(i->id) == 0); + duplicates.insert(i->id); + } + } + + using namespace libtorrent::dht; + + 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 }; + + boost::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 a = address_v4::from_string(ips[i]); + node_id id = generate_id_impl(a, rs[i]); + TEST_CHECK(id[0] == prefixes[i][0]); + TEST_CHECK(id[1] == prefixes[i][1]); + TEST_CHECK((id[2] & 0xf8) == (prefixes[i][2] & 0xf8)); + + TEST_CHECK(id[19] == rs[i]); + fprintf(stderr, "IP address: %s r: %d node ID: %s\n", ips[i] + , rs[i], to_hex(id.to_string()).c_str()); + } + } + + // test traversal algorithms + + dht::key_desc_t 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]; + + dht::key_desc_t 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]; + + dht::key_desc_t 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}, + }; + + bdecode_node get_item_keys[6]; + + + // bootstrap + + g_sent_packets.clear(); + do + { + dht::node node(&s, sett, (node_id::min)(), &observer, cnt); + + udp::endpoint initial_node(address_v4::from_string("4.4.4.4"), 1234); + std::vector nodesv; + nodesv.push_back(initial_node); + node.bootstrap(nodesv, boost::bind(&nop)); + + TEST_EQUAL(g_sent_packets.size(), 1); + if (g_sent_packets.empty()) break; + TEST_EQUAL(g_sent_packets.front().first, initial_node); + + lazy_from_entry(g_sent_packets.front().second, response); + ret = verify_message(response, find_node_desc, find_node_keys, error_string + , sizeof(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")) break; + } + else + { + fprintf(stderr, " invalid find_node request: %s\n", print_entry(response).c_str()); + TEST_ERROR(error_string); + break; + } + + udp::endpoint found_node(address_v4::from_string("5.5.5.5"), 2235); + nodes_t nodes; + nodes.push_back(found_node); + g_sent_packets.clear(); + send_dht_response(node, response, initial_node, msg_args().nodes(nodes)); + + TEST_EQUAL(g_sent_packets.size(), 1); + if (g_sent_packets.empty()) break; + TEST_EQUAL(g_sent_packets.front().first, found_node); + + lazy_from_entry(g_sent_packets.front().second, response); + ret = verify_message(response, find_node_desc, find_node_keys, error_string + , sizeof(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")) break; + } + else + { + fprintf(stderr, " invalid find_node request: %s\n", print_entry(response).c_str()); + TEST_ERROR(error_string); + break; + } + + g_sent_packets.clear(); + send_dht_response(node, response, found_node); + + TEST_CHECK(g_sent_packets.empty()); + TEST_EQUAL(node.num_global_nodes(), 3); + } while (false); + + // get_peers + + g_sent_packets.clear(); + do + { + dht::node_id target = to_hash("1234876923549721020394873245098347598635"); + dht::node node(&s, sett, (node_id::min)(), &observer, cnt); + + udp::endpoint initial_node(address_v4::from_string("4.4.4.4"), 1234); + node.m_table.add_node(initial_node); + + node.announce(target, 1234, false, get_peers_cb); + + TEST_EQUAL(g_sent_packets.size(), 1); + if (g_sent_packets.empty()) break; + TEST_EQUAL(g_sent_packets.front().first, initial_node); + + lazy_from_entry(g_sent_packets.front().second, response); + ret = verify_message(response, get_peers_desc, get_peers_keys, error_string + , sizeof(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") break; + } + else + { + fprintf(stderr, " invalid get_peers request: %s\n", print_entry(response).c_str()); + TEST_ERROR(error_string); + break; + } + + std::set peers[2]; + peers[0].insert(tcp::endpoint(address_v4::from_string("4.1.1.1"), 4111)); + peers[0].insert(tcp::endpoint(address_v4::from_string("4.1.1.2"), 4112)); + peers[0].insert(tcp::endpoint(address_v4::from_string("4.1.1.3"), 4113)); + + udp::endpoint next_node(address_v4::from_string("5.5.5.5"), 2235); + nodes_t nodes; + nodes.push_back(next_node); + + g_sent_packets.clear(); + send_dht_response(node, response, initial_node + , msg_args().nodes(nodes).token("10").port(1234).peers(peers[0])); + + TEST_EQUAL(g_sent_packets.size(), 1); + if (g_sent_packets.empty()) break; + TEST_EQUAL(g_sent_packets.front().first, next_node); + + lazy_from_entry(g_sent_packets.front().second, response); + ret = verify_message(response, get_peers_desc, get_peers_keys, error_string + , sizeof(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") break; + } + else + { + fprintf(stderr, " invalid get_peers request: %s\n", print_entry(response).c_str()); + TEST_ERROR(error_string); + break; + } + + peers[1].insert(tcp::endpoint(address_v4::from_string("4.1.1.4"), 4114)); + peers[1].insert(tcp::endpoint(address_v4::from_string("4.1.1.5"), 4115)); + peers[1].insert(tcp::endpoint(address_v4::from_string("4.1.1.6"), 4116)); + + g_sent_packets.clear(); + send_dht_response(node, response, next_node + , msg_args().token("11").port(1234).peers(peers[1])); + + for (std::list >::iterator i = g_sent_packets.begin() + , end(g_sent_packets.end()); i != end; ++i) + { +// fprintf(stderr, " %s:%d: %s\n", i->first.address().to_string(ec).c_str() +// , i->first.port(), i->second.to_string().c_str()); + TEST_EQUAL(i->second["q"].string(), "announce_peer"); + } + + g_sent_packets.clear(); + + for (int i = 0; i < 2; ++i) + { + for (std::set::iterator peer = peers[i].begin(); peer != peers[i].end(); ++peer) + { + TEST_CHECK(std::find(g_got_peers.begin(), g_got_peers.end(), *peer) != g_got_peers.end()); + } + } + g_got_peers.clear(); + } while (false); + + // immutable get + + g_sent_packets.clear(); + do + { + dht::node node(&s, sett, (node_id::min)(), &observer, cnt); + + udp::endpoint initial_node(address_v4::from_string("4.4.4.4"), 1234); + node.m_table.add_node(initial_node); + + node.get_item(items[0].target, get_immutable_item_cb); + + TEST_EQUAL(g_sent_packets.size(), 1); + if (g_sent_packets.empty()) break; + TEST_EQUAL(g_sent_packets.front().first, initial_node); + + lazy_from_entry(g_sent_packets.front().second, response); + ret = verify_message(response, get_item_desc, get_item_keys, error_string + , sizeof(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") break; + } + else + { + fprintf(stderr, " invalid get request: %s\n", print_entry(response).c_str()); + TEST_ERROR(error_string); + break; + } + + g_sent_packets.clear(); + send_dht_response(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()) break; + + TEST_EQUAL(g_got_items.front().value(), items[0].ent); + g_got_items.clear(); + + } while (false); + + // mutable get + + g_sent_packets.clear(); + do + { + dht::node node(&s, sett, (node_id::min)(), &observer, cnt); + + udp::endpoint initial_node(address_v4::from_string("4.4.4.4"), 1234); + node.m_table.add_node(initial_node); + + node.get_item(public_key, std::string(), get_mutable_item_cb); + + TEST_EQUAL(g_sent_packets.size(), 1); + if (g_sent_packets.empty()) break; + TEST_EQUAL(g_sent_packets.front().first, initial_node); + + lazy_from_entry(g_sent_packets.front().second, response); + ret = verify_message(response, get_item_desc, get_item_keys, error_string + , sizeof(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") break; + } + else + { + fprintf(stderr, " invalid get request: %s\n", print_entry(response).c_str()); + TEST_ERROR(error_string); + break; + } + + g_sent_packets.clear(); + + itemv.second = bencode(buffer, items[0].ent); + sign_mutable_item(itemv, empty_salt, seq, public_key, private_key, signature); + send_dht_response(node, response, initial_node + , msg_args() + .token("10") + .port(1234) + .value(items[0].ent) + .key(std::string(public_key, item_pk_len)) + .sig(std::string(signature, item_sig_len)) + .seq(seq)); + + TEST_CHECK(g_sent_packets.empty()); + TEST_EQUAL(g_got_items.size(), 1); + if (g_got_items.empty()) break; + + TEST_EQUAL(g_got_items.front().value(), items[0].ent); + TEST_CHECK(memcmp(g_got_items.front().pk().data(), public_key, item_pk_len) == 0); + TEST_CHECK(memcmp(g_got_items.front().sig().data(), signature, item_sig_len) == 0); + TEST_EQUAL(int(g_got_items.front().seq()), seq); + g_got_items.clear(); + + } while (false); + + dht::key_desc_t 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]; + + dht::key_desc_t 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, item_pk_len, 0}, + {"seq", bdecode_node::int_t, 0, 0}, + {"sig", bdecode_node::string_t, item_sig_len, 0}, + {"token", bdecode_node::string_t, 2, 0}, + {"v", bdecode_node::none_t, 0, key_desc_t::last_child}, + }; + + bdecode_node put_mutable_item_keys[11]; + + // immutable put + g_sent_packets.clear(); + for (int loop = 0; loop < 9; loop++) + { + // set the branching factor to k to make this a little easier + int old_branching = sett.search_branching; + sett.search_branching = 8; + dht::node node(&s, sett, (node_id::min)(), &observer, cnt); + enum { num_test_nodes = 8 }; + node_entry nodes[num_test_nodes] = + { node_entry(items[0].target, udp::endpoint(address_v4::from_string("1.1.1.1"), 1231)) + , node_entry(items[1].target, udp::endpoint(address_v4::from_string("2.2.2.2"), 1232)) + , node_entry(items[2].target, udp::endpoint(address_v4::from_string("3.3.3.3"), 1233)) + , node_entry(items[3].target, udp::endpoint(address_v4::from_string("4.4.4.4"), 1234)) + , node_entry(items[4].target, udp::endpoint(address_v4::from_string("5.5.5.5"), 1235)) + , node_entry(items[5].target, udp::endpoint(address_v4::from_string("6.6.6.6"), 1236)) + , node_entry(items[6].target, udp::endpoint(address_v4::from_string("7.7.7.7"), 1237)) + , node_entry(items[7].target, udp::endpoint(address_v4::from_string("8.8.8.8"), 1238)) }; + + for (int i = 0; i < num_test_nodes; ++i) + node.m_table.add_node(nodes[i]); + + 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( + std::pair(flat_data.c_str(), flat_data.size())); + + node.put_item(target, put_data, boost::bind(&put_immutable_item_cb, _1, loop)); + + TEST_EQUAL(g_sent_packets.size(), 8); + if (g_sent_packets.size() != 8) break; + + for (int i = 0; i < 8; ++i) + { + std::list >::iterator packet = find_packet(nodes[i].ep()); + TEST_CHECK(packet != g_sent_packets.end()); + if (packet == g_sent_packets.end()) continue; + + lazy_from_entry(packet->second, response); + ret = verify_message(response, get_item_desc, get_item_keys, error_string + , sizeof(error_string)); + if (!ret) + { + fprintf(stderr, " invalid get request: %s\n", print_entry(response).c_str()); + TEST_ERROR(error_string); + continue; + } + char t[10]; + snprintf(t, sizeof(t), "%02d", i); + + msg_args args; + args.token(t).port(1234).nid(nodes[i].id).nodes(nodes_t(1, nodes[i])); + send_dht_response(node, response, nodes[i].ep(), args); + g_sent_packets.erase(packet); + } + + TEST_EQUAL(g_sent_packets.size(), 8); + if (g_sent_packets.size() != 8) break; + + itemv.second = bencode(buffer, put_data); + + for (int i = 0; i < 8; ++i) + { + std::list >::iterator packet = find_packet(nodes[i].ep()); + TEST_CHECK(packet != g_sent_packets.end()); + if (packet == g_sent_packets.end()) continue; + + lazy_from_entry(packet->second, response); + ret = verify_message(response, put_immutable_item_desc, put_immutable_item_keys + , error_string, sizeof(error_string)); + if (ret) + { + TEST_EQUAL(put_immutable_item_keys[0].string_value(), "q"); + TEST_EQUAL(put_immutable_item_keys[2].string_value(), "put"); + std::pairv = put_immutable_item_keys[6].data_section(); + TEST_EQUAL(std::string(v.first, v.second), flat_data); + char t[10]; + snprintf(t, sizeof(t), "%02d", i); + TEST_EQUAL(put_immutable_item_keys[5].string_value(), t); + if (put_immutable_item_keys[0].string_value() != "q" + || put_immutable_item_keys[2].string_value() != "put") continue; + + if (i < loop) send_dht_response(node, response, nodes[i].ep()); + } + else + { + fprintf(stderr, " invalid immutable put request: %s\n", print_entry(response).c_str()); + TEST_ERROR(error_string); + continue; + } + } + sett.search_branching = old_branching; + g_sent_packets.clear(); + g_put_item.clear(); + g_put_count = 0; + + }; + + // mutable put + g_sent_packets.clear(); + for (int loop = 0; loop < 9; loop++) + { + // set the branching factor to k to make this a little easier + int old_branching = sett.search_branching; + sett.search_branching = 8; + dht::node node(&s, sett, (node_id::min)(), &observer, cnt); + enum { num_test_nodes = 8 }; + node_entry nodes[num_test_nodes] = + { node_entry(items[0].target, udp::endpoint(address_v4::from_string("1.1.1.1"), 1231)) + , node_entry(items[1].target, udp::endpoint(address_v4::from_string("2.2.2.2"), 1232)) + , node_entry(items[2].target, udp::endpoint(address_v4::from_string("3.3.3.3"), 1233)) + , node_entry(items[3].target, udp::endpoint(address_v4::from_string("4.4.4.4"), 1234)) + , node_entry(items[4].target, udp::endpoint(address_v4::from_string("5.5.5.5"), 1235)) + , node_entry(items[5].target, udp::endpoint(address_v4::from_string("6.6.6.6"), 1236)) + , node_entry(items[6].target, udp::endpoint(address_v4::from_string("7.7.7.7"), 1237)) + , node_entry(items[7].target, udp::endpoint(address_v4::from_string("8.8.8.8"), 1238)) }; + + for (int i = 0; i < num_test_nodes; ++i) + node.m_table.add_node(nodes[i]); + + g_put_item.assign(items[0].ent, empty_salt, seq, public_key, private_key); + std::string sig(g_put_item.sig().data(), item_sig_len); + node.put_item(public_key, std::string() + , boost::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; + + for (int i = 0; i < 8; ++i) + { + std::list >::iterator packet = find_packet(nodes[i].ep()); + TEST_CHECK(packet != g_sent_packets.end()); + if (packet == g_sent_packets.end()) continue; + + lazy_from_entry(packet->second, response); + ret = verify_message(response, get_item_desc, get_item_keys, error_string + , sizeof(error_string)); + if (!ret) + { + fprintf(stderr, " invalid get request: %s\n", print_entry(response).c_str()); + TEST_ERROR(error_string); + continue; + } + char t[10]; + snprintf(t, sizeof(t), "%02d", i); + + msg_args args; + args.token(t).port(1234).nid(nodes[i].id).nodes(nodes_t(1, nodes[i])); + + send_dht_response(node, response, nodes[i].ep(), args); + g_sent_packets.erase(packet); + } + + TEST_EQUAL(g_sent_packets.size(), 8); + if (g_sent_packets.size() != 8) break; + + itemv.second = bencode(buffer, items[0].ent); + + for (int i = 0; i < 8; ++i) + { + std::list >::iterator packet = find_packet(nodes[i].ep()); + TEST_CHECK(packet != g_sent_packets.end()); + if (packet == g_sent_packets.end()) continue; + + lazy_from_entry(packet->second, response); + ret = verify_message(response, put_mutable_item_desc, put_mutable_item_keys + , error_string, sizeof(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(public_key, item_pk_len)); + TEST_EQUAL(put_mutable_item_keys[7].int_value(), seq); + TEST_EQUAL(put_mutable_item_keys[8].string_value(), sig); + std::pair v = put_mutable_item_keys[10].data_section(); + TEST_EQUAL(v.second, itemv.second); + TEST_CHECK(memcmp(v.first, itemv.first, itemv.second) == 0); + char t[10]; + snprintf(t, sizeof(t), "%02d", i); + TEST_EQUAL(put_mutable_item_keys[9].string_value(), t); + if (put_mutable_item_keys[0].string_value() != "q" + || put_mutable_item_keys[2].string_value() != "put") continue; + + if (i < loop) send_dht_response(node, response, nodes[i].ep()); + } + else + { + fprintf(stderr, " invalid put request: %s\n", print_entry(response).c_str()); + TEST_ERROR(error_string); + continue; + } + } + sett.search_branching = old_branching; + g_sent_packets.clear(); + g_put_item.clear(); + g_put_count = 0; + } + + // verify that done() is only invoked once + // See PR 252 + g_sent_packets.clear(); + do + { + // set the branching factor to k to make this a little easier + int old_branching = sett.search_branching; + sett.search_branching = 8; + dht::node node(&s, sett, (node_id::min)(), &observer, cnt); + sha1_hash target = hasher(public_key, item_pk_len).final(); + enum { num_test_nodes = 9 }; // we need K + 1 nodes to create the failing sequence + node_entry nodes[num_test_nodes] = + { node_entry(target, udp::endpoint(address_v4::from_string("1.1.1.1"), 1231)) + , node_entry(target, udp::endpoint(address_v4::from_string("2.2.2.2"), 1232)) + , node_entry(target, udp::endpoint(address_v4::from_string("3.3.3.3"), 1233)) + , node_entry(target, udp::endpoint(address_v4::from_string("4.4.4.4"), 1234)) + , node_entry(target, udp::endpoint(address_v4::from_string("5.5.5.5"), 1235)) + , node_entry(target, udp::endpoint(address_v4::from_string("6.6.6.6"), 1236)) + , node_entry(target, udp::endpoint(address_v4::from_string("7.7.7.7"), 1237)) + , node_entry(target, udp::endpoint(address_v4::from_string("8.8.8.8"), 1238)) + , node_entry(target, udp::endpoint(address_v4::from_string("9.9.9.9"), 1239)) }; + + // invert the ith most significant byte so that the test nodes are + // progressivly closer to the target item + for (int i = 0; i < num_test_nodes; ++i) + nodes[i].id[i] = ~nodes[i].id[i]; + + // add the first k nodes to the subject's routing table + for (int i = 0; i < 8; ++i) + node.m_table.add_node(nodes[i]); + + // kick off a mutable put request + g_put_item.assign(items[0].ent, empty_salt, seq, public_key, private_key); + node.put_item(public_key, std::string() + , boost::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) break; + + // 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; + + std::list >::iterator packet = find_packet(nodes[i].ep()); + TEST_CHECK(packet != g_sent_packets.end()); + if (packet == g_sent_packets.end()) continue; + + lazy_from_entry(packet->second, response); + ret = verify_message(response, get_item_desc, get_item_keys, error_string + , sizeof(error_string)); + if (!ret) + { + fprintf(stderr, " invalid get request: %s\n", print_entry(response).c_str()); + TEST_ERROR(error_string); + continue; + } + char t[10]; + snprintf(t, sizeof(t), "%02d", i); + + msg_args args; + args.token(t).port(1234).nid(nodes[i].id); + + // add the address of the closest node to the first response + if (i == 1) + args.nodes(nodes_t(1, nodes[8])); + + send_dht_response(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; + sett.search_branching = old_branching; + } while (false); +} + +void get_test_keypair(char* public_key, char* private_key) +{ + from_hex("77ff84905a91936367c01360803104f92432fcd904a43511876df5cdf3e7e548", 64, public_key); + from_hex("e06d3183d14159228433ed599221b80bd0a5ce8352e4bdf0262f76786ef1c74d" + "b7e7a9fea2c0eb269d61e3b38e450a22e754941ac78479d6c54e1faf6037881d", 128, private_key); +} + +TORRENT_TEST(signing_test1) +{ + // test vector 1 + + // test content + std::pair test_content("12:Hello World!", 15); + // test salt + std::pair test_salt("foobar", 6); + + char private_key[item_sk_len]; + char public_key[item_pk_len]; + get_test_keypair(public_key, private_key); + std::pair empty_salt; + + char signature[item_sig_len]; + + sign_mutable_item(test_content, empty_salt, 1, public_key + , private_key, signature); + + TEST_EQUAL(to_hex(std::string(signature, 64)) + , "305ac8aeb6c9c151fa120f120ea2cfb923564e11552d06a5d856091e5e853cff" + "1260d3f39e4999684aa92eb73ffd136e6f4f3ecbfda0ce53a1608ecd7ae21f01"); + + sha1_hash target_id = item_target_id(empty_salt, public_key); + TEST_EQUAL(to_hex(target_id.to_string()), "4a533d47ec9c7d95b1ad75f576cffc641853b750"); +} + +TORRENT_TEST(signing_test2) +{ + + char private_key[item_sk_len]; + char public_key[item_pk_len]; + get_test_keypair(public_key, private_key); + + // test content + std::pair test_content("12:Hello World!", 15); + + char signature[item_sig_len]; + // test salt + std::pair test_salt("foobar", 6); + + // test vector 2 (the keypair is the same as test 1) + sign_mutable_item(test_content, test_salt, 1, public_key + , private_key, signature); + + TEST_EQUAL(to_hex(std::string(signature, 64)) + , "6834284b6b24c3204eb2fea824d82f88883a3d95e8b4a21b8c0ded553d17d17d" + "df9a8a7104b1258f30bed3787e6cb896fca78c58f8e03b5f18f14951a87d9a08"); + + sha1_hash target_id = item_target_id(test_salt, public_key); + TEST_EQUAL(to_hex(target_id.to_string()), "411eba73b6f087ca51a3795d9c8c938d365e32c1"); +} + +TORRENT_TEST(signing_test3) +{ + // test vector 3 + + // test content + std::pair test_content("12:Hello World!", 15); + + sha1_hash target_id = item_target_id(test_content); + TEST_EQUAL(to_hex(target_id.to_string()), "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); + fprintf(stderr, "%s\n", print_entry(ent).c_str()); + + bool ret = verify_message(ent, msg_desc, msg_keys, error_string + , sizeof(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); + fprintf(stderr, "%s\n", print_entry(ent).c_str()); + + ret = verify_message(ent, msg_desc, msg_keys, error_string + , sizeof(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); + fprintf(stderr, "%s\n", print_entry(ent).c_str()); + + ret = verify_message(ent, msg_desc, msg_keys, error_string + , sizeof(error_string)); + TEST_CHECK(!ret); + fprintf(stderr, "%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); + fprintf(stderr, "%s\n", print_entry(ent).c_str()); + + ret = verify_message(ent, msg_desc, msg_keys, error_string + , sizeof(error_string)); + TEST_CHECK(!ret); + fprintf(stderr, "%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); + fprintf(stderr, "%s\n", print_entry(ent).c_str()); + + ret = verify_message(ent, msg_desc, msg_keys, error_string + , sizeof(error_string)); + TEST_CHECK(!ret); + fprintf(stderr, "%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); + fprintf(stderr, "%s\n", print_entry(ent).c_str()); + TEST_CHECK(ent.type() == bdecode_node::list_t); + if (ent.type() == bdecode_node::list_t) + { + TEST_CHECK(ent.list_size() == 2); + if (ent.list_size() == 2) + { + TEST_CHECK(ent.list_at(0).dict_find_int_value("") == 1); + TEST_CHECK(ent.list_at(1).string_value() == ""); + } + } +} + +TORRENT_TEST(routing_table_uniform) +{ + // test routing table + dht_settings sett = test_settings(); + obs observer; + + sett.extended_routing_table = false; + node_id id = to_hash("1234876923549721020394873245098347598635"); + node_id diff = to_hash("15764f7459456a9453f8719b09547c11d5f34061"); + + routing_table tbl(id, 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; + } + 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(), 5); + +#if defined TORRENT_DHT_VERBOSE_LOGGING || defined TORRENT_DEBUG + tbl.print_state(std::cerr); +#endif +} + +TORRENT_TEST(routing_table_balance) +{ + dht_settings sett = test_settings(); + obs observer; + + sett.extended_routing_table = false; + node_id id = to_hash("1234876923549721020394873245098347598635"); + + routing_table tbl(id, 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[4] = i; + tbl.node_seen(id, rand_udp_ep(), 20 + (id[19] & 0xff)); + } + printf("num_active_buckets: %d\n", tbl.num_active_buckets()); + TEST_EQUAL(tbl.num_active_buckets(), 2); + +#if defined TORRENT_DEBUG + tbl.print_state(std::cerr); +#endif +} + +TORRENT_TEST(routing_table_extended) +{ + dht_settings sett = test_settings(); + obs observer; + sett.extended_routing_table = true; + 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); + std::random_shuffle(node_id_prefix.begin(), node_id_prefix.end()); + + routing_table tbl(id, 8, sett, &observer); + for (int 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); + +#if defined TORRENT_DEBUG + tbl.print_state(std::cerr); +#endif +} + +void inserter(std::set* nodes, node_entry const& ne) +{ + nodes->insert(nodes->begin(), ne.id); +} + +TORRENT_TEST(routing_table_set_id) +{ + dht_settings sett = test_settings(); + sett.enforce_node_id = false; + sett.extended_routing_table = 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); + std::random_shuffle(node_id_prefix.begin(), node_id_prefix.end()); + routing_table tbl(id, 8, sett, &observer); + for (int 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(boost::bind(&inserter, &original_nodes, _1)); + +#if defined TORRENT_DEBUG + tbl.print_state(std::cerr); +#endif + + id = to_hash("ffffffffffffffffffffffffffffffffffffffff"); + + tbl.update_node_id(id); + + TEST_CHECK(tbl.num_active_buckets() <= 4); + std::set remaining_nodes; + tbl.for_each_node(boost::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()); + +#if defined TORRENT_DEBUG + tbl.print_state(std::cerr); +#endif +} + + +TORRENT_TEST(read_only_node) +{ + dht_settings sett = test_settings(); + sett.read_only = true; + mock_socket s; + obs observer; + counters cnt; + + dht::node node(&s, sett, node_id(0), &observer, cnt); + udp::endpoint source(address::from_string("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("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(node.size().get<0>(), 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(address_v4::from_string("4.4.4.4"), 1234); + node.m_table.add_node(initial_node); + 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 get_item_desc[] = { + {"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}, + }; + + lazy_from_entry(g_sent_packets.front().second, request); + bool ret = verify_message(request, get_item_desc, parsed, error_string + , sizeof(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(node.size().get<0>(), 1); + + // now, disable read_only, try again. + g_sent_packets.clear(); + sett.read_only = false; + + send_dht_request(node, "get", source, &response); + // sender should be added to routing table, there are 2 nodes now. + TEST_EQUAL(node.size().get<0>(), 2); + + g_sent_packets.clear(); + 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. + lazy_from_entry(g_sent_packets.front().second, request); + ret = verify_message(request, get_item_desc, parsed, error_string + , sizeof(error_string)); + + TEST_CHECK(ret); + TEST_CHECK(!parsed[3]); + + lazy_from_entry(g_sent_packets.back().second, request); + ret = verify_message(request, get_item_desc, parsed, error_string + , sizeof(error_string)); + + TEST_CHECK(ret); + TEST_CHECK(!parsed[3]); +} + +TORRENT_TEST(invalid_error_msg) +{ + dht_settings sett = test_settings(); + mock_socket s; + obs observer; + counters cnt; + + dht::node node(&s, sett, node_id(0), &observer, cnt); + udp::endpoint source(address::from_string("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) fprintf(stderr, "bdecode failed: %s\n", ec.message().c_str()); + + dht::msg m(decoded, source); + node.incoming(m); + + bool found = false; + for (int i = 0; i < int(observer.m_log.size()); ++i) + { + if (observer.m_log[i].find("INCOMING ERROR") != std::string::npos + && observer.m_log[i].find("(malformed)") != std::string::npos) + found = true; + + printf("%s\n", observer.m_log[i].c_str()); + } + + TEST_EQUAL(found, true); +} + +TORRENT_TEST(rpc_invalid_error_msg) +{ + dht_settings sett = test_settings(); + mock_socket s; + obs observer; + counters cnt; + + dht::routing_table table(node_id(), 8, sett, &observer); + dht::rpc_manager rpc(node_id(), sett, table, &s, &observer); + dht::node node(&s, sett, node_id(0), &observer, cnt); + + udp::endpoint source(address::from_string("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(); + boost::intrusive_ptr algo(new dht::traversal_algorithm( + node, node_id())); + + observer_ptr o(new (rpc.allocate_observer()) null_observer(algo, source, node_id())); +#if defined TORRENT_DEBUG || defined TORRENT_RELEASE_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) fprintf(stderr, "bdecode failed: %s\n", ec.message().c_str()); + + dht::msg m(decoded, source); + node_id nid; + rpc.incoming(m, &nid); + + bool found = false; + for (int i = 0; i < int(observer.m_log.size()); ++i) + { + if (observer.m_log[i].find("reply with") != std::string::npos + && observer.m_log[i].find("(malformed)") != std::string::npos + && observer.m_log[i].find("error") != std::string::npos) + found = true; + + printf("%s\n", observer.m_log[i].c_str()); + } + + TEST_EQUAL(found, true); +} + +// test bucket distribution +TORRENT_TEST(node_id_bucket_distribution) +{ + 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) + { + printf("%3d ", nodes_per_bucket[i]); + } + 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(dht_verify_node_address) +{ + obs observer; + // initial setup taken from dht test above + dht_settings s; + s.extended_routing_table = false; + node_id id = to_hash("3123456789abcdef01232456789abcdef0123456"); + const int bucket_size = 10; + dht::routing_table table(id, bucket_size, s, &observer); + std::vector nodes; + TEST_EQUAL(table.size().get<0>(), 0); + + node_id tmp = id; + node_id diff = to_hash("15764f7459456a9453f8719b09547c11d5f34061"); + + add_and_replace(tmp, diff); + table.node_seen(tmp, udp::endpoint(address::from_string("4.4.4.4"), 4), 10); + table.find_node(id, nodes, 0, 10); + TEST_EQUAL(table.size().get<0>(), 1); + TEST_EQUAL(nodes.size(), 1); + + // incorrect data, wrong id + table.node_seen(to_hash("0123456789abcdef01232456789abcdef0123456") + , udp::endpoint(address::from_string("4.4.4.4"), 4), 10); + table.find_node(id, nodes, 0, 10); + + TEST_EQUAL(table.size().get<0>(), 1); + TEST_EQUAL(nodes.size(), 1); + + // incorrect data, wrong IP + table.node_seen(tmp + , udp::endpoint(address::from_string("4.4.4.6"), 4), 10); + table.find_node(id, nodes, 0, 10); + + TEST_EQUAL(table.size().get<0>(), 1); + TEST_EQUAL(nodes.size(), 1); +} +#endif + diff --git a/test/test_dht_storage.cpp b/test/test_dht_storage.cpp new file mode 100644 index 0000000..a79f4e7 --- /dev/null +++ b/test/test_dht_storage.cpp @@ -0,0 +1,320 @@ +/* + +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_DISABLE_DHT + +#include "libtorrent/config.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/kademlia/node.hpp" // for verify_message +#include "libtorrent/bencode.hpp" +#include "libtorrent/socket_io.hpp" // for hash_address +#include "libtorrent/broadcast_socket.hpp" // for supports_ipv6 +#include "libtorrent/performance_counters.hpp" // for counters +#include "libtorrent/random.hpp" +#include "libtorrent/ed25519.hpp" + +#include "libtorrent/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 "libtorrent/random.hpp" +#include "libtorrent/ed25519.hpp" +#include + +#include "test.hpp" +#include "setup_transfer.hpp" + +using namespace libtorrent; +using namespace libtorrent::dht; +namespace lt = libtorrent; + +namespace +{ + dht_settings test_settings() { + dht_settings sett; + sett.max_torrents = 2; + sett.max_dht_items = 2; + sett.item_lifetime = seconds(120 * 60).count(); + return sett; + } + + static sha1_hash to_hash(char const *s) { + sha1_hash ret; + from_hex(s, 40, (char *) &ret[0]); + return ret; + } + + bool g_storage_constructor_invoked = false; + + dht_storage_interface* dht_custom_storage_constructor(sha1_hash const& id + , dht_settings const& settings) + { + g_storage_constructor_invoked = true; + return dht_default_storage_constructor(id, settings); + } +} + +const sha1_hash n1 = to_hash("5fbfbff10c5d6a4ec8a88e4c6ab4c28b95eee401"); +const sha1_hash n2 = to_hash("5fbfbff10c5d6a4ec8a88e4c6ab4c28b95eee402"); +const sha1_hash n3 = to_hash("5fbfbff10c5d6a4ec8a88e4c6ab4c28b95eee403"); +const sha1_hash n4 = to_hash("5fbfbff10c5d6a4ec8a88e4c6ab4c28b95eee404"); + +TORRENT_TEST(announce_peer) +{ + dht_settings sett = test_settings(); + boost::scoped_ptr s(dht_default_storage_constructor(node_id(0), sett)); + TEST_CHECK(s.get() != NULL); + + entry peers; + s->get_peers(n1, false, false, peers); + + TEST_CHECK(peers["n"].string().empty()) + TEST_CHECK(peers["values"].list().empty()); + + tcp::endpoint p1 = ep("124.31.75.21", 1); + tcp::endpoint p2 = ep("124.31.75.22", 1); + tcp::endpoint p3 = ep("124.31.75.23", 1); + tcp::endpoint p4 = ep("124.31.75.24", 1); + + s->announce_peer(n1, p1, "torrent_name", false); + s->get_peers(n1, false, false, 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); + bool r = s->get_peers(n1, false, false, peers); + TEST_CHECK(!r); +} + +TORRENT_TEST(put_immutable_item) +{ + dht_settings sett = test_settings(); + boost::scoped_ptr s(dht_default_storage_constructor(node_id(0), sett)); + TEST_CHECK(s.get() != NULL); + + entry item; + bool r = s->get_immutable_item(n4, item); + TEST_CHECK(!r); + + s->put_immutable_item(n4, "123", 3, address::from_string("124.31.75.21")); + r = s->get_immutable_item(n4, item); + TEST_CHECK(r); + + s->put_immutable_item(n1, "123", 3, address::from_string("124.31.75.21")); + s->put_immutable_item(n2, "123", 3, address::from_string("124.31.75.21")); + s->put_immutable_item(n3, "123", 3, address::from_string("124.31.75.21")); + r = s->get_immutable_item(n1, item); + TEST_CHECK(!r); + + r = s->get_mutable_item(n4, 0, false, item); + TEST_CHECK(!r); + + char public_key[item_pk_len]; + char signature[item_sig_len]; + s->put_mutable_item(n4, "123", 3, signature, 1, public_key, "salt", 4, address::from_string("124.31.75.21")); + r = s->get_mutable_item(n4, 0, false, item); + TEST_CHECK(r); +} + +TORRENT_TEST(counters) +{ + dht_settings sett = test_settings(); + boost::scoped_ptr s(dht_default_storage_constructor(node_id(0), sett)); + + TEST_CHECK(s.get() != NULL); + + sha1_hash n1 = to_hash("5fbfbff10c5d6a4ec8a88e4c6ab4c28b95eee401"); + sha1_hash n2 = to_hash("5fbfbff10c5d6a4ec8a88e4c6ab4c28b95eee402"); + sha1_hash n3 = to_hash("5fbfbff10c5d6a4ec8a88e4c6ab4c28b95eee403"); + sha1_hash n4 = to_hash("5fbfbff10c5d6a4ec8a88e4c6ab4c28b95eee404"); + + TEST_EQUAL(s->counters().peers, 0); + TEST_EQUAL(s->counters().torrents, 0); + + tcp::endpoint p1 = ep("124.31.75.21", 1); + tcp::endpoint p2 = ep("124.31.75.22", 1); + tcp::endpoint p3 = ep("124.31.75.23", 1); + tcp::endpoint 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, address::from_string("124.31.75.21")); + TEST_EQUAL(s->counters().immutable_data, 1); + + s->put_immutable_item(n1, "123", 3, address::from_string("124.31.75.21")); + s->put_immutable_item(n2, "123", 3, address::from_string("124.31.75.21")); + s->put_immutable_item(n3, "123", 3, address::from_string("124.31.75.21")); + TEST_EQUAL(s->counters().immutable_data, 2); + + char public_key[item_pk_len]; + char signature[item_sig_len]; + s->put_mutable_item(n4, "123", 3, signature, 1, public_key, "salt", 4, address::from_string("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); + bool r = ses.is_dht_running(); + TEST_CHECK(!r); + + 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 + r = ses.is_dht_running(); + TEST_CHECK(r); + 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); + + bool r = ses.is_dht_running(); + TEST_CHECK(r); + + ses.set_dht_storage(dht_custom_storage_constructor); + + p.set_bool(settings_pack::enable_dht, false); + ses.apply_settings(p); // async with dispatch + r = ses.is_dht_running(); + TEST_CHECK(!r); + 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 + r = ses.is_dht_running(); + TEST_CHECK(r); + TEST_EQUAL(g_storage_constructor_invoked, true); +} + +TORRENT_TEST(peer_limit) +{ + dht_settings sett = test_settings(); + sett.max_peers = 42; + boost::scoped_ptr s(dht_default_storage_constructor(node_id(0), sett)); + TEST_CHECK(s.get() != NULL); + + for (int i = 0; i < 200; ++i) + { + s->announce_peer(n1, tcp::endpoint(rand_v4(), lt::random()) + , "torrent_name", false); + dht_storage_counters cnt = s->counters(); + TEST_CHECK(cnt.peers <= 42); + } + dht_storage_counters cnt = s->counters(); + TEST_EQUAL(cnt.peers, 42); +} + +TORRENT_TEST(torrent_limit) +{ + dht_settings sett = test_settings(); + sett.max_torrents = 42; + boost::scoped_ptr s(dht_default_storage_constructor(node_id(0), sett)); + TEST_CHECK(s.get() != NULL); + + for (int i = 0; i < 200; ++i) + { + s->announce_peer(rand_hash(), tcp::endpoint(rand_v4(), lt::random()) + , "", false); + dht_storage_counters cnt = s->counters(); + TEST_CHECK(cnt.torrents <= 42); + } + dht_storage_counters cnt = s->counters(); + TEST_EQUAL(cnt.torrents, 42); +} + +TORRENT_TEST(immutable_item_limit) +{ + dht_settings sett = test_settings(); + sett.max_dht_items = 42; + boost::scoped_ptr s(dht_default_storage_constructor(node_id(0), sett)); + TEST_CHECK(s.get() != NULL); + + for (int i = 0; i < 200; ++i) + { + s->put_immutable_item(rand_hash(), "123", 3, rand_v4()); + dht_storage_counters cnt = s->counters(); + TEST_CHECK(cnt.immutable_data <= 42); + } + dht_storage_counters cnt = s->counters(); + TEST_EQUAL(cnt.immutable_data, 42); +} + +TORRENT_TEST(mutable_item_limit) +{ + dht_settings sett = test_settings(); + sett.max_dht_items = 42; + boost::scoped_ptr s(dht_default_storage_constructor(node_id(0), sett)); + TEST_CHECK(s.get() != NULL); + + char public_key[item_pk_len]; + char signature[item_sig_len]; + for (int i = 0; i < 200; ++i) + { + s->put_mutable_item(rand_hash(), "123", 3, signature, 1, public_key, "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); +} + +#endif + diff --git a/test/test_direct_dht.cpp b/test/test_direct_dht.cpp new file mode 100644 index 0000000..5d2392b --- /dev/null +++ b/test/test_direct_dht.cpp @@ -0,0 +1,140 @@ +/* + +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" + +#ifndef TORRENT_DISABLE_EXTENSIONS + +#include "libtorrent/config.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/extensions.hpp" +#include "libtorrent/alert_types.hpp" + +using namespace libtorrent; +namespace lt = libtorrent; + +namespace +{ + +struct test_plugin : plugin +{ + virtual void register_dht_extensions(dht_extensions_t& ext) + { + ext.push_back(dht_extensions_t::value_type("test_good", &good_response)); + } + + static bool good_response(udp::endpoint const& source + , bdecode_node const& request, entry& response) + { + if (request.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(20)); + // it shouldn't take more than 20 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 NULL; + 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 // #ifndef TORRENT_DISABLE_EXTENSIONS + +TORRENT_TEST(direct_dht_request) +{ +#ifndef TORRENT_DISABLE_EXTENSIONS + settings_pack sp; + sp.set_bool(settings_pack::enable_lsd, false); + sp.set_bool(settings_pack::enable_natpmp, false); + sp.set_bool(settings_pack::enable_upnp, false); + sp.set_str(settings_pack::dht_bootstrap_nodes, ""); + sp.set_int(settings_pack::max_retry_port_bind, 800); + sp.set_str(settings_pack::listen_interfaces, "127.0.0.1:42434"); + lt::session responder(sp, 0); + sp.set_str(settings_pack::listen_interfaces, "127.0.0.1:45434"); + lt::session requester(sp, 0); + + responder.add_extension(boost::static_pointer_cast(boost::make_shared())); + + // successful request + + entry r; + r["q"] = "test_good"; + requester.dht_direct_request(udp::endpoint(address::from_string("127.0.0.1"), responder.listen_port()) + , r, (void*)12345); + + dht_direct_response_alert* ra = get_direct_response(requester); + TEST_CHECK(ra); + if (ra) + { + bdecode_node response = ra->response(); + TEST_EQUAL(ra->addr.address(), address::from_string("127.0.0.1")); + TEST_EQUAL(ra->addr.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, (void*)12345); + } + + // failed request + + requester.dht_direct_request(udp::endpoint(address::from_string("127.0.0.1"), 53545) + , r, (void*)123456); + + ra = get_direct_response(requester); + TEST_CHECK(ra); + if (ra) + { + TEST_EQUAL(ra->addr.address(), address::from_string("127.0.0.1")); + TEST_EQUAL(ra->addr.port(), 53545); + TEST_EQUAL(ra->response().type(), bdecode_node::none_t); + TEST_EQUAL(ra->userdata, (void*)123456); + } +#endif // #ifndef TORRENT_DISABLE_EXTENSIONS +} diff --git a/test/test_dos_blocker.cpp b/test/test_dos_blocker.cpp new file mode 100644 index 0000000..3d0fdb2 --- /dev/null +++ b/test/test_dos_blocker.cpp @@ -0,0 +1,97 @@ +/* + +Copyright (c) 2013, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/kademlia/dos_blocker.hpp" +#include "libtorrent/kademlia/dht_observer.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/socket_io.hpp" // for print_endpoint +#include + +using namespace libtorrent; + +struct log_t : libtorrent::dht::dht_logger +{ + virtual void log(dht_logger::module_t m, char const* fmt, ...) + TORRENT_OVERRIDE TORRENT_FORMAT(3, 4) + { + va_list v; + va_start(v, fmt); + vfprintf(stderr, fmt, v); + va_end(v); + } + + virtual void log_packet(message_direction_t dir, char const* pkt, int len + , udp::endpoint node) TORRENT_OVERRIDE + { + libtorrent::bdecode_node print; + libtorrent::error_code ec; + int ret = bdecode(pkt, pkt + len, print, ec, NULL, 100, 100); + TEST_EQUAL(ret, 0); + + std::string msg = print_entry(print, true); + printf("%s", msg.c_str()); + + char const* prefix[2] = { "<==", "==>"}; + printf("%s [%s] %s", prefix[dir], print_endpoint(node).c_str() + , msg.c_str()); + } +}; + +TORRENT_TEST(dos_blocker) +{ +#ifndef TORRENT_DISABLE_DHT + using namespace libtorrent::dht; + + log_t l; + dos_blocker b; + + address spammer = address_v4::from_string("10.10.10.10"); + + time_point now = clock_type::now(); + for (int i = 0; i < 1000; ++i) + { + b.incoming(spammer, now, &l); + now += milliseconds(1); + TEST_EQUAL(b.incoming(rand_v4(), now, &l), true); + now += milliseconds(1); + } + + now += milliseconds(1); + + TEST_EQUAL(b.incoming(spammer, now, &l), false); +#endif +} + diff --git a/test/test_enum_net.cpp b/test/test_enum_net.cpp new file mode 100644 index 0000000..2510209 --- /dev/null +++ b/test/test_enum_net.cpp @@ -0,0 +1,103 @@ +/* + +Copyright (c) 2008-2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/enum_net.hpp" +#include "libtorrent/broadcast_socket.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/error_code.hpp" + +using namespace libtorrent; + +TORRENT_TEST(is_local) +{ + error_code ec; + TEST_CHECK(is_local(address::from_string("192.168.0.1", ec))); + TEST_CHECK(!ec); + TEST_CHECK(is_local(address::from_string("10.1.1.56", ec))); + TEST_CHECK(!ec); + TEST_CHECK(!is_local(address::from_string("14.14.251.63", ec))); + TEST_CHECK(!ec); +} + +TORRENT_TEST(is_loopback) +{ + error_code ec; + TEST_CHECK(is_loopback(address::from_string("127.0.0.1", ec))); + TEST_CHECK(!ec); +#if TORRENT_USE_IPV6 + if (supports_ipv6()) + { + error_code ec; + TEST_CHECK(is_loopback(address::from_string("::1", ec))); + TEST_CHECK(!ec); + } +#endif +} + +TORRENT_TEST(is_any) +{ + TEST_CHECK(is_any(address_v4::any())); + error_code ec; + TEST_CHECK(!is_any(address::from_string("31.53.21.64", ec))); + TEST_CHECK(!ec); +#if TORRENT_USE_IPV6 + if (supports_ipv6()) + { + TEST_CHECK(is_any(address_v6::any())); + TEST_CHECK(!ec); + } +#endif +} + +TORRENT_TEST(match_addr_mask) +{ + error_code ec; + TEST_CHECK(match_addr_mask( + address::from_string("10.0.1.176", ec), + address::from_string("10.0.1.176", ec), + address::from_string("255.255.255.0", ec))); + TEST_CHECK(!ec); + + TEST_CHECK(match_addr_mask( + address::from_string("10.0.1.3", ec), + address::from_string("10.0.3.3", ec), + address::from_string("255.255.0.0", ec))); + TEST_CHECK(!ec); + + TEST_CHECK(!match_addr_mask( + address::from_string("10.0.1.3", ec), + address::from_string("10.1.3.3", ec), + address::from_string("255.255.0.0", ec))); + TEST_CHECK(!ec); +} + diff --git a/test/test_fast_extension.cpp b/test/test_fast_extension.cpp new file mode 100644 index 0000000..61e8cee --- /dev/null +++ b/test/test_fast_extension.cpp @@ -0,0 +1,1022 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "test_utils.hpp" + +#include "libtorrent/socket.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/alloca.hpp" +#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_/disable_warnings_push.hpp" + +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +using namespace libtorrent; +namespace lt = libtorrent; + +void log(char const* fmt, ...) +{ + va_list v; + va_start(v, fmt); + + char buf[1024]; + vsnprintf(buf, sizeof(buf), fmt, v); + va_end(v); + + fprintf(stderr, "\x1b[1m\x1b[36m%s: %s\x1b[0m\n" + , time_now_string(), buf); +} + +void print_session_log(lt::session& ses) +{ + print_alerts(ses, "ses", true, true); +} + +int read_message(tcp::socket& s, char* buffer, int max_size) +{ + using namespace libtorrent::detail; + error_code ec; + boost::asio::read(s, boost::asio::buffer(buffer, 4) + , boost::asio::transfer_all(), ec); + if (ec) + { + TEST_ERROR(ec.message()); + return -1; + } + char* ptr = buffer; + int length = read_int32(ptr); + if (length > max_size) + { + log("message size: %d", length); + TEST_ERROR("message size exceeds max limt"); + return -1; + } + + boost::asio::read(s, boost::asio::buffer(buffer, length) + , boost::asio::transfer_all(), ec); + if (ec) + { + TEST_ERROR(ec.message()); + return -1; + } + return length; +} + +void print_message(char const* buffer, int len) +{ + 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"}; + + char message[50]; + char extra[300]; + extra[0] = 0; + if (len == 0) + { + strcpy(message, "keepalive"); + } + else + { + int msg = buffer[0]; + if (msg >= 0 && msg < int(sizeof(message_name)/sizeof(message_name[0]))) + strcpy(message, message_name[msg]); + else if (msg == 20) + snprintf(message, sizeof(message), "extension msg [%d]", buffer[1]); + else + snprintf(message, sizeof(message), "unknown[%d]", msg); + + if (msg == 0x6 && len == 13) + { + peer_request r; + const char* ptr = buffer + 1; + r.piece = detail::read_int32(ptr); + r.start = detail::read_int32(ptr); + r.length = detail::read_int32(ptr); + snprintf(extra, sizeof(extra), "p: %d s: %d l: %d", r.piece, r.start, r.length); + } + else if (msg == 0x11 && len == 5) + { + const char* ptr = buffer + 1; + int index = detail::read_int32(ptr); + snprintf(extra, sizeof(extra), "p: %d", index); + } + else if (msg == 20 && len > 4 && buffer[1] == 0 ) + { + snprintf(extra, sizeof(extra), "%s" + , bdecode(buffer + 2, buffer + len).to_string().c_str()); + } + } + + log("<== %s %s", message, extra); +} + +void send_allow_fast(tcp::socket& s, int piece) +{ + log("==> allow fast: %d", piece); + using namespace libtorrent::detail; + char msg[] = "\0\0\0\x05\x11\0\0\0\0"; + char* ptr = msg + 5; + write_int32(piece, ptr); + error_code ec; + boost::asio::write(s, boost::asio::buffer(msg, 9) + , boost::asio::transfer_all(), ec); + if (ec) TEST_ERROR(ec.message()); +} + +void send_suggest_piece(tcp::socket& s, int piece) +{ + log("==> suggest piece: %d", piece); + using namespace libtorrent::detail; + char msg[] = "\0\0\0\x05\x0d\0\0\0\0"; + char* ptr = msg + 5; + write_int32(piece, ptr); + error_code ec; + boost::asio::write(s, boost::asio::buffer(msg, 9) + , boost::asio::transfer_all(), ec); + if (ec) TEST_ERROR(ec.message()); +} + +void send_keepalive(tcp::socket& s) +{ + log("==> keepalive"); + char msg[] = "\0\0\0\0"; + error_code ec; + boost::asio::write(s, boost::asio::buffer(msg, 4) + , boost::asio::transfer_all(), ec); + if (ec) TEST_ERROR(ec.message()); +} + +void send_unchoke(tcp::socket& s) +{ + log("==> unchoke"); + char msg[] = "\0\0\0\x01\x01"; + error_code ec; + boost::asio::write(s, boost::asio::buffer(msg, 5) + , boost::asio::transfer_all(), ec); + if (ec) TEST_ERROR(ec.message()); +} + +void send_have_all(tcp::socket& s) +{ + log("==> have_all"); + char msg[] = "\0\0\0\x01\x0e"; // have_all + error_code ec; + boost::asio::write(s, boost::asio::buffer(msg, 5) + , boost::asio::transfer_all(), ec); + if (ec) TEST_ERROR(ec.message()); +} + +void send_have_none(tcp::socket& s) +{ + log("==> have_none"); + char msg[] = "\0\0\0\x01\x0f"; // have_none + error_code ec; + boost::asio::write(s, boost::asio::buffer(msg, 5) + , boost::asio::transfer_all(), ec); + if (ec) TEST_ERROR(ec.message()); +} + +void send_bitfield(tcp::socket& s, char const* bits) +{ + using namespace libtorrent::detail; + + int num_pieces = strlen(bits); + int packet_size = (num_pieces+7)/8 + 5; + char* msg = (char*)TORRENT_ALLOCA(char, packet_size); + memset(msg, 0, packet_size); + char* ptr = msg; + 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, packet_size) + , boost::asio::transfer_all(), ec); + if (ec) TEST_ERROR(ec.message()); +} + +void do_handshake(tcp::socket& s, sha1_hash const& ih, char* buffer) +{ + char handshake[] = "\x13" "BitTorrent protocol\0\0\0\0\0\x10\0\x04" + " " // space for info-hash + "aaaaaaaaaaaaaaaaaaaa"; // peer-id + log("==> handshake"); + error_code ec; + std::memcpy(handshake + 28, ih.begin(), 20); + boost::asio::write(s, boost::asio::buffer(handshake, sizeof(handshake) - 1) + , boost::asio::transfer_all(), ec); + if (ec) + { + TEST_ERROR(ec.message()); + return; + } + + // read handshake + boost::asio::read(s, boost::asio::buffer(buffer, 68) + , boost::asio::transfer_all(), ec); + if (ec) + { + TEST_ERROR(ec.message()); + return; + } + log("<== handshake"); + + TEST_CHECK(buffer[0] == 19); + TEST_CHECK(std::memcmp(buffer + 1, "BitTorrent protocol", 19) == 0); + + char* extensions = buffer + 20; + // check for fast extension support + TEST_CHECK(extensions[7] & 0x4); + + // check for extension protocol support + bool const lt_extension_protocol = (extensions[5] & 0x10) != 0; +#ifndef TORRENT_DISABLE_EXTENSIONS + TEST_CHECK(lt_extension_protocol == true); +#else + TEST_CHECK(lt_extension_protocol == false); +#endif + + // check for DHT support + bool const dht_support = (extensions[7] & 0x1) != 0; +#ifndef TORRENT_DISABLE_DHT + TEST_CHECK(dht_support == true); +#else + TEST_CHECK(dht_support == false); +#endif + + TEST_CHECK(std::memcmp(buffer + 28, ih.begin(), 20) == 0); +} + +void send_extension_handshake(tcp::socket& s, entry const& e) +{ + std::vector buf; + + // reserve space for the message header + // uint32: packet-length + // uint8: 20 (extension message) + // uint8: 0 (handshake) + buf.resize(4 + 1 + 1); + + bencode(std::back_inserter(buf), e); + + using namespace libtorrent::detail; + + char* ptr = &buf[0]; + write_uint32(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 libtorrent::detail; + + log("==> request %d (%d,%d)", req.piece, req.start, req.length); + char msg[] = "\0\0\0\x0d\x06 "; // have_none + char* ptr = msg + 5; + write_uint32(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, char* recv_buffer, int size) +{ + for (;;) + { + int len = read_message(s, recv_buffer, size); + if (len == -1) + { + TEST_ERROR("failed to read message"); + return entry(); + } + print_message(recv_buffer, len); + + 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 + 2, recv_buffer + len); + } +} + +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 libtorrent::detail; + + char* ptr = &buf[0]; + write_uint32(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, char* recv_buffer, int size) +{ + for (;;) + { + int len = read_message(s, recv_buffer, size); + if (len == -1) + { + TEST_ERROR("failed to read message"); + return entry(); + } + print_message(recv_buffer, len); + + if (len < 4) continue; + int msg = recv_buffer[0]; + if (msg != 20) continue; + int extmsg = recv_buffer[1]; + if (extmsg != 1) continue; + + return bdecode(recv_buffer + 2, recv_buffer + len); + } +} + +boost::shared_ptr setup_peer(tcp::socket& s, sha1_hash& ih + , boost::shared_ptr& ses, bool incoming = true + , int flags = 0, torrent_handle* th = NULL) +{ + boost::shared_ptr t = ::create_torrent(); + ih = t->info_hash(); + settings_pack sett; + sett.set_str(settings_pack::listen_interfaces, "0.0.0.0:48900"); + sett.set_int(settings_pack::alert_mask, alert::all_categories); + 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); + 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); + ses.reset(new lt::session(sett, lt::session::add_default_plugins)); + + error_code ec; + add_torrent_params p; + p.flags &= ~add_torrent_params::flag_paused; + p.flags &= ~add_torrent_params::flag_auto_managed; + p.flags |= flags; + p.ti = t; + p.save_path = "./tmp1_fast"; + + remove("./tmp1_fast/temporary", ec); + if (ec) log("remove(): %s", ec.message().c_str()); + ec.clear(); + torrent_handle ret = ses->add_torrent(p, ec); + if (th) *th = ret; + + // wait for the torrent to be ready + if ((flags & add_torrent_params::flag_seed_mode) == 0) + { + wait_for_downloading(*ses, "ses"); + } + + if (incoming) + { + s.connect(tcp::endpoint(address::from_string("127.0.0.1", ec), ses->listen_port()), ec); + if (ec) TEST_ERROR(ec.message()); + } + else + { + tcp::acceptor l(s.get_io_service()); + l.open(tcp::v4()); + l.bind(tcp::endpoint(address_v4::from_string("127.0.0.1") + , 3000 + rand() % 60000)); + 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; +} + +// makes sure that pieces that are allowed and then +// rejected aren't requested again +TORRENT_TEST(reject_fast) +{ + std::cerr << "\n === test reject ===\n" << std::endl; + + sha1_hash ih; + boost::shared_ptr ses; + io_service ios; + tcp::socket s(ios); + setup_peer(s, ih, ses); + + char recv_buffer[1000]; + do_handshake(s, ih, recv_buffer); + print_session_log(*ses); + send_have_all(s); + print_session_log(*ses); + + std::vector allowed_fast; + allowed_fast.push_back(0); + allowed_fast.push_back(1); + allowed_fast.push_back(2); + allowed_fast.push_back(3); + + std::for_each(allowed_fast.begin(), allowed_fast.end() + , boost::bind(&send_allow_fast, boost::ref(s), _1)); + print_session_log(*ses); + + while (!allowed_fast.empty()) + { + print_session_log(*ses); + int len = read_message(s, recv_buffer, sizeof(recv_buffer)); + if (len == -1) break; + print_message(recv_buffer, len); + int msg = recv_buffer[0]; + if (msg != 0x6) continue; + + using namespace libtorrent::detail; + char* ptr = recv_buffer + 1; + int 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(); + test_sleep(500); + print_session_log(*ses); +} + +TORRENT_TEST(invalid_suggest) +{ + std::cerr << "\n === test suggest ===\n" << std::endl; + + sha1_hash ih; + boost::shared_ptr ses; + io_service ios; + tcp::socket s(ios); + setup_peer(s, ih, ses); + + char recv_buffer[1000]; + do_handshake(s, ih, recv_buffer); + print_session_log(*ses); + send_have_all(s); + print_session_log(*ses); + + // this is an invalid suggest message. We would not expect to receive a + // request for that piece index. + send_suggest_piece(s, -234); + send_unchoke(s); + test_sleep(500); + print_session_log(*ses); + + int len = read_message(s, recv_buffer, sizeof(recv_buffer)); + int idx = -1; + while (len > 0) + { + if (recv_buffer[0] == 6) + { + char* ptr = recv_buffer + 1; + idx = detail::read_int32(ptr); + break; + } + len = read_message(s, recv_buffer, sizeof(recv_buffer)); + } + TEST_CHECK(idx != -234); + TEST_CHECK(idx != -1); + s.close(); +} + +TORRENT_TEST(reject_suggest) +{ + std::cerr << "\n === test suggest ===\n" << std::endl; + + sha1_hash ih; + boost::shared_ptr ses; + io_service ios; + tcp::socket s(ios); + setup_peer(s, ih, ses); + + char recv_buffer[1000]; + do_handshake(s, ih, recv_buffer); + print_session_log(*ses); + send_have_all(s); + print_session_log(*ses); + + std::vector suggested; + suggested.push_back(0); + suggested.push_back(1); + suggested.push_back(2); + suggested.push_back(3); + + std::for_each(suggested.begin(), suggested.end() + , boost::bind(&send_suggest_piece, boost::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 len = read_message(s, recv_buffer, sizeof(recv_buffer)); + if (len == -1) break; + print_message(recv_buffer, len); + int msg = recv_buffer[0]; + fail_counter--; + if (msg != 0x6) continue; + + using namespace libtorrent::detail; + char* ptr = recv_buffer + 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(); + test_sleep(500); + print_session_log(*ses); +} + +TORRENT_TEST(suggest_order) +{ + std::cerr << "\n === test suggest ===\n" << std::endl; + + sha1_hash ih; + boost::shared_ptr ses; + io_service ios; + tcp::socket s(ios); + setup_peer(s, ih, ses); + + char recv_buffer[1000]; + do_handshake(s, ih, recv_buffer); + print_session_log(*ses); + send_have_all(s); + print_session_log(*ses); + + std::vector suggested; + suggested.push_back(0); + suggested.push_back(1); + suggested.push_back(2); + suggested.push_back(3); + + std::for_each(suggested.begin(), suggested.end() + , boost::bind(&send_suggest_piece, boost::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 len = read_message(s, recv_buffer, sizeof(recv_buffer)); + if (len == -1) break; + print_message(recv_buffer, len); + int msg = recv_buffer[0]; + fail_counter--; + + // we're just interested in requests + if (msg != 0x6) continue; + + using namespace libtorrent::detail; + char* ptr = recv_buffer + 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(); + test_sleep(500); + print_session_log(*ses); +} + +TORRENT_TEST(multiple_bitfields) +{ + std::cerr << "\n === test multiple bitfields ===\n" << std::endl; + + sha1_hash ih; + boost::shared_ptr ses; + io_service ios; + tcp::socket s(ios); + boost::shared_ptr ti = setup_peer(s, ih, ses); + print_session_log(*ses); + + char recv_buffer[1000]; + do_handshake(s, ih, recv_buffer); + print_session_log(*ses); + + std::string bitfield; + bitfield.resize(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(); + test_sleep(500); + print_session_log(*ses); +} + +TORRENT_TEST(multiple_have_all) +{ + std::cerr << "\n === test multiple have_all ===\n" << std::endl; + + sha1_hash ih; + boost::shared_ptr ses; + io_service ios; + tcp::socket s(ios); + boost::shared_ptr ti = setup_peer(s, ih, ses); + + char recv_buffer[1000]; + do_handshake(s, ih, recv_buffer); + + print_session_log(*ses); + + send_have_all(s); + print_session_log(*ses); + send_have_all(s); + print_session_log(*ses); + send_have_none(s); + print_session_log(*ses); + send_have_all(s); + print_session_log(*ses); + + s.close(); + print_session_log(*ses); + test_sleep(500); + print_session_log(*ses); +} + +#ifndef TORRENT_DISABLE_EXTENSIONS +// makes sure that pieces that are lost are not requested +TORRENT_TEST(dont_have) +{ + using namespace libtorrent::detail; + + std::cerr << "\n === test dont_have ===\n" << std::endl; + + sha1_hash ih; + torrent_handle th; + boost::shared_ptr ses; + io_service ios; + tcp::socket s(ios); + boost::shared_ptr ti = setup_peer(s, ih, ses, true, 0, &th); + + char recv_buffer[1000]; + do_handshake(s, ih, recv_buffer); + print_session_log(*ses); + send_have_all(s); + print_session_log(*ses); + + test_sleep(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 len = read_message(s, recv_buffer, sizeof(recv_buffer)); + if (len == -1) break; + print_message(recv_buffer, len); + if (len == 0) continue; + int msg = recv_buffer[0]; + if (msg != 20) continue; + int ext_msg = recv_buffer[1]; + if (ext_msg != 0) continue; + + bdecode_node e; + int pos = 0; + int ret = bdecode(recv_buffer + 2, recv_buffer + len, e, ec, &pos); + if (ret != 0) + { + log("failed to parse extension handshake: %s at pos %d" + , ec.message().c_str(), pos); + } + TEST_EQUAL(ret, 0); + + 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 = 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); + + test_sleep(1000); + + print_session_log(*ses); + + th.get_peer_info(pi); + + TEST_EQUAL(pi.size(), 1); + if (pi.size() != 1) return; + + TEST_EQUAL(pi[0].flags & peer_info::seed, 0); + TEST_EQUAL(pi[0].pieces.count(), pi[0].pieces.size() - 1); + TEST_EQUAL(pi[0].pieces[3], false); + TEST_EQUAL(pi[0].pieces[2], true); + TEST_EQUAL(pi[0].pieces[1], true); + TEST_EQUAL(pi[0].pieces[0], true); + + print_session_log(*ses); +} + +// 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 libtorrent::detail; + + std::cerr << "\n === test invalid metadata ===\n" << std::endl; + + sha1_hash ih; + boost::shared_ptr ses; + io_service ios; + tcp::socket s(ios); + boost::shared_ptr ti = setup_peer(s, ih, ses); + + char recv_buffer[1000]; + do_handshake(s, ih, recv_buffer); + print_session_log(*ses); + send_have_all(s); + print_session_log(*ses); + + entry extensions; + extensions["m"]["ut_metadata"] = 1; + send_extension_handshake(s, extensions); + + extensions = read_extension_handshake(s, recv_buffer, sizeof(recv_buffer)); + + int ut_metadata = 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 + , sizeof(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 + , sizeof(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); +} + +TORRENT_TEST(invalid_request) +{ + std::cerr << "\n === test request ===\n" << std::endl; + + sha1_hash ih; + boost::shared_ptr ses; + io_service ios; + tcp::socket s(ios); + setup_peer(s, ih, ses); + + char recv_buffer[1000]; + do_handshake(s, ih, recv_buffer); + print_session_log(*ses); + send_have_none(s); + + peer_request req; + req.piece = 124134235; + req.start = 0; + req.length = 0x4000; + send_request(s, req); +} + +void have_all_test(bool const incoming) +{ + sha1_hash ih; + boost::shared_ptr ses; + io_service ios; + tcp::socket s(ios); + setup_peer(s, ih, ses, incoming, add_torrent_params::flag_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, sizeof(recv_buffer)); + if (len == -1) + { + TEST_ERROR("failed to receive have-all despite advertising support for FAST"); + break; + } + print_message(recv_buffer, len); + int const msg = recv_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; + } + } +} + +TORRENT_TEST(outgoing_have_all) +{ + std::cerr << "\n === test outgoing have-all ===\n" << std::endl; + have_all_test(true); +} + +TORRENT_TEST(incoming_have_all) +{ + std::cerr << "\n === test outgoing have-all ===\n" << std::endl; + have_all_test(false); +} + +#endif // TORRENT_DISABLE_EXTENSIONS + +// 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..24445d5 --- /dev/null +++ b/test/test_fence.cpp @@ -0,0 +1,205 @@ +#include "libtorrent/storage.hpp" +#include "libtorrent/disk_io_job.hpp" +#include "test.hpp" + +#include + +using namespace libtorrent; + +TORRENT_TEST(empty_fence) +{ + libtorrent::disk_job_fence fence; + counters cnt; + + disk_io_job test_job[10]; + + // issue 5 jobs. None of them should be blocked by a fence + int ret = 0; + // add a fence job + ret = fence.raise_fence(&test_job[5], &test_job[6], cnt); + // since we don't have any outstanding jobs + // we need to post this job + TEST_CHECK(ret == 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; + libtorrent::disk_job_fence fence; + + disk_io_job test_job[10]; + + // issue 5 jobs. None of them should be blocked by a fence + 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 = fence.raise_fence(&test_job[5], &test_job[6], cnt); + // since we have outstanding jobs, no need + // to post anything + TEST_CHECK(ret == disk_job_fence::fence_post_flush); + + ret = fence.is_blocked(&test_job[7]); + TEST_CHECK(ret == true); + ret = fence.is_blocked(&test_job[8]); + TEST_CHECK(ret == true); + + tailqueue jobs; + + fence.job_complete(&test_job[3], jobs); + TEST_CHECK(jobs.size() == 0); + fence.job_complete(&test_job[2], jobs); + TEST_CHECK(jobs.size() == 0); + fence.job_complete(&test_job[4], jobs); + TEST_CHECK(jobs.size() == 0); + fence.job_complete(&test_job[1], jobs); + TEST_CHECK(jobs.size() == 0); + fence.job_complete(&test_job[0], jobs); + TEST_EQUAL(jobs.size(), 0); + + // the flush job completes + fence.job_complete(&test_job[6], jobs); + + // this was the last job. Now we should be + // able to run the fence job + TEST_EQUAL(jobs.size(), 1); + + TEST_CHECK(jobs.first() == &test_job[5]); + jobs.pop_front(); + + // complete the fence job + fence.job_complete(&test_job[5], jobs); + + // now it's fine to post the blocked jobs + TEST_EQUAL(jobs.size(), 2); + TEST_CHECK(jobs.first() == &test_job[7]); + + // the disk_io_fence has an assert in its destructor + // to make sure all outstanding jobs are completed, so we must + // complete them before we're done + fence.job_complete(&test_job[7], jobs); + fence.job_complete(&test_job[8], jobs); +} + +TORRENT_TEST(double_fence) +{ + counters cnt; + libtorrent::disk_job_fence fence; + + disk_io_job test_job[10]; + + // issue 5 jobs. None of them should be blocked by a fence + int ret = 0; + 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 = fence.raise_fence(&test_job[5], &test_job[6], cnt); + // since we have outstanding jobs, no need + // to post anything + TEST_CHECK(ret == disk_job_fence::fence_post_flush); + + ret = fence.raise_fence(&test_job[7], &test_job[8], cnt); + // since we have outstanding jobs, no need + // to post anything + TEST_CHECK(ret == disk_job_fence::fence_post_none); + fprintf(stderr, "ret: %d\n", ret); + + ret = fence.is_blocked(&test_job[9]); + TEST_CHECK(ret == true); + + tailqueue jobs; + + fence.job_complete(&test_job[3], jobs); + TEST_CHECK(jobs.size() == 0); + fence.job_complete(&test_job[2], jobs); + TEST_CHECK(jobs.size() == 0); + fence.job_complete(&test_job[4], jobs); + TEST_CHECK(jobs.size() == 0); + fence.job_complete(&test_job[1], jobs); + TEST_CHECK(jobs.size() == 0); + fence.job_complete(&test_job[0], jobs); + TEST_CHECK(jobs.size() == 0); + fence.job_complete(&test_job[6], jobs); + // this was the last job. Now we should be + // able to run the fence job + TEST_CHECK(jobs.size() == 1); + + TEST_CHECK(jobs.first() == &test_job[5]); + jobs.pop_front(); + + // complete the fence job + fence.job_complete(&test_job[5], jobs); + + // now it's fine to run the next fence job + // first we get the flush job + TEST_CHECK(jobs.size() == 1); + TEST_CHECK(jobs.first() == &test_job[8]); + jobs.pop_front(); + + fence.job_complete(&test_job[8], jobs); + + // then the fence itself + TEST_CHECK(jobs.size() == 1); + TEST_CHECK(jobs.first() == &test_job[7]); + jobs.pop_front(); + + fence.job_complete(&test_job[7], jobs); + + // and now we can run the remaining blocked job + TEST_CHECK(jobs.size() == 1); + TEST_CHECK(jobs.first() == &test_job[9]); + + // the disk_io_fence has an assert in its destructor + // to make sure all outstanding jobs are completed, so we must + // complete them before we're done + fence.job_complete(&test_job[9], jobs); +} + diff --git a/test/test_file.cpp b/test/test_file.cpp new file mode 100644 index 0000000..349e730 --- /dev/null +++ b/test/test_file.cpp @@ -0,0 +1,394 @@ +/* + +Copyright (c) 2012, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/file.hpp" +#include "test.hpp" +#include "setup_transfer.hpp" // for test_sleep +#include // for strcmp +#include +#include + +using namespace libtorrent; + +int touch_file(std::string const& filename, int size) +{ + using namespace libtorrent; + + std::vector v; + v.resize(size); + for (int i = 0; i < size; ++i) + v[i] = i & 255; + + file f; + error_code ec; + if (!f.open(filename, file::write_only, ec)) return -1; + if (ec) return -1; + file::iovec_t b = {&v[0], v.size()}; + boost::int64_t written = f.writev(0, &b, 1, ec); + if (written != int(v.size())) return -3; + if (ec) return -3; + return 0; +} + +TORRENT_TEST(create_directory) +{ + error_code ec; + create_directory("__foobar__", ec); + TEST_CHECK(!ec); + + file_status st; + stat_file("__foobar__", &st, ec); + TEST_CHECK(!ec); + + TEST_CHECK(st.mode & file_status::directory); + + remove("__foobar__", ec); + 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 + test_sleep(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); + fprintf(stderr, "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) fprintf(stderr, "create_directory: %s\n", ec.message().c_str()); + TEST_CHECK(!ec); + + std::string cwd = current_working_directory(); + + touch_file(combine_path("file_test_dir", "abc"), 10); + touch_file(combine_path("file_test_dir", "def"), 100); + touch_file(combine_path("file_test_dir", "ghi"), 1000); + + std::set files; + for (directory i("file_test_dir", ec); !i.done(); i.next(ec)) + { + std::string f = i.file(); + TEST_CHECK(files.count(f) == 0); + files.insert(f); + fprintf(stderr, " %s\n", f.c_str()); + } + + TEST_CHECK(files.count("abc") == 1); + TEST_CHECK(files.count("def") == 1); + TEST_CHECK(files.count("ghi") == 1); + TEST_CHECK(files.count("..") == 1); + TEST_CHECK(files.count(".") == 1); + files.clear(); + + recursive_copy("file_test_dir", "file_test_dir2", ec); + + for (directory i("file_test_dir2", ec); !i.done(); i.next(ec)) + { + std::string f = i.file(); + TEST_CHECK(files.count(f) == 0); + files.insert(f); + fprintf(stderr, " %s\n", f.c_str()); + } + + remove_all("file_test_dir", ec); + if (ec) fprintf(stderr, "remove_all: %s\n", ec.message().c_str()); + remove_all("file_test_dir2", ec); + if (ec) fprintf(stderr, "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 + +#if TORRENT_USE_UNC_PATHS + 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"); +#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"); + + TEST_EQUAL(filename("blah"), "blah"); + TEST_EQUAL(filename("/blah/foo/bar"), "bar"); + TEST_EQUAL(filename("/blah/foo/bar/"), "bar"); + TEST_EQUAL(filename("blah/"), "blah"); + +#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 + + // 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()); +} + +// test split_string +TORRENT_TEST(split_string) +{ + char const* tags[10]; + char tags_str[] = " this is\ta test\t string\x01to be split and it cannot " + "extend over the limit of elements \t"; + int ret = split_string(tags, 10, tags_str); + + TEST_CHECK(ret == 10); + TEST_CHECK(strcmp(tags[0], "this") == 0); + TEST_CHECK(strcmp(tags[1], "is") == 0); + TEST_CHECK(strcmp(tags[2], "a") == 0); + TEST_CHECK(strcmp(tags[3], "test") == 0); + TEST_CHECK(strcmp(tags[4], "string") == 0); + TEST_CHECK(strcmp(tags[5], "to") == 0); + TEST_CHECK(strcmp(tags[6], "be") == 0); + TEST_CHECK(strcmp(tags[7], "split") == 0); + TEST_CHECK(strcmp(tags[8], "and") == 0); + TEST_CHECK(strcmp(tags[9], "it") == 0); + + // replace_extension + std::string test = "foo.bar"; + replace_extension(test, "txt"); + TEST_EQUAL(test, "foo.txt"); + + test = "_"; + replace_extension(test, "txt"); + TEST_EQUAL(test, "_.txt"); + + test = "1.2.3/_"; + replace_extension(test, "txt"); + TEST_EQUAL(test, "1.2.3/_.txt"); +} + +// file class +TORRENT_TEST(file) +{ + error_code ec; + file f; +#if TORRENT_USE_UNC_PATHS || !defined WIN32 + TEST_CHECK(f.open("con", file::read_write, ec)); +#else + TEST_CHECK(f.open("test_file", file::read_write, ec)); +#endif + if (ec) + fprintf(stderr, "open failed: [%s] %s\n", ec.category().name(), ec.message().c_str()); + TEST_EQUAL(ec, error_code()); + if (ec) fprintf(stderr, "%s\n", ec.message().c_str()); + file::iovec_t b = {(void*)"test", 4}; + TEST_EQUAL(f.writev(0, &b, 1, ec), 4); + if (ec) + fprintf(stderr, "writev failed: [%s] %s\n", ec.category().name(), ec.message().c_str()); + TEST_CHECK(!ec); + char test_buf[5] = {0}; + b.iov_base = test_buf; + b.iov_len = 4; + TEST_EQUAL(f.readv(0, &b, 1, ec), 4); + if (ec) + fprintf(stderr, "readv failed: [%s] %s\n", ec.category().name(), ec.message().c_str()); + TEST_EQUAL(ec, error_code()); + TEST_CHECK(strcmp(test_buf, "test") == 0); + f.close(); +} + +TORRENT_TEST(hard_link) +{ + // try to create a hard link to see what happens + // first create a regular file to then add another link to. + + // create a file, write some stuff to it, create a hard link to that file, + // read that file and assert we get the same stuff we wrote to the first file + error_code ec; + file f; + TEST_CHECK(f.open("original_file", file::read_write, ec)); + if (ec) + fprintf(stderr, "open failed: [%s] %s\n", ec.category().name(), ec.message().c_str()); + TEST_EQUAL(ec, error_code()); + + file::iovec_t b = {(void*)"abcdefghijklmnopqrstuvwxyz", 26}; + TEST_EQUAL(f.writev(0, &b, 1, ec), 26); + if (ec) + fprintf(stderr, "writev failed: [%s] %s\n", ec.category().name(), ec.message().c_str()); + TEST_EQUAL(ec, error_code()); + f.close(); + + hard_link("original_file", "second_link", ec); + + if (ec) + fprintf(stderr, "hard_link failed: [%s] %s\n", ec.category().name(), ec.message().c_str()); + TEST_EQUAL(ec, error_code()); + + + TEST_CHECK(f.open("second_link", file::read_write, ec)); + if (ec) + fprintf(stderr, "open failed: [%s] %s\n", ec.category().name(), ec.message().c_str()); + TEST_EQUAL(ec, error_code()); + + char test_buf[27] = {0}; + b.iov_base = test_buf; + b.iov_len = 27; + TEST_EQUAL(f.readv(0, &b, 1, ec), 26); + if (ec) + fprintf(stderr, "readv failed: [%s] %s\n", ec.category().name(), ec.message().c_str()); + TEST_EQUAL(ec, error_code()); + TEST_CHECK(strcmp(test_buf, "abcdefghijklmnopqrstuvwxyz") == 0); + f.close(); + + remove("original_file", ec); + if (ec) + fprintf(stderr, "remove failed: [%s] %s\n", ec.category().name(), ec.message().c_str()); + + remove("second_link", ec); + if (ec) + fprintf(stderr, "remove failed: [%s] %s\n", ec.category().name(), ec.message().c_str()); +} + +TORRENT_TEST(coalesce_buffer) +{ + error_code ec; + file f; + TEST_CHECK(f.open("test_file", file::read_write, ec)); + if (ec) + fprintf(stderr, "open failed: [%s] %s\n", ec.category().name(), ec.message().c_str()); + TEST_EQUAL(ec, error_code()); + if (ec) fprintf(stderr, "%s\n", ec.message().c_str()); + file::iovec_t b[2] = {{(void*)"test", 4}, {(void*)"foobar", 6}}; + TEST_EQUAL(f.writev(0, b, 2, ec, file::coalesce_buffers), 4 + 6); + if (ec) + fprintf(stderr, "writev failed: [%s] %s\n", ec.category().name(), ec.message().c_str()); + TEST_CHECK(!ec); + char test_buf1[5] = {0}; + char test_buf2[7] = {0}; + b[0].iov_base = test_buf1; + b[0].iov_len = 4; + b[1].iov_base = test_buf2; + b[1].iov_len = 6; + TEST_EQUAL(f.readv(0, b, 2, ec), 4 + 6); + if (ec) + { + fprintf(stderr, "readv failed: [%s] %s\n" + , ec.category().name(), ec.message().c_str()); + } + TEST_EQUAL(ec, error_code()); + TEST_CHECK(strcmp(test_buf1, "test") == 0); + TEST_CHECK(strcmp(test_buf2, "foobar") == 0); + f.close(); +} + diff --git a/test/test_file_progress.cpp b/test/test_file_progress.cpp new file mode 100644 index 0000000..da6e812 --- /dev/null +++ b/test/test_file_progress.cpp @@ -0,0 +1,110 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test_utils.hpp" +#include "libtorrent/aux_/file_progress.hpp" +#include "libtorrent/file_storage.hpp" +#include "libtorrent/piece_picker.hpp" + +using namespace libtorrent; + +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((fs.total_size() + piece_size - 1) / piece_size); + + for (int idx = 0; idx < fs.num_pieces(); ++idx) + { + piece_picker picker; + picker.init(4, fs.total_size() % 4, fs.num_pieces()); + picker.we_have(idx); + + aux::file_progress fp; + fp.init(picker, fs); + + std::vector vec; + fp.export_progress(vec); + + boost::uint64_t sum = 0; + for (int i = 0; i < int(vec.size()); ++i) + sum += vec[i]; + + TEST_EQUAL(int(sum), fs.piece_size(idx)); + } +} + +TORRENT_TEST(init2) +{ + // test the init function to make sure it assigns + // the correct number of bytes across the files + const int piece_size = 256; + + file_storage fs; + fs.add_file("torrent/1", 100000); + fs.add_file("torrent/2", 10); + fs.set_piece_length(piece_size); + fs.set_num_pieces((fs.total_size() + piece_size - 1) / piece_size); + + for (int idx = 0; idx < fs.num_pieces(); ++idx) + { + piece_picker picker; + picker.init(4, fs.total_size() % 4, fs.num_pieces()); + picker.we_have(idx); + + std::vector vec; + aux::file_progress fp; + + fp.init(picker, fs); + fp.export_progress(vec); + + boost::uint64_t sum = 0; + for (int i = 0; i < int(vec.size()); ++i) + sum += vec[i]; + + TEST_EQUAL(int(sum), fs.piece_size(idx)); + } +} + +// TODO: test the update function too + diff --git a/test/test_file_storage.cpp b/test/test_file_storage.cpp new file mode 100644 index 0000000..ccb4b0a --- /dev/null +++ b/test/test_file_storage.cpp @@ -0,0 +1,222 @@ +/* + +Copyright (c) 2013, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "setup_transfer.hpp" + +#include "libtorrent/file_storage.hpp" +#include "libtorrent/file.hpp" + +using namespace libtorrent; + +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((st.total_size() + st.piece_length() - 1) / 0x4000); + + TEST_EQUAL(st.file_name(0), "a"); + TEST_EQUAL(st.file_name(1), "b"); + TEST_EQUAL(st.file_name(2), "a"); + TEST_EQUAL(st.file_name(3), "b"); + TEST_EQUAL(st.name(), "test"); + + TEST_EQUAL(st.file_path(0), combine_path("test", "a")); + TEST_EQUAL(st.file_path(1), combine_path("test", "b")); + TEST_EQUAL(st.file_path(2), combine_path("test", combine_path("c", "a"))); + TEST_EQUAL(st.file_path(3), combine_path("test", combine_path("c", "b"))); + + TEST_EQUAL(st.file_size(0), 10000); + TEST_EQUAL(st.file_size(1), 20000); + TEST_EQUAL(st.file_size(2), 30000); + TEST_EQUAL(st.file_size(3), 40000); + + TEST_EQUAL(st.file_offset(0), 0); + TEST_EQUAL(st.file_offset(1), 10000); + TEST_EQUAL(st.file_offset(2), 30000); + TEST_EQUAL(st.file_offset(3), 60000); + + TEST_EQUAL(st.total_size(), 100000); + TEST_EQUAL(st.piece_length(), 0x4000); + printf("%d\n", st.num_pieces()); + TEST_EQUAL(st.num_pieces(), (100000 + 0x3fff) / 0x4000); +} + +TORRENT_TEST(rename_file) +{ + // test rename_file + file_storage st; + setup_test_storage(st); + + st.rename_file(0, combine_path("test", combine_path("c", "d"))); + TEST_EQUAL(st.file_path(0, "."), combine_path(".", combine_path("test" + , combine_path("c", "d")))); + TEST_EQUAL(st.file_path(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(0, "c:\\tmp\\a"); + TEST_EQUAL(st.file_path(0, "."), "c:\\tmp\\a"); +#else + st.rename_file(0, "/tmp/a"); + TEST_EQUAL(st.file_path(0, "."), "/tmp/a"); +#endif +} + +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(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(0, ""), "a"); + + st.rename_file(0, combine_path("test", combine_path("c", "d"))); + TEST_EQUAL(st.file_path(0, "."), combine_path(".", combine_path("test", combine_path("c", "d")))); + TEST_EQUAL(st.file_path(0, ""), combine_path("test", combine_path("c", "d"))); + +#ifdef TORRENT_WINDOWS + st.rename_file(0, "c:\\tmp\\a"); + TEST_EQUAL(st.file_path(0, "."), "c:\\tmp\\a"); + TEST_EQUAL(st.file_path(0, "c:\\test-1\\test2"), "c:\\tmp\\a"); +#else + st.rename_file(0, "/tmp/a"); + TEST_EQUAL(st.file_path(0, "."), "/tmp/a"); + TEST_EQUAL(st.file_path(0, "/usr/local/temp"), "/tmp/a"); +#endif + + st.rename_file(0, combine_path("tmp", "a")); + TEST_EQUAL(st.file_path(0, "."), combine_path("tmp", "a")); +} + +TORRENT_TEST(pointer_offset) +{ + // test applying pointer offset + file_storage st; + char const filename[] = "test1fooba"; + + st.add_file_borrow(filename, 5, combine_path("test-torrent-1", "test1") + , 10); + + // test filename_ptr and filename_len + TEST_EQUAL(st.file_name_ptr(0), filename); + TEST_EQUAL(st.file_name_len(0), 5); + + TEST_EQUAL(st.file_path(0, ""), combine_path("test-torrent-1", "test1")); + TEST_EQUAL(st.file_path(0, "tmp"), combine_path("tmp" + , combine_path("test-torrent-1", "test1"))); + + // apply a pointer offset of 5 bytes. The name of the file should + // change to "fooba". + + st.apply_pointer_offset(5); + + TEST_EQUAL(st.file_path(0, ""), combine_path("test-torrent-1", "fooba")); + TEST_EQUAL(st.file_path(0, "tmp"), combine_path("tmp" + , combine_path("test-torrent-1", "fooba"))); + + // test filename_ptr and filename_len + TEST_EQUAL(st.file_name_ptr(0), filename + 5); + TEST_EQUAL(st.file_name_len(0), 5); +} + +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(0, 0, 10); + TEST_EQUAL(rq.piece, 0); + TEST_EQUAL(rq.start, 0); + TEST_EQUAL(rq.length, 10); + rq = fs.map_file(5, 0, 10); + TEST_EQUAL(rq.piece, 7); + TEST_EQUAL(rq.start, 298); + TEST_EQUAL(rq.length, 10); + rq = fs.map_file(5, 0, 1000); + TEST_EQUAL(rq.piece, 7); + TEST_EQUAL(rq.start, 298); + TEST_EQUAL(rq.length, 841); +} + +TORRENT_TEST(file_path_hash) +{ + // test file_path_hash and path_hash. Make sure we can detect a path + // whose name collides with + file_storage fs; + fs.set_piece_length(512); + fs.add_file(combine_path("temp_storage", "Foo"), 17); + fs.add_file(combine_path("temp_storage", "foo"), 612); + + fprintf(stderr, "path: %s\n", fs.file_path(0).c_str()); + fprintf(stderr, "file: %s\n", fs.file_path(1).c_str()); + boost::uint32_t file_hash0 = fs.file_path_hash(0, "a"); + boost::uint32_t file_hash1 = fs.file_path_hash(1, "a"); + TEST_EQUAL(file_hash0, file_hash1); +} + +// TODO: test file_storage::optimize +// TODO: test map_block +// TODO: test piece_size(int piece) +// TODO: test file_index_at_offset +// TODO: test file attributes +// TODO: test symlinks +// TODO: test pad_files +// TODO: test reorder_file (make sure internal_file_entry::swap() is used) + diff --git a/test/test_gzip.cpp b/test/test_gzip.cpp new file mode 100644 index 0000000..45177ef --- /dev/null +++ b/test/test_gzip.cpp @@ -0,0 +1,77 @@ +/* + +Copyright (c) 2014, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/file.hpp" +#include "test.hpp" +#include "libtorrent/gzip.hpp" +#include "setup_transfer.hpp" // for load_file +#include "libtorrent/file.hpp" // for combine_path + +using namespace libtorrent; + +TORRENT_TEST(zeroes) +{ + std::vector zipped; + error_code ec; + load_file(combine_path("..", "zeroes.gz"), zipped, ec, 1000000); + if (ec) fprintf(stderr, "failed to open file: (%d) %s\n", ec.value() + , ec.message().c_str()); + TEST_CHECK(!ec); + + std::vector inflated; + inflate_gzip(&zipped[0], zipped.size(), inflated, 1000000, ec); + + if (ec) { + fprintf(stderr, "failed to unzip: %s\n", ec.message().c_str()); + } + TEST_CHECK(!ec); + TEST_CHECK(inflated.size() > 0); + for (int i = 0; i < int(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) fprintf(stderr, "failed to open file: (%d) %s\n", ec.value() + , ec.message().c_str()); + TEST_CHECK(!ec); + + std::vector inflated; + inflate_gzip(&zipped[0], zipped.size(), inflated, 1000000, ec); + + // we expect this to fail + TEST_CHECK(ec); +} + diff --git a/test/test_hasher.cpp b/test/test_hasher.cpp new file mode 100644 index 0000000..35d1c2b --- /dev/null +++ b/test/test_hasher.cpp @@ -0,0 +1,77 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/hasher.hpp" +#include "libtorrent/hex.hpp" // from_hex + +#include "test.hpp" + +using namespace libtorrent; + +// test vectors from RFC 3174 +// http://www.faqs.org/rfcs/rfc3174.html + +char const* test_array[4] = +{ + "abc", + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + "a", + "0123456701234567012345670123456701234567012345670123456701234567" +}; + +long int repeat_count[4] = { 1, 1, 1000000, 10 }; + +char const* result_array[4] = +{ + "A9993E364706816ABA3E25717850C26C9CD0D89D", + "84983E441C3BD26EBAAE4AA1F95129E5E54670F1", + "34AA973CD4C4DAA4F61EEB2BDBAD27316534016F", + "DEA356A2CDDD90C7A7ECEDC5EBB563934F460452" +}; + + +TORRENT_TEST(hasher) +{ + using namespace libtorrent; + + for (int test = 0; test < 4; ++test) + { + hasher h; + for (int i = 0; i < repeat_count[test]; ++i) + h.update(test_array[test], std::strlen(test_array[test])); + + sha1_hash result; + from_hex(result_array[test], 40, (char*)&result[0]); + TEST_CHECK(result == h.final()); + } +} + diff --git a/test/test_heterogeneous_queue.cpp b/test/test_heterogeneous_queue.cpp new file mode 100644 index 0000000..3254312 --- /dev/null +++ b/test/test_heterogeneous_queue.cpp @@ -0,0 +1,300 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/heterogeneous_queue.hpp" + +struct A +{ + int a; + explicit A(int a_) : a(a_) {} + virtual int type() = 0; + virtual ~A() {} +}; + +struct B : A +{ + int b; + explicit B(int a_, int b_) : A(a_), b(b_) {} + virtual int type() { return 1; } +}; + +struct C : A +{ + char c[100]; + explicit C(int a_, int c_) : A(a_) + { + memset(c, c_, sizeof(c)); + } + virtual int type() { return 2; } +}; + +struct D +{ + static int instances; + D() { ++instances; } + D(D const& d) { ++instances; } + + ~D() { --instances; } +}; + +struct E +{ + E(char const* msg) : string_member(msg) {} + std::string string_member; +}; + +int D::instances = 0; + +struct F +{ + 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); + } + +#if __cplusplus >= 201103L + F(F&& f_) + : 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; + } +#endif + + ~F() + { + TEST_EQUAL(constructed, true); + TEST_EQUAL(destructed, false); + TEST_EQUAL(self, this); + destructed = true; + constructed = false; + } + + void check_invariant() + { + TEST_EQUAL(constructed, true); + TEST_EQUAL(destructed, false); + TEST_EQUAL(gutted, false); + TEST_EQUAL(self, this); + } + + F* self; + int f; + bool constructed; + bool destructed; + bool gutted; +private: + // non-copyable + F& operator=(F const& f); +}; + +// test push_back of heterogeneous types +// and retrieval of their pointers +TORRENT_TEST(push_back) +{ + using namespace libtorrent; + + heterogeneous_queue q; + q.push_back(B(0, 1)); + TEST_EQUAL(q.size(), 1); + q.push_back(B(2, 3)); + TEST_EQUAL(q.size(), 2); + q.push_back(B(4, 5)); + TEST_EQUAL(q.size(), 3); + q.push_back(C(6, 7)); + TEST_EQUAL(q.size(), 4); + q.push_back(C(8, 9)); + TEST_EQUAL(q.size(), 5); + q.push_back(C(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); +} + +// test swap +TORRENT_TEST(swap) +{ + using namespace libtorrent; + + heterogeneous_queue q1; + heterogeneous_queue q2; + + q1.push_back(B(0, 1)); + q1.push_back(B(2, 3)); + q1.push_back(B(4, 5)); + TEST_EQUAL(q1.size(), 3); + + q2.push_back(C(6, 7)); + q2.push_back(C(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 libtorrent; + + heterogeneous_queue q; + TEST_EQUAL(D::instances, 0); + + q.push_back(D()); + TEST_EQUAL(D::instances, 1); + q.push_back(D()); + TEST_EQUAL(D::instances, 2); + q.push_back(D()); + TEST_EQUAL(D::instances, 3); + q.push_back(D()); + TEST_EQUAL(D::instances, 4); + + q.clear(); + + TEST_EQUAL(D::instances, 0); +} + +// test copy/move +TORRENT_TEST(copy_move) +{ + using namespace libtorrent; + + 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.push_back(F(i)); + + std::vector ptrs; + q.get_pointers(ptrs); + + TEST_EQUAL(int(ptrs.size()), 1000); + + for (int i = 0; i < int(ptrs.size()); ++i) + { + ptrs[i]->check_invariant(); + TEST_EQUAL(ptrs[i]->f, i); + } + + // destroy all objects, asserting that their invariant still holds + q.clear(); +} + +TORRENT_TEST(nontrivial) +{ + using namespace libtorrent; + + heterogeneous_queue q; + for (int i = 0; i < 10000; ++i) + { + q.push_back(E("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..e5de2ba --- /dev/null +++ b/test/test_http_connection.cpp @@ -0,0 +1,227 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "test_utils.hpp" + +#include "libtorrent/socket.hpp" +#include "libtorrent/socket_io.hpp" // print_endpoint +#include "libtorrent/http_connection.hpp" +#include "libtorrent/resolver.hpp" + +#include +#include +#include + +using namespace libtorrent; + +io_service ios; +resolver res(ios); + +int connect_handler_called = 0; +int handler_called = 0; +int data_size = 0; +int http_status = 0; +error_code g_error_code; +char data_buffer[4000]; + +void print_http_header(http_parser const& p) +{ + std::cerr << time_now_string() << " < " << p.status_code() << " " << p.message() << std::endl; + + for (std::multimap::const_iterator i + = p.headers().begin(), end(p.headers().end()); i != end; ++i) + { + std::cerr << time_now_string() << " < " << i->first << ": " << i->second << std::endl; + } +} + +void http_connect_handler(http_connection& c) +{ + ++connect_handler_called; + TEST_CHECK(c.socket().is_open()); + error_code ec; + std::cerr << time_now_string() << " connected to: " << print_endpoint(c.socket().remote_endpoint(ec)) + << std::endl; +// this is not necessarily true when using a proxy and proxying hostnames +// TEST_CHECK(c.socket().remote_endpoint(ec).address() == address::from_string("127.0.0.1", ec)); +} + +void http_handler(error_code const& ec, http_parser const& parser + , char const* data, int size, http_connection& c) +{ + ++handler_called; + data_size = size; + g_error_code = ec; + TORRENT_ASSERT(size == 0 || parser.finished()); + + if (parser.header_finished()) + { + http_status = parser.status_code(); + if (http_status == 200) + { + TEST_CHECK(memcmp(data, data_buffer, size) == 0); + } + } + 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::cerr << " ===== TESTING: " << url << " =====" << std::endl; + + std::cerr << time_now_string() + << " expecting: size: " << size + << " status: " << status + << " connected: " << connected + << " error: " << (ec?ec->message():"no error") << std::endl; + + boost::shared_ptr h(new http_connection(ios + , res, &::http_handler, true, 1024*1024, &::http_connect_handler)); + h->get(url, seconds(1), 0, &ps, 5, "test/user-agent", address_v4::any() + , 0, auth); + ios.reset(); + error_code e; + ios.run(e); + if (e) std::cerr << time_now_string() << " run failed: " << e.message() << std::endl; + + std::cerr << time_now_string() << " connect_handler_called: " << connect_handler_called << std::endl; + std::cerr << time_now_string() << " handler_called: " << handler_called << std::endl; + std::cerr << time_now_string() << " status: " << http_status << std::endl; + std::cerr << time_now_string() << " size: " << data_size << std::endl; + std::cerr << time_now_string() << " expected-size: " << size << std::endl; + std::cerr << time_now_string() << " error_code: " << g_error_code.message() << std::endl; + TEST_CHECK(connect_handler_called == connected); + TEST_CHECK(handler_called == 1); + TEST_CHECK(data_size == size || size == -1); + TEST_CHECK(!ec || g_error_code == *ec); + TEST_CHECK(http_status == status || status == -1); +} + +void write_test_file() +{ + std::srand(std::time(0)); + std::generate(data_buffer, data_buffer + sizeof(data_buffer), &std::rand); + error_code ec; + file test_file("test_file", file::write_only, ec); + TEST_CHECK(!ec); + if (ec) fprintf(stderr, "file error: %s\n", ec.message().c_str()); + file::iovec_t b = { data_buffer, 3216}; + test_file.writev(0, &b, 1, ec); + TEST_CHECK(!ec); + if (ec) fprintf(stderr, "file error: %s\n", ec.message().c_str()); + test_file.close(); +} + +enum suite_flags_t +{ + flag_chunked_encoding = 1, + flag_keepalive = 2 +}; + +void run_suite(std::string const& protocol + , settings_pack::proxy_type_t proxy_type + , int flags = flag_keepalive) +{ + write_test_file(); + + // starting the web server will also generate test_file.gz (from test_file) + // so it has to happen after we write test_file + int port = start_web_server(protocol == "https" + , flags & flag_chunked_encoding + , flags & flag_keepalive); + + 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 = start_proxy(ps.type); + + typedef boost::optional err; + + char url[256]; + 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"); + + // 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"); + printf("gethostbyname(\"non-existent-domain.se\") = %p. h_errno = %d\n", h, h_errno); + if (h == 0 && h_errno == HOST_NOT_FOUND) + { + 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(); +} + +#ifdef TORRENT_USE_OPENSSL +TORRENT_TEST(no_proxy_ssl) { run_suite("https", settings_pack::none); } +TORRENT_TEST(http_ssl) { run_suite("https", settings_pack::http); } +TORRENT_TEST(http_pw_ssl) { run_suite("https", settings_pack::http_pw); } +#endif // USE_OPENSSL + +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..6835a12 --- /dev/null +++ b/test/test_http_parser.cpp @@ -0,0 +1,571 @@ +/* + +Copyright (c) 2012, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/http_parser.hpp" +#include "libtorrent/parse_url.hpp" + +#include +#include + +using namespace libtorrent; +using boost::tuple; +using boost::make_tuple; +using boost::tie; + +tuple feed_bytes(http_parser& parser, char const* str) +{ + tuple ret(0, 0, false); + tuple prev(0, 0, false); + for (int chunks = 1; chunks < 70; ++chunks) + { + ret = make_tuple(0, 0, false); + parser.reset(); + buffer::const_interval recv_buf(str, str); + for (; *str;) + { + int chunk_size = (std::min)(chunks, int(strlen(recv_buf.end))); + if (chunk_size == 0) break; + recv_buf.end += chunk_size; + int payload, protocol; + bool error = false; + tie(payload, protocol) = parser.incoming(recv_buf, error); + ret.get<0>() += payload; + ret.get<1>() += protocol; + ret.get<2>() |= error; +// std::cerr << payload << ", " << protocol << ", " << chunk_size << std::endl; + TORRENT_ASSERT(payload + protocol == chunk_size || ret.get<2>()); + } + TEST_CHECK(prev == make_tuple(0, 0, false) || ret == prev || ret.get<2>()); + if (!ret.get<2>()) + { + TEST_EQUAL(ret.get<0>() + ret.get<1>(), int(strlen(str))); + } + + prev = ret; + } + return ret; +} + +TORRENT_TEST(http_parser) +{ + // HTTP request parser + http_parser parser; + boost::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 == make_tuple(4, 64, false)); + TEST_CHECK(parser.finished()); + TEST_CHECK(std::equal(parser.get_body().begin, parser.get_body().end, "test")); + TEST_CHECK(parser.header("content-type") == "text/plain"); + TEST_CHECK(atoi(parser.header("content-length").c_str()) == 4); + + 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 == make_tuple(0, int(strlen(upnp_response)), false)); + TEST_CHECK(parser.get_body().left() == 0); + 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 == make_tuple(0, int(strlen(http1_response)), false)); + TEST_CHECK(parser.get_body().left() == 0); + 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 == make_tuple(0, int(strlen(close_response)), false)); + TEST_CHECK(parser.get_body().left() == 0); + 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 == make_tuple(0, int(strlen(keep_alive_response)), false)); + TEST_CHECK(parser.get_body().left() == 0); + 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 == 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 == 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); + + printf("payload: %d protocol: %d\n", received.get<0>(), received.get<1>()); + TEST_CHECK(received == 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()); + typedef std::pair chunk_range; + 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 == make_tuple(5, int(strlen(tracker_response) - 5), false)); + TEST_CHECK(parser.get_body().left() == 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 == 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(received.get<2>() == 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(received.get<2>() == 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(received.get<2>() == 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 == 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"; + boost::int64_t chunk_size; + int header_size; + bool ret = parser.parse_chunk_header(buffer::const_interval(chunk_header1, chunk_header1 + 10) + , &chunk_size, &header_size); + TEST_EQUAL(ret, false); + ret = parser.parse_chunk_header(buffer::const_interval(chunk_header1, 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(buffer::const_interval(chunk_header2, 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) + == make_tuple("http", "foo:bar", "host.com", 80, "/path/to/file")); + + TEST_CHECK(parse_url_components("http://host.com/path/to/file", ec) + == make_tuple("http", "", "host.com", -1, "/path/to/file")); + + TEST_CHECK(parse_url_components("ftp://host.com:21/path/to/file", ec) + == make_tuple("ftp", "", "host.com", 21, "/path/to/file")); + + TEST_CHECK(parse_url_components("http://host.com/path?foo:bar@foo:", ec) + == make_tuple("http", "", "host.com", -1, "/path?foo:bar@foo:")); + + TEST_CHECK(parse_url_components("http://192.168.0.1/path/to/file", ec) + == 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) + == make_tuple("http", "", "2001:ff00::1", 42, "/path/to/file")); + + // leading spaces are supposed to be stripped + TEST_CHECK(parse_url_components(" \thttp://[2001:ff00::1]:42/path/to/file", ec) + == make_tuple("http", "", "2001:ff00::1", 42, "/path/to/file")); + + parse_url_components("http://[2001:ff00::1:42/path/to/file", ec); + TEST_CHECK(ec == error_code(errors::expected_close_bracket_in_address)); + + parse_url_components("http:/", ec); + TEST_CHECK(ec == error_code(errors::unsupported_url_protocol)); + ec.clear(); + + parse_url_components("http:", ec); + TEST_CHECK(ec == error_code(errors::unsupported_url_protocol)); + ec.clear(); + + // test resolve_redirect_location + + TEST_EQUAL(resolve_redirect_location("http://example.com/a/b", "a") + , "http://example.com/a/a"); + + TEST_EQUAL(resolve_redirect_location("http://example.com/a/b", "c/d/e/") + , "http://example.com/a/c/d/e/"); + + TEST_EQUAL(resolve_redirect_location("http://example.com/a/b", "../a") + , "http://example.com/a/../a"); + + TEST_EQUAL(resolve_redirect_location("http://example.com/a/b", "/c") + , "http://example.com/c"); + + TEST_EQUAL(resolve_redirect_location("http://example.com/a/b", "http://test.com/d") + , "http://test.com/d"); + + TEST_EQUAL(resolve_redirect_location("my-custom-scheme://example.com/a/b", "http://test.com/d") + , "http://test.com/d"); + + TEST_EQUAL(resolve_redirect_location("http://example.com", "/d") + , "http://example.com/d"); + + TEST_EQUAL(resolve_redirect_location("http://example.com", "d") + , "http://example.com/d"); + + TEST_EQUAL(resolve_redirect_location("my-custom-scheme://example.com/a/b", "/d") + , "my-custom-scheme://example.com/d"); + + TEST_EQUAL(resolve_redirect_location("my-custom-scheme://example.com/a/b", "c/d") + , "my-custom-scheme://example.com/a/c/d"); + + // if the referrer is invalid, just respond the verbatim location + + TEST_EQUAL(resolve_redirect_location("example.com/a/b", "/c/d") + , "/c/d"); + + // is_ok_status + + TEST_EQUAL(is_ok_status(200), true); + TEST_EQUAL(is_ok_status(206), true); + TEST_EQUAL(is_ok_status(299), false); + TEST_EQUAL(is_ok_status(300), true); + TEST_EQUAL(is_ok_status(399), true); + TEST_EQUAL(is_ok_status(400), false); + TEST_EQUAL(is_ok_status(299), false); + + // is_redirect + + TEST_EQUAL(is_redirect(299), false); + TEST_EQUAL(is_redirect(100), false); + TEST_EQUAL(is_redirect(300), true); + TEST_EQUAL(is_redirect(399), true); + TEST_EQUAL(is_redirect(400), false); +} + +TORRENT_TEST(chunked_encoding) +{ + char const* chunked_input = + "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "Content-Type: text/plain\r\n" + "\r\n" + "4\r\ntest\r\n4\r\n1234\r\n10\r\n0123456789abcdef\r\n" + "0\r\n\r\n"; + + http_parser parser; + boost::tuple const received + = feed_bytes(parser, chunked_input); + + TEST_EQUAL(strlen(chunked_input), 24 + 94) + TEST_CHECK(received == make_tuple(24, 94, false)); + TEST_CHECK(parser.finished()); + + char mutable_buffer[100]; + memcpy(mutable_buffer, parser.get_body().begin, parser.get_body().left()); + int len = parser.collapse_chunk_headers(mutable_buffer, parser.get_body().left()); + + TEST_CHECK(std::equal(mutable_buffer, mutable_buffer + len, "test12340123456789abcdef")); +} + +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; + boost::tuple const received + = feed_bytes(parser, chunked_input); + + TEST_CHECK(boost::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; + boost::tuple const received + = feed_bytes(parser, chunked_input); + + TEST_CHECK(boost::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; + boost::tuple const received + = feed_bytes(parser, chunked_input); + + TEST_CHECK(boost::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; + boost::tuple const received + = feed_bytes(parser, chunked_input); + + TEST_CHECK(boost::get<2>(received) == true); +} + +TORRENT_TEST(invalid_chunk_afl) +{ + boost::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, // -89abc9abcdef + 0x67, 0x3a, 0x20, 0x63, 0x68, 0x75, 0x6e, 0x6b, // � + 0x65, 0x64, 0x0d, 0x0a, 0x0d, 0x0d, 0x0a, 0x0d, // T����������def + 0x0a, 0x0a, 0x2d, 0x38, 0x39, 0x61, 0x62, 0x63, // � + 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x0d, // T�����������est-headyr: foobar + 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; + boost::tuple const received + = feed_bytes(parser, reinterpret_cast(invalid_chunked_input)); + + TEST_CHECK(boost::get<2>(received) == true); +} + diff --git a/test/test_identify_client.cpp b/test/test_identify_client.cpp new file mode 100644 index 0000000..2683fca --- /dev/null +++ b/test/test_identify_client.cpp @@ -0,0 +1,50 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/identify_client.hpp" + +using namespace libtorrent; + +TORRENT_TEST(identify_client) +{ +#ifndef TORRENT_NO_DEPRECATE + TEST_EQUAL(identify_client(peer_id("-AZ123B-............")), "Azureus 1.2.3.11"); + TEST_EQUAL(identify_client(peer_id("-AZ1230-............")), "Azureus 1.2.3"); + TEST_EQUAL(identify_client(peer_id("S123--..............")), "Shadow 1.2.3"); + TEST_EQUAL(identify_client(peer_id("S\x1\x2\x3....\0...........")), "Shadow 1.2.3"); + TEST_EQUAL(identify_client(peer_id("M1-2-3--............")), "Mainline 1.2.3"); + TEST_EQUAL(identify_client(peer_id("\0\0\0\0\0\0\0\0\0\0\0\0........")), "Generic"); + TEST_EQUAL(identify_client(peer_id("-xx1230-............")), "xx 1.2.3"); +#endif +} + diff --git a/test/test_ip_filter.cpp b/test/test_ip_filter.cpp new file mode 100644 index 0000000..21ac404 --- /dev/null +++ b/test/test_ip_filter.cpp @@ -0,0 +1,303 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/ip_filter.hpp" +#include + +#include "test.hpp" +#include "settings.hpp" +#include "libtorrent/socket_io.hpp" +#include "libtorrent/session.hpp" + +/* + +Currently this test only tests that the filter can handle +IPv4 addresses. Maybe it should be extended to IPv6 as well, +but the actual code is just a template, so it is probably +pretty safe to assume that as long as it works for IPv4 it +also works for IPv6. + +*/ + +using namespace libtorrent; + +template +bool compare(ip_range const& lhs + , ip_range const& rhs) +{ + return lhs.first == rhs.first + && lhs.last == rhs.last + && lhs.flags == rhs.flags; +} + +#define IP(x) address::from_string(x, ec) +#define IP4(x) address_v4::from_string(x, ec) +#define IP6(x) address_v6::from_string(x, ec) + +template +void test_rules_invariant(std::vector > const& r, ip_filter const& f) +{ + typedef typename std::vector >::const_iterator iterator; + TEST_CHECK(!r.empty()); + if (r.empty()) return; + + error_code ec; + if (sizeof(r.front().first) == sizeof(address_v4)) + { + TEST_CHECK(r.front().first == IP("0.0.0.0")); + TEST_CHECK(r.back().last == IP("255.255.255.255")); + } + else + { + TEST_CHECK(r.front().first == IP("::0")); + TEST_CHECK(r.back().last == IP("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")); + } + + for (iterator i(r.begin()), j(boost::next(r.begin())) + , end(r.end()); j != end; ++j, ++i) + { + TEST_EQUAL(f.access(i->last), int(i->flags)); + TEST_EQUAL(f.access(j->first), int(j->flags)); + TEST_CHECK(detail::plus_one(i->last.to_bytes()) == j->first.to_bytes()); + } +} + +TORRENT_TEST(session_get_ip_filter) +{ + using namespace libtorrent; + session ses(settings()); + ip_filter const& ipf = ses.get_ip_filter(); +#if TORRENT_USE_IPV6 + TEST_EQUAL(boost::get<0>(ipf.export_filter()).size(), 1); +#else + TEST_EQUAL(ipf.export_filter().size(), 1); +#endif +} + +TORRENT_TEST(ip_filter) +{ + using namespace libtorrent; + + std::vector > range; + error_code ec; + + // **** test joining of ranges at the end **** + ip_range expected1[] = + { + {IP4("0.0.0.0"), IP4("0.255.255.255"), 0} + , {IP4("1.0.0.0"), IP4("3.0.0.0"), ip_filter::blocked} + , {IP4("3.0.0.1"), IP4("255.255.255.255"), 0} + }; + + { + ip_filter f; + f.add_rule(IP("1.0.0.0"), IP("2.0.0.0"), ip_filter::blocked); + f.add_rule(IP("2.0.0.1"), IP("3.0.0.0"), ip_filter::blocked); + +#if TORRENT_USE_IPV6 + range = boost::get<0>(f.export_filter()); +#else + range = f.export_filter(); +#endif + test_rules_invariant(range, f); + + TEST_CHECK(range.size() == 3); + TEST_CHECK(std::equal(range.begin(), range.end(), expected1, &compare)); + + } + + // **** test joining of ranges at the start **** + + { + ip_filter f; + f.add_rule(IP("2.0.0.1"), IP("3.0.0.0"), ip_filter::blocked); + f.add_rule(IP("1.0.0.0"), IP("2.0.0.0"), ip_filter::blocked); + +#if TORRENT_USE_IPV6 + range = boost::get<0>(f.export_filter()); +#else + range = f.export_filter(); +#endif + test_rules_invariant(range, f); + + TEST_CHECK(range.size() == 3); + TEST_CHECK(std::equal(range.begin(), range.end(), expected1, &compare)); + + } + + + // **** test joining of overlapping ranges at the start **** + + { + ip_filter f; + f.add_rule(IP("2.0.0.1"), IP("3.0.0.0"), ip_filter::blocked); + f.add_rule(IP("1.0.0.0"), IP("2.4.0.0"), ip_filter::blocked); + +#if TORRENT_USE_IPV6 + range = boost::get<0>(f.export_filter()); +#else + range = f.export_filter(); +#endif + test_rules_invariant(range, f); + + TEST_CHECK(range.size() == 3); + TEST_CHECK(std::equal(range.begin(), range.end(), expected1, &compare)); + + } + + + // **** test joining of overlapping ranges at the end **** + + { + ip_filter f; + f.add_rule(IP("1.0.0.0"), IP("2.4.0.0"), ip_filter::blocked); + f.add_rule(IP("2.0.0.1"), IP("3.0.0.0"), ip_filter::blocked); + +#if TORRENT_USE_IPV6 + range = boost::get<0>(f.export_filter()); +#else + range = f.export_filter(); +#endif + test_rules_invariant(range, f); + + TEST_CHECK(range.size() == 3); + TEST_CHECK(std::equal(range.begin(), range.end(), expected1, &compare)); + + } + + + // **** test joining of multiple overlapping ranges 1 **** + + { + ip_filter f; + f.add_rule(IP("1.0.0.0"), IP("2.0.0.0"), ip_filter::blocked); + f.add_rule(IP("3.0.0.0"), IP("4.0.0.0"), ip_filter::blocked); + f.add_rule(IP("5.0.0.0"), IP("6.0.0.0"), ip_filter::blocked); + f.add_rule(IP("7.0.0.0"), IP("8.0.0.0"), ip_filter::blocked); + + f.add_rule(IP("1.0.1.0"), IP("9.0.0.0"), ip_filter::blocked); + +#if TORRENT_USE_IPV6 + range = boost::get<0>(f.export_filter()); +#else + range = f.export_filter(); +#endif + test_rules_invariant(range, f); + + TEST_CHECK(range.size() == 3); + ip_range expected[] = + { + {IP4("0.0.0.0"), IP4("0.255.255.255"), 0} + , {IP4("1.0.0.0"), IP4("9.0.0.0"), ip_filter::blocked} + , {IP4("9.0.0.1"), IP4("255.255.255.255"), 0} + }; + + TEST_CHECK(std::equal(range.begin(), range.end(), expected, &compare)); + + } + + // **** test joining of multiple overlapping ranges 2 **** + + { + ip_filter f; + f.add_rule(IP("1.0.0.0"), IP("2.0.0.0"), ip_filter::blocked); + f.add_rule(IP("3.0.0.0"), IP("4.0.0.0"), ip_filter::blocked); + f.add_rule(IP("5.0.0.0"), IP("6.0.0.0"), ip_filter::blocked); + f.add_rule(IP("7.0.0.0"), IP("8.0.0.0"), ip_filter::blocked); + + f.add_rule(IP("0.0.1.0"), IP("7.0.4.0"), ip_filter::blocked); + +#if TORRENT_USE_IPV6 + range = boost::get<0>(f.export_filter()); +#else + range = f.export_filter(); +#endif + test_rules_invariant(range, f); + + TEST_CHECK(range.size() == 3); + ip_range expected[] = + { + {IP4("0.0.0.0"), IP4("0.0.0.255"), 0} + , {IP4("0.0.1.0"), IP4("8.0.0.0"), ip_filter::blocked} + , {IP4("8.0.0.1"), IP4("255.255.255.255"), 0} + }; + + TEST_CHECK(std::equal(range.begin(), range.end(), expected, &compare)); + + } + + // **** test IPv6 **** + +#if TORRENT_USE_IPV6 + + ip_range expected2[] = + { + {IP6("::0"), IP6("0:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), 0} + , {IP6("1::"), IP6("3::"), ip_filter::blocked} + , {IP6("3::1"), IP6("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), 0} + }; + + { + ip_filter f; + f.add_rule(IP("2::1"), IP("3::"), ip_filter::blocked); + f.add_rule(IP("1::"), IP("2::"), ip_filter::blocked); + + std::vector > range; + range = boost::get<1>(f.export_filter()); + test_rules_invariant(range, f); + + TEST_EQUAL(range.size(), 3); + TEST_CHECK(std::equal(range.begin(), range.end(), expected2, &compare)); + + } +#endif + + port_filter pf; + + // default contructed 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..a41efef --- /dev/null +++ b/test/test_ip_voter.cpp @@ -0,0 +1,216 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/ip_voter.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/socket_io.hpp" +#include "libtorrent/aux_/session_impl.hpp" +#include "libtorrent/broadcast_socket.hpp" // for supports_ipv6() +#include "setup_transfer.hpp" // for rand_v4 + +using namespace libtorrent; + +bool cast_vote(ip_voter& ipv, address ext_ip, address voter) +{ + bool new_ip = ipv.cast_vote(ext_ip, 1, voter); + fprintf(stderr, "%15s -> %-15s\n" + , print_address(voter).c_str() + , print_address(ext_ip).c_str()); + if (new_ip) + { + fprintf(stderr, " \x1b[1mnew external IP: %s\x1b[0m\n" + , print_address(ipv.external_address()).c_str()); + } + return new_ip; +} + +// test the case where every time we get a new IP. Make sure +// we don't flap +TORRENT_TEST(test_random) +{ + init_rand_address(); + + ip_voter ipv; + + address_v4 addr1(address_v4::from_string("51.41.61.132")); + + bool new_ip = cast_vote(ipv, addr1, rand_v4()); + TEST_CHECK(new_ip); + TEST_CHECK(ipv.external_address() == addr1); + for (int i = 0; i < 1000; ++i) + { + new_ip = cast_vote(ipv, rand_v4(), rand_v4()); + TEST_CHECK(!new_ip); + } + TEST_CHECK(ipv.external_address() == addr1); +} + +TORRENT_TEST(two_ips) +{ + init_rand_address(); + + ip_voter ipv; + + address_v4 addr1(address_v4::from_string("51.1.1.1")); + address_v4 addr2(address_v4::from_string("53.3.3.3")); + + // addr1 is the first address we see, which is the one we pick. Even though + // we'll have as many votes for addr2, we shouldn't flap, since addr2 never + // gets an overwhelming majority. + bool new_ip = cast_vote(ipv, addr1, rand_v4()); + TEST_CHECK(new_ip); + for (int i = 0; i < 1000; ++i) + { + new_ip = cast_vote(ipv, addr2, rand_v4()); + TEST_CHECK(!new_ip); + new_ip = cast_vote(ipv, rand_v4(), rand_v4()); + TEST_CHECK(!new_ip); + new_ip = cast_vote(ipv, addr1, rand_v4()); + TEST_CHECK(!new_ip); + + TEST_CHECK(ipv.external_address() == addr1); + } +} + +TORRENT_TEST(one_ip) +{ + init_rand_address(); + + ip_voter ipv; + + address_v4 start_addr(address_v4::from_string("93.12.63.174")); + address_v4 addr1(address_v4::from_string("51.1.1.1")); + address_v4 addr2(address_v4::from_string("53.3.3.3")); + + bool new_ip = cast_vote(ipv, start_addr, rand_v4()); + TEST_CHECK(new_ip); + TEST_CHECK(ipv.external_address() != addr1); + TEST_CHECK(ipv.external_address() == start_addr); + for (int i = 0; i < 30; ++i) + { + new_ip = cast_vote(ipv, addr2, rand_v4()); + if (new_ip) break; + new_ip = cast_vote(ipv, rand_v4(), rand_v4()); + if (new_ip) break; + new_ip = cast_vote(ipv, addr1, rand_v4()); + if (new_ip) break; + new_ip = cast_vote(ipv, addr1, rand_v4()); + if (new_ip) break; + + } + + TEST_CHECK(ipv.external_address() == addr1); + + for (int i = 0; i < 500; ++i) + { + new_ip = cast_vote(ipv, addr2, rand_v4()); + TEST_CHECK(!new_ip); + new_ip = cast_vote(ipv, rand_v4(), rand_v4()); + TEST_CHECK(!new_ip); + new_ip = cast_vote(ipv, addr1, rand_v4()); + TEST_CHECK(!new_ip); + new_ip = cast_vote(ipv, addr1, rand_v4()); + TEST_CHECK(!new_ip); + } + + TEST_CHECK(ipv.external_address() == addr1); +} + +TORRENT_TEST(externa_ip_1) +{ + init_rand_address(); + + // test external ip voting + external_ip ipv1; + + // test a single malicious node + // adds 50 legitimate responses from different peers + // and 50 malicious responses from the same peer + error_code ec; + address real_external = address_v4::from_string("5.5.5.5", ec); + TEST_CHECK(!ec); + address malicious = address_v4::from_string("4.4.4.4", ec); + TEST_CHECK(!ec); + for (int i = 0; i < 50; ++i) + { + ipv1.cast_vote(real_external, aux::session_impl::source_dht, rand_v4()); + ipv1.cast_vote(rand_v4(), aux::session_impl::source_dht, malicious); + } + TEST_CHECK(ipv1.external_address(rand_v4()) == real_external); +} + +TORRENT_TEST(externa_ip_2) +{ + init_rand_address(); + + external_ip ipv2; + + // test a single malicious node + // adds 50 legitimate responses from different peers + // and 50 consistent malicious responses from the same peer + error_code ec; + address malicious = address_v4::from_string("4.4.4.4", ec); + TEST_CHECK(!ec); + address real_external1 = address_v4::from_string("5.5.5.5", ec); + TEST_CHECK(!ec); + address real_external2; +#if TORRENT_USE_IPV6 + if (supports_ipv6()) + { + real_external2 = address_v6::from_string("2f80::", ec); + TEST_CHECK(!ec); + } +#endif + malicious = address_v4::from_string("4.4.4.4", ec); + TEST_CHECK(!ec); + address malicious_external = address_v4::from_string("3.3.3.3", ec); + TEST_CHECK(!ec); + for (int i = 0; i < 50; ++i) + { + ipv2.cast_vote(real_external1, aux::session_impl::source_dht, rand_v4()); +#if TORRENT_USE_IPV6 + if (supports_ipv6()) + ipv2.cast_vote(real_external2, aux::session_impl::source_dht, rand_v6()); +#endif + ipv2.cast_vote(malicious_external, aux::session_impl::source_dht, malicious); + } + TEST_CHECK(ipv2.external_address(rand_v4()) == real_external1); +#if TORRENT_USE_IPV6 + if (supports_ipv6()) + TEST_CHECK(ipv2.external_address(rand_v6()) == real_external2); +#endif + +} + diff --git a/test/test_linked_list.cpp b/test/test_linked_list.cpp new file mode 100644 index 0000000..ccbb73c --- /dev/null +++ b/test/test_linked_list.cpp @@ -0,0 +1,198 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/linked_list.hpp" + +using namespace libtorrent; + +struct test_node : list_node +{ + test_node(int v) : val(v) {} + int val; +}; + +void compare(linked_list const& list, int* array, int size) +{ + TEST_EQUAL(list.size(), size); + + int idx = 0; + for (test_node const* i = list.front(); i != NULL; i = i->next, ++idx) + { + TEST_EQUAL(i->val, array[idx]); + } +} + +TORRENT_TEST(push_back) +{ + test_node n0(0); + test_node n1(1); + + linked_list list; + + list.push_back(&n0); + list.push_back(&n1); + + int expected[] = { 0, 1 }; + compare(list, expected, 2); +} + +TORRENT_TEST(push_front) +{ + test_node n0(0); + test_node n1(1); + + linked_list list; + + list.push_back(&n1); + list.push_front(&n0); + + int expected[] = { 0, 1 }; + compare(list, expected, 2); +} + +TORRENT_TEST(erase_begin) +{ + test_node n0(0); + test_node n1(1); + test_node n2(2); + + linked_list list; + + list.push_back(&n0); + list.push_back(&n1); + list.push_back(&n2); + + list.erase(&n0); + + int expected[] = { 1, 2 }; + compare(list, expected, 2); +} + +TORRENT_TEST(erase_end) +{ + test_node n0(0); + test_node n1(1); + test_node n2(2); + + linked_list list; + + list.push_back(&n0); + list.push_back(&n1); + list.push_back(&n2); + + list.erase(&n2); + + int expected[] = { 0, 1 }; + compare(list, expected, 2); +} + +TORRENT_TEST(erase_middle) +{ + test_node n0(0); + test_node n1(1); + test_node n2(2); + + linked_list list; + + list.push_back(&n0); + list.push_back(&n1); + list.push_back(&n2); + + list.erase(&n1); + + int expected[] = { 0, 2 }; + compare(list, expected, 2); +} + +TORRENT_TEST(erase_last) +{ + test_node n0(0); + + linked_list list; + + list.push_back(&n0); + + list.erase(&n0); + + int expected[] = { -1 }; + compare(list, expected, 0); + + TEST_CHECK(list.empty()); +} + +TORRENT_TEST(iterate_forward) +{ + test_node n0(0); + test_node n1(1); + test_node n2(2); + + linked_list list; + + list.push_back(&n0); + list.push_back(&n1); + list.push_back(&n2); + + list_iterator it = list.iterate(); + TEST_EQUAL(it.get(), &n0); + it.next(); + TEST_EQUAL(it.get(), &n1); + it.next(); + TEST_EQUAL(it.get(), &n2); + it.next(); + TEST_EQUAL(it.get(), static_cast(NULL)); +} + +TORRENT_TEST(iterate_backward) +{ + test_node n0(0); + test_node n1(1); + test_node n2(2); + + linked_list list; + + list.push_back(&n0); + list.push_back(&n1); + list.push_back(&n2); + + list_iterator it = list.iterate(); + it.next(); + it.next(); + TEST_EQUAL(it.get(), &n2); + it.prev(); + TEST_EQUAL(it.get(), &n1); + it.prev(); + TEST_EQUAL(it.get(), &n0); + it.prev(); + TEST_EQUAL(it.get(), static_cast(NULL)); +} + diff --git a/test/test_lsd.cpp b/test/test_lsd.cpp new file mode 100644 index 0000000..f550aec --- /dev/null +++ b/test/test_lsd.cpp @@ -0,0 +1,116 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/session.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/torrent_status.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/thread.hpp" +#include + +#include "test.hpp" +#include "setup_transfer.hpp" +#include + +void test_lsd() +{ + using namespace libtorrent; + namespace lt = libtorrent; + + // 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_bool(settings_pack::allow_multiple_connections_per_ip, true); + 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, "127.0.0.1:48100"); + + lt::session ses1(pack); + + pack.set_str(settings_pack::listen_interfaces, "127.0.0.1:49100"); + lt::session ses2(pack); + + torrent_handle tor1; + torrent_handle tor2; + + using boost::tuples::ignore; + boost::tie(tor1, tor2, ignore) = setup_transfer(&ses1, &ses2, 0, true, false, false, "_lsd" + , 16 * 1024, 0, false, 0, false); + + for (int i = 0; i < 30; ++i) + { + print_alerts(ses1, "ses1", true); + print_alerts(ses2, "ses2", true); + + torrent_status st1 = tor1.status(); + torrent_status st2 = tor2.status(); + + print_ses_rate(i, &st1, &st2); + + if (st2.is_seeding /*&& st3.is_seeding*/) break; + test_sleep(1000); + } + + TEST_CHECK(tor2.status().is_seeding); + + if (tor2.status().is_seeding) std::cerr << "done\n"; + + // this allows shutting down the sessions in parallel + p1 = ses1.abort(); + p2 = ses2.abort(); +} + +TORRENT_TEST(lsd) +{ + using namespace libtorrent; + + // 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..df20dee --- /dev/null +++ b/test/test_magnet.cpp @@ -0,0 +1,392 @@ +/* + +Copyright (c) 2012, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "libtorrent/magnet_uri.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/torrent_info.hpp" // for announce_entry +#include "libtorrent/announce_entry.hpp" +#include "settings.hpp" + +using namespace libtorrent; +namespace lt = libtorrent; + +void test_remove_url(std::string url) +{ + lt::session s(settings()); + add_torrent_params p; + p.flags &= ~add_torrent_params::flag_paused; + p.flags &= ~add_torrent_params::flag_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); +} + +TORRENT_TEST(remove_url) +{ + test_remove_url("magnet:?xt=urn:btih:0123456789abcdef0123456789abcdef01234567"); +} + +TORRENT_TEST(remove_url2) +{ + test_remove_url("http://non-existent.com/test.torrent"); +} + +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::upnp_ignore_nonrouters, true); + pack.set_bool(settings_pack::coalesce_writes, true); + pack.set_bool(settings_pack::close_redundant_connections, false); + pack.set_int(settings_pack::auto_scrape_interval, 235); + pack.set_int(settings_pack::auto_scrape_min_interval, 62); + boost::scoped_ptr s(new lt::session(pack)); + + TEST_EQUAL(pack.get_str(settings_pack::user_agent), "test"); + TEST_EQUAL(pack.get_int(settings_pack::tracker_receive_timeout), 1234); + +#ifndef TORRENT_DISABLE_DHT + dht_settings dhts; + dhts.max_peers_reply = 70; + s->set_dht_settings(dhts); +#endif +/* +#ifndef TORRENT_DISABLE_DHT + dht_settings dht_sett; + s->set_dht_settings(dht_sett); +#endif +*/ + entry session_state; + s->save_state(session_state); + + // test magnet link parsing + add_torrent_params p; + p.flags &= ~add_torrent_params::flag_paused; + p.flags &= ~add_torrent_params::flag_auto_managed; + p.save_path = "."; + error_code ec; + p.url = "magnet:?xt=urn:btih:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" + "&tr=http://1" + "&tr=http://2" + "&tr=http://3" + "&dn=foo" + "&dht=127.0.0.1:43"; + torrent_handle t = s->add_torrent(p, ec); + TEST_CHECK(!ec); + if (ec) fprintf(stderr, "%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.url = "magnet:" + "?tr=http://1" + "&tr=http://2" + "&dn=foo" + "&dht=127.0.0.1:43" + "&xt=urn:btih:c352cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"; + torrent_handle t2 = s->add_torrent(p, ec); + TEST_CHECK(!ec); + if (ec) fprintf(stderr, "%s\n", ec.message().c_str()); + + trackers = t2.trackers(); + TEST_EQUAL(trackers.size(), 2); + + p.url = "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"; + torrent_handle t3 = s->add_torrent(p, ec); + TEST_CHECK(!ec); + if (ec) fprintf(stderr, "%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"); + fprintf(stderr, "1: %s\n", trackers[0].url.c_str()); + } + if (trackers.size() > 1) + { + TEST_EQUAL(trackers[1].url, "udp://tracker.publicbt.com:80"); + fprintf(stderr, "2: %s\n", trackers[1].url.c_str()); + } + if (trackers.size() > 2) + { + TEST_EQUAL(trackers[2].url, "udp://tracker.ccc.de:80"); + fprintf(stderr, "3: %s\n", trackers[2].url.c_str()); + } + + TEST_EQUAL(to_hex(t.info_hash().to_string()), "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"); + + p1 = s->abort(); + s.reset(new lt::session(settings())); + + std::vector buf; + bencode(std::back_inserter(buf), session_state); + bdecode_node session_state2; + int ret = bdecode(&buf[0], &buf[0] + buf.size(), session_state2, ec); + TEST_CHECK(ret == 0); + + fprintf(stderr, "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") == 0); + + s->load_state(session_state2); + +#define CMP_SET(x) fprintf(stderr, #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_missing_hash) +{ + // parse_magnet_uri + error_code ec; + add_torrent_params p; + parse_magnet_uri("magnet:?dn=foo&dht=127.0.0.1:43", p, ec); + TEST_EQUAL(ec, error_code(errors::missing_info_hash_in_uri)); + ec.clear(); +} + +TORRENT_TEST(parse_base32_hash) +{ + // parse_magnet_uri + error_code ec; + add_torrent_params p; + parse_magnet_uri("magnet:?xt=urn:btih:MFRGCYTBMJQWEYLCMFRGCYTBMJQWEYLC", p, ec); + TEST_CHECK(!ec); + TEST_EQUAL(p.info_hash, sha1_hash("abababababababababab")); + ec.clear(); +} + +TORRENT_TEST(parse_web_seeds) +{ + // parse_magnet_uri + error_code ec; + add_torrent_params p; + parse_magnet_uri("magnet:?xt=urn:btih:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd&ws=http://foo.com/bar&ws=http://bar.com/foo", p, ec); + TEST_CHECK(!ec); + 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"); + ec.clear(); +} + +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", p, ec); + TEST_EQUAL(ec, error_code(errors::missing_info_hash_in_uri)); + ec.clear(); +} + +TORRENT_TEST(parse_short_hash) +{ + error_code ec; + add_torrent_params p; + parse_magnet_uri("magnet:?xt=urn:btih:abababab", p, ec); + TEST_EQUAL(ec, error_code(errors::invalid_info_hash)); + ec.clear(); +} + +TORRENT_TEST(parse_long_hash) +{ + error_code ec; + add_torrent_params p; + parse_magnet_uri("magnet:?xt=urn:btih:ababababababababababab", p, ec); + TEST_EQUAL(ec, error_code(errors::invalid_info_hash)); + ec.clear(); +} + +TORRENT_TEST(parse_space_hash) +{ + error_code ec; + add_torrent_params p; + parse_magnet_uri("magnet:?xt=urn:btih: abababababababababab", p, ec); + TEST_EQUAL(ec, error_code(errors::invalid_info_hash)); + ec.clear(); +} + +TORRENT_TEST(parse_peer) +{ + std::vector peers; + parse_magnet_uri_peers("magnet:?xt=urn:btih:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd&dn=foo&x.pe=127.0.0.1:43&x.pe=&x.pe=:100&x.pe=[::1]:45", peers); +#if TORRENT_USE_IPV6 + TEST_EQUAL(peers.size(), 2); + TEST_EQUAL(peers[0], ep("127.0.0.1", 43)); + TEST_EQUAL(peers[1], ep("::1", 45)); +#else + TEST_EQUAL(peers.size(), 1); + TEST_EQUAL(peers[0], ep("127.0.0.1", 43)); +#endif +} + +#ifndef TORRENT_DISABLE_DHT +TORRENT_TEST(parse_dht_node) +{ + error_code ec; + add_torrent_params p; + parse_magnet_uri("magnet:?xt=urn:btih:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd&dn=foo&dht=127.0.0.1:43", p, ec); + TEST_CHECK(!ec); + if (ec) fprintf(stderr, "%s\n", ec.message().c_str()); + ec.clear(); + + TEST_EQUAL(p.dht_nodes.size(), 1); + TEST_EQUAL(p.dht_nodes[0].first, "127.0.0.1"); + TEST_EQUAL(p.dht_nodes[0].second, 43); +} +#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'); + printf("%s\n", &buf[0]); + error_code ec; + torrent_info ti(&buf[0], buf.size(), ec); + + TEST_EQUAL(al.size(), ti.trackers().size()); + + std::string magnet = make_magnet_uri(ti); + 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'); + printf("%s\n", &buf[0]); + error_code ec; + torrent_info ti(&buf[0], buf.size(), ec); + + std::string magnet = make_magnet_uri(ti); + 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); +} + diff --git a/test/test_merkle.cpp b/test/test_merkle.cpp new file mode 100644 index 0000000..1b0d7e0 --- /dev/null +++ b/test/test_merkle.cpp @@ -0,0 +1,107 @@ +/* + +Copyright (c) 2012, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/aux_/merkle.hpp" + +TORRENT_TEST(merkle) +{ + using namespace libtorrent; + + // test merkle_*() functions + + // this is the structure: + // 0 + // 1 2 + // 3 4 5 6 + // 7 8 9 10 11 12 13 14 + // num_leafs = 8 + + TEST_EQUAL(merkle_num_leafs(1), 1); + TEST_EQUAL(merkle_num_leafs(2), 2); + TEST_EQUAL(merkle_num_leafs(3), 4); + TEST_EQUAL(merkle_num_leafs(4), 4); + TEST_EQUAL(merkle_num_leafs(5), 8); + TEST_EQUAL(merkle_num_leafs(6), 8); + TEST_EQUAL(merkle_num_leafs(7), 8); + TEST_EQUAL(merkle_num_leafs(8), 8); + TEST_EQUAL(merkle_num_leafs(9), 16); + TEST_EQUAL(merkle_num_leafs(10), 16); + TEST_EQUAL(merkle_num_leafs(11), 16); + TEST_EQUAL(merkle_num_leafs(12), 16); + TEST_EQUAL(merkle_num_leafs(13), 16); + TEST_EQUAL(merkle_num_leafs(14), 16); + TEST_EQUAL(merkle_num_leafs(15), 16); + TEST_EQUAL(merkle_num_leafs(16), 16); + TEST_EQUAL(merkle_num_leafs(17), 32); + TEST_EQUAL(merkle_num_leafs(18), 32); + + // parents + TEST_EQUAL(merkle_get_parent(1), 0); + TEST_EQUAL(merkle_get_parent(2), 0); + TEST_EQUAL(merkle_get_parent(3), 1); + TEST_EQUAL(merkle_get_parent(4), 1); + TEST_EQUAL(merkle_get_parent(5), 2); + TEST_EQUAL(merkle_get_parent(6), 2); + TEST_EQUAL(merkle_get_parent(7), 3); + TEST_EQUAL(merkle_get_parent(8), 3); + TEST_EQUAL(merkle_get_parent(9), 4); + TEST_EQUAL(merkle_get_parent(10), 4); + TEST_EQUAL(merkle_get_parent(11), 5); + TEST_EQUAL(merkle_get_parent(12), 5); + TEST_EQUAL(merkle_get_parent(13), 6); + TEST_EQUAL(merkle_get_parent(14), 6); + + // siblings + TEST_EQUAL(merkle_get_sibling(1), 2); + TEST_EQUAL(merkle_get_sibling(2), 1); + TEST_EQUAL(merkle_get_sibling(3), 4); + TEST_EQUAL(merkle_get_sibling(4), 3); + TEST_EQUAL(merkle_get_sibling(5), 6); + TEST_EQUAL(merkle_get_sibling(6), 5); + TEST_EQUAL(merkle_get_sibling(7), 8); + TEST_EQUAL(merkle_get_sibling(8), 7); + TEST_EQUAL(merkle_get_sibling(9), 10); + TEST_EQUAL(merkle_get_sibling(10), 9); + TEST_EQUAL(merkle_get_sibling(11), 12); + TEST_EQUAL(merkle_get_sibling(12), 11); + TEST_EQUAL(merkle_get_sibling(13), 14); + TEST_EQUAL(merkle_get_sibling(14), 13); + + // total number of nodes given the number of leafs + TEST_EQUAL(merkle_num_nodes(1), 1); + TEST_EQUAL(merkle_num_nodes(2), 3); + TEST_EQUAL(merkle_num_nodes(4), 7); + TEST_EQUAL(merkle_num_nodes(8), 15); + TEST_EQUAL(merkle_num_nodes(16), 31); +} + diff --git a/test/test_packet_buffer.cpp b/test/test_packet_buffer.cpp new file mode 100644 index 0000000..7452034 --- /dev/null +++ b/test/test_packet_buffer.cpp @@ -0,0 +1,166 @@ +/* + +Copyright (c) 2012, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/packet_buffer.hpp" + +using libtorrent::packet_buffer; + +// test packet_buffer +TORRENT_TEST(insert) +{ + packet_buffer pb; + + int a123 = 123; + int a125 = 125; + int a500 = 500; + int a501 = 501; + + TEST_EQUAL(pb.capacity(), 0); + TEST_EQUAL(pb.size(), 0); + TEST_EQUAL(pb.span(), 0); + + pb.insert(123, &a123); + TEST_EQUAL(pb.at(123 + 16), 0); + + TEST_CHECK(pb.at(123) == &a123); + TEST_CHECK(pb.capacity() > 0); + TEST_EQUAL(pb.size(), 1); + TEST_EQUAL(pb.span(), 1); + TEST_EQUAL(pb.cursor(), 123); + + pb.insert(125, &a125); + + TEST_CHECK(pb.at(125) == &a125); + TEST_EQUAL(pb.size(), 2); + TEST_EQUAL(pb.span(), 3); + TEST_EQUAL(pb.cursor(), 123); + + pb.insert(500, &a500); + TEST_EQUAL(pb.size(), 3); + TEST_EQUAL(pb.span(), 501 - 123); + TEST_EQUAL(pb.capacity(), 512); + + pb.insert(500, &a501); + TEST_EQUAL(pb.size(), 3); + pb.insert(500, &a500); + TEST_EQUAL(pb.size(), 3); + + TEST_CHECK(pb.remove(123) == &a123); + TEST_EQUAL(pb.size(), 2); + TEST_EQUAL(pb.span(), 501 - 125); + TEST_EQUAL(pb.cursor(), 125); + TEST_CHECK(pb.remove(125) == &a125); + TEST_EQUAL(pb.size(), 1); + TEST_EQUAL(pb.span(), 1); + TEST_EQUAL(pb.cursor(), 500); + + TEST_CHECK(pb.remove(500) == &a500); + TEST_EQUAL(pb.size(), 0); + TEST_EQUAL(pb.span(), 0); + + for (int i = 0; i < 0xff; ++i) + { + int index = (i + 0xfff0) & 0xffff; + pb.insert(index, reinterpret_cast(index + 1)); + fprintf(stderr, "insert: %u (mask: %x)\n", index, int(pb.capacity() - 1)); + TEST_EQUAL(pb.capacity(), 512); + if (i >= 14) + { + index = (index - 14) & 0xffff; + fprintf(stderr, "remove: %u\n", index); + TEST_CHECK(pb.remove(index) == reinterpret_cast(index + 1)); + TEST_EQUAL(pb.size(), 14); + } + } +} + +TORRENT_TEST(wrap) +{ + // test wrapping the indices + packet_buffer pb; + + TEST_EQUAL(pb.size(), 0); + + pb.insert(0xfffe, (void*)1); + TEST_CHECK(pb.at(0xfffe) == (void*)1); + + pb.insert(2, (void*)2); + TEST_CHECK(pb.at(2) == (void*)2); + + pb.remove(0xfffe); + TEST_CHECK(pb.at(0xfffe) == (void*)0); + TEST_CHECK(pb.at(2) == (void*)2); +} + +TORRENT_TEST(wrap2) +{ + // test wrapping the indices + packet_buffer pb; + + TEST_EQUAL(pb.size(), 0); + + pb.insert(0xfff3, (void*)1); + TEST_CHECK(pb.at(0xfff3) == (void*)1); + + int new_index = (0xfff3 + pb.capacity()) & 0xffff; + pb.insert(new_index, (void*)2); + TEST_CHECK(pb.at(new_index) == (void*)2); + + void* old = pb.remove(0xfff3); + TEST_CHECK(old == (void*)1); + TEST_CHECK(pb.at(0xfff3) == (void*)0); + TEST_CHECK(pb.at(new_index) == (void*)2); +} + +TORRENT_TEST(reverse_wrap) +{ + // test wrapping the indices backwards + packet_buffer pb; + + TEST_EQUAL(pb.size(), 0); + + pb.insert(0xfff3, (void*)1); + TEST_CHECK(pb.at(0xfff3) == (void*)1); + + int new_index = (0xfff3 + pb.capacity()) & 0xffff; + pb.insert(new_index, (void*)2); + TEST_CHECK(pb.at(new_index) == (void*)2); + + void* old = pb.remove(0xfff3); + TEST_CHECK(old == (void*)1); + TEST_CHECK(pb.at(0xfff3) == (void*)0); + TEST_CHECK(pb.at(new_index) == (void*)2); + + pb.insert(0xffff, (void*)0xffff); +} + diff --git a/test/test_part_file.cpp b/test/test_part_file.cpp new file mode 100644 index 0000000..7176978 --- /dev/null +++ b/test/test_part_file.cpp @@ -0,0 +1,147 @@ +/* + +Copyright (c) 2012, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include "test.hpp" +#include "libtorrent/part_file.hpp" +#include "libtorrent/file.hpp" +#include "libtorrent/error_code.hpp" + +using namespace libtorrent; + +TORRENT_TEST(part_file) +{ + error_code ec; + std::string cwd = complete("."); + + remove_all(combine_path(cwd, "partfile_test_dir"), ec); + if (ec) fprintf(stderr, "remove_all: %s\n", ec.message().c_str()); + remove_all(combine_path(cwd, "partfile_test_dir2"), ec); + if (ec) fprintf(stderr, "remove_all: %s\n", ec.message().c_str()); + + int piece_size = 16 * 0x4000; + char buf[1024]; + + { + create_directory(combine_path(cwd, "partfile_test_dir"), ec); + if (ec) fprintf(stderr, "create_directory: %s\n", ec.message().c_str()); + create_directory(combine_path(cwd, "partfile_test_dir2"), ec); + if (ec) fprintf(stderr, "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) fprintf(stderr, "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[i] = i; + + file::iovec_t v = {&buf, 1024}; + pf.writev(&v, 1, 10, 0, ec); + if (ec) fprintf(stderr, "part_file::writev: %s\n", ec.message().c_str()); + + pf.flush_metadata(ec); + if (ec) fprintf(stderr, "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) fprintf(stderr, "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"))); + + memset(buf, 0, sizeof(buf)); + + pf.readv(&v, 1, 10, 0, ec); + if (ec) fprintf(stderr, "part_file::readv: %s\n", ec.message().c_str()); + + for (int i = 0; i < 1024; ++i) + TEST_CHECK(buf[i] == char(i)); + } + + { + // load the part file back in + part_file pf(combine_path(cwd, "partfile_test_dir2"), "partfile.parts", 100, piece_size); + + memset(buf, 0, sizeof(buf)); + + file::iovec_t v = {&buf, 1024}; + pf.readv(&v, 1, 10, 0, ec); + if (ec) fprintf(stderr, "part_file::readv: %s\n", ec.message().c_str()); + + for (int i = 0; i < 1024; ++i) + TEST_CHECK(buf[i] == char(i)); + + // test exporting the piece to a file + + std::string output_filename = combine_path(combine_path(cwd, "partfile_test_dir") + , "part_file_test_export"); + file output(output_filename, file::read_write, ec); + if (ec) fprintf(stderr, "export open file: %s\n", ec.message().c_str()); + + pf.export_file(output, 10 * piece_size, 1024, ec); + if (ec) fprintf(stderr, "export_file: %s\n", ec.message().c_str()); + + pf.free_piece(10); + + pf.flush_metadata(ec); + if (ec) fprintf(stderr, "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) fprintf(stderr, "exists: %s\n", ec.message().c_str()); + + output.close(); + + // verify that the exported file is what we expect it to be + output.open(output_filename, file::read_only, ec); + if (ec) fprintf(stderr, "exported file open: %s\n", ec.message().c_str()); + + memset(buf, 0, sizeof(buf)); + + output.readv(0, &v, 1, ec); + if (ec) fprintf(stderr, "exported file read: %s\n", ec.message().c_str()); + + for (int i = 0; i < 1024; ++i) + TEST_CHECK(buf[i] == char(i)); + } +} + diff --git a/test/test_pe_crypto.cpp b/test/test_pe_crypto.cpp new file mode 100644 index 0000000..c06a6bd --- /dev/null +++ b/test/test_pe_crypto.cpp @@ -0,0 +1,192 @@ +/* + +Copyright (c) 2007, Un Shyam +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include + +#include "libtorrent/hasher.hpp" +#include "libtorrent/pe_crypto.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/random.hpp" + +#include "setup_transfer.hpp" +#include "test.hpp" + +extern "C" { +#include "libtorrent/tommath.h" +} + +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + +void test_enc_handler(libtorrent::crypto_plugin* a, libtorrent::crypto_plugin* b) +{ +#ifdef TORRENT_USE_VALGRIND + const int repcount = 10; +#else + const int repcount = 128; +#endif + for (int rep = 0; rep < repcount; ++rep) + { + int buf_len = rand() % (512 * 1024); + char* buf = new char[buf_len]; + char* cmp_buf = new char[buf_len]; + + std::generate(buf, buf + buf_len, &std::rand); + std::memcpy(cmp_buf, buf, buf_len); + + using namespace boost::asio; + std::vector iovec; + iovec.push_back(mutable_buffer(buf, buf_len)); + a->encrypt(iovec); + TEST_CHECK(!std::equal(buf, buf + buf_len, cmp_buf)); + TEST_CHECK(iovec.empty()); + int consume = 0; + int produce = buf_len; + int packet_size = 0; + iovec.push_back(mutable_buffer(buf, buf_len)); + b->decrypt(iovec, consume, produce, packet_size); + TEST_CHECK(std::equal(buf, buf + buf_len, cmp_buf)); + TEST_CHECK(iovec.empty()); + TEST_EQUAL(consume, 0); + TEST_EQUAL(produce, buf_len); + TEST_EQUAL(packet_size, 0); + + iovec.push_back(mutable_buffer(buf, buf_len)); + b->encrypt(iovec); + TEST_CHECK(!std::equal(buf, buf + buf_len, cmp_buf)); + TEST_CHECK(iovec.empty()); + consume = 0; + produce = buf_len; + packet_size = 0; + iovec.push_back(mutable_buffer(buf, buf_len)); + a->decrypt(iovec, consume, produce, packet_size); + TEST_CHECK(std::equal(buf, buf + buf_len, cmp_buf)); + TEST_CHECK(iovec.empty()); + TEST_EQUAL(consume, 0); + TEST_EQUAL(produce, buf_len); + TEST_EQUAL(packet_size, 0); + + delete[] buf; + delete[] cmp_buf; + } +} + +void print_key(char const* key) +{ + for (int i = 0;i < 96; ++i) + { + printf("%02x ", unsigned(key[i])); + } + printf("\n"); +} + +TORRENT_TEST(diffie_hellman) +{ + using namespace libtorrent; + +#ifdef TORRENT_USE_VALGRIND + const int repcount = 10; +#else + const int repcount = 128; +#endif + + 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_CHECK(std::equal(DH1.get_secret(), DH1.get_secret() + 96, DH2.get_secret())); + if (!std::equal(DH1.get_secret(), DH1.get_secret() + 96, DH2.get_secret())) + { + printf("DH1 local: "); + print_key(DH1.get_local_key()); + + printf("DH2 local: "); + print_key(DH2.get_local_key()); + + printf("DH1 shared_secret: "); + print_key(DH1.get_secret()); + + printf("DH2 shared_secret: "); + print_key(DH2.get_secret()); + } + } +} + +TORRENT_TEST(rc4) +{ + using namespace libtorrent; + + sha1_hash test1_key = hasher("test1_key",8).final(); + sha1_hash test2_key = hasher("test2_key",8).final(); + + fprintf(stderr, "testing RC4 handler\n"); + rc4_handler rc41; + rc41.set_incoming_key(&test2_key[0], 20); + rc41.set_outgoing_key(&test1_key[0], 20); + rc4_handler rc42; + rc42.set_incoming_key(&test1_key[0], 20); + rc42.set_outgoing_key(&test2_key[0], 20); + test_enc_handler(&rc41, &rc42); +} + +TORRENT_TEST(tommath) +{ + const unsigned char key[96] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, + 0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, + 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6, + 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, + 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, + 0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, + 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9, + 0xA6, 0x3A, 0x36, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x05, 0x63 + }; + + mp_int bigint; + mp_init(&bigint); + TEST_CHECK(mp_read_unsigned_bin(&bigint, key, sizeof(key)) == 0); + + TEST_EQUAL(mp_unsigned_bin_size(&bigint), sizeof(key)); + + mp_clear(&bigint); +} + +#else +TORRENT_TEST(disabled) +{ + fprintf(stderr, "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..d81d3ec --- /dev/null +++ b/test/test_peer_classes.cpp @@ -0,0 +1,116 @@ +/* + +Copyright (c) 2012, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/peer_class.hpp" +#include "libtorrent/peer_class_set.hpp" +#include "libtorrent/peer_class_type_filter.hpp" + +using namespace libtorrent; + +std::string class_name(peer_class_t id, peer_class_pool const& p) +{ + peer_class const* c = p.at(id); + TEST_CHECK(c != NULL); + if (c == NULL) return ""; + peer_class_info i; + c->get_info(&i); + return i.label; +} + +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 == id2 + 1); + + // make sure refcounting works + TEST_CHECK(class_name(id3, pool) == "test3"); + pool.incref(id3); + TEST_CHECK(class_name(id3, pool) == "test3"); + pool.decref(id3); + TEST_CHECK(class_name(id3, pool) == "test3"); + pool.decref(id3); + // it should have been deleted now + TEST_CHECK(pool.at(id3) == NULL); + + // 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 i; + pool.at(id2)->get_info(&i); + TEST_CHECK(i.upload_limit == 1000); + TEST_CHECK(i.download_limit == 2000); + + // test peer_class_type_filter + peer_class_type_filter filter; + + for (int i = 0; i < 5; ++i) + { + TEST_CHECK(filter.apply((libtorrent::peer_class_type_filter::socket_type_t)i + , 0xffffffff) == 0xffffffff); + } + + filter.disallow((libtorrent::peer_class_type_filter::socket_type_t)0, 0); + TEST_CHECK(filter.apply((libtorrent::peer_class_type_filter::socket_type_t)0 + , 0xffffffff) == 0xfffffffe); + TEST_CHECK(filter.apply((libtorrent::peer_class_type_filter::socket_type_t)1 + , 0xffffffff) == 0xffffffff); + filter.allow((libtorrent::peer_class_type_filter::socket_type_t)0, 0); + TEST_CHECK(filter.apply((libtorrent::peer_class_type_filter::socket_type_t)0 + , 0xffffffff) == 0xffffffff); + + TEST_CHECK(filter.apply((libtorrent::peer_class_type_filter::socket_type_t)0, 0) == 0); + filter.add((libtorrent::peer_class_type_filter::socket_type_t)0, 0); + TEST_CHECK(filter.apply((libtorrent::peer_class_type_filter::socket_type_t)0, 0) == 1); + filter.remove((libtorrent::peer_class_type_filter::socket_type_t)0, 0); + TEST_CHECK(filter.apply((libtorrent::peer_class_type_filter::socket_type_t)0, 0) == 0); + + pool.decref(id2); + pool.decref(id1); + TEST_CHECK(pool.at(id2) == NULL); + TEST_CHECK(pool.at(id1) == NULL); +} + diff --git a/test/test_peer_list.cpp b/test/test_peer_list.cpp new file mode 100644 index 0000000..9bc4f0d --- /dev/null +++ b/test/test_peer_list.cpp @@ -0,0 +1,948 @@ +/* + +Copyright (c) 2013, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/peer_list.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/torrent_peer_allocator.hpp" +#include "libtorrent/peer_connection_interface.hpp" +#include "libtorrent/stat.hpp" +#include "libtorrent/ip_voter.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/peer_info.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/socket_io.hpp" + +#include "test.hpp" +#include "setup_transfer.hpp" +#include +#include + +using namespace libtorrent; + +struct mock_torrent; + +struct mock_peer_connection : peer_connection_interface + , boost::enable_shared_from_this +{ + mock_peer_connection(mock_torrent* tor, bool out, tcp::endpoint const& remote) + : m_choked(false) + , m_outgoing(out) + , m_tp(NULL) + , m_remote(remote) + , m_local(ep("127.0.0.1", 8080)) + , m_disconnect_called(false) + , m_torrent(*tor) + { + for (int i = 0; i < 20; ++i) m_id[i] = rand(); + } + + virtual ~mock_peer_connection() {} + +#if !defined TORRENT_DISABLE_LOGGING + virtual void peer_log(peer_log_alert::direction_t dir, char const* event + , char const* fmt, ...) const + { + va_list v; + va_start(v, fmt); + vprintf(fmt, v); + va_end(v); + } +#endif + + bool was_disconnected() const { return m_disconnect_called; } + void set_local_ep(tcp::endpoint const& ep) { m_local = ep; } + + libtorrent::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; + bool m_disconnect_called; + mock_torrent& m_torrent; + + virtual void get_peer_info(peer_info& p) const {} + virtual tcp::endpoint const& remote() const { return m_remote; } + virtual tcp::endpoint local_endpoint() const { return m_local; } + virtual void disconnect(error_code const& ec + , operation_t op, int error = 0); + virtual peer_id const& pid() const { return m_id; } + virtual void set_holepunch_mode() {} + virtual torrent_peer* peer_info_struct() const { return m_tp; } + virtual void set_peer_info(torrent_peer* pi) { m_tp = pi; } + virtual bool is_outgoing() const { return m_outgoing; } + virtual void add_stat(boost::int64_t downloaded, boost::int64_t uploaded) + { m_stat.add_stat(downloaded, uploaded); } + virtual bool fast_reconnect() const { return true; } + virtual bool is_choked() const { return m_choked; } + virtual bool failed() const { return false; } + virtual libtorrent::stat const& statistics() const { return m_stat; } +}; + +struct mock_torrent +{ + mock_torrent(torrent_state* st) : m_p(NULL), m_state(st) {} + virtual ~mock_torrent() {} + + bool connect_to_peer(torrent_peer* peerinfo, bool ignore_limit = false) + { + TORRENT_ASSERT(peerinfo->connection == NULL); + if (peerinfo->connection) return false; + boost::shared_ptr c + = boost::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; + } + +#ifndef TORRENT_DISABLE_LOGGING + void debug_log(const char* fmt, ...) const + { + va_list v; + va_start(v, fmt); + vprintf(fmt, v); + va_end(v); + } +#endif + + peer_list* m_p; + torrent_state* m_state; + std::vector > m_connections; +}; + +void mock_peer_connection::disconnect(error_code const& ec + , operation_t op, int error) +{ + m_torrent.m_p->connection_closed(*this, 0, m_torrent.m_state); + std::vector >::iterator i + = std::find(m_torrent.m_connections.begin(), m_torrent.m_connections.end() + , shared_from_this()); + if (i != m_torrent.m_connections.end()) m_torrent.m_connections.erase(i); + + m_tp = 0; + m_disconnect_called = true; +} + +bool has_peer(peer_list const& p, tcp::endpoint const& ep) +{ + std::pair its + = p.find_peers(ep.address()); + return its.first != its.second; +} + +torrent_state init_state(torrent_peer_allocator& allocator + , external_ip& ext_ip) +{ + torrent_state st; + st.is_finished = false; + st.is_paused = false; + st.max_peerlist_size = 1000; + st.allow_multiple_connections_per_ip = false; + st.peer_allocator = &allocator; + st.ip = &ext_ip; + 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, 0, 0, &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; +static external_ip ext_ip; + +// test multiple peers with the same IP +// when disallowing it +TORRENT_TEST(multiple_ips_disallowed) +{ + torrent_state st = init_state(allocator, ext_ip); + mock_torrent t(&st); + peer_list p; + t.m_p = &p; + TEST_EQUAL(p.num_connect_candidates(), 0); + torrent_peer* peer1 = p.add_peer(ep("10.0.0.2", 3000), 0, 0, &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), 0, 0, &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(allocator, ext_ip); + mock_torrent t(&st); + st.allow_multiple_connections_per_ip = true; + peer_list p; + t.m_p = &p; + torrent_peer* peer1 = p.add_peer(ep("10.0.0.2", 3000), 0, 0, &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), 0, 0, &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(allocator, ext_ip); + mock_torrent t(&st); + st.allow_multiple_connections_per_ip = true; + peer_list p; + t.m_p = &p; + torrent_peer* peer1 = p.add_peer(ep("10.0.0.2", 3000), 0, 0, &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 == NULL); + st.erased.clear(); + + torrent_peer* peer2 = p.add_peer(ep("10.0.0.2", 9020), 0, 0, &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(allocator, ext_ip); + mock_torrent t(&st); + st.allow_multiple_connections_per_ip = false; + peer_list p; + t.m_p = &p; + torrent_peer* peer1 = p.add_peer(ep("10.0.0.2", 3000), 0, 0, &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 == NULL); + st.erased.clear(); + + torrent_peer* peer2 = p.add_peer(ep("10.0.0.2", 9020), 0, 0, &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(allocator, ext_ip); + mock_torrent t(&st); + st.allow_multiple_connections_per_ip = false; + peer_list p; + t.m_p = &p; + TEST_EQUAL(p.num_connect_candidates(), 0); + boost::shared_ptr c + = boost::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(allocator, ext_ip); + mock_torrent t(&st); + st.allow_multiple_connections_per_ip = true; + peer_list p; + t.m_p = &p; + + torrent_peer* peer2 = p.add_peer(ep("10.0.0.1", 4000), 0, 0, &st); + TEST_CHECK(peer2); + + TEST_EQUAL(p.num_connect_candidates(), 1); + boost::shared_ptr c + = boost::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(); +} + +// test ip filter +TORRENT_TEST(ip_filter) +{ + torrent_state st = init_state(allocator, ext_ip); + mock_torrent t(&st); + st.allow_multiple_connections_per_ip = false; + peer_list p; + 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); + + boost::shared_ptr con1 + = static_cast(peer1->connection)->shared_from_this(); + TEST_EQUAL(con1->was_disconnected(), false); + boost::shared_ptr con2 + = static_cast(peer2->connection)->shared_from_this(); + 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(address_v4::from_string("11.0.0.0"), address_v4::from_string("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], address_v4::from_string("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(allocator, ext_ip); + mock_torrent t(&st); + st.allow_multiple_connections_per_ip = false; + peer_list p; + 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); + + boost::shared_ptr con1 + = static_cast(peer1->connection)->shared_from_this(); + TEST_EQUAL(con1->was_disconnected(), false); + boost::shared_ptr con2 + = static_cast(peer2->connection)->shared_from_this(); + 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], address_v4::from_string("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(allocator, ext_ip); + mock_torrent t(&st); + st.allow_multiple_connections_per_ip = false; + peer_list p; + t.m_p = &p; + + torrent_peer* peer1 = add_peer(p, st, ep("10.0.0.1", 4000)); + + TEST_EQUAL(p.num_connect_candidates(), 1); + boost::shared_ptr c + = boost::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 = boost::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(allocator, ext_ip); + mock_torrent t(&st); + st.max_peerlist_size = 100; + st.allow_multiple_connections_per_ip = true; + peer_list p; + 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 == NULL || st.erased.size() > 0) + { + fprintf(stderr, "unexpected rejection of peer: %s | %d in list. " + "added peer %p, erased %d peers\n" + , print_endpoint(ep).c_str(), p.num_peers(), 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(), 0, 0, &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 == NULL); +} + +// test set_ip_filter +TORRENT_TEST(set_ip_filter) +{ + torrent_state st = init_state(allocator, ext_ip); + std::vector
    banned; + + mock_torrent t(&st); + peer_list p; + t.m_p = &p; + + for (int i = 0; i < 100; ++i) + { + p.add_peer(tcp::endpoint( + address_v4((10 << 24) + ((i + 10) << 16)), 353), 0, 0, &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(address_v4::from_string("10.13.0.0") + , address_v4::from_string("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(), address_v4::from_string("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(allocator, ext_ip); + std::vector
    banned; + + mock_torrent t(&st); + peer_list p; + t.m_p = &p; + + for (int i = 0; i < 100; ++i) + { + p.add_peer(tcp::endpoint( + address_v4((10 << 24) + ((i + 10) << 16)), i + 10), 0, 0, &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(), address_v4::from_string("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(allocator, ext_ip); + + mock_torrent t(&st); + peer_list p; + t.m_p = &p; + + for (int i = 0; i < 100; ++i) + { + torrent_peer* peer = p.add_peer(tcp::endpoint( + address_v4((10 << 24) + ((i + 10) << 16)), i + 10), 0, 0, &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(allocator, ext_ip); + + mock_torrent t(&st); + peer_list p; + t.m_p = &p; + + for (int i = 0; i < 100; ++i) + { + torrent_peer* peer = p.add_peer(tcp::endpoint( + address_v4((10 << 24) + ((i + 10) << 16)), i + 10), 0, 0, &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(allocator, ext_ip); + std::vector
    banned; + + mock_torrent t(&st); + peer_list p; + 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(address_v4::from_string("10.10.0.1") + , address_v4::from_string("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(allocator, ext_ip); + std::vector
    banned; + + mock_torrent t(&st); + peer_list p; + 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(allocator, ext_ip); + mock_torrent t(&st); + st.allow_multiple_connections_per_ip = false; + peer_list p; + 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); + + boost::shared_ptr con_out + = static_cast(peer->connection)->shared_from_this(); + con_out->set_local_ep(ep("10.0.0.2", 8080)); + + boost::shared_ptr con_in + = boost::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(allocator, ext_ip); + mock_torrent t(&st); + st.allow_multiple_connections_per_ip = false; + peer_list p; + t.m_p = &p; + + // we are 10.0.0.1 and the other peer is 10.0.0.2 + + // first incoming connection + boost::shared_ptr con1 + = boost::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 + boost::shared_ptr con2 + = boost::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(allocator, ext_ip); + mock_torrent t(&st); + st.allow_multiple_connections_per_ip = false; + peer_list p; + 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); + + boost::shared_ptr con_out + = static_cast(peer->connection)->shared_from_this(); + con_out->set_local_ep(ep("10.0.0.1", 3163)); + + // and the incoming connection + boost::shared_ptr con_in + = boost::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 (we win) +TORRENT_TEST(double_connection_win) +{ + torrent_state st = init_state(allocator, ext_ip); + mock_torrent t(&st); + st.allow_multiple_connections_per_ip = false; + peer_list p; + 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); + + boost::shared_ptr con_out + = static_cast(peer->connection)->shared_from_this(); + con_out->set_local_ep(ep("10.0.0.1", 3163)); + + //and the incoming connection + boost::shared_ptr con_in + = boost::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(allocator, ext_ip); + st.max_peerlist_size = 5; + mock_torrent t(&st); + st.allow_multiple_connections_per_ip = false; + peer_list p; + 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); + + boost::shared_ptr con_in + = boost::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(allocator, ext_ip); + st.max_peerlist_size = 5; + mock_torrent t(&st); + st.allow_multiple_connections_per_ip = false; + peer_list p; + 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), 0, 0, &st); + TEST_CHECK(peer6 == NULL); + 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..a1c79eb --- /dev/null +++ b/test/test_peer_priority.cpp @@ -0,0 +1,108 @@ +/* + +Copyright (c) 2012, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/peer_list.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/broadcast_socket.hpp" // for supports_ipv6() +#include + +#include "test.hpp" + +using namespace libtorrent; + +boost::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(); +} + +TORRENT_TEST(peer_priority) +{ + + // when the IP is the same, we hash the ports, sorted + boost::uint32_t p = peer_priority( + tcp::endpoint(address::from_string("230.12.123.3"), 0x4d2) + , tcp::endpoint(address::from_string("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( + tcp::endpoint(address::from_string("230.12.123.1"), 0x4d2) + , tcp::endpoint(address::from_string("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( + tcp::endpoint(address::from_string("230.12.23.1"), 0x4d2) + , tcp::endpoint(address::from_string("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( + tcp::endpoint(address::from_string("230.120.23.1"), 0x4d2) + , tcp::endpoint(address::from_string("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( + tcp::endpoint(address::from_string("123.213.32.10"), 0) + , tcp::endpoint(address::from_string("98.76.54.32"), 0)) + , 0xec2d7224); + + TEST_EQUAL(peer_priority( + tcp::endpoint(address::from_string("123.213.32.10"), 0) + , tcp::endpoint(address::from_string("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( + tcp::endpoint(address::from_string("ffff:ffff:ffff:ffff::1"), 0x4d2) + , tcp::endpoint(address::from_string("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( + tcp::endpoint(address::from_string("ffff:ffff:ffff:ffff::1"), 0x4d2) + , tcp::endpoint(address::from_string("ffff:0fff:ffff:ffff::1"), 0x12c)); + TEST_EQUAL(p, hash_buffer( + "\xff\xff\x0f\xff\x55\x55\x55\x55\x00\x00\x00\x00\x00\x00\x00\x01" + "\xff\xff\xff\xff\x55\x55\x55\x55\x00\x00\x00\x00\x00\x00\x00\x01", 32)); + } +} + diff --git a/test/test_pex.cpp b/test/test_pex.cpp new file mode 100644 index 0000000..2b305f9 --- /dev/null +++ b/test/test_pex.cpp @@ -0,0 +1,170 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" + +#ifndef TORRENT_DISABLE_EXTENSIONS + +#include "libtorrent/session.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/extensions/ut_pex.hpp" +#include "libtorrent/thread.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/torrent_status.hpp" +#include + +#include "setup_transfer.hpp" +#include + +void test_pex() +{ + using namespace libtorrent; + namespace lt = libtorrent; + + // 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; + + int mask = alert::all_categories + & ~(alert::progress_notification + | alert::performance_warning + | alert::stats_notification); + + // this is to avoid everything finish from a single peer + // immediately. To make the swarm actually connect all + // three peers before finishing. + settings_pack pack; + pack.set_int(settings_pack::alert_mask, mask); + pack.set_int(settings_pack::download_rate_limit, 2000); + pack.set_int(settings_pack::upload_rate_limit, 2000); + pack.set_int(settings_pack::max_retry_port_bind, 800); + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:48200"); + + pack.set_bool(settings_pack::enable_dht, false); + pack.set_bool(settings_pack::enable_upnp, false); + pack.set_bool(settings_pack::enable_natpmp, false); + + pack.set_int(settings_pack::out_enc_policy, settings_pack::pe_forced); + pack.set_int(settings_pack::in_enc_policy, settings_pack::pe_forced); + + lt::session ses1(pack); + + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:49200"); + + lt::session ses3(pack); + + // make the peer connecting the two worthless to transfer + // data, to force peer 3 to connect directly to peer 1 through pex + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:50200"); + lt::session ses2(pack); + + ses1.add_extension(create_ut_pex_plugin); + ses2.add_extension(create_ut_pex_plugin); + + torrent_handle tor1; + torrent_handle tor2; + torrent_handle tor3; + + boost::tie(tor1, tor2, tor3) = setup_transfer(&ses1, &ses2, &ses3, true, false, false, "_pex"); + + ses2.apply_settings(pack); + + test_sleep(100); + + // in this test, ses1 is a seed, ses2 is connected to ses1 and ses3. + // the expected behavior is that ses2 will introduce ses1 and ses3 to each other + error_code ec; + tor2.connect_peer(tcp::endpoint(address::from_string("127.0.0.1", ec), ses1.listen_port())); + tor2.connect_peer(tcp::endpoint(address::from_string("127.0.0.1", ec), ses3.listen_port())); + + torrent_status st1; + torrent_status st2; + torrent_status st3; + for (int i = 0; i < 610; ++i) + { + print_alerts(ses1, "ses1"); + print_alerts(ses2, "ses2"); + print_alerts(ses3, "ses3"); + + st1 = tor1.status(); + st2 = tor2.status(); + st3 = tor3.status(); + + print_ses_rate(i / 10.f, &st1, &st2, &st3); + + // this is the success condition + if (st1.num_peers == 2 && st2.num_peers == 2 && st3.num_peers == 2) + break; + + // this suggests that we failed. If session 3 completes without + // actually connecting to session 1, everything was transferred + // through session 2 + if (st3.state == torrent_status::seeding) break; + + test_sleep(100); + } + + TEST_CHECK(st1.num_peers == 2 && st2.num_peers == 2 && st3.num_peers == 2) + + if (!tor2.status().is_seeding && tor3.status().is_seeding) std::cerr << "done\n"; + + // this allows shutting down the sessions in parallel + p1 = ses1.abort(); + p2 = ses2.abort(); + p3 = ses3.abort(); +} +#endif // TORRENT_DISABLE_EXTENSIONS + +TORRENT_TEST(pex) +{ +#ifndef TORRENT_DISABLE_EXTENSIONS + using namespace libtorrent; + + // in case the previous run was terminated + error_code ec; + remove_all("tmp1_pex", ec); + remove_all("tmp2_pex", ec); + remove_all("tmp3_pex", ec); + + test_pex(); + + remove_all("tmp1_pex", ec); + remove_all("tmp2_pex", ec); + remove_all("tmp3_pex", ec); +#endif // TORRENT_DISABLE_EXTENSIONS +} + + diff --git a/test/test_piece_picker.cpp b/test/test_piece_picker.cpp new file mode 100644 index 0000000..1894985 --- /dev/null +++ b/test/test_piece_picker.cpp @@ -0,0 +1,1869 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/piece_picker.hpp" +#include "libtorrent/torrent_peer.hpp" +#include "libtorrent/bitfield.hpp" +#include "libtorrent/performance_counters.hpp" +#include "libtorrent/random.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "test.hpp" + +using namespace libtorrent; + +const int blocks_per_piece = 4; + +bitfield string2vec(char const* have_str) +{ + const int num_pieces = strlen(have_str); + bitfield have(num_pieces, false); + for (int i = 0; i < num_pieces; ++i) + if (have_str[i] != ' ') have.set_bit(i); + return have; +} + +ipv4_peer* tmp_peer = 0; + +tcp::endpoint endp; +ipv4_peer tmp0(endp, false, 0); +ipv4_peer tmp1(endp, false, 0); +ipv4_peer tmp2(endp, false, 0); +ipv4_peer tmp3(endp, false, 0); +ipv4_peer tmp4(endp, false, 0); +ipv4_peer tmp5(endp, false, 0); +ipv4_peer tmp6(endp, false, 0); +ipv4_peer tmp7(endp, false, 0); +ipv4_peer tmp8(endp, false, 0); +ipv4_peer tmp9(endp, false, 0); +ipv4_peer peer_struct(endp, true, 0); + +const std::vector empty_vector; + +// 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 +boost::shared_ptr setup_picker( + char const* availability + , char const* have_str + , char const* priority + , char const* partial) +{ + const int num_pieces = strlen(availability); + TORRENT_ASSERT(int(strlen(have_str)) == num_pieces); + + boost::shared_ptr p(new piece_picker); + p->init(blocks_per_piece, blocks_per_piece, num_pieces); + + for (int i = 0; i < num_pieces; ++i) + { + const int avail = availability[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]); + } + + bitfield have = string2vec(have_str); + + for (int i = 0; i < num_pieces; ++i) + { + if (partial[i] == 0) break; + + if (partial[i] == ' ') continue; + + int blocks = 0; + if (partial[i] >= '0' && partial[i] <= '9') + blocks = partial[i] - '0'; + else + blocks = partial[i] - '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)) == bool(blocks & (1 << j))); + 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)) == bool(blocks & (1 << j))); + TEST_CHECK(p->is_finished(piece_block(i, j)) == bool(blocks & (1 << j))); + } + + 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), i); + + TEST_EQUAL(st.finished, counter); + TEST_EQUAL(st.finished + st.requested + st.writing, counter); + + TEST_CHECK(p->is_piece_finished(i) == (counter == 4)); + } + + for (int i = 0; i < num_pieces; ++i) + { + if (priority[i] == 0) break; + const int prio = priority[i] - '0'; + assert(prio >= 0); + p->set_piece_priority(i, prio); + + TEST_CHECK(p->piece_priority(i) == prio); + } + + for (int i = 0; i < 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))); + } + + std::vector availability_vec; + p->get_availability(availability_vec); + for (int i = 0; i < num_pieces; ++i) + { + const int avail = availability[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(boost::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::cerr << " verify: " << picked.size() << " " << blocks.size() << std::endl; + return picked.size() == blocks.size(); +} + +void print_availability(boost::shared_ptr const& p) +{ + std::vector avail; + p->get_availability(avail); + printf("[ "); + for (std::vector::iterator i = avail.begin() + , end(avail.end()); i != end; ++i) + { + printf("%d ", *i); + } + printf("]\n"); +} + +bool verify_availability(boost::shared_ptr const& p, char const* a) +{ + std::vector avail; + p->get_availability(avail); + for (std::vector::iterator 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 (int i = 0; i < int(picked.size()); ++i) + { + std::cout << "(" << picked[i].piece_index << ", " << picked[i].block_index << ") "; + } + std::cout << std::endl; +} + +void print_title(char const* name) +{ + std::cerr << "==== " << name << " ====\n"; +} + +std::vector pick_pieces(boost::shared_ptr const& p + , char const* availability + , int num_blocks + , int prefer_contiguous_blocks + , torrent_peer* peer_struct + , int 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 + , options, suggested_pieces, 20, pc); + print_pick(picked); + TEST_CHECK(verify_pick(p, picked)); + return picked; +} + +int test_pick(boost::shared_ptr const& p + , int options = piece_picker::rarest_first) +{ + const std::vector empty_vector; + std::vector picked = pick_pieces(p, "*******", 1, 0, 0 + , options, empty_vector); + if (picked.empty()) return -1; + return picked[0].piece_index; +} + +// TODO: 2 split this up into smaller tests (where we print_title) +TORRENT_TEST(piece_picker) +{ + tcp::endpoint endp; + piece_picker::downloading_piece st; +#if TORRENT_USE_ASSERTS + 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; +#endif + tmp_peer = &tmp1; + std::vector picked; + boost::shared_ptr p; + const int options = piece_picker::rarest_first; + std::pair dc; + counters pc; + + print_title("test piece_block"); + + TEST_CHECK(piece_block(0, 0) != piece_block(0, 1)); + TEST_CHECK(piece_block(0, 0) != piece_block(1, 0)); + TEST_CHECK(!(piece_block(0, 0) != piece_block(0, 0))); + + TEST_CHECK(!(piece_block(0, 0) == piece_block(0, 1))); + TEST_CHECK(!(piece_block(0, 0) == piece_block(1, 0))); + TEST_CHECK(piece_block(0, 0) == piece_block(0, 0)); + + TEST_CHECK(!(piece_block(0, 1) < piece_block(0, 0))); + TEST_CHECK(!(piece_block(1, 0) < piece_block(0, 0))); + TEST_CHECK(piece_block(0, 0) < piece_block(0, 1)); + TEST_CHECK(piece_block(0, 0) < piece_block(1, 0)); + TEST_CHECK(!(piece_block(0, 0) < piece_block(0, 0))); + TEST_CHECK(!(piece_block(1, 0) < piece_block(1, 0))); + TEST_CHECK(!(piece_block(0, 1) < piece_block(0, 1))); + +// ======================================================== + + // test abort_download + print_title("test abort_download"); + + p = setup_picker("1111111", " ", "7110000", ""); + picked = pick_pieces(p, "*******", blocks_per_piece, 0, tmp_peer + , options, empty_vector); + TEST_CHECK(p->is_requested(piece_block(0, 0)) == false); + TEST_CHECK(std::find(picked.begin(), picked.end(), piece_block(0,0)) != picked.end()); + + p->abort_download(piece_block(0,0), tmp_peer); + picked = pick_pieces(p, "*******", blocks_per_piece, 0, tmp_peer + , options, empty_vector); + TEST_CHECK(p->is_requested(piece_block(0, 0)) == false); + TEST_CHECK(std::find(picked.begin(), picked.end(), piece_block(0,0)) != picked.end()); + + p->mark_as_downloading(piece_block(0,0), &tmp1); + picked = pick_pieces(p, "*******", blocks_per_piece, 0, tmp_peer + , options, empty_vector); + TEST_CHECK(p->is_requested(piece_block(0, 0)) == true); + TEST_CHECK(std::find(picked.begin(), picked.end(), piece_block(0,0)) == picked.end()); + + p->abort_download(piece_block(0,0), tmp_peer); + picked = pick_pieces(p, "*******", blocks_per_piece, 0, tmp_peer + , options, empty_vector); + TEST_CHECK(p->is_requested(piece_block(0, 0)) == false); + TEST_CHECK(std::find(picked.begin(), picked.end(), piece_block(0,0)) != picked.end()); + + p->mark_as_downloading(piece_block(0,0), &tmp1); + p->mark_as_downloading(piece_block(0,1), &tmp1); + p->abort_download(piece_block(0,0), tmp_peer); + picked = pick_pieces(p, "*******", blocks_per_piece, 0, tmp_peer + , options, empty_vector); + TEST_CHECK(p->is_requested(piece_block(0, 0)) == false); + TEST_CHECK(std::find(picked.begin(), picked.end(), piece_block(0,0)) != picked.end()); + + p->mark_as_downloading(piece_block(0,0), &tmp1); + p->mark_as_writing(piece_block(0,0), &tmp1); + p->write_failed(piece_block(0,0)); + picked = pick_pieces(p, "*******", blocks_per_piece, 0, tmp_peer + , options, empty_vector); + TEST_CHECK(std::find(picked.begin(), picked.end(), piece_block(1,0)) != picked.end() + || std::find(picked.begin(), picked.end(), piece_block(2,0)) != picked.end()); + TEST_CHECK(std::find(picked.begin(), picked.end(), piece_block(0,0)) == picked.end()); + p->restore_piece(0); + picked = pick_pieces(p, "*******", blocks_per_piece, 0, tmp_peer + , options, empty_vector); + TEST_CHECK(p->is_requested(piece_block(0, 0)) == false); + TEST_CHECK(std::find(picked.begin(), picked.end(), piece_block(0,0)) != picked.end()); + + p->mark_as_downloading(piece_block(0,0), &tmp1); + p->mark_as_writing(piece_block(0,0), &tmp1); + p->mark_as_finished(piece_block(0,0), &tmp1); + p->abort_download(piece_block(0,0), tmp_peer); + picked = pick_pieces(p, "*******", blocks_per_piece, 0, tmp_peer + , options, empty_vector); + TEST_CHECK(p->is_requested(piece_block(0, 0)) == false); + TEST_CHECK(std::find(picked.begin(), picked.end(), piece_block(0,0)) == picked.end()); + +// ======================================================== + + print_title("test abort_download"); + + p = setup_picker("1111111", " ", "7110000", ""); + p->mark_as_downloading(piece_block(0,0), &tmp1); + p->mark_as_finished(piece_block(0,1), 0); + p->piece_info(0, st); + TEST_EQUAL(st.requested, 1); + TEST_EQUAL(st.finished, 1); + p->abort_download(piece_block(0,0), tmp_peer); + p->piece_info(0, st); + TEST_EQUAL(st.requested, 0); + TEST_EQUAL(st.finished, 1); + picked = pick_pieces(p, "*******", blocks_per_piece, 0, 0 + , options, empty_vector); + TEST_CHECK(p->is_requested(piece_block(0, 0)) == false); + TEST_CHECK(std::find(picked.begin(), picked.end(), piece_block(0,0)) != picked.end()); + +// ======================================================== + + print_title("test get_downloaders"); + + p = setup_picker("1111111", " ", "7110000", ""); + + p->mark_as_downloading(piece_block(0, 2), &tmp1); + p->mark_as_writing(piece_block(0, 2), &tmp1); + p->abort_download(piece_block(0, 2), &tmp1); + p->mark_as_downloading(piece_block(0, 2), &tmp2); + p->mark_as_writing(piece_block(0, 2), &tmp2); + + std::vector d; + p->get_downloaders(d, 0); + TEST_EQUAL(d.size(), 4); + TEST_CHECK(d[0] == NULL); + TEST_CHECK(d[1] == NULL); + TEST_CHECK(d[2] == &tmp2); + TEST_CHECK(d[3] == NULL); + + p->mark_as_downloading(piece_block(0, 3), &tmp1); + p->abort_download(piece_block(0, 3), &tmp1); + p->mark_as_downloading(piece_block(0, 3), &tmp2); + p->mark_as_writing(piece_block(0, 3), &tmp2); + + p->get_downloaders(d, 0); + + TEST_EQUAL(d.size(), 4); + TEST_CHECK(d[0] == NULL); + TEST_CHECK(d[1] == NULL); + TEST_CHECK(d[2] == &tmp2); + TEST_CHECK(d[3] == &tmp2); + + // if we ask for downloaders for a piece that's not + // curently being downloaded, we get zeroes back + p->get_downloaders(d, 1); + + TEST_EQUAL(d.size(), 4); + TEST_CHECK(d[0] == NULL); + TEST_CHECK(d[1] == NULL); + TEST_CHECK(d[2] == NULL); + TEST_CHECK(d[3] == NULL); + +// ======================================================== + + p = setup_picker("2222", " ", "", ""); + + for (int i = 0; i < 4; ++i) + for (int k = 0; k < blocks_per_piece; ++k) + p->mark_as_downloading(piece_block(i, k), &tmp1); + + p->mark_as_downloading(piece_block(0, 0), &tmp2); + + fprintf(stderr, "num_peers: %d\n", p->num_peers(piece_block(0, 0))); + TEST_EQUAL(p->num_peers(piece_block(0, 0)), 2); + + p->abort_download(piece_block(0, 0), &tmp1); + + fprintf(stderr, "num_peers: %d\n", p->num_peers(piece_block(0, 0))); + TEST_EQUAL(p->num_peers(piece_block(0, 0)), 1); + +// ======================================================== + + // make sure the block that is picked is from piece 1, since it + // it is the piece with the lowest availability + print_title("test pick lowest availability"); + p = setup_picker("2223333", "* * * ", "", ""); + TEST_CHECK(test_pick(p) == 1); + +// ======================================================== + + // make sure pieces with equal priority and availability + // are picked at random + print_title("test random pick at same priority"); + std::map random_prio_pieces; + for (int i = 0; i < 100; ++i) + { + p = setup_picker("1111112", " ", "", ""); + ++random_prio_pieces[test_pick(p)]; + } + TEST_CHECK(random_prio_pieces.size() == 6); + for (std::map::iterator i = random_prio_pieces.begin() + , end(random_prio_pieces.end()); i != end; ++i) + std::cout << i->first << ": " << i->second << " "; + std::cout << std::endl; + +// ======================================================== + + // make sure the block that is picked is from piece 5, since it + // has the highest priority among the available pieces + print_title("test pick highest priority"); + p = setup_picker("1111111", " ", "1111121", ""); + TEST_CHECK(test_pick(p) == 5); + + p = setup_picker("1111111", " ", "1171121", ""); + TEST_CHECK(test_pick(p) == 2); + + p = setup_picker("1111111", " ", "1131521", ""); + TEST_CHECK(test_pick(p) == 4); + +// ======================================================== + + print_title("test reverse rarest first"); + p = setup_picker("4179253", " ", "", ""); + 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[i] == piece_block(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); + +// ======================================================== + + // 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 + print_title("test pick whole pieces"); + p = setup_picker("2212222", " ", "1111111", "1023460"); + 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[i].piece_index, 2); + + p = setup_picker("1111111", " ", "1111111", ""); + picked = pick_pieces(p, "****** ", 1, blocks_per_piece + , &peer_struct, options, empty_vector); + TEST_EQUAL(int(picked.size()), blocks_per_piece); + for (int i = 0; i < blocks_per_piece && i < int(picked.size()); ++i) + TEST_EQUAL(picked[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[i] == piece_block(i / blocks_per_piece, i % blocks_per_piece)); + +// ======================================================== + + // 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 + print_title("test distributed copies"); + p = setup_picker("1233333", "* ", "", ""); + dc = p->distributed_copies(); + TEST_CHECK(dc == std::make_pair(2, 5000 / 7)); + +// ======================================================== + + // make sure filtered pieces are ignored + print_title("test filtered pieces"); + p = setup_picker("1111111", " ", "0010000", ""); + TEST_CHECK(test_pick(p, piece_picker::rarest_first) == 2); + TEST_CHECK(test_pick(p, piece_picker::rarest_first | piece_picker::reverse) == 2); + TEST_CHECK(test_pick(p, piece_picker::sequential) == 2); + TEST_CHECK(test_pick(p, piece_picker::sequential | piece_picker::reverse) == 2); + +// ======================================================== + + // make sure we_dont_have works + print_title("test we_dont_have"); + p = setup_picker("1111111", "*******", "0100000", ""); + TEST_CHECK(p->have_piece(1)); + TEST_CHECK(p->have_piece(2)); + p->we_dont_have(1); + p->we_dont_have(2); + TEST_CHECK(!p->have_piece(1)); + TEST_CHECK(!p->have_piece(2)); + picked = pick_pieces(p, "*** ** ", 1, 0, 0, options, empty_vector); + TEST_CHECK(int(picked.size()) > 0); + TEST_CHECK(picked.front().piece_index == 1); + +// ======================================================== + + // make sure we can split m_seed when removing a refcount + print_title("test dec_refcount split seed"); + p = setup_picker("0000000", " ", "0000000", ""); + p->inc_refcount_all(0); + + std::vector avail; + p->get_availability(avail); + TEST_EQUAL(avail.size(), 7); + TEST_CHECK(avail[0] != 0); + TEST_CHECK(avail[1] != 0); + TEST_CHECK(avail[2] != 0); + TEST_CHECK(avail[3] != 0); + TEST_CHECK(avail[4] != 0); + + p->dec_refcount(3, 0); + + p->get_availability(avail); + TEST_EQUAL(avail.size(), 7); + + TEST_CHECK(avail[0] != 0); + TEST_CHECK(avail[1] != 0); + TEST_CHECK(avail[2] != 0); + TEST_CHECK(avail[3] == 0); + TEST_CHECK(avail[4] != 0); + +// ======================================================== + + // make sure init preserves priorities + print_title("test init"); + p = setup_picker("1111111", " ", "1111111", ""); + + TEST_CHECK(p->num_filtered() == 0); + TEST_CHECK(p->num_have_filtered() == 0); + TEST_CHECK(p->num_have() == 0); + + p->set_piece_priority(0, 0); + TEST_CHECK(p->num_filtered() == 1); + TEST_CHECK(p->num_have_filtered() == 0); + TEST_CHECK(p->num_have() == 0); + + p->we_have(0); + + TEST_CHECK(p->num_filtered() == 0); + TEST_CHECK(p->num_have_filtered() == 1); + TEST_CHECK(p->num_have() == 1); + + p->init(blocks_per_piece, blocks_per_piece, blocks_per_piece * 7); + TEST_CHECK(p->piece_priority(0) == 0); + TEST_CHECK(p->num_filtered() == 1); + TEST_CHECK(p->num_have_filtered() == 0); + TEST_CHECK(p->num_have() == 0); + +// ======================================================== + + // make sure requested blocks aren't picked + print_title("test don't pick requested blocks"); + p = setup_picker("1111111", " ", "", ""); + picked = pick_pieces(p, "*******", 1, 0, 0, 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, 0, options, empty_vector); + TEST_CHECK(int(picked.size()) > 0); + TEST_CHECK(picked.front() != first); + +// ======================================================== + + // make sure downloading pieces have higher priority + print_title("test downloading piece priority"); + p = setup_picker("1111111", " ", "", ""); + picked = pick_pieces(p, "*******", 1, 0, 0, options, empty_vector); + TEST_CHECK(int(picked.size()) > 0); + 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, 0, options, empty_vector); + TEST_CHECK(int(picked.size()) > 0); + TEST_CHECK(picked.front() != first); + TEST_CHECK(picked.front().piece_index == first.piece_index); + +// ======================================================== + + // 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. + print_title("test partial piece order (rarest first)"); + p = setup_picker("1112111", " ", "", "013700f"); + picked = pick_pieces(p, "*******", 1, 0, 0 + , options | piece_picker::prioritize_partials, empty_vector); + TEST_CHECK(int(picked.size()) > 0); + TEST_CHECK(picked.front() == piece_block(2, 2) + || picked.front() == piece_block(2, 3)); + + // 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 + + print_title("test partial piece order (most complete)"); + p = setup_picker("1111111", " ", "", "013700f"); + picked = pick_pieces(p, "*******", 1, 0, 0 + , options | piece_picker::prioritize_partials, empty_vector); + TEST_CHECK(int(picked.size()) > 0); + TEST_CHECK(picked.front() == piece_block(3, 3)); + + // if we don't use rarest first when we prioritize partials, but instead use + // sequential order, make sure we pick the right one + + print_title("test partial piece order (sequential)"); + p = setup_picker("1111111", " ", "", "013700f"); + picked = pick_pieces(p, "*******", 1, 0, 0 + , piece_picker::sequential | piece_picker::prioritize_partials, empty_vector); + TEST_CHECK(int(picked.size()) > 0); + TEST_CHECK(picked.front() == piece_block(1, 1) + || picked.front() == piece_block(1, 2) + || picked.front() == piece_block(1, 3)); + +// ======================================================== + + // make sure the random piece picker can still pick partial pieces + print_title("test random picking (downloading piece)"); + p = setup_picker("1111111", " ", "", "013700f"); + picked = pick_pieces(p, " *** *", 1, 0, 0 + , 0, empty_vector); + TEST_CHECK(int(picked.size()) > 0); + TEST_CHECK(picked.front() == piece_block(1, 1) + || picked.front() == piece_block(2, 2) + || picked.front() == piece_block(3, 3)); + + // make sure the random piece picker can still pick partial pieces + // even when prefer_contiguous_blocks is set + print_title("test random picking (downloading piece, prefer contiguous)"); + p = setup_picker("1111111", " ", "", "013700f"); + picked = pick_pieces(p, " *** *", 1, 4, 0 + , 0, empty_vector); + TEST_CHECK(int(picked.size()) > 0); + TEST_CHECK(picked.front() == piece_block(1, 1) + || picked.front() == piece_block(2, 2) + || picked.front() == piece_block(3, 3)); + + +// ======================================================== + + // test sequential download + print_title("test sequential download"); + p = setup_picker("7654321", " ", "", ""); + picked = pick_pieces(p, "*******", 7 * blocks_per_piece, 0, 0 + , 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[i] == piece_block(i / blocks_per_piece, i % blocks_per_piece)); + +// ======================================================== + + // test reverse sequential download + print_title("test reverse sequential download"); + p = setup_picker("7654321", " ", "", ""); + picked = pick_pieces(p, "*******", 7 * blocks_per_piece, 0, 0 + , 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[i] == piece_block(6 - (i / blocks_per_piece), i % blocks_per_piece)); + +// ======================================================== + + // test priority sequential download + print_title("test priority sequential download"); + p = setup_picker("7654321", " ", "1117071", ""); + picked = pick_pieces(p, "*******", 7 * blocks_per_piece, 0, 0 + , 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[i].piece_index == 3 || picked[i].piece_index == 5); + + int expected[] = {-1, -1, 0, 1, 2, 6}; + for (int i = 2 * blocks_per_piece; i < int(picked.size()); ++i) + TEST_EQUAL(picked[i].piece_index, expected[i / blocks_per_piece]); + +// ======================================================== + + // sweep up, we_have() + print_title("test cursors. sweep up, we_have"); + p = setup_picker("7654321", " ", "", ""); + for (int i = 0; i < 7; ++i) + { + TEST_EQUAL(p->cursor(), i); + TEST_EQUAL(p->reverse_cursor(), 7); + p->we_have(i); + } + TEST_CHECK(p->is_finished()); + TEST_CHECK(p->is_seeding()); + TEST_EQUAL(p->cursor(), 7); + TEST_EQUAL(p->reverse_cursor(), 0); + + // sweep up, set_piece_priority() + print_title("test cursors. sweep up, set_piece_priority"); + p = setup_picker("7654321", " ", "", ""); + for (int i = 0; i < 7; ++i) + { + TEST_EQUAL(p->cursor(), i); + TEST_EQUAL(p->reverse_cursor(), 7); + p->set_piece_priority(i, 0); + } + TEST_CHECK(p->is_finished()); + TEST_CHECK(!p->is_seeding()); + TEST_EQUAL(p->cursor(), 7); + TEST_EQUAL(p->reverse_cursor(), 0); + + // sweep down, we_have() + print_title("test cursors. sweep down, we_have"); + p = setup_picker("7654321", " ", "", ""); + for (int i = 6; i >= 0; --i) + { + TEST_EQUAL(p->cursor(), 0); + TEST_EQUAL(p->reverse_cursor(), i + 1); + p->we_have(i); + } + TEST_CHECK(p->is_finished()); + TEST_CHECK(p->is_seeding()); + TEST_EQUAL(p->cursor(), 7); + TEST_EQUAL(p->reverse_cursor(), 0); + + // sweep down, set_piece_priority() + print_title("test cursors. sweep down, set_piece_priority"); + p = setup_picker("7654321", " ", "", ""); + for (int i = 6; i >= 0; --i) + { + TEST_EQUAL(p->cursor(), 0); + TEST_EQUAL(p->reverse_cursor(), i + 1); + p->set_piece_priority(i, 0); + } + TEST_CHECK(p->is_finished()); + TEST_CHECK(!p->is_seeding()); + TEST_EQUAL(p->cursor(), 7); + TEST_EQUAL(p->reverse_cursor(), 0); + + // sweep in, set_piece_priority() + print_title("test cursors. sweep in, set_piece_priority"); + p = setup_picker("7654321", " ", "", ""); + for (int left = 0, right = 6; left <= 3 && right >= 3; ++left, --right) + { + TEST_EQUAL(p->cursor(), left); + TEST_EQUAL(p->reverse_cursor(), right + 1); + p->set_piece_priority(left, 0); + p->set_piece_priority(right, 0); + } + TEST_CHECK(p->is_finished()); + TEST_CHECK(!p->is_seeding()); + TEST_EQUAL(p->cursor(), 7); + TEST_EQUAL(p->reverse_cursor(), 0); + + // sweep in, we_have() + print_title("test cursors. sweep in, we_have"); + p = setup_picker("7654321", " ", "", ""); + for (int left = 0, right = 6; left <= 3 && right >= 3; ++left, --right) + { + TEST_EQUAL(p->cursor(), left); + TEST_EQUAL(p->reverse_cursor(), right + 1); + p->we_have(left); + p->we_have(right); + } + TEST_CHECK(p->is_finished()); + TEST_CHECK(p->is_seeding()); + TEST_EQUAL(p->cursor(), 7); + TEST_EQUAL(p->reverse_cursor(), 0); + + + print_title("test cursors. sweep up, we_dont_have"); + p = setup_picker("7654321", "*******", "", ""); + TEST_CHECK(p->is_finished()); + TEST_CHECK(p->is_seeding()); + TEST_EQUAL(p->cursor(), 7); + TEST_EQUAL(p->reverse_cursor(), 0); + for (int i = 0; i < 7; ++i) + { + p->we_dont_have(i); + TEST_EQUAL(p->cursor(), 0); + TEST_EQUAL(p->reverse_cursor(), i + 1); + } + TEST_CHECK(!p->is_finished()); + TEST_CHECK(!p->is_seeding()); + TEST_EQUAL(p->cursor(), 0); + TEST_EQUAL(p->reverse_cursor(), 7); + + print_title("test cursors. sweep down, we_dont_have"); + p = setup_picker("7654321", "*******", "", ""); + TEST_CHECK(p->is_finished()); + TEST_CHECK(p->is_seeding()); + TEST_EQUAL(p->cursor(), 7); + TEST_EQUAL(p->reverse_cursor(), 0); + for (int i = 6; i >= 0; --i) + { + p->we_dont_have(i); + TEST_EQUAL(p->cursor(), i); + TEST_EQUAL(p->reverse_cursor(), 7); + } + TEST_CHECK(!p->is_finished()); + TEST_CHECK(!p->is_seeding()); + TEST_EQUAL(p->cursor(), 0); + TEST_EQUAL(p->reverse_cursor(), 7); + + print_title("test cursors. sweep out, we_dont_have"); + p = setup_picker("7654321", "*******", "", ""); + TEST_CHECK(p->is_finished()); + TEST_CHECK(p->is_seeding()); + TEST_EQUAL(p->cursor(), 7); + TEST_EQUAL(p->reverse_cursor(), 0); + for (int left = 3, right = 3; left >= 0 && right < 7; --left, ++right) + { + p->we_dont_have(left); + p->we_dont_have(right); + TEST_EQUAL(p->cursor(), left); + TEST_EQUAL(p->reverse_cursor(), right + 1); + } + TEST_CHECK(!p->is_finished()); + TEST_CHECK(!p->is_seeding()); + TEST_EQUAL(p->cursor(), 0); + TEST_EQUAL(p->reverse_cursor(), 7); + + // test cursors + print_title("test cursors"); + p = setup_picker("7654321", " ", "", ""); + TEST_EQUAL(p->cursor(), 0); + TEST_EQUAL(p->reverse_cursor(), 7); + p->we_have(1); + TEST_EQUAL(p->cursor(), 0); + TEST_EQUAL(p->reverse_cursor(), 7); + p->we_have(0); + TEST_EQUAL(p->cursor(), 2); + TEST_EQUAL(p->reverse_cursor(), 7); + p->we_have(5); + TEST_EQUAL(p->cursor(), 2); + TEST_EQUAL(p->reverse_cursor(), 7); + p->we_have(6); + TEST_EQUAL(p->cursor(), 2); + TEST_EQUAL(p->reverse_cursor(), 5); + p->we_have(4); + p->we_have(3); + p->we_have(2); + TEST_EQUAL(p->cursor(), 7); + TEST_EQUAL(p->reverse_cursor(), 0); + + p = setup_picker("7654321", " ", "", ""); + TEST_EQUAL(p->cursor() , 0); + TEST_EQUAL(p->reverse_cursor(), 7); + p->set_piece_priority(1, 0); + TEST_EQUAL(p->cursor(), 0); + TEST_EQUAL(p->reverse_cursor(), 7); + p->set_piece_priority(0, 0); + TEST_EQUAL(p->cursor(), 2); + TEST_EQUAL(p->reverse_cursor(), 7); + p->set_piece_priority(5, 0); + TEST_EQUAL(p->cursor(), 2); + TEST_EQUAL(p->reverse_cursor(), 7); + p->set_piece_priority(6, 0); + TEST_EQUAL(p->cursor(), 2); + TEST_EQUAL(p->reverse_cursor(), 5); + p->set_piece_priority(4, 0); + p->set_piece_priority(3, 0); + p->set_piece_priority(2, 0); + TEST_EQUAL(p->cursor(), 7); + TEST_EQUAL(p->reverse_cursor(), 0); + p->set_piece_priority(3, 1); + TEST_EQUAL(p->cursor(), 3); + TEST_EQUAL(p->reverse_cursor(), 4); + +// ======================================================== + + // test piece priorities + print_title("test piece priorities"); + p = setup_picker("5555555", " ", "7654321", ""); + TEST_CHECK(p->num_filtered() == 0); + TEST_CHECK(p->num_have_filtered() == 0); + p->set_piece_priority(0, 0); + TEST_CHECK(p->num_filtered() == 1); + TEST_CHECK(p->num_have_filtered() == 0); + p->mark_as_finished(piece_block(0,0), 0); + p->we_have(0); + TEST_CHECK(p->num_filtered() == 0); + TEST_CHECK(p->num_have_filtered() == 1); + + p->we_dont_have(0); + p->set_piece_priority(0, 7); + + picked = pick_pieces(p, "*******", 7 * blocks_per_piece, 0, 0 + , options, empty_vector); + TEST_CHECK(int(picked.size()) == 7 * blocks_per_piece); + + for (int i = 0; i < int(picked.size()); ++i) + TEST_CHECK(picked[i] == piece_block(i / blocks_per_piece, i % blocks_per_piece)); + + // test changing priority on a piece we have + p->we_have(0); + p->set_piece_priority(0, 0); + p->set_piece_priority(0, 1); + p->set_piece_priority(0, 0); + + std::vector prios; + p->piece_priorities(prios); + TEST_CHECK(prios.size() == 7); + int prio_comp[] = {0, 6, 5, 4, 3, 2, 1}; + TEST_CHECK(std::equal(prios.begin(), prios.end(), prio_comp)); + + std::vector filter; + p->filtered_pieces(filter); + TEST_CHECK(prios.size() == 7); + bool filter_comp[] = {true, false, false, false, false, false, false}; + TEST_CHECK(std::equal(filter.begin(), filter.end(), filter_comp)); + +// ======================================================== + + // test restore_piece + print_title("test restore piece"); + p = setup_picker("1234567", " ", "", ""); + p->mark_as_finished(piece_block(0,0), 0); + p->mark_as_finished(piece_block(0,1), 0); + p->mark_as_finished(piece_block(0,2), 0); + p->mark_as_finished(piece_block(0,3), 0); + + picked = pick_pieces(p, "*******", 1, 0, 0, options, empty_vector); + TEST_CHECK(int(picked.size()) >= 1); + TEST_CHECK(picked.front().piece_index == 1); + + p->restore_piece(0); + picked = pick_pieces(p, "*******", 1, 0, 0, options, empty_vector); + TEST_CHECK(int(picked.size()) >= 1); + TEST_CHECK(picked.front().piece_index == 0); + + p->mark_as_finished(piece_block(0,0), 0); + p->mark_as_finished(piece_block(0,1), 0); + p->mark_as_finished(piece_block(0,2), 0); + p->mark_as_finished(piece_block(0,3), 0); + p->set_piece_priority(0, 0); + + picked = pick_pieces(p, "*******", 1, 0, 0, options, empty_vector); + TEST_CHECK(int(picked.size()) >= 1); + TEST_CHECK(picked.front().piece_index == 1); + + p->restore_piece(0); + picked = pick_pieces(p, "*******", 1, 0, 0, options, empty_vector); + TEST_CHECK(int(picked.size()) >= 1); + TEST_CHECK(picked.front().piece_index == 1); + + p->set_piece_priority(0, 7); + picked = pick_pieces(p, "*******", 1, 0, 0, options, empty_vector); + TEST_CHECK(int(picked.size()) >= 1); + TEST_CHECK(picked.front().piece_index == 0); + +// ======================================================== + + // test random mode + print_title("test random pick"); + p = setup_picker("1234567", " ", "1111122", ""); + std::set random_pieces; + for (int i = 0; i < 100; ++i) + random_pieces.insert(test_pick(p, 0)); + TEST_CHECK(random_pieces.size() == 7); + + random_pieces.clear(); + for (int i = 0; i < 7; ++i) + { + int piece = test_pick(p, 0); + p->we_have(piece); + random_pieces.insert(piece); + } + TEST_CHECK(random_pieces.size() == 7); + +// ======================================================== + + // make sure the piece picker will pick pieces that + // are already requested from other peers if it has to + print_title("test picking downloading blocks"); + p = setup_picker("1111111", " ", "", ""); + p->mark_as_downloading(piece_block(2,2), &tmp1); + p->mark_as_downloading(piece_block(1,2), &tmp1); + + picked.clear(); + p->pick_pieces(string2vec("*******"), picked, 7 * blocks_per_piece, 0, 0 + , 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, 0 + , 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, 0 + , 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, 0 + , 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); + + // don't pick locked pieces + picked.clear(); + p->lock_piece(1); + p->pick_pieces(string2vec(" ** "), picked, 7, 0, 0 + , 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); + + p->restore_piece(1); + p->mark_as_downloading(piece_block(2,0), &tmp1); + p->mark_as_downloading(piece_block(2,1), &tmp1); + p->mark_as_downloading(piece_block(2,3), &tmp1); + p->mark_as_downloading(piece_block(1,0), &tmp1); + p->mark_as_downloading(piece_block(1,1), &tmp1); + p->mark_as_downloading(piece_block(1,2), &tmp1); + p->mark_as_downloading(piece_block(1,3), &tmp1); + + picked.clear(); + p->pick_pieces(string2vec(" ** "), picked, 2, 0, 0 + , 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, 0 + , 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, 0 + , 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); + +// ======================================================== + + // test clear_peer + print_title("test clear_peer"); + p = setup_picker("1123333", " ", "", ""); + p->mark_as_downloading(piece_block(0, 0), &tmp1); + p->mark_as_downloading(piece_block(0, 1), &tmp2); + p->mark_as_downloading(piece_block(0, 2), &tmp3); + p->mark_as_downloading(piece_block(1, 1), &tmp1); + p->mark_as_downloading(piece_block(2, 1), &tmp2); + p->mark_as_downloading(piece_block(3, 1), &tmp3); + + std::vector dls; + torrent_peer* expected_dls1[] = {&tmp1, &tmp2, &tmp3, 0}; + torrent_peer* expected_dls2[] = {0, &tmp1, 0, 0}; + torrent_peer* expected_dls3[] = {0, &tmp2, 0, 0}; + torrent_peer* expected_dls4[] = {0, &tmp3, 0, 0}; + torrent_peer* expected_dls5[] = {&tmp1, 0, &tmp3, 0}; + p->get_downloaders(dls, 0); + TEST_CHECK(std::equal(dls.begin(), dls.end(), expected_dls1)); + p->get_downloaders(dls, 1); + TEST_CHECK(std::equal(dls.begin(), dls.end(), expected_dls2)); + p->get_downloaders(dls, 2); + TEST_CHECK(std::equal(dls.begin(), dls.end(), expected_dls3)); + p->get_downloaders(dls, 3); + TEST_CHECK(std::equal(dls.begin(), dls.end(), expected_dls4)); + + p->clear_peer(&tmp2); + p->get_downloaders(dls, 0); + TEST_CHECK(std::equal(dls.begin(), dls.end(), expected_dls5)); + +// ======================================================== + + // test have_all and have_none + print_title("test have_all and have_none"); + p = setup_picker("0123333", "* ", "", ""); + 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, &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); + +// ======================================================== + + // test have_all and have_none + print_title("test have_all and have_none with sequential download"); + p = setup_picker("0123333", "* ", "", ""); + 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); + +// ======================================================== + + // test inc_ref and dec_ref + print_title("test inc_ref dec_ref"); + p = setup_picker("1233333", " * ", "", ""); + TEST_CHECK(test_pick(p) == 0); + + p->dec_refcount(0, &tmp0); + TEST_CHECK(test_pick(p) == 1); + + p->dec_refcount(4, &tmp0); + p->dec_refcount(4, &tmp1); + TEST_CHECK(test_pick(p) == 4); + + // decrease refcount on something that's not in the piece list + p->dec_refcount(5, &tmp0); + p->inc_refcount(5, &tmp0); + + bitfield bits = string2vec("* "); + TEST_EQUAL(bits.get_bit(0), true); + TEST_EQUAL(bits.get_bit(1), false); + TEST_EQUAL(bits.get_bit(2), false); + TEST_EQUAL(bits.get_bit(3), false); + TEST_EQUAL(bits.get_bit(4), false); + TEST_EQUAL(bits.get_bit(5), false); + TEST_EQUAL(bits.get_bit(6), false); + p->inc_refcount(bits, &tmp0); + bits = string2vec(" * "); + + TEST_EQUAL(bits.get_bit(0), false); + TEST_EQUAL(bits.get_bit(1), false); + TEST_EQUAL(bits.get_bit(2), false); + TEST_EQUAL(bits.get_bit(3), false); + TEST_EQUAL(bits.get_bit(4), true); + TEST_EQUAL(bits.get_bit(5), false); + TEST_EQUAL(bits.get_bit(6), false); + p->dec_refcount(bits, &tmp2); + TEST_EQUAL(test_pick(p), 0); + +// ======================================================== + + // test unverified_blocks, marking blocks and get_downloader + print_title("test unverified blocks"); + p = setup_picker("1111111", " ", "", "0300700"); + TEST_CHECK(p->unverified_blocks() == 2 + 3); + TEST_CHECK(p->get_downloader(piece_block(4, 0)) == tmp_peer); + TEST_CHECK(p->get_downloader(piece_block(4, 1)) == tmp_peer); + TEST_CHECK(p->get_downloader(piece_block(4, 2)) == tmp_peer); + TEST_CHECK(p->get_downloader(piece_block(4, 3)) == 0); + p->mark_as_downloading(piece_block(4, 3), &peer_struct); + TEST_CHECK(p->get_downloader(piece_block(4, 3)) == &peer_struct); + p->piece_info(4, st); + TEST_CHECK(st.requested == 1); + TEST_CHECK(st.writing == 0); + TEST_CHECK(st.finished == 3); + TEST_CHECK(p->unverified_blocks() == 2 + 3); + p->mark_as_writing(piece_block(4, 3), &peer_struct); + TEST_CHECK(p->get_downloader(piece_block(4, 3)) == &peer_struct); + p->piece_info(4, st); + TEST_CHECK(st.requested == 0); + TEST_CHECK(st.writing == 1); + TEST_CHECK(st.finished == 3); + TEST_CHECK(p->unverified_blocks() == 2 + 3); + p->mark_as_finished(piece_block(4, 3), &peer_struct); + TEST_CHECK(p->get_downloader(piece_block(4, 3)) == &peer_struct); + p->piece_info(4, st); + TEST_CHECK(st.requested == 0); + TEST_CHECK(st.writing == 0); + TEST_CHECK(st.finished == 4); + TEST_CHECK(p->unverified_blocks() == 2 + 4); + p->we_have(4); + p->piece_info(4, st); + TEST_CHECK(st.requested == 0); + TEST_CHECK(st.writing == 0); + TEST_CHECK(st.finished == 4); + TEST_CHECK(p->get_downloader(piece_block(4, 3)) == 0); + TEST_CHECK(p->unverified_blocks() == 2); + +// ======================================================== + + // test prefer_contiguous_blocks + print_title("test prefer contiguous blocks"); + p = setup_picker("1111111", " ", "", ""); + picked = pick_pieces(p, "*******", 1, 3 * blocks_per_piece + , 0, options, empty_vector); + TEST_CHECK(int(picked.size()) >= 3 * blocks_per_piece); + piece_block b = picked.front(); + for (int i = 1; i < int(picked.size()); ++i) + { + TEST_CHECK(picked[i].piece_index * blocks_per_piece + picked[i].block_index + == b.piece_index * blocks_per_piece + b.block_index + 1); + b = picked[i]; + } + + picked = pick_pieces(p, "*******", 1, 3 * blocks_per_piece + , 0, options, empty_vector); + TEST_CHECK(int(picked.size()) >= 3 * blocks_per_piece); + b = picked.front(); + for (int i = 1; i < int(picked.size()); ++i) + { + TEST_CHECK(picked[i].piece_index * blocks_per_piece + picked[i].block_index + == b.piece_index * blocks_per_piece + b.block_index + 1); + b = picked[i]; + } + + // make sure pieces that don't match the 'whole pieces' requirement + // are picked if there's no other choice + p = setup_picker("1111111", " ", "", ""); + p->mark_as_downloading(piece_block(2,2), &tmp1); + picked = pick_pieces(p, "*******", 7 * blocks_per_piece - 1, blocks_per_piece + , 0, options, empty_vector); + TEST_CHECK(picked.size() == 7 * blocks_per_piece - 1); + TEST_CHECK(std::find(picked.begin(), picked.end(), piece_block(2,2)) == picked.end()); + + // test aligned whole pieces + print_title("test prefer aligned whole pieces"); + p = setup_picker("2222221222222222", " ", "", ""); + picked = pick_pieces(p, "****************", 1, 4 * blocks_per_piece, 0 + , 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 (std::vector::iterator i = picked.begin() + , end(picked.end()); i != end; ++i) + picked_pieces.insert(picked_pieces.begin(), i->piece_index); + + TEST_CHECK(picked_pieces.size() == 4); + int expected_pieces[] = {4,5,6,7}; + TEST_CHECK(std::equal(picked_pieces.begin(), picked_pieces.end(), expected_pieces)) + +//#error test picking with partial pieces and other peers present so that both backup_pieces and backup_pieces2 are used + +// ======================================================== + + // test parole mode + print_title("test parole mode"); + p = setup_picker("3333133", " ", "", ""); + p->mark_as_finished(piece_block(0, 0), 0); + picked = pick_pieces(p, "*******", 1, blocks_per_piece, 0 + + , 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[i] == piece_block(0, i + 1)); + + // make sure that the partial piece is not picked by a + // peer that is has not downloaded/requested the other blocks + picked = pick_pieces(p, "*******", 1, blocks_per_piece + , &peer_struct + , options | piece_picker::on_parole | piece_picker::prioritize_partials, empty_vector); + TEST_EQUAL(int(picked.size()), blocks_per_piece); + for (int i = 1; i < int(picked.size()); ++i) + TEST_CHECK(picked[i] == piece_block(4, i)); + +// ======================================================== + + // test suggested pieces + print_title("test suggested pieces"); + p = setup_picker("1111222233334444", " ", "", ""); + int v[] = {1, 5}; + const std::vector suggested_pieces(v, v + 2); + + picked = pick_pieces(p, "****************", 1, blocks_per_piece + , 0, options, suggested_pieces); + TEST_CHECK(int(picked.size()) >= blocks_per_piece); + for (int i = 1; i < int(picked.size()); ++i) + TEST_CHECK(picked[i] == piece_block(1, i)); + p->set_piece_priority(0, 0); + p->set_piece_priority(1, 0); + p->set_piece_priority(2, 0); + p->set_piece_priority(3, 0); + + picked = pick_pieces(p, "****************", 1, blocks_per_piece + , 0, options, suggested_pieces); + TEST_CHECK(int(picked.size()) >= blocks_per_piece); + for (int i = 1; i < int(picked.size()); ++i) + TEST_CHECK(picked[i] == piece_block(5, i)); + + p = setup_picker("1111222233334444", "**** ", "", ""); + picked = pick_pieces(p, "****************", 1, blocks_per_piece + , 0, options, suggested_pieces); + TEST_CHECK(int(picked.size()) >= blocks_per_piece); + for (int i = 1; i < int(picked.size()); ++i) + TEST_CHECK(picked[i] == piece_block(5, i)); + +// ======================================================== + + // test bitfield optimization + print_title("test bitfield optimization"); + // we have less than half of the pieces + p = setup_picker("2122222211221222", " ", "", ""); + // make sure it's not dirty + pick_pieces(p, "****************", 1, blocks_per_piece, 0); + 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, 0); + p->inc_refcount(string2vec(" ** ** * * "), &tmp8); + print_availability(p); + TEST_CHECK(verify_availability(p, "1132123201220322")); + +// ======================================================== + + // test seed optimizaton + print_title("test seed optimization"); + p = setup_picker("0000000000000000", " ", "", ""); + + // make sure it's not dirty + pick_pieces(p, "****************", 1, blocks_per_piece, 0); + + p->inc_refcount_all(&tmp0); + print_availability(p); + TEST_CHECK(verify_availability(p, "1111111111111111")); + + pick_pieces(p, "****************", 1, blocks_per_piece, 0); + p->dec_refcount(string2vec(" **** ** "), &tmp0); + print_availability(p); + TEST_CHECK(verify_availability(p, "1100001100111111")); + + pick_pieces(p, "****************", 1, blocks_per_piece, 0); + p->inc_refcount(string2vec(" **** ** "), &tmp0); + TEST_CHECK(verify_availability(p, "1111111111111111")); + + pick_pieces(p, "****************", 1, blocks_per_piece, 0); + 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, 0); + p->dec_refcount(3, &tmp1); + print_availability(p); + TEST_CHECK(verify_availability(p, "1110111111111111")); + + p->inc_refcount(string2vec("****************"), &tmp2); + print_availability(p); + TEST_CHECK(verify_availability(p, "2221222222222222")); + + p->inc_refcount(string2vec("* * * * * * * * "), &tmp3); + print_availability(p); + TEST_CHECK(verify_availability(p, "3231323232323232")); + + p->dec_refcount(string2vec("****************"), &tmp2); + print_availability(p); + TEST_CHECK(verify_availability(p, "2120212121212121")); + + p->dec_refcount(string2vec("* * * * * * * * "), &tmp3); + print_availability(p); + TEST_CHECK(verify_availability(p, "1110111111111111")); + +// ======================================================== + + // test reversed peers + print_title("test reversed peers"); + p = setup_picker("3333333", " *****", "", ""); + + // a reversed peer picked a block from piece 0 + // This should make the piece reversed + p->mark_as_downloading(piece_block(0,0), &tmp1 + , piece_picker::reverse); + + TEST_EQUAL(test_pick(p, piece_picker::rarest_first), 1); + + // make sure another reversed peer pick the same piece + TEST_EQUAL(test_pick(p, piece_picker::rarest_first | piece_picker::reverse), 0); + +// ======================================================== + + // test reversed pieces upgrading to normal pieces + print_title("test reversed piece upgrade"); + + p = setup_picker("3333333", " *****", "", ""); + + // make piece 0 partial and reversed + p->mark_as_downloading(piece_block(0,1), &tmp1 + , piece_picker::reverse); + TEST_EQUAL(test_pick(p), 1); + + // now have a regular peer pick the reversed block. It should now + // have turned into a regular one and be prioritized + p->mark_as_downloading(piece_block(0,2), &tmp1); + TEST_EQUAL(test_pick(p), 0); + + +// ======================================================== + + // test pieces downgrading to reversed pieces + print_title("test reversed piece downgrade"); +// now make sure a piece can be demoted to reversed if there are no +// other outstanding requests + + p = setup_picker("3333333", " ", "", ""); + + // make piece 0 partial and not reversed + p->mark_as_finished(piece_block(0,1), &tmp1); + + // a reversed peer picked a block from piece 0 + // This should make the piece reversed + p->mark_as_downloading(piece_block(0,0), &tmp1 + , piece_picker::reverse); + + TEST_EQUAL(test_pick(p, piece_picker::rarest_first | piece_picker::reverse), 0); + +// ======================================================== + + print_title("test piece_stats"); + + p = setup_picker("3456789", "* ", "", "0300000"); + + piece_picker::piece_stats_t stat = p->piece_stats(0); + TEST_EQUAL(stat.peer_count, 3); + TEST_EQUAL(stat.have, 1); + TEST_EQUAL(stat.downloading, 0); + + stat = p->piece_stats(1); + TEST_EQUAL(stat.peer_count, 4); + TEST_EQUAL(stat.have, 0); + TEST_EQUAL(stat.downloading, 1); + +// ======================================================== + + print_title("test piece passed"); + + p = setup_picker("1111111", "* ", "", "0300000"); + + TEST_EQUAL(p->has_piece_passed(0), true); + TEST_EQUAL(p->has_piece_passed(1), false); + TEST_EQUAL(p->num_passed(), 1); + TEST_EQUAL(p->num_have(), 1); + + p->piece_passed(1); + TEST_EQUAL(p->num_passed(), 2); + TEST_EQUAL(p->num_have(), 1); + + p->we_have(1); + TEST_EQUAL(p->num_have(), 2); + + p->mark_as_finished(piece_block(2,0), &tmp1); + p->piece_passed(2); + TEST_EQUAL(p->num_passed(), 3); + // just because the hash check passed doesn't mean + // we "have" the piece. We need to write it to disk first + TEST_EQUAL(p->num_have(), 2); + + // piece 2 already passed the hash check, as soon as we've + // written all the blocks to disk, we should have that piece too + p->mark_as_finished(piece_block(2,1), &tmp1); + p->mark_as_finished(piece_block(2,2), &tmp1); + p->mark_as_finished(piece_block(2,3), &tmp1); + TEST_EQUAL(p->num_have(), 3); + TEST_EQUAL(p->have_piece(2), true); + +// ======================================================== + + print_title("test piece passed (causing we_have)"); + + p = setup_picker("1111111", "* ", "", "0700000"); + + TEST_EQUAL(p->has_piece_passed(0), true); + TEST_EQUAL(p->has_piece_passed(1), false); + TEST_EQUAL(p->num_passed(), 1); + TEST_EQUAL(p->num_have(), 1); + + p->mark_as_finished(piece_block(1,3), &tmp1); + TEST_EQUAL(p->num_passed(), 1); + TEST_EQUAL(p->num_have(), 1); + + p->piece_passed(1); + TEST_EQUAL(p->num_passed(), 2); + TEST_EQUAL(p->num_have(), 2); + +// ======================================================== + + print_title("test break_one_seed"); + + 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).peer_count, 3); + + p->dec_refcount(0, &tmp1); + + TEST_EQUAL(p->piece_stats(0).peer_count, 2); + TEST_EQUAL(p->piece_stats(1).peer_count, 3); + TEST_EQUAL(p->piece_stats(2).peer_count, 3); + TEST_EQUAL(p->piece_stats(3).peer_count, 3); + +// ======================================================== + + print_title("test we dont have"); + + p = setup_picker("1111111", "* * ", "1101111", ""); + TEST_EQUAL(p->has_piece_passed(0), true); + TEST_EQUAL(p->has_piece_passed(1), false); + TEST_EQUAL(p->has_piece_passed(2), true); + TEST_EQUAL(p->num_passed(), 2); + TEST_EQUAL(p->num_have(), 2); + TEST_EQUAL(p->num_have_filtered(), 1); + TEST_EQUAL(p->num_filtered(), 0); + + p->we_dont_have(0); + + TEST_EQUAL(p->has_piece_passed(0), false); + TEST_EQUAL(p->has_piece_passed(1), false); + TEST_EQUAL(p->has_piece_passed(2), true); + TEST_EQUAL(p->num_passed(), 1); + TEST_EQUAL(p->num_have(), 1); + TEST_EQUAL(p->num_have_filtered(), 1); + + p = setup_picker("1111111", "* * ", "1101111", ""); + TEST_EQUAL(p->has_piece_passed(0), true); + TEST_EQUAL(p->has_piece_passed(1), false); + TEST_EQUAL(p->has_piece_passed(2), true); + TEST_EQUAL(p->num_passed(), 2); + TEST_EQUAL(p->num_have(), 2); + TEST_EQUAL(p->num_have_filtered(), 1); + TEST_EQUAL(p->num_filtered(), 0); + + p->we_dont_have(2); + + TEST_EQUAL(p->has_piece_passed(0), true); + TEST_EQUAL(p->has_piece_passed(1), false); + TEST_EQUAL(p->has_piece_passed(2), false); + TEST_EQUAL(p->num_passed(), 1); + TEST_EQUAL(p->num_have(), 1); + TEST_EQUAL(p->num_have_filtered(), 0); + +// ======================================================== + + print_title("test we dont have (don't have but passed hash check)"); + + p = setup_picker("1111111", "* * ", "1101111", "0200000"); + + TEST_EQUAL(p->has_piece_passed(0), true); + TEST_EQUAL(p->has_piece_passed(1), false); + TEST_EQUAL(p->have_piece(0), true) + TEST_EQUAL(p->have_piece(1), false) + + p->piece_passed(1); + + TEST_EQUAL(p->has_piece_passed(0), true); + TEST_EQUAL(p->has_piece_passed(1), true); + TEST_EQUAL(p->have_piece(1), false) + + p->we_dont_have(1); + + TEST_EQUAL(p->has_piece_passed(0), true); + TEST_EQUAL(p->has_piece_passed(1), false); + TEST_EQUAL(p->have_piece(1), false) + +// ======================================================== + + print_title("test write_failed"); + + p = setup_picker("1111111", "* * ", "1101111", "0200000"); + + TEST_EQUAL(p->has_piece_passed(0), true); + TEST_EQUAL(p->has_piece_passed(1), false); + TEST_EQUAL(p->have_piece(1), false); + + p->piece_passed(1); + + TEST_EQUAL(p->has_piece_passed(0), true); + TEST_EQUAL(p->has_piece_passed(1), true); + TEST_EQUAL(p->have_piece(1), false); + + p->mark_as_writing(piece_block(1, 0), &tmp1); + p->write_failed(piece_block(1, 0)); + + TEST_EQUAL(p->has_piece_passed(0), true); + TEST_EQUAL(p->has_piece_passed(1), false); + TEST_EQUAL(p->have_piece(1), false); + + // make sure write_failed() and lock_piece() actually + // locks the piece, and that it won't be picked. + // also make sure restore_piece() unlocks it and makes + // it available for picking again. + + picked = pick_pieces(p, " * ", 1, blocks_per_piece, 0); + TEST_EQUAL(picked.size(), 0); + + p->restore_piece(1); + + picked = pick_pieces(p, " * ", 1, blocks_per_piece, 0); + TEST_EQUAL(picked.size(), blocks_per_piece); + + // locking pieces only works on partial pieces + p->mark_as_writing(piece_block(1, 0), &tmp1); + p->lock_piece(1); + + picked = pick_pieces(p, " * ", 1, blocks_per_piece, 0); + TEST_EQUAL(picked.size(), 0); + +// ======================================================== + + print_title("test write_failed (clear piece)"); + + p = setup_picker("1111111", "* * ", "1101111", ""); + + stat = p->piece_stats(1); + TEST_EQUAL(stat.downloading, 0); + + p->mark_as_writing(piece_block(1, 0), &tmp1); + + stat = p->piece_stats(1); + TEST_EQUAL(stat.downloading, 1); + + p->write_failed(piece_block(1, 0)); + + stat = p->piece_stats(1); + TEST_EQUAL(stat.downloading, 0); + +// ======================================================== + + print_title("test mark_as_canceled"); + + p = setup_picker("1111111", "* * ", "1101111", ""); + + stat = p->piece_stats(1); + TEST_EQUAL(stat.downloading, 0); + + p->mark_as_writing(piece_block(1, 0), &tmp1); + + stat = p->piece_stats(1); + TEST_EQUAL(stat.downloading, 1); + + p->mark_as_canceled(piece_block(1, 0), &tmp1); + stat = p->piece_stats(1); + TEST_EQUAL(stat.downloading, 0); + +// ======================================================== + + print_title("test get_download_queue"); + + p = setup_picker("1111111", " ", "1101111", "0327000"); + + std::vector downloads + = p->get_download_queue(); + + // the download queue should have piece 1, 2 and 3 in it + TEST_EQUAL(downloads.size(), 3); + + TEST_CHECK(std::find_if(downloads.begin(), downloads.end() + , boost::bind(&piece_picker::downloading_piece::index, _1) + == boost::uint32_t(1)) != downloads.end()); + TEST_CHECK(std::find_if(downloads.begin(), downloads.end() + , boost::bind(&piece_picker::downloading_piece::index, _1) + == boost::uint32_t(2)) != downloads.end()); + TEST_CHECK(std::find_if(downloads.begin(), downloads.end() + , boost::bind(&piece_picker::downloading_piece::index, _1) + == boost::uint32_t(3)) != downloads.end()); + +// ======================================================== + + print_title("test get_download_queue_size"); + + p = setup_picker("1111111", " ", "1111111", "0327ff0"); + + TEST_EQUAL(p->get_download_queue_size(), 5); + + p->set_piece_priority(1, 0); + + 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); + +// ======================================================== + + print_title("test time_critical_mode"); + + p = setup_picker("1111111", " ", "1654741", "0352000"); + + // rarest-first + picked = pick_pieces(p, "*******", 7 * blocks_per_piece, 0, tmp_peer + , piece_picker::rarest_first | piece_picker::time_critical_mode, empty_vector); + TEST_EQUAL(picked.size(), blocks_per_piece); + for (int i = 0; i < int(picked.size()); ++i) + TEST_EQUAL(picked[0].piece_index, 4); + + // reverse rarest-first + picked = pick_pieces(p, "*******", 7 * blocks_per_piece, 0, tmp_peer + , piece_picker::reverse | piece_picker::rarest_first + | piece_picker::time_critical_mode, empty_vector); + TEST_EQUAL(picked.size(), blocks_per_piece); + for (int i = 0; i < int(picked.size()); ++i) + TEST_EQUAL(picked[0].piece_index, 4); + + // sequential + picked = pick_pieces(p, "*******", 7 * blocks_per_piece, 0, tmp_peer + , piece_picker::sequential | piece_picker::time_critical_mode, empty_vector); + TEST_EQUAL(picked.size(), blocks_per_piece); + for (int i = 0; i < int(picked.size()); ++i) + TEST_EQUAL(picked[0].piece_index, 4); + + // reverse sequential + picked = pick_pieces(p, "*******", 7 * blocks_per_piece, 0, tmp_peer + , piece_picker::reverse | piece_picker::sequential + | piece_picker::time_critical_mode, empty_vector); + TEST_EQUAL(picked.size(), blocks_per_piece); + for (int i = 0; i < int(picked.size()); ++i) + TEST_EQUAL(picked[0].piece_index, 4); + + // just critical + picked = pick_pieces(p, "*******", 7 * blocks_per_piece, 0, tmp_peer + , piece_picker::time_critical_mode, empty_vector); + TEST_EQUAL(picked.size(), blocks_per_piece); + for (int i = 0; i < int(picked.size()); ++i) + TEST_EQUAL(picked[0].piece_index, 4); + + // prioritize_partials + picked = pick_pieces(p, "*******", 7 * blocks_per_piece, 0, tmp_peer + , piece_picker::prioritize_partials | piece_picker::time_critical_mode, empty_vector); + TEST_EQUAL(picked.size(), blocks_per_piece); + for (int i = 0; i < int(picked.size()); ++i) + TEST_EQUAL(picked[0].piece_index, 4); + + // even when a non-critical piece is suggested should we ignore it + picked = pick_pieces(p, "*******", 7 * blocks_per_piece, 0, tmp_peer + , piece_picker::rarest_first | piece_picker::time_critical_mode + , suggested_pieces); + TEST_EQUAL(picked.size(), blocks_per_piece); + for (int i = 0; i < int(picked.size()); ++i) + TEST_EQUAL(picked[0].piece_index, 4); +} + diff --git a/test/test_primitives.cpp b/test/test_primitives.cpp new file mode 100644 index 0000000..7a1f91c --- /dev/null +++ b/test/test_primitives.cpp @@ -0,0 +1,163 @@ +/* + +Copyright (c) 2008-2012, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/entry.hpp" +#include "libtorrent/broadcast_socket.hpp" +#include "libtorrent/socket_io.hpp" // for print_endpoint +#include "libtorrent/announce_entry.hpp" + +#include "test.hpp" +#include "setup_transfer.hpp" + +using namespace libtorrent; + +sha1_hash to_hash(char const* s) +{ + sha1_hash ret; + from_hex(s, 40, (char*)&ret[0]); + return ret; +} + +address_v4 v4(char const* str) +{ + error_code ec; + return address_v4::from_string(str, ec); +} + +#if TORRENT_USE_IPV6 +address_v6 v6(char const* str) +{ + error_code ec; + return address_v6::from_string(str, ec); +} +#endif + +TORRENT_TEST(primitives) +{ + using namespace libtorrent; + error_code ec; + + // make sure the retry interval keeps growing + // on failing announces + announce_entry ae("dummy"); + int last = 0; + aux::session_settings sett; + sett.set_int(settings_pack::tracker_backoff, 250); + for (int i = 0; i < 10; ++i) + { + ae.failed(sett, 5); + int delay = ae.next_announce_in(); + TEST_CHECK(delay > last); + last = delay; + fprintf(stderr, "%d, ", delay); + } + fprintf(stderr, "\n"); + + // test error codes + 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"); + TEST_CHECK(error_code(errors::unsupported_protocol_version).message() == "unsupported protocol version"); + TEST_CHECK(error_code(errors::no_i2p_router).message() == "no i2p router is set up"); + TEST_CHECK(error_code(errors::http_parse_error).message() == "Invalid HTTP header"); + TEST_CHECK(error_code(errors::error_code_max).message() == "Unknown error"); + + TEST_CHECK(error_code(errors::unauthorized, get_http_category()).message() == "401 Unauthorized"); + TEST_CHECK(error_code(errors::service_unavailable, get_http_category()).message() == "503 Service Unavailable"); + + // test snprintf + + char msg[10]; + snprintf(msg, sizeof(msg), "too %s format string", "long"); + TEST_CHECK(strcmp(msg, "too long ") == 0); + + if (supports_ipv6()) + { + // make sure the assumption we use in policy's peer list hold + std::multimap peers; + std::multimap::iterator i; + peers.insert(std::make_pair(address::from_string("::1", ec), 0)); + peers.insert(std::make_pair(address::from_string("::2", ec), 3)); + peers.insert(std::make_pair(address::from_string("::3", ec), 5)); + i = peers.find(address::from_string("::2", ec)); + TEST_CHECK(i != peers.end()); + if (i != peers.end()) + { + TEST_CHECK(i->first == address::from_string("::2", ec)); + TEST_CHECK(i->second == 3); + } + } + + // test network functions + + // CIDR distance test + sha1_hash h1 = to_hash("0123456789abcdef01232456789abcdef0123456"); + sha1_hash h2 = to_hash("0123456789abcdef01232456789abcdef0123456"); + TEST_CHECK(common_bits(&h1[0], &h2[0], 20) == 160); + h2 = to_hash("0120456789abcdef01232456789abcdef0123456"); + TEST_CHECK(common_bits(&h1[0], &h2[0], 20) == 14); + h2 = to_hash("012f456789abcdef01232456789abcdef0123456"); + TEST_CHECK(common_bits(&h1[0], &h2[0], 20) == 12); + h2 = to_hash("0123456789abcdef11232456789abcdef0123456"); + TEST_CHECK(common_bits(&h1[0], &h2[0], 20) == 16 * 4 + 3); + + // test print_endpoint, parse_endpoint and print_address + TEST_EQUAL(print_endpoint(ep("127.0.0.1", 23)), "127.0.0.1:23"); +#if TORRENT_USE_IPV6 + TEST_EQUAL(print_endpoint(ep("ff::1", 1214)), "[ff::1]:1214"); +#endif + ec.clear(); + TEST_EQUAL(parse_endpoint("127.0.0.1:23", ec), ep("127.0.0.1", 23)); + TEST_CHECK(!ec); + ec.clear(); +#if TORRENT_USE_IPV6 + TEST_EQUAL(parse_endpoint(" \t[ff::1]:1214 \r", ec), ep("ff::1", 1214)); + TEST_CHECK(!ec); +#endif + TEST_EQUAL(print_address(v4("241.124.23.5")), "241.124.23.5"); +#if TORRENT_USE_IPV6 + TEST_EQUAL(print_address(v6("2001:ff::1")), "2001:ff::1"); + parse_endpoint("[ff::1]", ec); + TEST_EQUAL(ec, error_code(errors::invalid_port, get_libtorrent_category())); +#endif + + parse_endpoint("[ff::1:5", ec); + TEST_EQUAL(ec, error_code(errors::expected_close_bracket_in_address, get_libtorrent_category())); + + // test address_to_bytes + TEST_EQUAL(address_to_bytes(address_v4::from_string("10.11.12.13")), "\x0a\x0b\x0c\x0d"); + TEST_EQUAL(address_to_bytes(address_v4::from_string("16.5.127.1")), "\x10\x05\x7f\x01"); + + // test endpoint_to_bytes + TEST_EQUAL(endpoint_to_bytes(udp::endpoint(address_v4::from_string("10.11.12.13"), 8080)), "\x0a\x0b\x0c\x0d\x1f\x90"); + TEST_EQUAL(endpoint_to_bytes(udp::endpoint(address_v4::from_string("16.5.127.1"), 12345)), "\x10\x05\x7f\x01\x30\x39"); +} + diff --git a/test/test_priority.cpp b/test/test_priority.cpp new file mode 100644 index 0000000..a388825 --- /dev/null +++ b/test/test_priority.cpp @@ -0,0 +1,424 @@ +/* + +Copyright (c) 2008-2013, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/session.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/thread.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/file.hpp" +#include "libtorrent/torrent_info.hpp" +#include +#include + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "settings.hpp" +#include +#include + +using namespace libtorrent; +namespace lt = libtorrent; +using boost::tuples::ignore; + +const int mask = alert::all_categories & ~(alert::performance_warning | alert::stats_notification); + +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; +} + +void cleanup() +{ + error_code ec; + remove_all("tmp1_priorities", ec); + remove_all("tmp2_priorities", ec); + remove_all("tmp1_priorities_moved", ec); + remove_all("tmp2_priorities_moved", ec); +} + +void test_transfer(settings_pack const& sett) +{ + // this allows shutting down the sessions in parallel + std::vector sp; + + cleanup(); + + 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::alert_mask, mask); + + pack.set_int(settings_pack::out_enc_policy, settings_pack::pe_disabled); + pack.set_int(settings_pack::in_enc_policy, settings_pack::pe_disabled); + pack.set_bool(settings_pack::allow_multiple_connections_per_ip, false); + pack.set_int(settings_pack::unchoke_slots_limit, 8); + + pack.set_bool(settings_pack::enable_upnp, false); + pack.set_bool(settings_pack::enable_natpmp, false); + pack.set_bool(settings_pack::enable_lsd, false); + pack.set_bool(settings_pack::enable_dht, false); + + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:48075"); + pack.set_int(settings_pack::alert_mask, mask); + + lt::session ses1(pack); + + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:49075"); + pack.set_int(settings_pack::alert_mask, mask); + lt::session ses2(pack); + + torrent_handle tor1; + torrent_handle tor2; + + error_code ec; + create_directory("tmp1_priority", ec); + std::ofstream file("tmp1_priority/temporary"); + boost::shared_ptr t = ::create_torrent(&file, "temporary", 16 * 1024, 13, false); + file.close(); + + add_torrent_params addp; + addp.flags &= ~add_torrent_params::flag_paused; + addp.flags &= ~add_torrent_params::flag_auto_managed; + + wait_for_listen(ses1, "ses1"); + wait_for_listen(ses2, "ses1"); + + peer_disconnects = 0; + + // test using piece sizes smaller than 16kB + boost::tie(tor1, tor2, ignore) = setup_transfer(&ses1, &ses2, 0 + , true, false, true, "_priority", 8 * 1024, &t, false, 0); + + int num_pieces = tor2.torrent_file()->num_pieces(); + std::vector priorities(num_pieces, 1); + // set half of the pieces to priority 0 + std::fill(priorities.begin(), priorities.begin() + (num_pieces / 2), 0); + tor2.prioritize_pieces(priorities); + std::cerr << "setting priorities: "; + std::copy(priorities.begin(), priorities.end(), std::ostream_iterator(std::cerr, ", ")); + std::cerr << std::endl; + + for (int i = 0; i < 200; ++i) + { + print_alerts(ses1, "ses1", true, true, true, &on_alert); + print_alerts(ses2, "ses2", true, 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::cerr << "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); + + if (peer_disconnects >= 2) break; + + // if nothing is being transferred after 2 seconds, we're failing the test + if (st1.upload_payload_rate == 0 && i > 20) break; + + test_sleep(100); + } + + TEST_CHECK(!tor2.status().is_seeding); + TEST_CHECK(tor2.status().is_finished); + + if (tor2.status().is_finished) + std::cerr << "torrent is finished (50% complete)" << std::endl; + else return; + + std::vector priorities2 = tor2.piece_priorities(); + std::copy(priorities2.begin(), priorities2.end(), std::ostream_iterator(std::cerr, ", ")); + std::cerr << std::endl; + TEST_CHECK(std::equal(priorities.begin(), priorities.end(), priorities2.begin())); + + std::cerr << "force recheck" << std::endl; + tor2.force_recheck(); + + priorities2 = tor2.piece_priorities(); + std::copy(priorities2.begin(), priorities2.end(), std::ostream_iterator(std::cerr, ", ")); + std::cerr << 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, true, &on_alert); + print_alerts(ses2, "ses2", true, true, true, &on_alert); + + st2 = tor2.status(); + if (i % 10 == 0) + { + std::cerr << int(st2.progress * 100) << "% " << std::endl; + } + if (st2.state == torrent_status::finished) break; + test_sleep(100); + } + + TEST_EQUAL(st2.state, torrent_status::finished); + + if (st2.state != torrent_status::finished) + return; + + std::cerr << "recheck complete" << std::endl; + + priorities2 = tor2.piece_priorities(); + std::copy(priorities2.begin(), priorities2.end(), std::ostream_iterator(std::cerr, ", ")); + std::cerr << std::endl; + TEST_CHECK(std::equal(priorities.begin(), priorities.end(), priorities2.begin())); + + tor2.pause(); + wait_for_alert(ses2, torrent_paused_alert::alert_type, "ses2"); + + fprintf(stderr, "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::cerr << "ses2: " << a->message() << std::endl; + if (alert_cast(a)) + { + bencode(std::back_inserter(resume_data) + , *alert_cast(a)->resume_data); + fprintf(stderr, "saved resume data\n"); + goto done; + } + else if (alert_cast(a)) + { + fprintf(stderr, "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; + + fprintf(stderr, "%s\n", &resume_data[0]); + + ses2.remove_torrent(tor2); + + std::cerr << "removed" << std::endl; + + test_sleep(100); + + std::cout << "re-adding" << std::endl; + add_torrent_params p; + p.flags &= ~add_torrent_params::flag_paused; + p.flags &= ~add_torrent_params::flag_auto_managed; + p.ti = t; + p.save_path = "tmp2_priority"; + p.resume_data = resume_data; + 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, true, &on_alert); + print_alerts(ses2, "ses2", true, true, true, &on_alert); + + st1 = tor1.status(); + st2 = tor2.status(); + + TEST_CHECK(st1.state == torrent_status::seeding); + + if (st2.is_finished) break; + + test_sleep(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); + 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::cerr, ", ")); + std::cerr << std::endl; + + // drain alerts + print_alerts(ses1, "ses1", true, true, true, &on_alert); + print_alerts(ses2, "ses2", true, 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, true, &on_alert); + print_alerts(ses2, "ses2", true, 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) + { + fprintf(stderr, "too many disconnects (%d), exiting\n", peer_disconnects); + break; + } + + test_sleep(100); + } + + st2 = tor2.status(); + if (!st2.is_seeding) + fprintf(stderr, "ses2 failed to reconnect to ses1!\n"); + TEST_CHECK(st2.is_seeding); + + sp.push_back(ses1.abort()); + sp.push_back(ses2.abort()); +} + +TORRENT_TEST(priority) +{ + using namespace libtorrent; + settings_pack p = settings(); + test_transfer(p); + cleanup(); +} + +// test to set piece and file priority on a torrent that doesn't have metadata +// yet +TORRENT_TEST(no_metadata_file_prio) +{ + settings_pack pack = settings(); + lt::session ses(pack); + + add_torrent_params addp; + addp.flags &= ~add_torrent_params::flag_paused; + addp.flags &= ~add_torrent_params::flag_auto_managed; + addp.info_hash = sha1_hash("abababababababababab"); + addp.save_path = "."; + torrent_handle h = ses.add_torrent(addp); + + h.file_priority(0, 0); + TEST_EQUAL(h.file_priority(0), 0); + h.file_priority(0, 1); + TEST_EQUAL(h.file_priority(0), 1); + + ses.remove_torrent(h); +} + +TORRENT_TEST(no_metadata_piece_prio) +{ + settings_pack pack = settings(); + lt::session ses(pack); + + add_torrent_params addp; + addp.flags &= ~add_torrent_params::flag_paused; + addp.flags &= ~add_torrent_params::flag_auto_managed; + addp.info_hash = sha1_hash("abababababababababab"); + addp.save_path = "."; + torrent_handle h = ses.add_torrent(addp); + + // you can't set piece priorities before the metadata has been downloaded + h.piece_priority(2, 0); + TEST_EQUAL(h.piece_priority(2), 4); + h.piece_priority(2, 1); + TEST_EQUAL(h.piece_priority(2), 4); + + ses.remove_torrent(h); +} diff --git a/test/test_privacy.cpp b/test/test_privacy.cpp new file mode 100644 index 0000000..cac9875 --- /dev/null +++ b/test/test_privacy.cpp @@ -0,0 +1,324 @@ +/* + +Copyright (c) 2013, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "dht_server.hpp" +#include "peer_server.hpp" +#include "udp_tracker.hpp" +#include "test_utils.hpp" +#include "settings.hpp" + +#include "libtorrent/alert.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/torrent_info.hpp" + +#include + +using namespace libtorrent; +namespace lt = libtorrent; + +char const* proxy_name[] = { + "none", + "socks4", + "socks5", + "socks5_pw", + "http", + "http_pw", + "i2p_proxy" +}; + +std::vector rejected_trackers; + +bool alert_predicate(libtorrent::alert const* a) +{ + anonymous_mode_alert const* am = alert_cast(a); + if (am == NULL) return false; + + if (am->kind == anonymous_mode_alert::tracker_not_anonymous) + rejected_trackers.push_back(am->str); + + return false; +} + +enum flags_t +{ + force_proxy_mode = 1, + expect_http_connection = 2, + expect_udp_connection = 4, + expect_http_reject = 8, + expect_udp_reject = 16, + expect_dht_msg = 32, + expect_peer_connection = 64, + expect_possible_udp_connection = 128, + expect_possible_dht_msg = 256, +}; + +session_proxy test_proxy(settings_pack::proxy_type_t proxy_type, int flags) +{ +#ifdef TORRENT_DISABLE_DHT + // if DHT is disabled, we won't get any requests to it + flags &= ~expect_dht_msg; +#endif + fprintf(stderr, "\n=== TEST == proxy: %s anonymous-mode: %s\n\n", proxy_name[proxy_type], (flags & force_proxy_mode) ? "yes" : "no"); + int http_port = start_web_server(); + int udp_port = start_udp_tracker(); + int dht_port = start_dht(); + int peer_port = start_peer(); + + int prev_udp_announces = num_udp_announces(); + + int const alert_mask = alert::all_categories + & ~alert::progress_notification + & ~alert::stats_notification; + + 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::force_proxy, flags & force_proxy_mode); + sett.set_int(settings_pack::alert_mask, alert_mask); + 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 + libtorrent::random() % 50000; + char iface[200]; + snprintf(iface, sizeof(iface), "127.0.0.1:%d", listen_port); + listen_port += (libtorrent::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, (settings_pack::proxy_type_t)proxy_type); + sett.set_int(settings_pack::proxy_port, 4444); + + lt::session* s = new lt::session(sett); + + error_code ec; + remove_all("tmp1_privacy", ec); + create_directory("tmp1_privacy", ec); + std::ofstream file(combine_path("tmp1_privacy", "temporary").c_str()); + boost::shared_ptr t = ::create_torrent(&file, "temporary", 16 * 1024, 13, false); + file.close(); + + char http_tracker_url[200]; + snprintf(http_tracker_url, sizeof(http_tracker_url), "http://127.0.0.1:%d/announce", http_port); + t->add_tracker(http_tracker_url, 0); + + char udp_tracker_url[200]; + snprintf(udp_tracker_url, sizeof(udp_tracker_url), "udp://127.0.0.1:%d/announce", udp_port); + t->add_tracker(udp_tracker_url, 1); + + add_torrent_params addp; + addp.flags &= ~add_torrent_params::flag_paused; + addp.flags &= ~add_torrent_params::flag_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 |= add_torrent_params::flag_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); + + printf("connect_peer: 127.0.0.1:%d\n", peer_port); + h.connect_peer(tcp::endpoint(address_v4::from_string("127.0.0.1"), peer_port)); + + rejected_trackers.clear(); + +#ifdef TORRENT_USE_VALGRIND + const int timeout = 100; +#else + const int timeout = 20; +#endif + + for (int i = 0; i < timeout; ++i) + { + print_alerts(*s, "s", false, false, false, &alert_predicate); + test_sleep(100); + + if (num_udp_announces() >= prev_udp_announces + 1 + && num_peer_hits() > 0) + break; + } + + // we should have announced to the tracker by now + if (flags & expect_possible_udp_connection) + { + // this flag is true if we may fail open, but also might not have had + // enough time to fail yet + TEST_CHECK(num_udp_announces() == prev_udp_announces + || num_udp_announces() == prev_udp_announces + 1); + } + else + { + TEST_EQUAL(num_udp_announces(), prev_udp_announces + bool(flags & expect_udp_connection)); + } + + if (flags & expect_possible_udp_connection) + { + // this flag is true if we may fail open, but also might not have had + // enough time to fail yet + TEST_CHECK(num_dht_hits() == 0 || num_dht_hits() == 1); + } + else + { + 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_udp_reject) + TEST_CHECK(std::find(rejected_trackers.begin(), rejected_trackers.end(), udp_tracker_url) != rejected_trackers.end()); + + if (flags & expect_http_reject) + TEST_CHECK(std::find(rejected_trackers.begin(), rejected_trackers.end(), http_tracker_url) != rejected_trackers.end()); + + fprintf(stderr, "%s: ~session\n", time_now_string()); + session_proxy pr = s->abort(); + delete s; + + stop_peer(); + stop_dht(); + stop_udp_tracker(); + stop_web_server(); + return pr; +} + +// 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); +} + +TORRENT_TEST(socks4) +{ + test_proxy(settings_pack::socks4, expect_udp_connection | expect_dht_msg); +} + +TORRENT_TEST(socks5) +{ + test_proxy(settings_pack::socks5, expect_possible_udp_connection | expect_possible_dht_msg); +} + +TORRENT_TEST(socks5_pw) +{ + test_proxy(settings_pack::socks5_pw,expect_possible_udp_connection | expect_possible_dht_msg); +} + +TORRENT_TEST(http) +{ + test_proxy(settings_pack::http, expect_udp_connection | expect_dht_msg); +} + +TORRENT_TEST(http_pt) +{ + test_proxy(settings_pack::http_pw, expect_udp_connection | expect_dht_msg); +} + +TORRENT_TEST(i2p) +{ + test_proxy(settings_pack::i2p_proxy, expect_udp_connection | expect_dht_msg); +} + +// using anonymous mode + +// anonymous mode doesn't require a proxy when one isn't configured. It could be +// used with a VPN for instance. This will all changed in 1.0, where anonymous +// mode is separated from force_proxy + +TORRENT_TEST(anon_no_proxy) +{ + test_proxy(settings_pack::none, force_proxy_mode | expect_peer_connection); +} + +TORRENT_TEST(anon_socks4) +{ + test_proxy(settings_pack::socks4, force_proxy_mode | expect_udp_reject); +} + +TORRENT_TEST(anon_socks5) +{ + test_proxy(settings_pack::socks5, force_proxy_mode); +} + +TORRENT_TEST(anon_socks5_pw) +{ + test_proxy(settings_pack::socks5_pw, force_proxy_mode); +} + +TORRENT_TEST(anon_http) +{ + test_proxy(settings_pack::http, force_proxy_mode | expect_udp_reject); +} + +TORRENT_TEST(anon_http_pw) +{ + test_proxy(settings_pack::http_pw, force_proxy_mode | expect_udp_reject); +} + +TORRENT_TEST(anon_i2p) +{ + test_proxy(settings_pack::i2p_proxy, force_proxy_mode); +} + diff --git a/test/test_random.cpp b/test/test_random.cpp new file mode 100644 index 0000000..7854bb8 --- /dev/null +++ b/test/test_random.cpp @@ -0,0 +1,65 @@ +/* + +Copyright (c) 2014, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "libtorrent/random.hpp" + +using namespace libtorrent; + +TORRENT_TEST(random) +{ + + const int repetitions = 200000; + + for (int byte = 0; byte < 4; ++byte) + { + int buckets[256]; + memset(buckets, 0, sizeof(buckets)); + + for (int i = 0; i < repetitions; ++i) + { + boost::uint32_t val = libtorrent::random(); + val >>= byte * 8; + ++buckets[val & 0xff]; + } + + for (int i = 0; i < 256; ++i) + { + const int expected = repetitions / 256; + // expect each bucket to be within 15% of the expected value + fprintf(stderr, "%d: %f\n", i, float(buckets[i] - expected) * 100.f / expected); + TEST_CHECK(abs(buckets[i] - expected) < expected / 6); + } + } +} + diff --git a/test/test_read_piece.cpp b/test/test_read_piece.cpp new file mode 100644 index 0000000..011c098 --- /dev/null +++ b/test/test_read_piece.cpp @@ -0,0 +1,163 @@ +/* + +Copyright (c) 2013, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/session.hpp" +#include "test.hpp" +#include "setup_transfer.hpp" +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/torrent_info.hpp" + +enum flags_t +{ + seed_mode = 1, + time_critical = 2 +}; + +void test_read_piece(int flags) +{ + using namespace libtorrent; + namespace lt = libtorrent; + + fprintf(stderr, "==== TEST READ PIECE =====\n"); + + // in case the previous run was terminated + error_code ec; + remove_all("tmp1_read_piece", ec); + if (ec) fprintf(stderr, "ERROR: removing tmp1_read_piece: (%d) %s\n" + , ec.value(), ec.message().c_str()); + + create_directory("tmp1_read_piece", ec); + if (ec) fprintf(stderr, "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) fprintf(stderr, "ERROR: creating directory test_torrent: (%d) %s\n" + , ec.value(), ec.message().c_str()); + + file_storage fs; + std::srand(10); + int piece_size = 0x4000; + + static const int file_sizes[] = { 100000, 10000 }; + + create_random_files(combine_path("tmp1_read_piece", "test_torrent") + , file_sizes, 2); + + add_files(fs, combine_path("tmp1_read_piece", "test_torrent")); + libtorrent::create_torrent t(fs, piece_size, 0x4000); + + // calculate the hash for all pieces + set_piece_hashes(t, "tmp1_read_piece", ec); + if (ec) fprintf(stderr, "ERROR: set_piece_hashes: (%d) %s\n" + , ec.value(), ec.message().c_str()); + + std::vector buf; + bencode(std::back_inserter(buf), t.generate()); + boost::shared_ptr ti(new torrent_info(&buf[0], buf.size(), ec)); + + fprintf(stderr, "generated torrent: %s tmp1_read_piece/test_torrent\n" + , to_hex(ti->info_hash().to_string()).c_str()); + + const int mask = alert::all_categories + & ~(alert::progress_notification + | alert::performance_warning + | alert::stats_notification); + + settings_pack sett; + sett.set_bool(settings_pack::enable_lsd, false); + sett.set_bool(settings_pack::enable_natpmp, false); + sett.set_bool(settings_pack::enable_upnp, false); + sett.set_bool(settings_pack::enable_dht, false); + sett.set_int(settings_pack::alert_mask, mask); + sett.set_str(settings_pack::listen_interfaces, "0.0.0.0:48000"); + sett.set_int(settings_pack::alert_mask, alert::all_categories); + lt::session ses(sett); + + add_torrent_params p; + p.flags &= ~add_torrent_params::flag_paused; + p.flags &= ~add_torrent_params::flag_auto_managed; + p.save_path = "tmp1_read_piece"; + p.ti = ti; + if (flags & seed_mode) + p.flags |= add_torrent_params::flag_seed_mode; + torrent_handle tor1 = ses.add_torrent(p, ec); + 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, 0, torrent_handle::alert_when_available); + } + else + { + tor1.read_piece(1); + } + + a = wait_for_alert(ses, read_piece_alert::alert_type, "ses"); + + TEST_CHECK(a); + if (a) + { + read_piece_alert const* rp = alert_cast(a); + TEST_CHECK(rp); + if (rp) + { + TEST_EQUAL(rp->piece, 1); + } + } + + remove_all("tmp1_read_piece", ec); + if (ec) fprintf(stderr, "ERROR: removing tmp1_read_piece: (%d) %s\n" + , ec.value(), ec.message().c_str()); +} + +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_receive_buffer.cpp b/test/test_receive_buffer.cpp new file mode 100644 index 0000000..7059864 --- /dev/null +++ b/test/test_receive_buffer.cpp @@ -0,0 +1,255 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "test.hpp" +#include "libtorrent/receive_buffer.hpp" + +using namespace libtorrent; + +struct allocator : buffer_allocator_interface +{ + void free_disk_buffer(char*) {} + char* allocate_disk_buffer(char const*) { TORRENT_ASSERT(false); return NULL; } + char* allocate_disk_buffer(bool& + , boost::shared_ptr + , char const*) { TORRENT_ASSERT(false); return NULL; } + char* async_allocate_disk_buffer(char const* + , boost::function const&) { TORRENT_ASSERT(false); return NULL; } + void reclaim_block(block_cache_reference ref) {} +}; + +TORRENT_TEST(recv_buffer_init) +{ + allocator a; + receive_buffer b(a); + + 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) +{ + allocator a; + receive_buffer b(a); + + b.cut(0, 1000); + // allocate some space to receive into + boost::array vec; + int num_bufs = b.reserve(vec, 1000); + + // since we don't have a disk buffer, there should only be a single + // range/buffer + TEST_EQUAL(num_bufs, 1); + + b.received(1000); + b.advance_pos(999); + + TEST_EQUAL(b.pos_at_end(), false); +} + +TORRENT_TEST(recv_buffer_pos_at_end_true) +{ + allocator a; + receive_buffer b(a); + b.cut(0, 1000); + b.reserve(1000); + boost::array vec; + int num_bufs = b.reserve(vec, 1000); + TEST_EQUAL(num_bufs, 1); + b.received(1000); + b.advance_pos(1000); + TEST_EQUAL(b.pos_at_end(), true); +} + +TORRENT_TEST(recv_buffer_packet_finished) +{ + allocator a; + receive_buffer b(a); + // packet_size = 10 + b.cut(0, 10); + b.reserve(1000); + boost::array vec; + int num_bufs = b.reserve(vec, 1000); + TEST_EQUAL(num_bufs, 1); + 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_disk_buffer) +{ + char disk_buffer; // fake disk buffer pointer + + allocator a; + receive_buffer b(a); + b.reserve(1000); + b.cut(0, 1000); // packet size = 1000 + boost::array vec; + b.assign_disk_buffer(&disk_buffer, 137); + int num_bufs = b.reserve(vec, 1000); + TEST_EQUAL(num_bufs, 2); + + // regular buffer disk buffer + // -----------------====== + // + // |----------------------| 1000 + // |-----| 137 + // |----------------| 863 + + TEST_EQUAL(boost::asio::buffer_size(vec[0]), 863); + TEST_EQUAL(boost::asio::buffer_size(vec[1]), 137); +} + +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + +TORRENT_TEST(recv_buffer_mutable_buffers_regular_and_disk) +{ + char disk_buffer; // fake disk buffer pointer + + allocator a; + receive_buffer b(a); + 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 + // and it has a disk buffer + b.assign_disk_buffer(&disk_buffer, 137); + std::vector vec; + packet_transferred = b.advance_pos(999); + TEST_EQUAL(packet_transferred, 999); + b.mutable_buffers(vec, 999); + TEST_EQUAL(vec.size(), 2); + + // previous packet + // | + // v regular buffer disk buffer + // - - - -----------------====== + // ^ + // | + // m_recv_start + + // |----------------------| 1000 packet size + // |-----| 137 disk buffer + // |----------------| 863 regular buffer + + TEST_EQUAL(boost::asio::buffer_size(vec[0]), 863); + TEST_EQUAL(boost::asio::buffer_size(vec[1]), 137 - 1); + TEST_EQUAL(boost::asio::buffer_size(vec[0]) + + boost::asio::buffer_size(vec[1]), 999); +} + +TORRENT_TEST(recv_buffer_mutable_buffers_regular_only) +{ + allocator a; + receive_buffer b(a); + 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 + std::vector vec; + packet_transferred = b.advance_pos(999); + TEST_EQUAL(packet_transferred, 999); + b.mutable_buffers(vec, 999); + TEST_EQUAL(vec.size(), 1); + + // previous packet + // | + // v regular buffer + // - - - ----------------------- + // ^ + // | + // m_recv_start + + // |----------------------| 1000 packet size + // |---------------------| 999 regular buffer + + TEST_EQUAL(boost::asio::buffer_size(vec[0]), 999); +} + +TORRENT_TEST(recv_buffer_mutable_buffers_disk) +{ + char disk_buffer; // fake disk buffer pointer + + allocator a; + receive_buffer b(a); + 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 + // and it has a disk buffer + b.assign_disk_buffer(&disk_buffer, 1000); + std::vector vec; + packet_transferred = b.advance_pos(999); + TEST_EQUAL(packet_transferred, 999); + b.mutable_buffers(vec, 999); + TEST_EQUAL(vec.size(), 1); + + // previous packet + // | + // v disk buffer + // - - - ======================= + // ^ + // | + // m_recv_start + + // |----------------------| 1000 packet size + // |----------------------| 999 disk buffer + + TEST_EQUAL(boost::asio::buffer_size(vec[0]), 999); + TEST_EQUAL(boost::asio::buffer_cast(vec[0]), &disk_buffer); +} + +#endif + diff --git a/test/test_recheck.cpp b/test/test_recheck.cpp new file mode 100644 index 0000000..4b290e7 --- /dev/null +++ b/test/test_recheck.cpp @@ -0,0 +1,118 @@ +/* + +Copyright (c) 2012, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/session.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/thread.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/file.hpp" +#include "libtorrent/error_code.hpp" +#include +#include + +#include "test.hpp" +#include "setup_transfer.hpp" + +#include +#include + +using namespace libtorrent; +namespace lt = libtorrent; + +const int mask = alert::all_categories & ~(alert::performance_warning | alert::stats_notification); + +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 < 400; ++i) + { + print_alerts(ses, "ses1"); + torrent_status st = h.status(); + fprintf(stderr, "%f %%\n", st.progress_ppm / 10000.f); + 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(10)) break; + test_sleep(500); + } + TEST_ERROR("torrent did not finish"); +} + +TORRENT_TEST(recheck) +{ + error_code ec; + settings_pack sett; + sett.set_str(settings_pack::listen_interfaces, "0.0.0.0:48675"); + sett.set_int(settings_pack::alert_mask, mask); + 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) fprintf(stderr, "create_directory: %s\n", ec.message().c_str()); + std::ofstream file("tmp1_recheck/temporary"); + boost::shared_ptr t = ::create_torrent(&file, "temporary", 4 * 1024 * 1024 + , 7, false); + file.close(); + + add_torrent_params param; + param.flags &= ~add_torrent_params::flag_paused; + param.flags &= ~add_torrent_params::flag_auto_managed; + param.ti = t; + param.save_path = "tmp1_recheck"; + param.flags |= add_torrent_params::flag_seed_mode; + torrent_handle tor1 = ses1.add_torrent(param, ec); + if (ec) fprintf(stderr, "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..e7eed22 --- /dev/null +++ b/test/test_remap_files.cpp @@ -0,0 +1,557 @@ +/* + +Copyright (c) 2014, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/session.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/torrent_info.hpp" +#include "setup_transfer.hpp" +#include "test.hpp" + +#include +#include + +using namespace libtorrent; +namespace lt = libtorrent; +using boost::tuples::ignore; + +namespace { + +template +boost::shared_ptr clone_ptr(boost::shared_ptr const& ptr) +{ + return boost::shared_ptr(new T(*ptr)); +} + +int peer_disconnects = 0; + +bool on_alert(alert const* a) +{ + if (alert_cast(a)) + ++peer_disconnects; + + return false; +} + +} // anonymous namespace + +void test_remap_files_gather(storage_mode_t storage_mode = storage_mode_sparse) +{ + // in case the previous run was terminated + error_code ec; + + int const alert_mask = alert::all_categories + & ~alert::progress_notification + & ~alert::stats_notification; + + session_proxy p1; + session_proxy p2; + + settings_pack sett; + 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); + sett.set_str(settings_pack::listen_interfaces, "0.0.0.0:48075"); + sett.set_int(settings_pack::alert_mask, alert_mask); + + lt::session ses1(sett); + + sett.set_str(settings_pack::listen_interfaces, "0.0.0.0:49075"); + lt::session ses2(sett); + + torrent_handle tor1; + torrent_handle tor2; + + create_directory("tmp1_remap", ec); + create_directory(combine_path("tmp1_remap", "test_torrent_dir"), ec); + if (ec) + { + fprintf(stderr, "error creating directory: %s\n" + , ec.message().c_str()); + TEST_CHECK(false); + return; + } + + static const int file_sizes[] = + { 50, 16000-50, 16000, 1700, 100, 8000, 8000, 1,1,10,10,10,1000,10,10,10,10,1000,10,10,10,1,1,1 + ,10,1000,1000,1000,10,1000,130,65000,340,750,20,300,400,5000,23000,900,43000,4000,43000,60, 40}; + + create_random_files(combine_path("tmp1_remap", "test_torrent_dir") + , file_sizes, sizeof(file_sizes)/sizeof(file_sizes[0])); + file_storage fs; + + // generate a torrent with pad files to make sure they + // are not requested web seeds + add_files(fs, combine_path("tmp1_remap", "test_torrent_dir")); + libtorrent::create_torrent ct(fs, 0x8000, 0x4000); + set_piece_hashes(ct, "tmp1_remap", ec); + if (ec) + { + fprintf(stderr, "error creating hashes for test torrent: %s\n" + , ec.message().c_str()); + TEST_CHECK(false); + return; + } + std::vector buf; + bencode(std::back_inserter(buf), ct.generate()); + boost::shared_ptr t(new torrent_info(&buf[0], buf.size(), ec)); + boost::shared_ptr t2(new torrent_info(&buf[0], buf.size(), ec)); + + // remap the files to a single one + file_storage st; + st.add_file("single_file", t->total_size()); + t2->remap_files(st); + + add_torrent_params params; + params.storage_mode = storage_mode; + params.flags &= ~add_torrent_params::flag_paused; + params.flags &= ~add_torrent_params::flag_auto_managed; + + wait_for_listen(ses1, "ses1"); + wait_for_listen(ses2, "ses2"); + + peer_disconnects = 0; + + // test using piece sizes smaller than 16kB + boost::tie(tor1, tor2, ignore) = setup_transfer(&ses1, &ses2, 0 + , true, false, true, "_remap", 8 * 1024, &t, false, ¶ms + , true, false, &t2); + + fprintf(stderr, "\ntesting remap gather\n\n"); + + for (int i = 0; i < 50; ++i) + { + print_alerts(ses1, "ses1", true, true, true, &on_alert); + print_alerts(ses2, "ses2", true, 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); + } + + 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::cerr << "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); + + if (peer_disconnects >= 2) break; + + test_sleep(100); + } + + torrent_status st2 = tor2.status(); + TEST_CHECK(st2.is_seeding); + + if (!st2.is_seeding) return; + + fprintf(stderr, "\ntesting force recheck\n\n"); + + // test force rechecking a seeding torrent with remapped files + tor2.force_recheck(); + + for (int i = 0; i < 50; ++i) + { + print_alerts(ses2, "ses2", true, true, true, &on_alert); + + torrent_status st2 = tor2.status(); + + if (i % 10 == 0) + { + print_ses_rate(i / 10.f, NULL, &st2); + } + + if (st2.state != torrent_status::checking_files) + { + static char const* state_str[] = + {"checking (q)", "checking", "dl metadata" + , "downloading", "finished", "seeding", "allocating", "checking (r)"}; + std::cerr << "st2 state: " << state_str[st2.state] << std::endl; + } + + if (st2.progress == 1.0) break; + + test_sleep(100); + } + + st2 = tor2.status(); + TEST_CHECK(st2.is_seeding); + + p1 = ses1.abort(); + p2 = ses2.abort(); +} + +void test_remap_files_scatter(storage_mode_t storage_mode = storage_mode_sparse) +{ + int num_files = 10; + + // in case the previous run was terminated + error_code ec; + + int const alert_mask = alert::all_categories + & ~alert::progress_notification + & ~alert::stats_notification; + + session_proxy p1; + session_proxy p2; + + settings_pack sett; + 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); + sett.set_str(settings_pack::listen_interfaces, "0.0.0.0:48075"); + sett.set_int(settings_pack::alert_mask, alert_mask); + + lt::session ses1(sett); + + sett.set_str(settings_pack::listen_interfaces, "0.0.0.0:49075"); + sett.set_int(settings_pack::alert_mask, alert_mask); + lt::session ses2(sett); + + torrent_handle tor1; + torrent_handle tor2; + + create_directory("tmp1_remap2", ec); + std::ofstream file("tmp1_remap2/temporary"); + boost::shared_ptr t = ::create_torrent(&file, "temporary", 32 * 1024, 7); + file.close(); + + file_storage fs; + for (int i = 0; i < num_files-1; ++i) + { + char name[100]; + snprintf(name, sizeof(name), "multifile/file%d.txt", i); + fs.add_file(name, t->total_size() / 10); + } + char name[100]; + snprintf(name, sizeof(name), "multifile/file%d.txt", num_files); + // the last file has to be a special case to make the size + // add up exactly (in case the total size is not divisible by 10). + fs.add_file(name, t->total_size() - fs.total_size()); + + boost::shared_ptr t2 = clone_ptr(t); + + t2->remap_files(fs); + + add_torrent_params params; + params.storage_mode = storage_mode; + params.flags &= ~add_torrent_params::flag_paused; + params.flags &= ~add_torrent_params::flag_auto_managed; + + wait_for_listen(ses1, "ses1"); + wait_for_listen(ses2, "ses2"); + + peer_disconnects = 0; + + // test using piece sizes smaller than 16kB + boost::tie(tor1, tor2, ignore) = setup_transfer(&ses1, &ses2, 0 + , true, false, true, "_remap2", 8 * 1024, &t, false, ¶ms + , true, false, &t2); + + fprintf(stderr, "\ntesting remap scatter\n\n"); + + for (int i = 0; i < 50; ++i) + { + print_alerts(ses1, "ses1", true, true, true, &on_alert); + print_alerts(ses2, "ses2", true, 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); + } + + 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::cerr << "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); + + if (peer_disconnects >= 2) break; + + test_sleep(100); + } + + torrent_status st2 = tor2.status(); + TEST_CHECK(st2.is_seeding); + + if (!st2.is_seeding) return; + + fprintf(stderr, "\ntesting force recheck\n\n"); + + // test force rechecking a seeding torrent with remapped files + tor2.force_recheck(); + + for (int i = 0; i < 50; ++i) + { + print_alerts(ses2, "ses2", true, true, true, &on_alert); + + torrent_status st2 = tor2.status(); + + if (i % 10 == 0) + { + print_ses_rate(i / 10.f, NULL, &st2); + } + + if (st2.state != torrent_status::checking_files) + { + static char const* state_str[] = + {"checking (q)", "checking", "dl metadata" + , "downloading", "finished", "seeding", "allocating", "checking (r)"}; + std::cerr << "st2 state: " << state_str[st2.state] << std::endl; + } + + if (st2.progress == 1.0) break; + + test_sleep(100); + } + + st2 = tor2.status(); + TEST_CHECK(st2.is_seeding); + + p1 = ses1.abort(); + p2 = ses2.abort(); +} + +void test_remap_files_prio(storage_mode_t storage_mode = storage_mode_sparse) +{ + // in case the previous run was terminated + error_code ec; + + int const alert_mask = alert::all_categories + & ~alert::progress_notification + & ~alert::stats_notification; + + session_proxy p1; + session_proxy p2; + + settings_pack sett; + 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); + sett.set_str(settings_pack::listen_interfaces, "0.0.0.0:48075"); + sett.set_int(settings_pack::alert_mask, alert_mask); + lt::session ses1(sett); + + sett.set_str(settings_pack::listen_interfaces, "0.0.0.0:49075"); + lt::session ses2(sett); + + torrent_handle tor1; + torrent_handle tor2; + + create_directory("tmp1_remap3", ec); + create_directory(combine_path("tmp1_remap3", "test_torrent_dir"), ec); + + // create a torrent with 2 files, remap them into 3 files and make sure + // the file priorities don't break things + static const int file_sizes[] = {100000, 100000}; + const int num_files = sizeof(file_sizes)/sizeof(file_sizes[0]); + + create_random_files(combine_path("tmp1_remap3", "test_torrent_dir") + , file_sizes, num_files); + + file_storage fs1; + const int piece_size = 0x4000; + + add_files(fs1, combine_path("tmp1_remap3", "test_torrent_dir")); + libtorrent::create_torrent ct(fs1, piece_size, 0x4000 + , libtorrent::create_torrent::optimize_alignment); + + // calculate the hash for all pieces + set_piece_hashes(ct, "tmp1_remap3", ec); + if (ec) fprintf(stderr, "ERROR: set_piece_hashes: (%d) %s\n" + , ec.value(), ec.message().c_str()); + + std::vector buf; + bencode(std::back_inserter(buf), ct.generate()); + boost::shared_ptr t(new torrent_info(&buf[0], buf.size(), ec)); + + int num_new_files = 3; + + file_storage fs; + for (int i = 0; i < num_new_files-1; ++i) + { + char name[100]; + snprintf(name, sizeof(name), "multifile/file%d.txt", i); + fs.add_file(name, t->total_size() / 10); + } + char name[100]; + snprintf(name, sizeof(name), "multifile/file%d.txt", num_new_files); + // the last file has to be a special case to make the size + // add up exactly (in case the total size is not divisible by 10). + fs.add_file(name, t->total_size() - fs.total_size()); + + boost::shared_ptr t2 = clone_ptr(t); + + t2->remap_files(fs); + + add_torrent_params params; + params.storage_mode = storage_mode; + params.flags |= add_torrent_params::flag_paused; + params.flags &= ~add_torrent_params::flag_auto_managed; + + wait_for_listen(ses1, "ses1"); + wait_for_listen(ses2, "ses2"); + + peer_disconnects = 0; + + // test using piece sizes smaller than 16kB + boost::tie(tor1, tor2, ignore) = setup_transfer(&ses1, &ses2, 0 + , true, false, true, "_remap3", 8 * 1024, &t, false, ¶ms + , true, false, &t2); + + std::vector file_prio(3, 1); + file_prio[0] = 0; + tor2.prioritize_files(file_prio); + + // torrent1 will attempt to connect to torrent2 + // make sure torrent2 is up and running by then + tor2.resume(); + test_sleep(500); + tor1.resume(); + + fprintf(stderr, "\ntesting remap scatter prio\n\n"); + + for (int i = 0; i < 50; ++i) + { + print_alerts(ses1, "ses1", true, true, true, &on_alert); + print_alerts(ses2, "ses2", true, true, true, &on_alert); + + torrent_status st1 = tor1.status(); + torrent_status st2 = tor2.status(); + + if (i % 10 == 0) + { + std::cerr + << "\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 + << std::endl; + } + + 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::cerr << "st2 state: " << state_str[st2.state] << std::endl; + } + + TEST_CHECK(st1.state == torrent_status::seeding); + TEST_CHECK(st2.state == torrent_status::downloading + || st2.state == torrent_status::checking_resume_data); + + if (peer_disconnects >= 2) break; + + test_sleep(100); + } + + torrent_status st2 = tor2.status(); + TEST_CHECK(st2.is_finished); + + p1 = ses1.abort(); + p2 = ses2.abort(); +} + +using namespace libtorrent; + +TORRENT_TEST(remap_files) +{ + test_remap_files_gather(); + + error_code ec; + remove_all("tmp1_remap", ec); + remove_all("tmp2_remap", ec); + remove_all("tmp1_remap2", ec); + remove_all("tmp2_remap2", ec); +} + +TORRENT_TEST(scatter) +{ + test_remap_files_scatter(); + + error_code ec; + remove_all("tmp1_remap", ec); + remove_all("tmp2_remap", ec); + remove_all("tmp1_remap2", ec); + remove_all("tmp2_remap2", ec); + remove_all("tmp1_remap3", ec); + remove_all("tmp2_remap3", ec); +} + +TORRENT_TEST(prio) +{ + test_remap_files_prio(); + + error_code ec; + remove_all("tmp1_remap", ec); + remove_all("tmp2_remap", ec); + remove_all("tmp1_remap2", ec); + remove_all("tmp2_remap2", ec); + remove_all("tmp1_remap3", ec); + remove_all("tmp2_remap3", ec); +} + diff --git a/test/test_resolve_links.cpp b/test/test_resolve_links.cpp new file mode 100644 index 0000000..c2f23c9 --- /dev/null +++ b/test/test_resolve_links.cpp @@ -0,0 +1,132 @@ +/* + +Copyright (c) 2014, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/resolve_links.hpp" +#include "libtorrent/file.hpp" // for combine_path +#include +#include + +using namespace libtorrent; + +struct test_torrent_t +{ + char const* filename1; + char const* filename2; + int 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) +{ + +#ifndef TORRENT_DISABLE_MUTABLE_TORRENTS + 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"; + fprintf(stderr, "loading %s\n", p.c_str()); + boost::shared_ptr ti1 = boost::make_shared(p); + + p = combine_path(path, e.filename2) + ".torrent"; + fprintf(stderr, "loading %s\n", p.c_str()); + boost::shared_ptr ti2 = boost::make_shared(p); + + fprintf(stderr, "resolving\n"); + resolve_links l(ti1); + l.match(ti2, "."); + + std::vector const& links = l.get_links(); + + int num_matches = std::count_if(links.begin(), links.end() + , boost::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 (int i = 0; i < int(links.size()); ++i) + { + TORRENT_ASSERT(i < fs.num_files()); + fprintf(stderr, "%s --> %s : %d\n", fs.file_name(i).c_str() + , links[i].ti ? to_hex(links[i].ti->info_hash() + .to_string()).c_str() : "", links[i].file_idx); + } + } + + TEST_EQUAL(num_matches, e.expected_matches); + + } +#endif // TORRENT_DISABLE_MUTABLE_TORRENTS +} + diff --git a/test/test_resume.cpp b/test/test_resume.cpp new file mode 100644 index 0000000..4f6ef33 --- /dev/null +++ b/test/test_resume.cpp @@ -0,0 +1,742 @@ +/* + +Copyright (c) 2014, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/session.hpp" +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/bencode.hpp" + +#include + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "settings.hpp" + +using namespace libtorrent; +namespace lt = libtorrent; + +boost::shared_ptr generate_torrent() +{ + file_storage fs; + fs.add_file("test_resume/tmp1", 128 * 1024 * 8); + fs.add_file("test_resume/tmp2", 128 * 1024); + fs.add_file("test_resume/tmp3", 128 * 1024); + lt::create_torrent t(fs, 128 * 1024, 6); + + t.add_tracker("http://torrent_file_tracker.com/announce"); + t.add_url_seed("http://torrent_file_url_seed.com/"); + + int num = t.num_pieces(); + TEST_CHECK(num > 0); + for (int i = 0; i < num; ++i) + { + sha1_hash ph; + for (int k = 0; k < 20; ++k) ph[k] = lt::random(); + t.set_hash(i, ph); + } + + std::vector buf; + bencode(std::back_inserter(buf), t.generate()); + return boost::make_shared(&buf[0], buf.size()); +} + +std::vector generate_resume_data(torrent_info* ti + , char const* file_priorities = "") +{ + entry rd; + + rd["file-format"] = "libtorrent resume file"; + rd["file-version"] = 1; + rd["info-hash"] = ti->info_hash().to_string(); + rd["blocks per piece"] = (std::max)(1, ti->piece_length() / 0x4000); + rd["pieces"] = std::string(ti->num_pieces(), '\x01'); + + rd["total_uploaded"] = 1337; + rd["total_downloaded"] = 1338; + rd["active_time"] = 1339; + rd["seeding_time"] = 1340; + rd["num_seeds"] = 1341; + rd["num_downloaders"] = 1342; + 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_scrape"] = 1349; + rd["last_download"] = 1350; + rd["last_upload"] = 1351; + rd["finished_time"] = 1352; + if (file_priorities && file_priorities[0]) + { + entry::list_type& file_prio = rd["file_priority"].list(); + for (int i = 0; file_priorities[i]; ++i) + file_prio.push_back(entry(file_priorities[i] - '0')); + } + + rd["piece_priority"] = std::string(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); + + return ret; +} + +torrent_handle test_resume_flags(lt::session& ses, int flags + , char const* file_priorities = "1111", char const* resume_file_prio = "") +{ + boost::shared_ptr ti = generate_torrent(); + + add_torrent_params p; + + p.ti = ti; + p.flags = flags; +#ifdef TORRENT_WINDOWS + p.save_path = "c:\\add_torrent_params save_path"; +#else + p.save_path = "/add_torrent_params save_path"; +#endif + p.trackers.push_back("http://add_torrent_params_tracker.com/announce"); + p.url_seeds.push_back("http://add_torrent_params_url_seed.com"); + + std::vector rd = generate_resume_data(ti.get(), resume_file_prio); + p.resume_data.swap(rd); + + p.max_uploads = 1; + p.max_connections = 2; + p.upload_limit = 3; + p.download_limit = 4; + + std::vector priorities_vector; + for (int i = 0; file_priorities[i]; ++i) + priorities_vector.push_back(file_priorities[i] - '0'); + + p.file_priorities = priorities_vector; + + torrent_handle h = ses.add_torrent(p); + torrent_status s = h.status(); + TEST_EQUAL(s.info_hash, ti->info_hash()); + return h; +} + +void default_tests(torrent_status const& s) +{ + // allow some slack in the time stamps since they are reported as + // relative times. If the computer is busy while running the unit test + // or running under valgrind it may take several seconds + TEST_CHECK(s.last_scrape >= 1349); + TEST_CHECK(s.time_since_download >= 1350); + TEST_CHECK(s.time_since_upload >= 1351); + TEST_CHECK(s.active_time >= 1339); + + TEST_CHECK(s.last_scrape < 1349 + 10); + TEST_CHECK(s.time_since_download < 1350 + 10); + TEST_CHECK(s.time_since_upload < 1351 + 10); + TEST_CHECK(s.active_time < 1339 + 10); + + TEST_EQUAL(s.finished_time, 1352); + TEST_EQUAL(s.seeding_time, 1340); + TEST_EQUAL(s.added_time, 1347); + TEST_EQUAL(s.completed_time, 1348); +} + +TORRENT_TEST(piece_priorities) +{ + lt::session ses(settings()); + boost::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, 0); + h.piece_priority(ti->num_pieces()-1, 0); + + h.save_resume_data(); + alert const* a = wait_for_alert(ses, save_resume_data_alert::alert_type); + + TEST_CHECK(a); + if (save_resume_data_alert const* ra = alert_cast(a)) + { + fprintf(stderr, "%s\n", ra->resume_data->to_string().c_str()); + entry::string_type prios = (*ra->resume_data)["piece_priority"].string(); + TEST_EQUAL(int(prios.size()), ti->num_pieces()); + TEST_EQUAL(prios[0], '\0'); + TEST_EQUAL(prios[1], '\x04'); + TEST_EQUAL(prios[ti->num_pieces()-1], '\0'); + + bencode(std::back_inserter(p.resume_data), *ra->resume_data); + } + + ses.remove_torrent(h); + + // now, make sure the piece priorities are loaded correctly + h = ses.add_torrent(p); + + TEST_EQUAL(h.piece_priority(0), 0); + TEST_EQUAL(h.piece_priority(1), 4); + TEST_EQUAL(h.piece_priority(ti->num_pieces()-1), 0); +} + +// TODO: test what happens when loading a resume file with both piece priorities +// and file priorities (file prio should take presedence) + +// TODO: make sure a resume file only ever contain file priorities OR piece +// priorities. Never both. + +// TODO: generally save + +TORRENT_TEST(file_priorities_default) +{ + lt::session ses(settings()); + std::vector file_priorities = test_resume_flags(ses, 0, "", "").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); +} + +TORRENT_TEST(file_priorities_resume_seed_mode) +{ + // in share mode file priorities should always be 0 + lt::session ses(settings()); + std::vector file_priorities = test_resume_flags(ses, + add_torrent_params::flag_share_mode, "", "123").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_seed_mode) +{ + // in share mode file priorities should always be 0 + lt::session ses(settings()); + std::vector file_priorities = test_resume_flags(ses, + add_torrent_params::flag_share_mode, "123", "").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(zero_file_prio) +{ + fprintf(stderr, "test_file_prio\n"); + + lt::session ses(settings()); + boost::shared_ptr ti = generate_torrent(); + add_torrent_params p; + p.ti = ti; + p.save_path = "."; + + entry rd; + + rd["file-format"] = "libtorrent resume file"; + rd["file-version"] = 1; + rd["info-hash"] = ti->info_hash().to_string(); + rd["blocks per piece"] = (std::max)(1, ti->piece_length() / 0x4000); + + entry::list_type& file_prio = rd["file_priority"].list(); + for (int i = 0; i < 100; ++i) + { + file_prio.push_back(entry(0)); + } + + std::string pieces(ti->num_pieces(), '\x01'); + rd["pieces"] = pieces; + + std::string pieces_prio(ti->num_pieces(), '\x01'); + rd["piece_priority"] = pieces_prio; + + bencode(back_inserter(p.resume_data), rd); + + torrent_handle h = ses.add_torrent(p); + + torrent_status s = h.status(); + TEST_EQUAL(s.total_wanted, 0); +} + +void test_seed_mode(bool file_prio, bool pieces_have, bool piece_prio + , bool all_files_zero = false) +{ + fprintf(stderr, "test_seed_mode file_prio: %d pieces_have: %d piece_prio: %d\n" + , file_prio, pieces_have, piece_prio); + + lt::session ses(settings()); + boost::shared_ptr ti = generate_torrent(); + add_torrent_params p; + p.ti = ti; + p.save_path = "."; + + entry rd; + + rd["file-format"] = "libtorrent resume file"; + rd["file-version"] = 1; + rd["info-hash"] = ti->info_hash().to_string(); + rd["blocks per piece"] = (std::max)(1, ti->piece_length() / 0x4000); + + if (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 (all_files_zero) + { + for (int i = 0; i < 100; ++i) + { + file_prio.push_back(entry(0)); + } + } + } + + std::string pieces(ti->num_pieces(), '\x01'); + if (pieces_have) + { + pieces[0] = '\0'; + } + rd["pieces"] = pieces; + + std::string pieces_prio(ti->num_pieces(), '\x01'); + if (piece_prio) + { + pieces_prio[0] = '\0'; + } + rd["piece_priority"] = pieces_prio; + + rd["seed_mode"] = 1; + + bencode(back_inserter(p.resume_data), rd); + + torrent_handle h = ses.add_torrent(p); + + torrent_status s = h.status(); + if (file_prio || piece_prio || pieces_have) + { + TEST_EQUAL(s.seed_mode, false); + } + else + { + TEST_EQUAL(s.seed_mode, true); + } +} + +TORRENT_TEST(seed_mode_file_prio) +{ + test_seed_mode(true, false, false); +} + +TORRENT_TEST(seed_mode_piece_prio) +{ + test_seed_mode(false, true, false); +} + +TORRENT_TEST(seed_mode_piece_have) +{ + test_seed_mode(false, false, true); +} + +TORRENT_TEST(seed_mode_preserve) +{ + test_seed_mode(false, false, false); +} + +TORRENT_TEST(resume_save_load) +{ + lt::session ses(settings()); + torrent_handle h = test_resume_flags(ses, 0, "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 == NULL) return; + + TEST_CHECK(a->resume_data); + + entry& e = *a->resume_data.get(); + entry::list_type& l = e["file_priority"].list(); + entry::list_type::iterator i = l.begin(); + + TEST_EQUAL(l.size(), 3); + TEST_EQUAL(*i++, 1); + TEST_EQUAL(*i++, 2); + TEST_EQUAL(*i++, 3); +} + +TORRENT_TEST(resume_save_load_resume) +{ + lt::session ses(settings()); + torrent_handle h = test_resume_flags(ses, 0, "", "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 == NULL) return; + + TEST_CHECK(a->resume_data); + + entry& e = *a->resume_data.get(); + entry::list_type& l = e["file_priority"].list(); + entry::list_type::iterator i = l.begin(); + + TEST_EQUAL(l.size(), 3); + TEST_EQUAL(*i++, 1); + TEST_EQUAL(*i++, 2); + TEST_EQUAL(*i++, 3); +} + +TORRENT_TEST(file_priorities_resume_override) +{ + // 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, + add_torrent_params::flag_override_resume_data, "", "123").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) +{ + lt::session ses(settings()); + std::vector file_priorities = test_resume_flags(ses, 0, "", "123").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) +{ + lt::session ses(settings()); + std::vector file_priorities = test_resume_flags(ses, 0, "010").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) +{ + lt::session ses(settings()); + std::vector file_priorities = test_resume_flags(ses, 0, "123").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) +{ + lt::session ses(settings()); + std::vector file_priorities = test_resume_flags(ses, 0, "4321").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) +{ + lt::session ses(settings()); + + torrent_status s = test_resume_flags(ses, 0).status(); + default_tests(s); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(s.save_path, "c:\\add_torrent_params save_path"); +#else + TEST_EQUAL(s.save_path, "/add_torrent_params save_path"); +#endif + TEST_EQUAL(s.sequential_download, false); + TEST_EQUAL(s.paused, false); + TEST_EQUAL(s.auto_managed, false); + TEST_EQUAL(s.seed_mode, false); + TEST_EQUAL(s.super_seeding, false); + TEST_EQUAL(s.share_mode, false); + TEST_EQUAL(s.upload_mode, false); + TEST_EQUAL(s.ip_filter_applies, false); + TEST_EQUAL(s.connections_limit, 1345); + TEST_EQUAL(s.uploads_limit, 1346); +} + +TORRENT_TEST(use_resume_save_path) +{ + lt::session ses(settings()); + torrent_status s = test_resume_flags(ses, add_torrent_params::flag_use_resume_save_path).status(); + default_tests(s); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(s.save_path, "c:\\resume_data save_path"); +#else + TEST_EQUAL(s.save_path, "/resume_data save_path"); +#endif + TEST_EQUAL(s.sequential_download, false); + TEST_EQUAL(s.paused, false); + TEST_EQUAL(s.auto_managed, false); + TEST_EQUAL(s.seed_mode, false); + TEST_EQUAL(s.super_seeding, false); + TEST_EQUAL(s.share_mode, false); + TEST_EQUAL(s.upload_mode, false); + TEST_EQUAL(s.ip_filter_applies, false); + TEST_EQUAL(s.connections_limit, 1345); + TEST_EQUAL(s.uploads_limit, 1346); +} + +TORRENT_TEST(override_resume_data) +{ + lt::session ses(settings()); + torrent_status s = test_resume_flags(ses + , add_torrent_params::flag_override_resume_data + | add_torrent_params::flag_paused).status(); + + default_tests(s); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(s.save_path, "c:\\add_torrent_params save_path"); +#else + TEST_EQUAL(s.save_path, "/add_torrent_params save_path"); +#endif + TEST_EQUAL(s.sequential_download, false); + TEST_EQUAL(s.paused, true); + TEST_EQUAL(s.auto_managed, false); + TEST_EQUAL(s.seed_mode, false); + TEST_EQUAL(s.super_seeding, false); + TEST_EQUAL(s.share_mode, false); + TEST_EQUAL(s.upload_mode, false); + TEST_EQUAL(s.ip_filter_applies, false); + TEST_EQUAL(s.connections_limit, 2); + TEST_EQUAL(s.uploads_limit, 1); +} + +TORRENT_TEST(seed_mode) +{ + lt::session ses(settings()); + torrent_status s = test_resume_flags(ses, add_torrent_params::flag_override_resume_data + | add_torrent_params::flag_seed_mode).status(); + default_tests(s); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(s.save_path, "c:\\add_torrent_params save_path"); +#else + TEST_EQUAL(s.save_path, "/add_torrent_params save_path"); +#endif + TEST_EQUAL(s.sequential_download, false); + TEST_EQUAL(s.paused, false); + TEST_EQUAL(s.auto_managed, false); + TEST_EQUAL(s.seed_mode, true); + TEST_EQUAL(s.super_seeding, false); + TEST_EQUAL(s.share_mode, false); + TEST_EQUAL(s.upload_mode, false); + TEST_EQUAL(s.ip_filter_applies, false); + TEST_EQUAL(s.connections_limit, 2); + TEST_EQUAL(s.uploads_limit, 1); +} + +TORRENT_TEST(upload_mode) +{ + lt::session ses(settings()); + torrent_status s = test_resume_flags(ses, add_torrent_params::flag_upload_mode).status(); + default_tests(s); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(s.save_path, "c:\\add_torrent_params save_path"); +#else + TEST_EQUAL(s.save_path, "/add_torrent_params save_path"); +#endif + TEST_EQUAL(s.sequential_download, false); + TEST_EQUAL(s.paused, false); + TEST_EQUAL(s.auto_managed, false); + TEST_EQUAL(s.seed_mode, false); + TEST_EQUAL(s.super_seeding, false); + TEST_EQUAL(s.share_mode, false); + TEST_EQUAL(s.upload_mode, true); + TEST_EQUAL(s.ip_filter_applies, false); + TEST_EQUAL(s.connections_limit, 1345); + TEST_EQUAL(s.uploads_limit, 1346); +} + +TORRENT_TEST(share_mode) +{ + lt::session ses(settings()); + torrent_status s = test_resume_flags(ses + , add_torrent_params::flag_override_resume_data + | add_torrent_params::flag_share_mode).status(); + default_tests(s); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(s.save_path, "c:\\add_torrent_params save_path"); +#else + TEST_EQUAL(s.save_path, "/add_torrent_params save_path"); +#endif + TEST_EQUAL(s.sequential_download, false); + TEST_EQUAL(s.paused, false); + TEST_EQUAL(s.auto_managed, false); + TEST_EQUAL(s.seed_mode, false); + TEST_EQUAL(s.super_seeding, false); + TEST_EQUAL(s.share_mode, true); + TEST_EQUAL(s.upload_mode, false); + TEST_EQUAL(s.ip_filter_applies, false); + TEST_EQUAL(s.connections_limit, 2); + TEST_EQUAL(s.uploads_limit, 1); +} + +TORRENT_TEST(auto_managed) +{ + lt::session ses(settings()); + // resume data overrides the auto-managed flag + torrent_status s = test_resume_flags(ses, add_torrent_params::flag_auto_managed).status(); + default_tests(s); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(s.save_path, "c:\\add_torrent_params save_path"); +#else + TEST_EQUAL(s.save_path, "/add_torrent_params save_path"); +#endif + TEST_EQUAL(s.sequential_download, false); + TEST_EQUAL(s.paused, false); + TEST_EQUAL(s.auto_managed, false); + TEST_EQUAL(s.seed_mode, false); + TEST_EQUAL(s.super_seeding, false); + TEST_EQUAL(s.share_mode, false); + TEST_EQUAL(s.upload_mode, false); + TEST_EQUAL(s.ip_filter_applies, false); + TEST_EQUAL(s.connections_limit, 1345); + TEST_EQUAL(s.uploads_limit, 1346); +} + +TORRENT_TEST(paused) +{ + lt::session ses(settings()); + // resume data overrides the paused flag + torrent_status s = test_resume_flags(ses, add_torrent_params::flag_paused).status(); + default_tests(s); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(s.save_path, "c:\\add_torrent_params save_path"); +#else + TEST_EQUAL(s.save_path, "/add_torrent_params save_path"); +#endif + TEST_EQUAL(s.sequential_download, false); + TEST_EQUAL(s.paused, false); + TEST_EQUAL(s.auto_managed, false); + TEST_EQUAL(s.seed_mode, false); + TEST_EQUAL(s.super_seeding, false); + TEST_EQUAL(s.share_mode, false); + TEST_EQUAL(s.upload_mode, false); + TEST_EQUAL(s.ip_filter_applies, false); + 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) +{ + // merge url seeds with resume data + fprintf(stderr, "flags: merge_resume_http_seeds\n"); + lt::session ses(settings()); + torrent_handle h = test_resume_flags(ses, + add_torrent_params::flag_merge_resume_http_seeds); + 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) +{ + // resume data overrides the .torrent_file + fprintf(stderr, "flags: no merge_resume_http_seed\n"); + lt::session ses(settings()); + torrent_handle h = test_resume_flags(ses, + add_torrent_params::flag_merge_resume_trackers); + 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); +} + diff --git a/test/test_session.cpp b/test/test_session.cpp new file mode 100644 index 0000000..fec541a --- /dev/null +++ b/test/test_session.cpp @@ -0,0 +1,260 @@ +/* + +Copyright (c) 2013, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/session.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#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 + +using namespace libtorrent; +namespace lt = libtorrent; + +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::cache_size, 100); + sett.set_int(settings_pack::max_queued_disk_bytes, 1000 * 16 * 1024); + + ses.apply_settings(sett); + + // verify that we get the appropriate performance warning because + // we're allowing a larger queue than we have cache. + + alert const* a; + for (;;) + { + a = wait_for_alert(ses, performance_alert::alert_type, "ses1"); + + if (a == NULL) break; + TEST_EQUAL(a->type(), performance_alert::alert_type); + + if (alert_cast(a)->warning_code + == performance_alert::too_high_disk_queue_limit) + 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); + + // make sure the destructor waits properly + // for the asynchronous call to set the alert + // mask completes, before it goes on to destruct + // the session object +} + +TORRENT_TEST(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 = boost::make_shared("", 0, boost::ref(ignore_errors)); + atp.save_path = "."; + error_code ec; + torrent_handle h = ses.add_torrent(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() + , boost::bind(&stats_metric::value_index, _1) + < boost::bind(&stats_metric::value_index, _2)); + + 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[i].value_index, i); + } +} + +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::add_torrent_params::flag_paused; + ps.save_path = "."; + + torrent_handle h = s.add_torrent(ps); + + test_sleep(2000); + h.resume(); + test_sleep(1000); + + TEST_CHECK(!h.status().paused); +} + +#if __cplusplus >= 201103L + +template +void test_save_restore(Set setup, Save s, Default d, Load l) +{ + entry st; + { + settings_pack p = settings(); + setup(p); + lt::session ses(p); + s(ses, st); + } + + { + settings_pack p = settings(); + d(p); + lt::session ses(p); + // the loading function takes a bdecode_node, so we have to transform the + // entry + printf("%s\n", st.to_string().c_str()); + std::vector buf; + bencode(std::back_inserter(buf), st); + bdecode_node state; + error_code ec; + int ret = bdecode(buf.data(), buf.data() + buf.size() + , state, ec, nullptr, 100, 1000); + TEST_EQUAL(ret, 0); + if (ec) + { + printf("bdecode: %s\n", ec.message().c_str()); + printf("%s\n", std::string(buf.data(), buf.size()).c_str()); + } + TEST_CHECK(!ec); + l(ses, state); + } +} + +TORRENT_TEST(save_restore_state) +{ + test_save_restore( + [](settings_pack& p) { + // set the cache size + p.set_int(settings_pack::cache_size, 1337); + }, + [](lt::session& ses, entry& st) { + ses.save_state(st); + }, + [](settings_pack& p) { + p.set_int(settings_pack::cache_size, 90); + }, + [](lt::session& ses, bdecode_node& st) { + ses.load_state(st); + // make sure we loaded the cache size correctly + settings_pack sett = ses.get_settings(); + TEST_EQUAL(sett.get_int(settings_pack::cache_size), 1337); + }); +} + +TORRENT_TEST(save_restore_state_save_filter) +{ + test_save_restore( + [](settings_pack& p) { + // set the cache size + p.set_int(settings_pack::cache_size, 1337); + }, + [](lt::session& ses, entry& st) { + // save everything _but_ the settings + ses.save_state(st, ~session::save_settings); + }, + [](settings_pack& p) { + p.set_int(settings_pack::cache_size, 90); + }, + [](lt::session& ses, bdecode_node& st) { + ses.load_state(st); + // make sure whatever we loaded did not include the cache size + settings_pack sett = ses.get_settings(); + TEST_EQUAL(sett.get_int(settings_pack::cache_size), 90); + }); +} + +TORRENT_TEST(save_restore_state_load_filter) +{ + test_save_restore( + [](settings_pack& p) { + // set the cache size + p.set_int(settings_pack::cache_size, 1337); + }, + [](lt::session& ses, entry& st) { + // save everything + ses.save_state(st); + }, + [](settings_pack& p) { + p.set_int(settings_pack::cache_size, 90); + }, + [](lt::session& ses, bdecode_node& st) { + // load everything _but_ the settings + ses.load_state(st, ~session::save_settings); + settings_pack sett = ses.get_settings(); + TEST_EQUAL(sett.get_int(settings_pack::cache_size), 90); + }); +} + +#endif + diff --git a/test/test_settings_pack.cpp b/test/test_settings_pack.cpp new file mode 100644 index 0000000..bf05d52 --- /dev/null +++ b/test/test_settings_pack.cpp @@ -0,0 +1,141 @@ +/* + +Copyright (c) 2012, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/bencode.hpp" +#include + +using namespace libtorrent; +using namespace libtorrent::aux; + +TORRENT_TEST(default_settings) +{ + aux::session_settings sett; + initialize_default_settings(sett); + + entry e; + save_settings_to_dict(sett, e.dict()); + // all default values are supposed to be skipped + // by save_settings + TEST_EQUAL(e.dict().size(), 0); + +#if defined TORRENT_DEBUG && TORRENT_USE_IOSTREAM + if (e.dict().size() > 0) + std::cerr << e << std::endl; +#endif +} + +TORRENT_TEST(apply_pack) +{ + aux::session_settings sett; + initialize_default_settings(sett); + settings_pack sp; + sp.set_int(settings_pack::max_out_request_queue, 1337); + + TEST_CHECK(sett.get_int(settings_pack::max_out_request_queue) != 1337); + + apply_pack(&sp, sett); + + TEST_EQUAL(sett.get_int(settings_pack::max_out_request_queue), 1337); + entry e; + save_settings_to_dict(sett, e.dict()); + TEST_EQUAL(e.dict().size(), 1); + + std::string out; + bencode(std::back_inserter(out), e); + TEST_EQUAL(out, "d21:max_out_request_queuei1337ee"); +} + +TORRENT_TEST(sparse_pack) +{ + settings_pack pack; + TEST_EQUAL(pack.has_val(settings_pack::send_redundant_have), false); + + pack.set_bool(settings_pack::send_redundant_have, true); + + TEST_EQUAL(pack.has_val(settings_pack::send_redundant_have), true); + TEST_EQUAL(pack.has_val(settings_pack::user_agent), false); + TEST_EQUAL(pack.has_val(settings_pack::lazy_bitfields), 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_CHECK(strcmp(name_for_setting(settings_pack:: n), #n) == 0) + + TEST_NAME(contiguous_recv_buffer); + TEST_NAME(choking_algorithm); + TEST_NAME(seeding_piece_quota); +#ifndef TORRENT_NO_DEPRECATE + TEST_NAME(half_open_limit); +#endif + TEST_NAME(peer_turnover_interval); + TEST_NAME(mmap_cache); +} + +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.has_val(settings_pack::lazy_bitfields), 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); + TEST_EQUAL(pack.has_val(settings_pack::lazy_bitfields), false); +} + +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"); +} + +// TODO: load_pack_from_dict + diff --git a/test/test_sha1_hash.cpp b/test/test_sha1_hash.cpp new file mode 100644 index 0000000..6122813 --- /dev/null +++ b/test/test_sha1_hash.cpp @@ -0,0 +1,112 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/sha1_hash.hpp" + +using namespace libtorrent; + +static sha1_hash to_hash(char const* s) +{ + sha1_hash ret; + from_hex(s, 40, (char*)&ret[0]); + return ret; +} + +TORRENT_TEST(sha1_hash) +{ + sha1_hash h1(0); + sha1_hash h2(0); + 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")); +} + diff --git a/test/test_sliding_average.cpp b/test/test_sliding_average.cpp new file mode 100644 index 0000000..5e97a54 --- /dev/null +++ b/test/test_sliding_average.cpp @@ -0,0 +1,114 @@ +/* + +Copyright (c) 2014, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/sliding_average.hpp" + +// 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 }; + +using namespace libtorrent; + +// make sure we react quickly for the first few samples +TORRENT_TEST(reaction_time) +{ + sliding_average<10> avg; + + avg.add_sample(-10); + avg.add_sample(10); + + TEST_EQUAL(avg.mean(), 0); +} + +TORRENT_TEST(reaction_time2) +{ + sliding_average<10> avg; + + avg.add_sample(10); + avg.add_sample(20); + + TEST_EQUAL(avg.mean(), 15); +} + +// make sure we converge +TORRENT_TEST(converge) +{ + sliding_average<10> 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<10> 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<10> 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<4> 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); + 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..e333a74 --- /dev/null +++ b/test/test_socket_io.cpp @@ -0,0 +1,171 @@ +/* + +Copyright (c) 2014, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "libtorrent/socket_io.hpp" +#include "libtorrent/socket.hpp" + +#include + +using namespace libtorrent; +using namespace libtorrent::detail; + +TORRENT_TEST(socket_io) +{ + // test address_to_bytes + TEST_EQUAL(address_to_bytes(address_v4::from_string("10.11.12.13")), "\x0a\x0b\x0c\x0d"); + TEST_EQUAL(address_to_bytes(address_v4::from_string("16.5.127.1")), "\x10\x05\x7f\x01"); + + // test endpoint_to_bytes + TEST_EQUAL(endpoint_to_bytes(udp::endpoint(address_v4::from_string("10.11.12.13"), 8080)), "\x0a\x0b\x0c\x0d\x1f\x90"); + TEST_EQUAL(endpoint_to_bytes(udp::endpoint(address_v4::from_string("16.5.127.1"), 12345)), "\x10\x05\x7f\x01\x30\x39"); + + std::string buf; + std::back_insert_iterator out1(buf); + write_address(address_v4::from_string("16.5.128.1"), out1); + TEST_EQUAL(buf, "\x10\x05\x80\x01"); + std::string::iterator in = buf.begin(); + address addr4 = read_v4_address(in); + TEST_EQUAL(addr4, address_v4::from_string("16.5.128.1")); + + buf.clear(); + std::back_insert_iterator out2(buf); + write_endpoint(udp::endpoint(address_v4::from_string("16.5.128.1"), 1337), out2); + TEST_EQUAL(buf, "\x10\x05\x80\x01\x05\x39"); + in = buf.begin(); + udp::endpoint ep4 = read_v4_endpoint(in); + TEST_EQUAL(ep4, udp::endpoint(address_v4::from_string("16.5.128.1"), 1337)); + +#if TORRENT_USE_IPV6 + buf.clear(); + std::back_insert_iterator out3(buf); + write_address(address_v6::from_string("1000::ffff"), out3); + TEST_CHECK(std::equal(buf.begin(), buf.end(), "\x10\0\0\0\0\0\0\0\0\0\0\0\0\0\xff\xff")); + in = buf.begin(); + address addr6 = read_v6_address(in); + TEST_EQUAL(addr6, address_v6::from_string("1000::ffff")); + + buf.clear(); + std::back_insert_iterator out4(buf); + write_endpoint(udp::endpoint(address_v6::from_string("1000::ffff"), 1337), out4); + 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); + in = buf.begin(); + udp::endpoint ep6 = read_v6_endpoint(in); + TEST_EQUAL(ep6, udp::endpoint(address_v6::from_string("1000::ffff"), 1337)); +#endif + + 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, list); + +#if TORRENT_USE_IPV6 + TEST_EQUAL(list.size(), 2); + TEST_EQUAL(list[1], udp::endpoint(address_v6::from_string("1000::ffff"), 1337)); +#else + TEST_EQUAL(list.size(), 1); +#endif + TEST_EQUAL(list[0], udp::endpoint(address_v4::from_string("16.5.128.1"), 1337)); + + entry e2 = bdecode(eplist, eplist + sizeof(eplist)-1); + list.clear(); + read_endpoint_list(&e2, list); + +#if TORRENT_USE_IPV6 + TEST_EQUAL(list.size(), 2); + TEST_EQUAL(list[1], udp::endpoint(address_v6::from_string("1000::ffff"), 1337)); +#else + TEST_EQUAL(list.size(), 1); +#endif + TEST_EQUAL(list[0], udp::endpoint(address_v4::from_string("16.5.128.1"), 1337)); +} + +TORRENT_TEST(parse_invalid_ipv4_endpoint) +{ + error_code ec; + tcp::endpoint endp; + + endp = parse_endpoint("127.0.0.1-4", ec); + TEST_CHECK(ec); + ec.clear(); + + endp = parse_endpoint("127.0.0.1", ec); + TEST_CHECK(ec); + ec.clear(); + + 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(); + + endp = parse_endpoint("127.0.0.1:4", ec); + TEST_CHECK(!ec); + TEST_EQUAL(endp, ep("127.0.0.1", 4)); + ec.clear(); +} + +#if TORRENT_USE_IPV6 +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]:4", ec); + TEST_CHECK(!ec); + TEST_EQUAL(endp, ep("::1", 4)); + ec.clear(); +} +#endif diff --git a/test/test_ssl.cpp b/test/test_ssl.cpp new file mode 100644 index 0000000..01c5bc7 --- /dev/null +++ b/test/test_ssl.cpp @@ -0,0 +1,625 @@ +/* + +Copyright (c) 2013, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/session.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/thread.hpp" +#include "libtorrent/file.hpp" +#include "libtorrent/session_status.hpp" +#include "libtorrent/torrent_info.hpp" + +#include "test.hpp" +#include "test_utils.hpp" +#include "setup_transfer.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include + +#include +#include +#include + +#ifdef TORRENT_USE_OPENSSL +#include // for asio::error::get_ssl_category() +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +using namespace libtorrent; +using boost::tuples::ignore; + +int const alert_mask = alert::all_categories +& ~alert::progress_notification +& ~alert::stats_notification; + +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 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; + if (e->error.category() == boost::asio::error::get_ssl_category()) + ++ssl_peer_disconnects; + } + + if (peer_error_alert const* e = alert_cast(a)) + { + ++peer_disconnects; + ++peer_errors; + + if (e->error.category() == boost::asio::error::get_ssl_category()) + ++ssl_peer_disconnects; + } + return false; +} + +void test_ssl(int test_idx, bool 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]; + + fprintf(stderr, "\n%s TEST: %s Protocol: %s\n\n", time_now_string(), test.name, use_utp ? "uTP": "TCP"); + + // in case the previous run was terminated + error_code ec; + remove_all("tmp1_ssl", ec); + remove_all("tmp2_ssl", ec); + + int ssl_port = 1024 + rand() % 50000; + settings_pack sett; + sett.set_int(settings_pack::alert_mask, alert_mask); + sett.set_int(settings_pack::max_retry_port_bind, 100); + sett.set_str(settings_pack::listen_interfaces, "0.0.0.0:48075"); + 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); + sett.set_int(settings_pack::ssl_listen, ssl_port); + + libtorrent::session ses1(sett, 0); + + if (test.downloader_has_ssl_listen_port) + sett.set_int(settings_pack::ssl_listen, ssl_port + 20); + else + sett.set_int(settings_pack::ssl_listen, 0); + + libtorrent::session ses2(sett, 0); + + 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"); + boost::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 &= ~add_torrent_params::flag_paused; + addp.flags &= ~add_torrent_params::flag_auto_managed; + + peer_disconnects = 0; + ssl_peer_disconnects = 0; + peer_errors = 0; + + boost::tie(tor1, tor2, ignore) = setup_transfer(&ses1, &ses2, 0 + , 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 + int port = 0; + if (test.use_ssl_ports) + if (test.downloader_has_ssl_listen_port) + port = ses2.ssl_listen_port(); + else + port = 13512; + else + port = ses2.listen_port(); + + fprintf(stderr, "\n\n%s: ses1: connecting peer port: %d\n\n\n" + , time_now_string(), port); + tor1.connect_peer(tcp::endpoint(address::from_string("127.0.0.1", ec) + , port)); + +#ifdef TORRENT_USE_VALGRIND + const int timeout = 100; +#else + const int timeout = 40; +#endif + for (int i = 0; i < timeout; ++i) + { + print_alerts(ses1, "ses1", true, true, true, &on_alert); + print_alerts(ses2, "ses2", true, true, true, &on_alert); + + torrent_status st1 = tor1.status(); + torrent_status st2 = tor2.status(); + + if (i % 10 == 0) + { + std::cerr << 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 (peer_disconnects >= 2) + { + fprintf(stderr, "too many disconnects (%d), breaking\n", peer_disconnects); + break; + } + + 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::cerr << "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); + + test_sleep(100); + } + + fprintf(stderr, "peer_errors: %d expected_errors: %d\n" + , peer_errors, test.peer_errors); + TEST_EQUAL(peer_errors > 0, test.peer_errors > 0); + + fprintf(stderr, "ssl_disconnects: %d expected: %d\n", ssl_peer_disconnects, test.ssl_disconnects); + TEST_EQUAL(ssl_peer_disconnects > 0, test.ssl_disconnects > 0); + + fprintf(stderr, "%s: EXPECT: %s\n", time_now_string(), test.expected_to_complete ? "SUCCEESS" : "FAILURE"); + fprintf(stderr, "%s: RESULT: %s\n", time_now_string(), tor2.status().is_seeding ? "SUCCEESS" : "FAILURE"); + TEST_EQUAL(tor2.status().is_seeding, test.expected_to_complete); + + // this allows shutting down the sessions in parallel + p1 = ses1.abort(); + p2 = ses2.abort(); +} + +std::string password_callback(int length, boost::asio::ssl::context::password_purpose p + , std::string pw) +{ + if (p != boost::asio::ssl::context::for_reading) return ""; + return pw; +} + +struct attack_t +{ + // flags controlling the connection attempt + boost::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 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(libtorrent::session& ses1, int port + , boost::shared_ptr const& t, boost::uint32_t flags) +{ + using boost::asio::ssl::context; + + fprintf(stderr, "\nMALICIOUS PEER TEST: "); + if (flags & invalid_certificate) fprintf(stderr, "invalid-certificate "); + else if (flags & valid_certificate) fprintf(stderr, "valid-certificate "); + else fprintf(stderr, "no-certificate "); + + if (flags & invalid_sni_hash) fprintf(stderr, "invalid-SNI-hash "); + else if (flags & valid_sni_hash) fprintf(stderr, "valid-SNI-hash "); + else fprintf(stderr, "no-SNI-hash "); + + if (flags & valid_bittorrent_hash) fprintf(stderr, "valid-bittorrent-hash "); + else fprintf(stderr, "invalid-bittorrent-hash "); + + fprintf(stderr, " port: %d\n", port); + + error_code ec; + boost::asio::io_service ios; + + // create the SSL context for this torrent. We need to + // inject the root certificate, and no other, to + // verify other peers against + context ctx(ios, context::sslv23); + + ctx.set_options(context::default_workarounds + | boost::asio::ssl::context::no_sslv2 + | boost::asio::ssl::context::single_dh_use); + + // we're a malicious peer, we don't have any interest + // in verifying peers + ctx.set_verify_mode(context::verify_none, ec); + if (ec) + { + fprintf(stderr, "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)) + { + fprintf(stderr, "set_password_callback\n"); + ctx.set_password_callback(boost::bind(&password_callback, _1, _2, "test"), ec); + if (ec) + { + fprintf(stderr, "Failed to set certificate password callback: %s\n" + , ec.message().c_str()); + TEST_CHECK(!ec); + return false; + } + fprintf(stderr, "use_certificate_file \"%s\"\n", certificate.c_str()); + ctx.use_certificate_file(certificate, context::pem, ec); + if (ec) + { + fprintf(stderr, "Failed to set certificate file: %s\n" + , ec.message().c_str()); + TEST_CHECK(!ec); + return false; + } + fprintf(stderr, "use_private_key_file \"%s\"\n", private_key.c_str()); + ctx.use_private_key_file(private_key, context::pem, ec); + if (ec) + { + fprintf(stderr, "Failed to set private key: %s\n" + , ec.message().c_str()); + TEST_CHECK(!ec); + return false; + } + fprintf(stderr, "use_tmp_dh_file \"%s\"\n", dh_params.c_str()); + ctx.use_tmp_dh_file(dh_params, ec); + if (ec) + { + fprintf(stderr, "Failed to set DH params: %s\n" + , ec.message().c_str()); + TEST_CHECK(!ec); + return false; + } + } + + boost::asio::ssl::stream ssl_sock(ios, ctx); + + fprintf(stderr, "connecting 127.0.0.1:%d\n", port); + ssl_sock.lowest_layer().connect(tcp::endpoint( + address_v4::from_string("127.0.0.1"), port), ec); + print_alerts(ses1, "ses1", true, true, true, &on_alert); + + if (ec) + { + fprintf(stderr, "Failed to connect: %s\n" + , ec.message().c_str()); + TEST_CHECK(!ec); + return false; + } + + if (flags & valid_sni_hash) + { + std::string name = to_hex(t->info_hash().to_string()); + fprintf(stderr, "SNI: %s\n", name.c_str()); + SSL_set_tlsext_host_name(ssl_sock.native_handle(), name.c_str()); + } + else if (flags & invalid_sni_hash) + { + char const hex_alphabet[] = "0123456789abcdef"; + std::string name; + name.reserve(40); + for (int i = 0; i < 40; ++i) + name += hex_alphabet[rand() % 16]; + + fprintf(stderr, "SNI: %s\n", name.c_str()); + SSL_set_tlsext_host_name(ssl_sock.native_handle(), name.c_str()); + } + + fprintf(stderr, "SSL handshake\n"); + ssl_sock.handshake(boost::asio::ssl::stream_base::client, ec); + + print_alerts(ses1, "ses1", true, true, true, &on_alert); + if (ec) + { + fprintf(stderr, "Failed SSL handshake: %s\n" + , ec.message().c_str()); + return false; + } + + char handshake[] = "\x13" "BitTorrent protocol\0\0\0\0\0\0\0\x04" + " " // space for info-hash + "aaaaaaaaaaaaaaaaaaaa" // peer-id + "\0\0\0\x01\x02"; // interested + + // fill in the info-hash + if (flags & valid_bittorrent_hash) + { + std::memcpy(handshake + 28, &t->info_hash()[0], 20); + } + else + { + // TODO: also test using a hash that refers to a valid torrent + // but that differs from the SNI hash + std::generate(handshake + 28, handshake + 48, &rand); + } + + // fill in the peer-id + std::generate(handshake + 48, handshake + 68, &rand); + + fprintf(stderr, "bittorrent handshake\n"); + boost::asio::write(ssl_sock, boost::asio::buffer(handshake, (sizeof(handshake) - 1)), ec); + print_alerts(ses1, "ses1", true, true, true, &on_alert); + if (ec) + { + fprintf(stderr, "failed to write bittorrent handshake: %s\n" + , ec.message().c_str()); + return false; + } + + char buf[68]; + fprintf(stderr, "read bittorrent handshake\n"); + boost::asio::read(ssl_sock, boost::asio::buffer(buf, sizeof(buf)), ec); + print_alerts(ses1, "ses1", true, true, true, &on_alert); + if (ec) + { + fprintf(stderr, "failed to read bittorrent handshake: %s\n" + , ec.message().c_str()); + return false; + } + + if (memcmp(buf, "\x13" "BitTorrent protocol", 20) != 0) + { + fprintf(stderr, "invalid bittorrent handshake\n"); + return false; + } + + if (memcmp(buf + 28, &t->info_hash()[0], 20) != 0) + { + fprintf(stderr, "invalid info-hash in bittorrent handshake\n"); + return false; + } + + fprintf(stderr, "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 ssl_port = 1024 + rand() % 50000; + settings_pack sett; + sett.set_int(settings_pack::alert_mask, alert_mask); + sett.set_int(settings_pack::max_retry_port_bind, 100); + sett.set_str(settings_pack::listen_interfaces, "0.0.0.0:48075"); + sett.set_int(settings_pack::ssl_listen, ssl_port); + 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); + + libtorrent::session ses1(sett, 0); + wait_for_listen(ses1, "ses1"); + + // create torrent + create_directory("tmp3_ssl", ec); + std::ofstream file("tmp3_ssl/temporary"); + boost::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 &= ~add_torrent_params::flag_paused; + addp.flags &= ~add_torrent_params::flag_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 success = try_connect(ses1, ssl_port, t, attacks[i].flags); + TEST_EQUAL(success, attacks[i].expect); + } +} +#endif // TORRENT_USE_OPENSSL + +TORRENT_TEST(malicious_peer) +{ +#ifdef TORRENT_USE_OPENSSL + test_malicious_peer(); +#endif +} + +#ifdef TORRENT_USE_OPENSSL +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); } +#endif + diff --git a/test/test_stat_cache.cpp b/test/test_stat_cache.cpp new file mode 100644 index 0000000..2e61172 --- /dev/null +++ b/test/test_stat_cache.cpp @@ -0,0 +1,80 @@ +/* + +Copyright (c) 2012, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/stat_cache.hpp" +#include "libtorrent/error_code.hpp" +#include "test.hpp" + +using namespace libtorrent; + +TORRENT_TEST(stat_cache) +{ + error_code ec; + + stat_cache sc; + + sc.init(10); + + for (int i = 0; i < 10; ++i) + { + TEST_CHECK(sc.get_filesize(i) == stat_cache::not_in_cache); + TEST_CHECK(sc.get_filetime(i) == stat_cache::not_in_cache); + } + + // out of bound accesses count as not-in-cache + TEST_CHECK(sc.get_filesize(10) == stat_cache::not_in_cache); + TEST_CHECK(sc.get_filesize(11) == stat_cache::not_in_cache); + + sc.set_error(3); + TEST_CHECK(sc.get_filesize(3) == stat_cache::cache_error); + + sc.set_noexist(3); + TEST_CHECK(sc.get_filesize(3) == stat_cache::no_exist); + + sc.set_cache(3, 101, 5555); + TEST_CHECK(sc.get_filesize(3) == 101); + TEST_CHECK(sc.get_filetime(3) == 5555); + + sc.set_error(11); + TEST_CHECK(sc.get_filesize(10) == stat_cache::not_in_cache); + TEST_CHECK(sc.get_filesize(11) == stat_cache::cache_error); + + sc.set_noexist(13); + TEST_CHECK(sc.get_filesize(12) == stat_cache::not_in_cache); + TEST_CHECK(sc.get_filesize(13) == stat_cache::no_exist); + + sc.set_cache(15, 1000, 3000); + TEST_CHECK(sc.get_filesize(14) == stat_cache::not_in_cache); + TEST_CHECK(sc.get_filesize(15) == 1000); + TEST_CHECK(sc.get_filetime(15) == 3000); +} + diff --git a/test/test_storage.cpp b/test/test_storage.cpp new file mode 100644 index 0000000..fef4a36 --- /dev/null +++ b/test/test_storage.cpp @@ -0,0 +1,1357 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "test_utils.hpp" + +#include "libtorrent/storage.hpp" +#include "libtorrent/file_pool.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/aux_/session_impl.hpp" +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/thread.hpp" +#include "libtorrent/torrent_info.hpp" + +#include +#include + +#include +#include + +using namespace libtorrent; +namespace lt = libtorrent; + +const int piece_size = 16 * 1024 * 16; +const int half = piece_size / 2; + +void signal_bool(bool* b, char const* string) +{ + *b = true; + std::cerr << time_now_string() << " " << string << std::endl; +} + +void on_read_piece(int ret, disk_io_job const& j, char const* data, int size) +{ + std::cerr << time_now_string() << " on_read_piece piece: " << j.piece << std::endl; + TEST_EQUAL(ret, size); + if (ret > 0) TEST_CHECK(std::equal(j.buffer.disk_block, j.buffer.disk_block + ret, data)); +} + +void on_check_resume_data(disk_io_job const* j, bool* done) +{ + std::cerr << time_now_string() << " on_check_resume_data ret: " << j->ret; + switch (j->ret) + { + case piece_manager::no_error: + std::cerr << time_now_string() << " success" << std::endl; + break; + case piece_manager::fatal_disk_error: + std::cerr << time_now_string() << " disk error: " << j->error.ec.message() + << " file: " << j->error.file << std::endl; + break; + case piece_manager::need_full_check: + std::cerr << time_now_string() << " need full check" << std::endl; + break; + case piece_manager::disk_check_aborted: + std::cerr << time_now_string() << " aborted" << std::endl; + break; + } + std::cerr << std::endl; + *done = true; +} + +void print_error(char const* call, int ret, storage_error const& ec) +{ + fprintf(stderr, "%s: %s() returned: %d error: \"%s\" in file: %d operation: %d\n" + , time_now_string(), call, ret, ec.ec.message().c_str(), ec.file, ec.operation); +} + +void run_until(io_service& ios, bool const& done) +{ + while (!done) + { + ios.reset(); + error_code ec; + ios.run_one(ec); + if (ec) + { + std::cerr << "run_one: " << ec.message().c_str() << std::endl; + return; + } + std::cerr << time_now_string() << " done: " << done << std::endl; + } +} + +void nop() {} + +boost::shared_ptr setup_torrent(file_storage& fs + , file_pool& fp + , std::vector& buf + , std::string const& test_path + , aux::session_settings& set) +{ + fs.add_file(combine_path("temp_storage", "test1.tmp"), 8); + fs.add_file(combine_path("temp_storage", combine_path("folder1", "test2.tmp")), 8); + fs.add_file(combine_path("temp_storage", combine_path("folder2", "test3.tmp")), 0); + fs.add_file(combine_path("temp_storage", combine_path("_folder3", "test4.tmp")), 0); + fs.add_file(combine_path("temp_storage", combine_path("_folder3", combine_path("subfolder", "test5.tmp"))), 8); + libtorrent::create_torrent t(fs, 4, -1, 0); + + char buf_[4] = {0, 0, 0, 0}; + sha1_hash h = hasher(buf_, 4).final(); + for (int i = 0; i < 6; ++i) t.set_hash(i, h); + + bencode(std::back_inserter(buf), t.generate()); + error_code ec; + + boost::shared_ptr info(boost::make_shared(&buf[0] + , buf.size(), boost::ref(ec), 0)); + + if (ec) + { + fprintf(stderr, "torrent_info constructor failed: %s\n" + , ec.message().c_str()); + } + + storage_params p; + p.files = &fs; + p.pool = &fp; + p.path = test_path; + p.mode = storage_mode_allocate; + boost::shared_ptr s(new default_storage(p)); + s->m_settings = &set; + + // allocate the files and create the directories + storage_error se; + s->initialize(se); + if (se) + { + TEST_ERROR(se.ec.message().c_str()); + fprintf(stderr, "default_storage::initialize %s: %d\n", se.ec.message().c_str(), int(se.file)); + } + + return s; +} + +typedef boost::shared_array buf_ptr; + +buf_ptr new_piece(int size) +{ + buf_ptr ret(static_cast(malloc(size)), &free); + std::generate(ret.get(), ret.get() + size, random_byte); + return ret; +} + +void run_storage_tests(boost::shared_ptr info + , file_storage& fs + , std::string const& test_path + , libtorrent::storage_mode_t storage_mode + , bool unbuffered) +{ + TORRENT_ASSERT(fs.num_files() > 0); + error_code ec; + create_directory(combine_path(test_path, "temp_storage"), ec); + if (ec) std::cerr << "create_directory '" << combine_path(test_path, "temp_storage") + << "': " << ec.message() << std::endl; + remove_all(combine_path(test_path, "temp_storage2"), ec); + if (ec && ec != boost::system::errc::no_such_file_or_directory) + std::cerr << "remove_all '" << combine_path(test_path, "temp_storage2") + << "': " << ec.message() << std::endl; + remove_all(combine_path(test_path, "part0"), ec); + if (ec && ec != boost::system::errc::no_such_file_or_directory) + std::cerr << "remove_all '" << combine_path(test_path, "part0") + << "': " << ec.message() << std::endl; + + int num_pieces = fs.num_pieces(); + TEST_CHECK(info->num_pieces() == num_pieces); + + buf_ptr piece0 = new_piece(piece_size); + buf_ptr piece1 = new_piece(piece_size); + buf_ptr piece2 = new_piece(piece_size); + + aux::session_settings set; + set.set_int(settings_pack::disk_io_write_mode + , unbuffered ? settings_pack::disable_os_cache + : settings_pack::enable_os_cache); + set.set_int(settings_pack::disk_io_read_mode + , unbuffered ? settings_pack::disable_os_cache + : settings_pack::enable_os_cache); + + char* piece = static_cast(malloc(piece_size)); + + { // avoid having two storages use the same files + file_pool fp; + boost::asio::io_service ios; + disk_buffer_pool dp(16 * 1024, ios, boost::bind(&nop)); + storage_params p; + p.path = test_path; + p.files = &fs; + p.pool = &fp; + p.mode = storage_mode; + boost::scoped_ptr s(new default_storage(p)); + s->m_settings = &set; + + storage_error ec; + s->initialize(ec); + TEST_CHECK(!ec); + if (ec) print_error("initialize", 0, ec); + + int ret = 0; + + // write piece 1 (in slot 0) + file::iovec_t iov = { piece1.get(), half}; + ret = s->writev(&iov, 1, 0, 0, 0, ec); + if (ret != half) print_error("writev", ret, ec); + + iov.iov_base = piece1.get() + half; + iov.iov_len = half; + ret = s->writev(&iov, 1, 0, half, 0, ec); + if (ret != half) print_error("writev", ret, ec); + + // test unaligned read (where the bytes are aligned) + iov.iov_base = piece + 3; + iov.iov_len = piece_size - 9; + ret = s->readv(&iov, 1, 0, 3, 0, ec); + if (ret != piece_size - 9) print_error("readv",ret, ec); + TEST_CHECK(std::equal(piece+3, piece + piece_size-9, piece1.get()+3)); + + // test unaligned read (where the bytes are not aligned) + iov.iov_base = piece; + iov.iov_len = piece_size - 9; + ret = s->readv(&iov, 1, 0, 3, 0, ec); + TEST_CHECK(ret == piece_size - 9); + if (ret != piece_size - 9) print_error("readv", ret, ec); + TEST_CHECK(std::equal(piece, piece + piece_size-9, piece1.get()+3)); + + // verify piece 1 + iov.iov_base = piece; + iov.iov_len = piece_size; + ret = s->readv(&iov, 1, 0, 0, 0, ec); + TEST_CHECK(ret == piece_size); + if (ret != piece_size) print_error("readv", ret, ec); + TEST_CHECK(std::equal(piece, piece + piece_size, piece1.get())); + + // do the same with piece 0 and 2 (in slot 1 and 2) + iov.iov_base = piece0.get(); + iov.iov_len = piece_size; + ret = s->writev(&iov, 1, 1, 0, 0, ec); + if (ret != piece_size) print_error("writev", ret, ec); + + iov.iov_base = piece2.get(); + iov.iov_len = piece_size; + ret = s->writev(&iov, 1, 2, 0, 0, ec); + if (ret != piece_size) print_error("writev", ret, ec); + + // verify piece 0 and 2 + iov.iov_base = piece; + iov.iov_len = piece_size; + ret = s->readv(&iov, 1, 1, 0, 0, ec); + if (ret != piece_size) print_error("readv", ret, ec); + TEST_CHECK(std::equal(piece, piece + piece_size, piece0.get())); + + iov.iov_base = piece; + iov.iov_len = piece_size; + ret = s->readv(&iov, 1, 2, 0, 0, ec); + if (ret != piece_size) print_error("readv", ret, ec); + TEST_CHECK(std::equal(piece, piece + piece_size, piece2.get())); + + s->release_files(ec); + } + + free(piece); +} + +void test_remove(std::string const& test_path, bool unbuffered) +{ + error_code ec; + remove_all(combine_path(test_path, "temp_storage"), ec); + if (ec && ec != boost::system::errc::no_such_file_or_directory) + std::cerr << "remove_all '" << combine_path(test_path, "temp_storage") + << "': " << ec.message() << std::endl; + TEST_CHECK(!exists(combine_path(test_path, "temp_storage"))); + + file_storage fs; + std::vector buf; + file_pool fp; + io_service ios; + disk_buffer_pool dp(16 * 1024, ios, boost::bind(&nop)); + + aux::session_settings set; + set.set_int(settings_pack::disk_io_write_mode + , unbuffered ? settings_pack::disable_os_cache + : settings_pack::enable_os_cache); + set.set_int(settings_pack::disk_io_read_mode + , unbuffered ? settings_pack::disable_os_cache + : settings_pack::enable_os_cache); + + boost::shared_ptr s = setup_torrent(fs, fp, buf, test_path, set); + + // directories are not created up-front, unless they contain + // an empty file (all of which are created up-front, along with + // all required directories) + // files are created on first write + TEST_CHECK(!exists(combine_path(test_path, combine_path("temp_storage" + , combine_path("_folder3", combine_path("subfolder", "test5.tmp")))))); + + // this directory and file is created up-front because it's an empty file + TEST_CHECK(exists(combine_path(test_path, combine_path("temp_storage" + , combine_path("folder2", "test3.tmp"))))); + + // this isn't + TEST_CHECK(!exists(combine_path(test_path, combine_path("temp_storage" + , combine_path("folder1", "test2.tmp"))))); + + file::iovec_t b = {&buf[0], 4}; + storage_error se; + s->writev(&b, 1, 2, 0, 0, se); + + TEST_CHECK(exists(combine_path(test_path, combine_path("temp_storage" + , combine_path("folder1", "test2.tmp"))))); + TEST_CHECK(!exists(combine_path(test_path, combine_path("temp_storage" + , combine_path("_folder3", combine_path("subfolder", "test5.tmp")))))); + file_status st; + stat_file(combine_path(test_path, combine_path("temp_storage" + , combine_path("folder1", "test2.tmp"))), &st, ec); + TEST_EQUAL(st.file_size, 8); + + s->writev(&b, 1, 4, 0, 0, se); + + TEST_CHECK(exists(combine_path(test_path, combine_path("temp_storage" + , combine_path("_folder3", combine_path("subfolder", "test5.tmp")))))); + stat_file(combine_path(test_path, combine_path("temp_storage" + , combine_path("_folder3", "test5.tmp"))), &st, ec); + TEST_EQUAL(st.file_size, 8); + + s->delete_files(session::delete_files, se); + if (se) print_error("delete_files", 0, se.ec); + + if (se) + { + TEST_ERROR(se.ec.message().c_str()); + fprintf(stderr, "default_storage::delete_files %s: %d\n", se.ec.message().c_str(), int(se.file)); + } + + TEST_CHECK(!exists(combine_path(test_path, "temp_storage"))); +} + +void test_rename(std::string const& test_path) +{ + error_code ec; + remove_all(combine_path(test_path, "temp_storage"), ec); + if (ec && ec != boost::system::errc::no_such_file_or_directory) + std::cerr << "remove_all '" << combine_path(test_path, "temp_storage") + << "': " << ec.message() << std::endl; + TEST_CHECK(!exists(combine_path(test_path, "temp_storage"))); + + file_storage fs; + std::vector buf; + file_pool fp; + io_service ios; + disk_buffer_pool dp(16 * 1024, ios, boost::bind(&nop)); + aux::session_settings set; + + boost::shared_ptr s = setup_torrent(fs, fp, buf, test_path + , set); + + // directories are not created up-front, unless they contain + // an empty file + std::string first_file = fs.file_path(0); + for (int i = 0; i < fs.num_files(); ++i) + { + TEST_CHECK(!exists(combine_path(test_path, combine_path("temp_storage" + , fs.file_path(i))))); + } + + storage_error se; + s->rename_file(0, "new_filename", se); + if (se.ec) + { + fprintf(stderr, "default_storage::rename_file failed: %s\n" + , se.ec.message().c_str()); + } + TEST_CHECK(!se.ec); + + TEST_EQUAL(s->files().file_path(0), "new_filename"); +} + +void test_check_files(std::string const& test_path + , libtorrent::storage_mode_t storage_mode + , bool unbuffered) +{ + boost::shared_ptr info; + + error_code ec; + const int piece_size = 16 * 1024; + remove_all(combine_path(test_path, "temp_storage"), ec); + if (ec && ec != boost::system::errc::no_such_file_or_directory) + std::cerr << "remove_all '" << combine_path(test_path, "temp_storage") + << "': " << ec.message() << std::endl; + file_storage fs; + fs.add_file("temp_storage/test1.tmp", piece_size); + fs.add_file("temp_storage/test2.tmp", piece_size * 2); + fs.add_file("temp_storage/test3.tmp", piece_size); + + buf_ptr piece0 = new_piece(piece_size); + buf_ptr piece2 = new_piece(piece_size); + + libtorrent::create_torrent t(fs, piece_size, -1, 0); + t.set_hash(0, hasher(piece0.get(), piece_size).final()); + t.set_hash(1, sha1_hash(0)); + t.set_hash(2, sha1_hash(0)); + t.set_hash(3, hasher(piece2.get(), piece_size).final()); + + create_directory(combine_path(test_path, "temp_storage"), ec); + if (ec) std::cerr << "create_directory: " << ec.message() << std::endl; + + std::ofstream f; + f.open(combine_path(test_path, combine_path("temp_storage", "test1.tmp")).c_str() + , std::ios::trunc | std::ios::binary); + f.write(piece0.get(), piece_size); + f.close(); + f.open(combine_path(test_path, combine_path("temp_storage", "test3.tmp")).c_str() + , std::ios::trunc | std::ios::binary); + f.write(piece2.get(), piece_size); + f.close(); + + std::vector buf; + bencode(std::back_inserter(buf), t.generate()); + info = boost::make_shared(&buf[0], buf.size(), boost::ref(ec), 0); + + aux::session_settings set; + file_pool fp; + boost::asio::io_service ios; + counters cnt; + disk_io_thread io(ios, cnt, NULL); + io.set_num_threads(1); + disk_buffer_pool dp(16 * 1024, ios, boost::bind(&nop)); + storage_params p; + p.files = &fs; + p.path = test_path; + p.pool = &fp; + p.mode = storage_mode; + + boost::shared_ptr dummy; + boost::shared_ptr pm = boost::make_shared(new default_storage(p), dummy, &fs); + libtorrent::mutex lock; + + bool done = false; + bdecode_node frd; + std::vector links; + io.async_check_fastresume(pm.get(), &frd, links + , boost::bind(&on_check_resume_data, _1, &done)); + io.submit_jobs(); + ios.reset(); + run_until(ios, done); + + io.set_num_threads(0); +} + +// TODO: 2 split this test up into smaller parts +void run_test(bool unbuffered) +{ + std::string test_path = current_working_directory(); + std::cerr << "\n=== " << test_path << " ===\n" << std::endl; + + boost::shared_ptr info; + + buf_ptr piece0 = new_piece(piece_size); + buf_ptr piece1 = new_piece(piece_size); + buf_ptr piece2 = new_piece(piece_size); + buf_ptr piece3 = new_piece(piece_size); + + { + error_code ec; + remove_all(combine_path(test_path, "temp_storage"), ec); + if (ec && ec != boost::system::errc::no_such_file_or_directory) + std::cerr << "remove_all '" << combine_path(test_path, "temp_storage") + << "': " << ec.message() << std::endl; + file_storage fs; + fs.add_file("temp_storage/test1.tmp", 17); + fs.add_file("temp_storage/test2.tmp", 612); + fs.add_file("temp_storage/test3.tmp", 0); + fs.add_file("temp_storage/test4.tmp", 0); + fs.add_file("temp_storage/test5.tmp", 3253); + fs.add_file("temp_storage/test6.tmp", 841); + const int last_file_size = 4 * piece_size - 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 | + + libtorrent::create_torrent t(fs, piece_size, -1, 0); + TEST_CHECK(t.num_pieces() == 4); + t.set_hash(0, hasher(piece0.get(), piece_size).final()); + t.set_hash(1, hasher(piece1.get(), piece_size).final()); + t.set_hash(2, hasher(piece2.get(), piece_size).final()); + t.set_hash(3, hasher(piece3.get(), piece_size).final()); + + std::vector buf; + bencode(std::back_inserter(buf), t.generate()); + info = boost::make_shared(&buf[0], buf.size(), boost::ref(ec), 0); + std::cerr << "=== test 1 === " << (unbuffered?"unbuffered":"buffered") << std::endl; + + // run_storage_tests writes piece 0, 1 and 2. not 3 + run_storage_tests(info, fs, test_path, storage_mode_sparse, unbuffered); + + // make sure the files have the correct size + std::string base = combine_path(test_path, "temp_storage"); + fprintf(stderr, "base = \"%s\"\n", base.c_str()); + TEST_EQUAL(file_size(combine_path(base, "test1.tmp")), 17); + TEST_EQUAL(file_size(combine_path(base, "test2.tmp")), 612); + + // these files should have been allocated as 0 size + TEST_CHECK(exists(combine_path(base, "test3.tmp"))); + TEST_CHECK(exists(combine_path(base, "test4.tmp"))); + TEST_CHECK(file_size(combine_path(base, "test3.tmp")) == 0); + TEST_CHECK(file_size(combine_path(base, "test4.tmp")) == 0); + + TEST_EQUAL(file_size(combine_path(base, "test5.tmp")), 3253); + TEST_EQUAL(file_size(combine_path(base, "test6.tmp")), 841); + printf("file: %d expected: %d last_file_size: %d, piece_size: %d\n" + , int(file_size(combine_path(base, "test7.tmp"))) + , int(last_file_size - piece_size), last_file_size, piece_size); + TEST_EQUAL(file_size(combine_path(base, "test7.tmp")), last_file_size - piece_size); + remove_all(combine_path(test_path, "temp_storage"), ec); + if (ec && ec != boost::system::errc::no_such_file_or_directory) + std::cerr << "remove_all '" << combine_path(test_path, "temp_storage") + << "': " << ec.message() << std::endl; + } + +// ============================================== + + { + error_code ec; + file_storage fs; + fs.add_file(combine_path("temp_storage", "test1.tmp"), 3 * piece_size); + libtorrent::create_torrent t(fs, piece_size, -1, 0); + TEST_CHECK(fs.file_path(0) == combine_path("temp_storage", "test1.tmp")); + t.set_hash(0, hasher(piece0.get(), piece_size).final()); + t.set_hash(1, hasher(piece1.get(), piece_size).final()); + t.set_hash(2, hasher(piece2.get(), piece_size).final()); + + std::vector buf; + bencode(std::back_inserter(buf), t.generate()); + info = boost::make_shared(&buf[0], buf.size(), boost::ref(ec), 0); + + std::cerr << "=== test 3 ===" << std::endl; + + run_storage_tests(info, fs, test_path, storage_mode_sparse, unbuffered); + + TEST_EQUAL(file_size(combine_path(test_path, combine_path("temp_storage", "test1.tmp"))), piece_size * 3); + remove_all(combine_path(test_path, "temp_storage"), ec); + if (ec && ec != boost::system::errc::no_such_file_or_directory) + std::cerr << "remove_all '" << combine_path(test_path, "temp_storage") + << "': " << ec.message() << std::endl; + +// ============================================== + + std::cerr << "=== test 4 ===" << std::endl; + + run_storage_tests(info, fs, test_path, storage_mode_allocate, unbuffered); + + std::cerr << file_size(combine_path(test_path, combine_path("temp_storage", "test1.tmp"))) << std::endl; + TEST_EQUAL(file_size(combine_path(test_path, combine_path("temp_storage", "test1.tmp"))), 3 * piece_size); + + remove_all(combine_path(test_path, "temp_storage"), ec); + if (ec && ec != boost::system::errc::no_such_file_or_directory) + std::cerr << "remove_all '" << combine_path(test_path, "temp_storage") + << "': " << ec.message() << std::endl; + + } + +// ============================================== + + std::cerr << "=== test 5 ===" << std::endl; + test_remove(test_path, unbuffered); + +// ============================================== + + std::cerr << "=== test 6 ===" << std::endl; + test_check_files(test_path, storage_mode_sparse, unbuffered); + test_check_files(test_path, storage_mode_sparse, unbuffered); + + std::cerr << "=== test 7 ===" << std::endl; + test_rename(test_path); +} + +TORRENT_TEST(fastresume) +{ + std::string test_path = current_working_directory(); + error_code ec; + std::cout << "\n\n=== test fastresume ===" << std::endl; + remove_all(combine_path(test_path, "tmp1"), ec); + if (ec && ec != boost::system::errc::no_such_file_or_directory) + std::cerr << "remove_all '" << combine_path(test_path, "tmp1") + << "': " << ec.message() << std::endl; + create_directory(combine_path(test_path, "tmp1"), ec); + if (ec) std::cerr << "create_directory '" << combine_path(test_path, "tmp1") + << "': " << ec.message() << std::endl; + std::ofstream file(combine_path(test_path, "tmp1/temporary").c_str()); + boost::shared_ptr t = ::create_torrent(&file); + file.close(); + TEST_CHECK(exists(combine_path(test_path, "tmp1/temporary"))); + if (!exists(combine_path(test_path, "tmp1/temporary"))) + return; + + entry resume; + { + const int mask = alert::all_categories + & ~(alert::progress_notification + | alert::performance_warning + | alert::stats_notification); + + 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_int(settings_pack::alert_mask, mask); + lt::session ses(pack); + + error_code ec; + + add_torrent_params p; + p.ti = boost::make_shared(boost::cref(*t)); + p.save_path = combine_path(test_path, "tmp1"); + p.storage_mode = storage_mode_sparse; + torrent_handle h = ses.add_torrent(p, ec); + 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; + } + test_sleep(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 = *alert_cast(ra)->resume_data; + 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::cerr << resume.to_string() << "\n"; + TEST_CHECK(resume.dict().find("file sizes") != resume.dict().end()); + + // make sure the fast resume check fails! since we removed the file + { + const int mask = alert::all_categories + & ~(alert::progress_notification + | alert::performance_warning + | alert::stats_notification); + + 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_int(settings_pack::alert_mask, mask); + lt::session ses(pack); + + add_torrent_params p; + p.flags &= ~add_torrent_params::flag_paused; + p.flags &= ~add_torrent_params::flag_auto_managed; + p.ti = boost::make_shared(boost::cref(*t)); + p.save_path = combine_path(test_path, "tmp1"); + p.storage_mode = storage_mode_sparse; + bencode(std::back_inserter(p.resume_data), resume); + torrent_handle h = ses.add_torrent(p, ec); + + 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) != 0); + } + remove_all(combine_path(test_path, "tmp1"), ec); + if (ec && ec != boost::system::errc::no_such_file_or_directory) + std::cerr << "remove_all '" << combine_path(test_path, "tmp1") + << "': " << ec.message() << std::endl; +} + +bool got_file_rename_alert(alert const* a) +{ + return alert_cast(a) + || alert_cast(a); +} + +TORRENT_TEST(rename_file_fastresume) +{ + std::string test_path = current_working_directory(); + error_code ec; + std::cout << "\n\n=== test rename file in fastresume ===" << std::endl; + remove_all(combine_path(test_path, "tmp2"), ec); + if (ec && ec != boost::system::errc::no_such_file_or_directory) + std::cerr << "remove_all '" << combine_path(test_path, "tmp2") + << "': " << ec.message() << std::endl; + create_directory(combine_path(test_path, "tmp2"), ec); + if (ec) std::cerr << "create_directory: " << ec.message() << std::endl; + std::ofstream file(combine_path(test_path, "tmp2/temporary").c_str()); + boost::shared_ptr t = ::create_torrent(&file); + file.close(); + TEST_CHECK(exists(combine_path(test_path, "tmp2/temporary"))); + + entry resume; + { + const int mask = alert::all_categories + & ~(alert::progress_notification + | alert::performance_warning + | alert::stats_notification); + + 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_int(settings_pack::alert_mask, mask); + lt::session ses(pack); + + add_torrent_params p; + p.ti = boost::make_shared(boost::cref(*t)); + p.save_path = combine_path(test_path, "tmp2"); + p.storage_mode = storage_mode_sparse; + torrent_handle h = ses.add_torrent(p, ec); + + h.rename_file(0, "testing_renamed_files"); + std::cout << "renaming file" << std::endl; + bool renamed = false; + for (int i = 0; i < 10; ++i) + { + if (print_alerts(ses, "ses", true, true, true, &got_file_rename_alert)) renamed = true; + torrent_status s = h.status(); + if (s.state == torrent_status::seeding && renamed) break; + test_sleep(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)->resume_data; + 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.dict().find("mapped_files") != resume.dict().end()); + TEST_CHECK(resume.dict().find("file sizes") != resume.dict().end()); + + std::cerr << resume.to_string() << "\n"; + + // make sure the fast resume check succeeds, even though we renamed the file + { + const int mask = alert::all_categories + & ~(alert::progress_notification + | alert::performance_warning + | alert::stats_notification); + + 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_int(settings_pack::alert_mask, mask); + lt::session ses(pack); + + add_torrent_params p; + p.ti = boost::make_shared(boost::cref(*t)); + p.save_path = combine_path(test_path, "tmp2"); + p.storage_mode = storage_mode_sparse; + bencode(std::back_inserter(p.resume_data), resume); + torrent_handle h = ses.add_torrent(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; + test_sleep(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)->resume_data; + ses.remove_torrent(h); + } + TEST_CHECK(resume.dict().find("mapped_files") != resume.dict().end()); + + std::cerr << resume.to_string() << "\n"; + + remove_all(combine_path(test_path, "tmp2"), ec); + if (ec && ec != boost::system::errc::no_such_file_or_directory) + std::cerr << "remove_all '" << combine_path(test_path, "tmp2") + << "': " << ec.message() << std::endl; +} + +void alloc_iov(file::iovec_t* iov, int num_bufs) +{ + for (int i = 0; i < num_bufs; ++i) + { + iov[i].iov_base = malloc(num_bufs * (i + 1)); + iov[i].iov_len = num_bufs * (i + 1); + } +} + +void fill_pattern(file::iovec_t* iov, int num_bufs) +{ + int counter = 0; + for (int i = 0; i < num_bufs; ++i) + { + unsigned char* buf = (unsigned char*)iov[i].iov_base; + for (int k = 0; k < int(iov[i].iov_len); ++k) + { + buf[k] = counter & 0xff; + ++counter; + } + } +} + +bool check_pattern(std::vector const& buf, int counter) +{ + unsigned char* p = (unsigned char*)&buf[0]; + for (int k = 0; k < int(buf.size()); ++k) + { + if (p[k] != (counter & 0xff)) return false; + ++counter; + } + return true; +} + +void fill_pattern2(file::iovec_t* iov, int num_bufs) +{ + for (int i = 0; i < num_bufs; ++i) + { + unsigned char* buf = (unsigned char*)iov[i].iov_base; + memset(buf, 0xfe, int(iov[i].iov_len)); + } +} + +void free_iov(file::iovec_t* iov, int num_bufs) +{ + for (int i = 0; i < num_bufs; ++i) + { + free(iov[i].iov_base); + iov[i].iov_len = 0; + iov[i].iov_base = NULL; + } +} + +TORRENT_TEST(iovec_copy_bufs) +{ + file::iovec_t iov1[10]; + file::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 = 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) + { + unsigned char* buf = (unsigned char*)iov2[i].iov_base; + for (int k = 0; k < int(iov2[i].iov_len); ++k) + { + TEST_EQUAL(int(buf[k]), (counter & 0xff)); + ++counter; + } + } + TEST_EQUAL(counter, 106); + + free_iov(iov1, 10); +} + +TORRENT_TEST(iovec_clear_bufs) +{ + file::iovec_t iov[10]; + alloc_iov(iov, 10); + fill_pattern(iov, 10); + + clear_bufs(iov, 10); + for (int i = 0; i < 10; ++i) + { + unsigned char* buf = (unsigned char*)iov[i].iov_base; + for (int k = 0; k < int(iov[i].iov_len); ++k) + { + TEST_EQUAL(int(buf[k]), 0); + } + } + free_iov(iov, 10); +} + +TORRENT_TEST(iovec_bufs_size) +{ + file::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) +{ + file::iovec_t iov1[10]; + file::iovec_t iov2[10]; + alloc_iov(iov1, 10); + fill_pattern(iov1, 10); + + memcpy(iov2, iov1, sizeof(iov1)); + + file::iovec_t* iov = iov2; + file::iovec_t* end = iov2 + 10; + + // advance iov 13 bytes. Make sure what's left fits pattern 1 shifted + // 13 bytes + advance_bufs(iov, 13); + + // make sure what's in + int counter = 13; + for (int i = 0; i < end - iov; ++i) + { + unsigned char* buf = (unsigned char*)iov[i].iov_base; + for (int k = 0; k < int(iov[i].iov_len); ++k) + { + TEST_EQUAL(int(buf[k]), (counter & 0xff)); + ++counter; + } + } + + free_iov(iov1, 10); +} + +TORRENT_TEST(unbuffered) { run_test(true); } +TORRENT_TEST(buffered) { run_test(false); } + +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((fs.total_size() + 0xfff) / 0x1000); + return fs; +} + +struct test_fileop : fileop +{ + test_fileop(int stripe_size) : m_stripe_size(stripe_size) {} + + int file_op(int file_index, boost::int64_t file_offset, int size + , file::iovec_t const* bufs, storage_error& ec) + { + if (file_index >= int(m_file_data.size())) + { + m_file_data.resize(file_index + 1); + } + + const int write_size = (std::min)(m_stripe_size, size); + + std::vector& file = m_file_data[file_index]; + + if (file_offset + write_size > int(file.size())) + { + file.resize(file_offset + write_size); + } + + int left = write_size; + while (left > 0) + { + const int copy_size = (std::min)(left, int(bufs->iov_len)); + memcpy(&file[file_offset], bufs->iov_base, copy_size); + ++bufs; + file_offset += copy_size; + left -= copy_size; + } + return write_size; + } + + int m_stripe_size; + std::vector > m_file_data; +}; + +struct test_read_fileop : fileop +{ + // EOF after size bytes read + test_read_fileop(int size) : m_size(size), m_counter(0) {} + + int file_op(int file_index, boost::int64_t file_offset, int size + , file::iovec_t const* bufs, storage_error& ec) + { + size = (std::min)(m_size, size); + const int read = size; + while (size > 0) + { + unsigned char* p = (unsigned char*)bufs->iov_base; + const int len = (std::min)(int(bufs->iov_len), size); + for (int i = 0; i < len; ++i) + { + p[i] = m_counter & 0xff; + ++m_counter; + } + size -= len; + m_size -= len; + ++bufs; + } + return read; + } + + int m_size; + int m_counter; +}; + +struct test_error_fileop : fileop +{ + // EOF after size bytes read + test_error_fileop(int error_file) + : m_error_file(error_file) {} + + int file_op(int file_index, boost::int64_t file_offset, int size + , file::iovec_t const* 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 = storage_error::read; + return -1; + } + return size; + } + + int m_error_file; +}; + +int count_bufs(file::iovec_t const* bufs, int bytes) +{ + int size = 0; + int count = 1; + if (bytes == 0) return 0; + for (file::iovec_t const* i = bufs;; ++i, ++count) + { + size += i->iov_len; + if (size >= bytes) return count; + } +} + +TORRENT_TEST(readwritev_stripe_1) +{ + const int num_bufs = 30; + file::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()); + + file::iovec_t iov2[num_bufs]; + copy_bufs(iov, fs.total_size(), iov2); + int num_bufs2 = count_bufs(iov2, fs.total_size()); + TEST_CHECK(num_bufs2 <= num_bufs); + + int ret = readwritev(fs, iov2, 0, 0, num_bufs2, fop, ec); + + TEST_EQUAL(ret, fs.total_size()); + TEST_EQUAL(fop.m_file_data.size(), 4); + TEST_EQUAL(fop.m_file_data[0].size(), 3); + TEST_EQUAL(fop.m_file_data[1].size(), 9); + TEST_EQUAL(fop.m_file_data[2].size(), 81); + TEST_EQUAL(fop.m_file_data[3].size(), 6561); + + TEST_CHECK(check_pattern(fop.m_file_data[0], 0)); + TEST_CHECK(check_pattern(fop.m_file_data[1], 3)); + TEST_CHECK(check_pattern(fop.m_file_data[2], 3 + 9)); + TEST_CHECK(check_pattern(fop.m_file_data[3], 3 + 9 + 81)); + + free_iov(iov, num_bufs); +} + +TORRENT_TEST(readwritev_single_buffer) +{ + file_storage fs = make_fs(); + test_fileop fop(10000000); + storage_error ec; + + std::vector buf(fs.total_size()); + file::iovec_t iov = { &buf[0], buf.size() }; + fill_pattern(&iov, 1); + + int ret = readwritev(fs, &iov, 0, 0, 1, fop, ec); + + TEST_EQUAL(ret, fs.total_size()); + TEST_EQUAL(fop.m_file_data.size(), 4); + TEST_EQUAL(fop.m_file_data[0].size(), 3); + TEST_EQUAL(fop.m_file_data[1].size(), 9); + TEST_EQUAL(fop.m_file_data[2].size(), 81); + TEST_EQUAL(fop.m_file_data[3].size(), 6561); + + TEST_CHECK(check_pattern(fop.m_file_data[0], 0)); + TEST_CHECK(check_pattern(fop.m_file_data[1], 3)); + TEST_CHECK(check_pattern(fop.m_file_data[2], 3 + 9)); + TEST_CHECK(check_pattern(fop.m_file_data[3], 3 + 9 + 81)); +} + +TORRENT_TEST(readwritev_read) +{ + file_storage fs = make_fs(); + test_read_fileop fop(10000000); + storage_error ec; + + std::vector buf(fs.total_size()); + file::iovec_t iov = { &buf[0], buf.size() }; + + // read everything + int ret = readwritev(fs, &iov, 0, 0, 1, fop, ec); + + 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(fs.total_size()); + file::iovec_t iov = { &buf[0] + , static_cast(fs.total_size()) }; + + // read everything + int ret = readwritev(fs, &iov, 0, 0, 1, fop, ec); + + TEST_EQUAL(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); + storage_error ec; + + std::vector buf(fs.total_size()); + file::iovec_t iov = { &buf[0] + , static_cast(fs.total_size()) }; + + // read everything + int ret = readwritev(fs, &iov, 0, 0, 1, fop, ec); + + TEST_EQUAL(ret, -1); + TEST_EQUAL(ec.file, 2); + TEST_EQUAL(ec.operation, storage_error::read); + TEST_EQUAL(ec.ec, boost::system::errc::permission_denied); + 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((fs.total_size() + 0xfff) / 0x1000); + test_read_fileop fop(10000000); + storage_error ec; + + std::vector buf(fs.total_size()); + file::iovec_t iov = { &buf[0] + , static_cast(fs.total_size()) }; + + // read everything + int ret = readwritev(fs, &iov, 0, 0, 1, fop, ec); + + TEST_EQUAL(ret, fs.total_size()); + TEST_CHECK(check_pattern(buf, 0)); +} + +void delete_dirs(std::string path) +{ + error_code ec; + remove_all(path, ec); + if (ec && ec != boost::system::errc::no_such_file_or_directory) + { + fprintf(stderr, "remove_all \"%s\": %s\n" + , path.c_str(), ec.message().c_str()); + } + TEST_CHECK(!exists(path)); +} + +TORRENT_TEST(move_storage_into_self) +{ + std::string const save_path = current_working_directory(); + delete_dirs(combine_path(save_path, "temp_storage")); + + aux::session_settings set; + file_storage fs; + std::vector buf; + file_pool fp; + io_service ios; + disk_buffer_pool dp(16 * 1024, ios, boost::bind(&nop)); + boost::shared_ptr s = setup_torrent(fs, fp, buf, save_path, set); + + file::iovec_t const b = {&buf[0], 4}; + storage_error se; + s->writev(&b, 1, 2, 0, 0, se); + + std::string const test_path = combine_path(save_path, combine_path("temp_storage", "folder1")); + s->move_storage(test_path, 0, se); + TEST_EQUAL(se.ec, boost::system::errc::success); + + TEST_CHECK(exists(combine_path(test_path, combine_path("temp_storage" + , combine_path("folder1", "test2.tmp"))))); + + // these directories and files are created up-front because they are empty files + TEST_CHECK(exists(combine_path(test_path, combine_path("temp_storage" + , combine_path("folder2", "test3.tmp"))))); + TEST_CHECK(exists(combine_path(test_path, combine_path("temp_storage" + , combine_path("_folder3", "test4.tmp"))))); +} + +TORRENT_TEST(dont_move_intermingled_files) +{ + std::string const save_path = combine_path(current_working_directory(), "save_path_1"); + delete_dirs(combine_path(save_path, "temp_storage")); + + std::string test_path = combine_path(current_working_directory(), "save_path_2"); + delete_dirs(combine_path(test_path, "temp_storage")); + + aux::session_settings set; + file_storage fs; + std::vector buf; + file_pool fp; + io_service ios; + disk_buffer_pool dp(16 * 1024, ios, boost::bind(&nop)); + boost::shared_ptr s = setup_torrent(fs, fp, buf, save_path, set); + + file::iovec_t b = {&buf[0], 4}; + storage_error se; + s->writev(&b, 1, 2, 0, 0, se); + + error_code ec; + create_directory(combine_path(save_path, combine_path("temp_storage" + , combine_path("_folder3", "alien_folder1"))), ec); + TEST_EQUAL(ec, boost::system::errc::success); + file f; + f.open(combine_path(save_path, combine_path("temp_storage", "alien1.tmp")) + , file::write_only, ec); + f.close(); + TEST_EQUAL(ec, boost::system::errc::success); + f.open(combine_path(save_path, combine_path("temp_storage" + , combine_path("folder1", "alien2.tmp"))), file::write_only, ec); + f.close(); + TEST_EQUAL(ec, boost::system::errc::success); + + s->move_storage(test_path, 0, se); + TEST_EQUAL(se.ec, boost::system::errc::success); + + // torrent files moved to new place + TEST_CHECK(exists(combine_path(test_path, combine_path("temp_storage" + , combine_path("folder1", "test2.tmp"))))); + // these directories and files are created up-front because they are empty files + TEST_CHECK(exists(combine_path(test_path, combine_path("temp_storage" + , combine_path("folder2", "test3.tmp"))))); + TEST_CHECK(exists(combine_path(test_path, combine_path("temp_storage" + , combine_path("_folder3", "test4.tmp"))))); + + // intermingled files and directories are still in old place + TEST_CHECK(exists(combine_path(save_path, combine_path("temp_storage" + , "alien1.tmp")))); + TEST_CHECK(!exists(combine_path(test_path, combine_path("temp_storage" + , "alien1.tmp")))); + TEST_CHECK(exists(combine_path(save_path, combine_path("temp_storage" + , combine_path("folder1", "alien2.tmp"))))); + TEST_CHECK(!exists(combine_path(test_path, combine_path("temp_storage" + , combine_path("folder1", "alien2.tmp"))))); + TEST_CHECK(exists(combine_path(save_path, combine_path("temp_storage" + , combine_path("_folder3", "alien_folder1"))))); + TEST_CHECK(!exists(combine_path(test_path, combine_path("temp_storage" + , combine_path("_folder3", "alien_folder1"))))); +} + diff --git a/test/test_string.cpp b/test/test_string.cpp new file mode 100644 index 0000000..6c74457 --- /dev/null +++ b/test/test_string.cpp @@ -0,0 +1,312 @@ +/* + +Copyright (c) 2012, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/aux_/escape_string.hpp" +#include "libtorrent/hex.hpp" +#include "libtorrent/string_util.hpp" +#include +#include // for strcmp + +using namespace libtorrent; + +TORRENT_TEST(string) +{ + // 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"); + + // test to/from hex conversion + + char const* str = "0123456789012345678901234567890123456789"; + char bin[20]; + TEST_CHECK(from_hex(str, 40, bin)); + char hex[41]; + to_hex(bin, 20, hex); + TEST_CHECK(strcmp(hex, str) == 0); + + TEST_CHECK(to_hex("\x55\x73") == "5573"); + TEST_CHECK(to_hex("\xaB\xd0") == "abd0"); + + // 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 to_lower + + TEST_CHECK(to_lower('C') == 'c'); + TEST_CHECK(to_lower('c') == 'c'); + TEST_CHECK(to_lower('-') == '-'); + TEST_CHECK(to_lower('&') == '&'); + + // 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 string_begins_no_case + + TEST_CHECK(string_begins_no_case("foobar", "FoobAR --")); + TEST_CHECK(!string_begins_no_case("foobar", "F00")); + + // test itoa + + TEST_CHECK(to_string(345).elems == std::string("345")); + TEST_CHECK(to_string(-345).elems == std::string("-345")); + TEST_CHECK(to_string(0).elems == std::string("0")); + TEST_CHECK(to_string(1000000000).elems == std::string("1000000000")); + + // 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"); + + // base32 test vectors from http://www.faqs.org/rfcs/rfc4648.html + + 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======"); + + 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{#&*()=") == ""); + + std::string test; + for (int i = 0; i < 255; ++i) + test += char(i); + + TEST_CHECK(base32decode(base32encode(test)) == test); + + // escape_string + char const* test_string = "!@#$%^&*()-_=+/,. %?"; + TEST_EQUAL(escape_string(test_string, strlen(test_string)) + , "!%40%23%24%25%5e%26*()-_%3d%2b%2f%2c.%20%25%3f"); + + // escape_path + TEST_EQUAL(escape_path(test_string, strlen(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, strlen(test_string)), ec) == test_string); + TEST_CHECK(!ec); + if (ec) fprintf(stderr, "%s\n", ec.message().c_str()); + + // need_encoding + char const* test_string2 = "!@$&()-_/,.%?"; + TEST_CHECK(need_encoding(test_string, strlen(test_string)) == true); + TEST_CHECK(need_encoding(test_string2, 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, strlen(test_string)), ec) + == test_string); + std::cerr << unescape_string(escape_string(test_string, strlen(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"); + + + // 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"); + + char hex_chars[] = "0123456789abcdefABCDEF"; + + for (int i = 1; i < 255; ++i) + { + bool hex = strchr(hex_chars, i) != NULL; + char c = i; + TEST_EQUAL(detail::is_hex(&c, 1), hex); + } + + TEST_EQUAL(detail::hex_to_int('0'), 0); + TEST_EQUAL(detail::hex_to_int('7'), 7); + TEST_EQUAL(detail::hex_to_int('a'), 10); + TEST_EQUAL(detail::hex_to_int('f'), 15); + TEST_EQUAL(detail::hex_to_int('b'), 11); + TEST_EQUAL(detail::hex_to_int('t'), -1); + TEST_EQUAL(detail::hex_to_int('g'), -1); + + std::string path = "a\\b\\c"; + convert_path_to_posix(path); + TEST_EQUAL(path, "a/b/c"); + + // url_has_argument + + TEST_CHECK(url_has_argument("http://127.0.0.1/test", "test") == ""); + TEST_CHECK(url_has_argument("http://127.0.0.1/test?foo=24", "bar") == ""); + TEST_CHECK(url_has_argument("http://127.0.0.1/test?foo=24", "foo") == "24"); + TEST_CHECK(url_has_argument("http://127.0.0.1/test?foo=24&bar=23", "foo") == "24"); + TEST_CHECK(url_has_argument("http://127.0.0.1/test?foo=24&bar=23", "bar") == "23"); + TEST_CHECK(url_has_argument("http://127.0.0.1/test?foo=24&bar=23&a=e", "bar") == "23"); + TEST_CHECK(url_has_argument("http://127.0.0.1/test?foo=24&bar=23&a=e", "a") == "e"); + TEST_CHECK(url_has_argument("http://127.0.0.1/test?foo=24&bar=23&a=e", "b") == ""); + + // resolve_file_url + +#ifdef TORRENT_WINDOWS + std::string p = "c:/blah/foo/bar\\"; + convert_path_to_windows(p); + TEST_EQUAL(p, "c:\\blah\\foo\\bar\\"); + TEST_EQUAL(resolve_file_url("file:///c:/blah/foo/bar"), "c:\\blah\\foo\\bar"); + TEST_EQUAL(resolve_file_url("file:///c:/b%3fah/foo/bar"), "c:\\b?ah\\foo\\bar"); + TEST_EQUAL(resolve_file_url("file://\\c:\\b%3fah\\foo\\bar"), "c:\\b?ah\\foo\\bar"); +#else + TEST_EQUAL(resolve_file_url("file:///c/blah/foo/bar"), "/c/blah/foo/bar"); + TEST_EQUAL(resolve_file_url("file:///c/b%3fah/foo/bar"), "/c/b?ah/foo/bar"); +#endif + + std::vector list; + parse_comma_separated_string(" a,b, c, d ,e \t,foobar\n\r,[::1]", list); + TEST_EQUAL(list.size(), 7); + TEST_EQUAL(list[0], "a"); + TEST_EQUAL(list[1], "b"); + TEST_EQUAL(list[2], "c"); + TEST_EQUAL(list[3], "d"); + TEST_EQUAL(list[4], "e"); + TEST_EQUAL(list[5], "foobar"); + TEST_EQUAL(list[6], "[::1]"); + + std::vector > list2; + parse_comma_separated_string_port(" a:4,b:35, c : 1000, d: 351 ,e \t:42,foobar:1337\n\r,[2001::1]:6881", list2); + TEST_EQUAL(list2.size(), 7); + TEST_EQUAL(list2[0].first, "a"); + TEST_EQUAL(list2[1].first, "b"); + TEST_EQUAL(list2[2].first, "c"); + TEST_EQUAL(list2[3].first, "d"); + TEST_EQUAL(list2[4].first, "e"); + TEST_EQUAL(list2[5].first, "foobar"); + TEST_EQUAL(list2[6].first, "2001::1"); + + TEST_EQUAL(list2[0].second, 4); + TEST_EQUAL(list2[1].second, 35); + TEST_EQUAL(list2[2].second, 1000); + TEST_EQUAL(list2[3].second, 351); + TEST_EQUAL(list2[4].second, 42); + TEST_EQUAL(list2[5].second, 1337); + TEST_EQUAL(list2[6].second, 6881); + + // test string_tokenize + + char test_tokenize[] = "a b c \"foo bar\" d\ne f"; + char* next = test_tokenize; + char* ptr = string_tokenize(next, ' ', &next); + TEST_EQUAL(ptr, std::string("a")); + + ptr = string_tokenize(next, ' ', &next); + TEST_EQUAL(ptr, std::string("b")); + + ptr = string_tokenize(next, ' ', &next); + TEST_EQUAL(ptr, std::string("c")); + + ptr = string_tokenize(next, ' ', &next); + TEST_EQUAL(ptr, std::string("\"foo bar\"")); + + ptr = string_tokenize(next, ' ', &next); + TEST_EQUAL(ptr, std::string("d\ne")); + + ptr = string_tokenize(next, ' ', &next); + TEST_EQUAL(ptr, std::string("f")); + + ptr = string_tokenize(next, ' ', &next); + TEST_EQUAL(ptr, static_cast(0)); + + 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")); +} + diff --git a/test/test_tailqueue.cpp b/test/test_tailqueue.cpp new file mode 100644 index 0000000..fe6d714 --- /dev/null +++ b/test/test_tailqueue.cpp @@ -0,0 +1,162 @@ +/* + +Copyright (c) 2012, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/tailqueue.hpp" + +using namespace libtorrent; + +struct test_node : tailqueue_node +{ + 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(((test_node*)i.get())->name, *expected); + i.next(); + ++expected; + } + TEST_EQUAL(expected[0], 0); +} + +void free_chain(tailqueue& q) +{ + test_node* chain = (test_node*)q.get_all(); + while(chain) + { + test_node* del = (test_node*)chain; + chain = (test_node*)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); +} + +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 = (test_node*)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 = (test_node*)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..55b6010 --- /dev/null +++ b/test/test_threads.cpp @@ -0,0 +1,134 @@ +/* + +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 "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/thread.hpp" +#include "test.hpp" +#include "setup_transfer.hpp" // for test_sleep + +using namespace libtorrent; + +void fun(condition_variable* s, libtorrent::mutex* m, int* waiting, int i) +{ + fprintf(stderr, "thread %d waiting\n", i); + libtorrent::mutex::scoped_lock l(*m); + *waiting += 1; + s->wait(l); + fprintf(stderr, "thread %d done\n", i); +} + +void increment(condition_variable* s, libtorrent::mutex* m, int* waiting, boost::atomic* c) +{ + libtorrent::mutex::scoped_lock l(*m); + *waiting += 1; + s->wait(l); + l.unlock(); + for (int i = 0; i < 1000000; ++i) + ++*c; +} + +void decrement(condition_variable* s, libtorrent::mutex* m, int* waiting, boost::atomic* c) +{ + libtorrent::mutex::scoped_lock l(*m); + *waiting += 1; + s->wait(l); + l.unlock(); + for (int i = 0; i < 1000000; ++i) + --*c; +} + +TORRENT_TEST(threads) +{ + condition_variable cond; + libtorrent::mutex m; + std::list threads; + int waiting = 0; + for (int i = 0; i < 20; ++i) + { + threads.push_back(new libtorrent::thread(boost::bind(&fun, &cond, &m, &waiting, i))); + } + + // make sure all threads are waiting on the condition_variable + libtorrent::mutex::scoped_lock l(m); + while (waiting < 20) + { + l.unlock(); + test_sleep(10); + l.lock(); + } + + cond.notify_all(); + l.unlock(); + + for (std::list::iterator i = threads.begin(); i != threads.end(); ++i) + { + (*i)->join(); + delete *i; + } + threads.clear(); + + waiting = 0; + boost::atomic c(0); + for (int i = 0; i < 3; ++i) + { + threads.push_back(new libtorrent::thread(boost::bind(&increment, &cond, &m, &waiting, &c))); + threads.push_back(new libtorrent::thread(boost::bind(&decrement, &cond, &m, &waiting, &c))); + } + + // make sure all threads are waiting on the condition_variable + l.lock(); + while (waiting < 6) + { + l.unlock(); + test_sleep(10); + l.lock(); + } + + cond.notify_all(); + l.unlock(); + + for (std::list::iterator i = threads.begin(); i != threads.end(); ++i) + { + (*i)->join(); + delete *i; + } + + TEST_CHECK(c == 0); +} + diff --git a/test/test_time.cpp b/test/test_time.cpp new file mode 100644 index 0000000..f8ddd30 --- /dev/null +++ b/test/test_time.cpp @@ -0,0 +1,100 @@ +/* + +Copyright (c) 2013, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "setup_transfer.hpp" // for test_sleep + +#include "libtorrent/time.hpp" +#include "libtorrent/thread.hpp" + +#include + +using namespace libtorrent; + +void check_timer_loop(mutex& m, time_point& last, condition_variable& cv) +{ + mutex::scoped_lock l(m); + cv.wait(l); + l.unlock(); + + for (int i = 0; i < 10000; ++i) + { + mutex::scoped_lock l(m); + time_point now = clock_type::now(); + TEST_CHECK(now >= last); + last = now; + } +} + +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; + } + + mutex m; + condition_variable cv; + libtorrent::thread t1(boost::bind(&check_timer_loop, boost::ref(m), boost::ref(last), boost::ref(cv))); + libtorrent::thread t2(boost::bind(&check_timer_loop, boost::ref(m), boost::ref(last), boost::ref(cv))); + libtorrent::thread t3(boost::bind(&check_timer_loop, boost::ref(m), boost::ref(last), boost::ref(cv))); + libtorrent::thread t4(boost::bind(&check_timer_loop, boost::ref(m), boost::ref(last), boost::ref(cv))); + + test_sleep(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..80e52e0 --- /dev/null +++ b/test/test_time_critical.cpp @@ -0,0 +1,41 @@ +/* + +Copyright (c) 2014, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "swarm_suite.hpp" + +TORRENT_TEST(time_crititcal) +{ + // with time critical pieces + test_swarm(time_critical); +} + + diff --git a/test/test_timestamp_history.cpp b/test/test_timestamp_history.cpp new file mode 100644 index 0000000..6dcf868 --- /dev/null +++ b/test/test_timestamp_history.cpp @@ -0,0 +1,57 @@ +/* + +Copyright (c) 2008-2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/timestamp_history.hpp" + +TORRENT_TEST(timestamp_history) +{ + using namespace libtorrent; + + 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..1659e91 --- /dev/null +++ b/test/test_torrent.cpp @@ -0,0 +1,395 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/session.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/thread.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/peer_info.hpp" +#include "libtorrent/extensions.hpp" +#include "settings.hpp" +#include +#include +#include + +#include "test.hpp" +#include "setup_transfer.hpp" + +using namespace libtorrent; +namespace lt = libtorrent; + +void test_running_torrent(boost::shared_ptr info, boost::int64_t file_size) +{ + settings_pack pack = settings(); + pack.set_int(settings_pack::alert_mask, alert::storage_notification); + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:48130"); + pack.set_int(settings_pack::max_retry_port_bind, 10); + lt::session ses(pack); + + std::vector zeroes; + zeroes.resize(1000, 0); + add_torrent_params p; + p.flags &= ~add_torrent_params::flag_paused; + p.flags &= ~add_torrent_params::flag_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) + { + fprintf(stderr, "add_torrent: %s\n", ec.message().c_str()); + return; + } + + std::vector ones(info->num_files(), 1); + h.prioritize_files(ones); + +// test_sleep(500); + torrent_status st = h.status(); + + TEST_EQUAL(st.total_wanted, file_size); // we want the single file + TEST_EQUAL(st.total_wanted_done, 0); + + std::vector prio(info->num_files(), 1); + prio[0] = 0; + h.prioritize_files(prio); + 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.file_priorities().size()), info->num_files()); + if (!st.is_seeding) + { + TEST_EQUAL(h.file_priorities()[0], 0); + if (info->num_files() > 1) + TEST_EQUAL(h.file_priorities()[1], 1); + if (info->num_files() > 2) + TEST_EQUAL(h.file_priorities()[2], 1); + } + + if (info->num_files() > 1) + { + prio[1] = 0; + h.prioritize_files(prio); + st = h.status(); + + TEST_EQUAL(st.total_wanted, file_size); + TEST_EQUAL(st.total_wanted_done, 0); + if (!st.is_seeding) + { + TEST_EQUAL(int(h.file_priorities().size()), info->num_files()); + TEST_EQUAL(h.file_priorities()[0], 0); + if (info->num_files() > 1) + TEST_EQUAL(h.file_priorities()[1], 0); + if (info->num_files() > 2) + TEST_EQUAL(h.file_priorities()[2], 1); + } + } + + if (info->num_pieces() > 0) + { + h.piece_priority(0, 1); + st = h.status(); + TEST_CHECK(st.pieces.size() > 0 && st.pieces[0] == false); + std::vector piece(info->piece_length()); + for (int i = 0; i < int(piece.size()); ++i) + piece[i] = (i % 26) + 'A'; + h.add_piece(0, &piece[0], torrent_handle::overwrite_existing); + + // wait until the piece is done writing and hashing + wait_for_alert(ses, piece_finished_alert::alert_type, "piece_finished_alert"); + st = h.status(); + TEST_CHECK(st.pieces.size() > 0); + + std::cout << "reading piece 0" << std::endl; + h.read_piece(0); + alert const* a = wait_for_alert(ses, read_piece_alert::alert_type, "read_piece"); + TEST_CHECK(a); + read_piece_alert const* rpa = alert_cast(a); + TEST_CHECK(rpa); + if (rpa) + { + std::cout << "SUCCEEDED!" << std::endl; + TEST_CHECK(memcmp(&piece[0], rpa->buffer.get(), info->piece_size(0)) == 0); + TEST_CHECK(rpa->size == info->piece_size(0)); + TEST_CHECK(rpa->piece == 0); + TEST_CHECK(hasher(&piece[0], piece.size()).final() == info->hash_for_piece(0)); + } + } +} + +TORRENT_TEST(long_names) +{ + 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; + + std::vector buf; + bencode(std::back_inserter(buf), torrent); + error_code ec; + boost::shared_ptr ti(boost::make_shared(&buf[0], buf.size(), boost::ref(ec))); + TEST_CHECK(!ec); +} + +TORRENT_TEST(total_wanted) +{ + file_storage fs; + + fs.add_file("test_torrent_dir4/tmp1", 1024); + fs.add_file("test_torrent_dir4/tmp2", 1024); + fs.add_file("test_torrent_dir4/tmp3", 1024); + fs.add_file("test_torrent_dir4/tmp4", 1024); + + libtorrent::create_torrent t(fs, 1024); + std::vector tmp; + bencode(std::back_inserter(tmp), t.generate()); + error_code ec; + boost::shared_ptr info(boost::make_shared( + &tmp[0], tmp.size(), boost::ref(ec))); + + settings_pack pack = settings(); + pack.set_int(settings_pack::alert_mask, alert::storage_notification); + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:48130"); + pack.set_int(settings_pack::max_retry_port_bind, 10); + lt::session ses(pack); + + add_torrent_params p; + p.ti = info; + p.save_path = "."; + + // we just want 1 out of 4 files, 1024 out of 4096 bytes + p.file_priorities.resize(4, 0); + p.file_priorities[1] = 1; + + p.ti = info; + + torrent_handle h = ses.add_torrent(p); + + torrent_status st = h.status(); + std::cout << "total_wanted: " << st.total_wanted << " : " << 1024 << std::endl; + TEST_EQUAL(st.total_wanted, 1024); + std::cout << "total_wanted_done: " << st.total_wanted_done << " : 0" << std::endl; + TEST_EQUAL(st.total_wanted_done, 0); +} + +TORRENT_TEST(added_peers) +{ + file_storage fs; + + fs.add_file("test_torrent_dir4/tmp1", 1024); + + libtorrent::create_torrent t(fs, 1024); + std::vector tmp; + bencode(std::back_inserter(tmp), t.generate()); + error_code ec; + boost::shared_ptr info(boost::make_shared( + &tmp[0], tmp.size(), boost::ref(ec))); + + settings_pack pack = settings(); + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:48130"); + pack.set_int(settings_pack::max_retry_port_bind, 10); + lt::session ses(pack); + + add_torrent_params p; + p.ti = info; + p.save_path = "."; + p.url = "?x.pe=127.0.0.1:48081&x.pe=127.0.0.2:48082"; + + torrent_handle h = ses.add_torrent(p); + + std::vector v; + h.get_full_peer_list(v); + TEST_EQUAL(v.size(), 2); +} + +TORRENT_TEST(torrent) +{ +/* { + remove("test_torrent_dir2/tmp1"); + remove("test_torrent_dir2/tmp2"); + remove("test_torrent_dir2/tmp3"); + file_storage fs; + boost::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); + libtorrent::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[0], piece.size()).final(); + int num = t.num_pieces(); + TEST_CHECK(t.num_pieces() > 0); + for (int i = 0; i < num; ++i) + t.set_hash(i, ph); + + std::vector tmp; + std::back_insert_iterator > out(tmp); + bencode(out, t.generate()); + error_code ec; + boost::shared_ptr info(boost::make_shared(&tmp[0], tmp.size(), boost::ref(ec), 0)); + TEST_CHECK(info->num_pieces() > 0); + + test_running_torrent(info, file_size); + } +*/ + { + file_storage fs; + + fs.add_file("test_torrent_dir2/tmp1", 1024); + libtorrent::create_torrent t(fs, 128 * 1024, 6); + + 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[0], piece.size()).final(); + int num = t.num_pieces(); + TEST_CHECK(t.num_pieces() > 0); + for (int i = 0; i < num; ++i) + t.set_hash(i, ph); + + std::vector tmp; + std::back_insert_iterator > out(tmp); + bencode(out, t.generate()); + error_code ec; + boost::shared_ptr info(boost::make_shared(&tmp[0], tmp.size(), boost::ref(ec), 0)); + test_running_torrent(info, 1024); + } +} + +#ifndef TORRENT_DISABLE_EXTENSIONS +struct test_plugin : libtorrent::torrent_plugin {}; + +struct plugin_creator +{ + plugin_creator(int& c) : m_called(c) {} + + boost::shared_ptr + operator()(torrent_handle const&, void*) + { + ++m_called; + return boost::make_shared(); + } + + int& m_called; +}; + +TORRENT_TEST(duplicate_is_not_error) +{ + file_storage fs; + + fs.add_file("test_torrent_dir2/tmp1", 1024); + libtorrent::create_torrent t(fs, 128 * 1024, 6); + + 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[0], piece.size()).final(); + int num = t.num_pieces(); + TEST_CHECK(t.num_pieces() > 0); + for (int i = 0; i < num; ++i) + t.set_hash(i, ph); + + std::vector tmp; + std::back_insert_iterator > out(tmp); + bencode(out, t.generate()); + error_code ec; + + int called = 0; + plugin_creator creator(called); + + add_torrent_params p; + p.ti = boost::make_shared(&tmp[0], tmp.size(), boost::ref(ec), 0); + p.flags &= ~add_torrent_params::flag_paused; + p.flags &= ~add_torrent_params::flag_auto_managed; + p.flags &= ~add_torrent_params::flag_duplicate_is_error; + p.save_path = "."; + p.extensions.push_back(creator); + + lt::session ses(settings()); + ses.async_add_torrent(p); + ses.async_add_torrent(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; + error_code ec; + + fs.add_file("test_torrent_dir2/tmp1", 0); + TEST_CHECK(fs.num_files() == 1); + TEST_CHECK(fs.total_size() == 0); + + ec.clear(); + libtorrent::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(); + libtorrent::create_torrent t2(fs); + set_piece_hashes(t2, ".", ec); + TEST_CHECK(ec); +} + + diff --git a/test/test_torrent_info.cpp b/test/test_torrent_info.cpp new file mode 100644 index 0000000..1e35d82 --- /dev/null +++ b/test/test_torrent_info.cpp @@ -0,0 +1,951 @@ +/* + +Copyright (c) 2012, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/file_storage.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/announce_entry.hpp" +#include "libtorrent/aux_/escape_string.hpp" // for convert_path_to_posix + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#if TORRENT_USE_IOSTREAM +#include +#endif + +using namespace libtorrent; + +#ifndef TORRENT_DISABLE_MUTABLE_TORRENTS +TORRENT_TEST(mutable_torrents) +{ + file_storage fs; + + fs.add_file("test/temporary.txt", 0x4000); + + libtorrent::create_torrent t(fs, 0x4000); + + // calculate the hash for all pieces + int num = t.num_pieces(); + sha1_hash ph; + for (int i = 0; i < num; ++i) + t.set_hash(i, ph); + + t.add_collection("collection1"); + t.add_collection("collection2"); + + t.add_similar_torrent(sha1_hash("abababababababababab")); + t.add_similar_torrent(sha1_hash("babababababababababa")); + + std::vector tmp; + std::back_insert_iterator > out(tmp); + + entry tor = t.generate(); + bencode(out, tor); + + torrent_info ti(&tmp[0], tmp.size()); + + 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 + +struct test_torrent_t +{ + char const* file; +}; + +using namespace libtorrent; + +static test_torrent_t test_torrents[] = +{ + { "base.torrent" }, + { "empty_path.torrent" }, + { "parent_path.torrent" }, + { "hidden_parent_path.torrent" }, + { "single_multi_file.torrent" }, + { "slash_path.torrent" }, + { "slash_path2.torrent" }, + { "slash_path3.torrent" }, + { "backslash_path.torrent" }, + { "url_list.torrent" }, + { "url_list2.torrent" }, + { "url_list3.torrent" }, + { "httpseed.torrent" }, + { "empty_httpseed.torrent" }, + { "long_name.torrent" }, + { "whitespace_url.torrent" }, + { "duplicate_files.torrent" }, + { "pad_file.torrent" }, + { "creation_date.torrent" }, + { "no_creation_date.torrent" }, + { "url_seed.torrent" }, + { "url_seed_multi.torrent" }, + { "url_seed_multi_space.torrent" }, + { "url_seed_multi_space_nolist.torrent" }, + { "root_hash.torrent" }, + { "empty_path_multi.torrent" }, + { "duplicate_web_seeds.torrent" }, + { "invalid_name2.torrent" }, + { "invalid_name3.torrent" }, + { "symlink1.torrent" }, + { "unordered.torrent" }, + { "symlink_zero_size.torrent" }, + { "pad_file_no_path.torrent" }, +}; + +struct test_failing_torrent_t +{ + char const* file; + error_code error; // the expected error +}; + +test_failing_torrent_t test_error_torrents[] = +{ + { "missing_piece_len.torrent", errors::torrent_missing_piece_length }, + { "invalid_piece_len.torrent", errors::torrent_missing_piece_length }, + { "negative_piece_len.torrent", errors::torrent_missing_piece_length }, + { "no_name.torrent", errors::torrent_missing_name }, + { "invalid_name.torrent", errors::torrent_missing_name }, + { "invalid_info.torrent", errors::torrent_missing_info }, + { "string.torrent", errors::torrent_is_no_dict }, + { "negative_size.torrent", errors::torrent_invalid_length }, + { "negative_file_size.torrent", errors::torrent_invalid_length }, + { "invalid_path_list.torrent", errors::torrent_invalid_name}, + { "missing_path_list.torrent", errors::torrent_missing_name }, + { "invalid_pieces.torrent", errors::torrent_missing_pieces }, + { "unaligned_pieces.torrent", errors::torrent_invalid_hashes }, + { "invalid_root_hash.torrent", errors::torrent_invalid_hashes }, + { "invalid_root_hash2.torrent", errors::torrent_missing_pieces }, + { "invalid_file_size.torrent", errors::torrent_invalid_length }, + { "invalid_symlink.torrent", errors::torrent_invalid_name }, +}; + +// TODO: test remap_files +// TODO: merkle torrents. specifically torrent_info::add_merkle_nodes and torrent with "root hash" +// TODO: torrent with 'p' (padfile) attribute +// TODO: torrent with 'h' (hidden) attribute +// TODO: torrent with 'x' (executable) attribute +// TODO: torrent with 'l' (symlink) attribute +// TODO: creating a merkle torrent (torrent_info::build_merkle_list) +// TODO: torrent with multiple trackers in multiple tiers, making sure we shuffle them (how do you test shuffling?, load it multiple times and make sure it's in different order at least once) +// TODO: torrents with a zero-length name +// TODO: torrents with a merkle tree and add_merkle_nodes +// TODO: torrent with a non-dictionary info-section +// TODO: torrents with DHT nodes +// TODO: torrent with url-list as a single string +// TODO: torrent with http seed as a single string +// TODO: torrent with a comment +// TODO: torrent with an SSL cert +// TODO: torrent with attributes (executable and hidden) +// TODO: torrent_info::add_tracker +// TODO: torrent_info::unload +// TODO: torrent_info constructor that takes an invalid bencoded buffer +// TODO: verify_encoding with a string that triggers character replacement + +TORRENT_TEST(add_url_seed) +{ + torrent_info ti(sha1_hash(" ")); + TEST_EQUAL(ti.web_seeds().size(), 0); + + ti.add_url_seed("http://test.com"); + + TEST_EQUAL(ti.web_seeds().size(), 1); + web_seed_entry we = ti.web_seeds()[0]; + TEST_EQUAL(we.type, web_seed_entry::url_seed); + TEST_EQUAL(we.url, "http://test.com"); +} + +TORRENT_TEST(add_http_seed) +{ + torrent_info ti(sha1_hash(" ")); + TEST_EQUAL(ti.web_seeds().size(), 0); + + ti.add_http_seed("http://test.com"); + + TEST_EQUAL(ti.web_seeds().size(), 1); + web_seed_entry we = ti.web_seeds()[0]; + TEST_EQUAL(we.type, web_seed_entry::http_seed); + TEST_EQUAL(we.url, "http://test.com"); +} + +TORRENT_TEST(set_web_seeds) +{ + torrent_info ti(sha1_hash(" ")); + TEST_EQUAL(ti.web_seeds().size(), 0); + + std::vector seeds; + web_seed_entry e1("http://test1.com", web_seed_entry::url_seed); + seeds.push_back(e1); + web_seed_entry e2("http://test2com", web_seed_entry::http_seed); + seeds.push_back(e2); + + ti.set_web_seeds(seeds); + + TEST_EQUAL(ti.web_seeds().size(), 2); + TEST_CHECK(ti.web_seeds() == seeds); +} + +#ifdef TORRENT_WINDOWS +#define SEPARATOR "\\" +#else +#define SEPARATOR "/" +#endif + +TORRENT_TEST(sanitize_long_path) +{ + // test 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_", 250); + 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", 250); + 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_trailing_dots) +{ + std::string path; + sanitize_append_path_element(path, "a", 1); + sanitize_append_path_element(path, "abc...", 6); + sanitize_append_path_element(path, "c", 1); +#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...", 6); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(path, "abc"); +#else + TEST_EQUAL(path, "abc..."); +#endif + + path.clear(); + sanitize_append_path_element(path, "abc.", 4); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(path, "abc"); +#else + TEST_EQUAL(path, "abc."); +#endif + + + path.clear(); + sanitize_append_path_element(path, "a. . .", 6); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(path, "a"); +#else + TEST_EQUAL(path, "a. . ."); +#endif +} + +TORRENT_TEST(sanitize_path_trailing_spaces) +{ + std::string path; + sanitize_append_path_element(path, "a", 1); + sanitize_append_path_element(path, "abc ", 6); + sanitize_append_path_element(path, "c", 1); +#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 ", 6); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(path, "abc"); +#else + TEST_EQUAL(path, "abc "); +#endif + + path.clear(); + sanitize_append_path_element(path, "abc ", 4); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(path, "abc"); +#else + TEST_EQUAL(path, "abc "); +#endif +} + +TORRENT_TEST(sanitize_path) +{ + std::string path; + sanitize_append_path_element(path, "/a/", 3); + sanitize_append_path_element(path, "b", 1); + sanitize_append_path_element(path, "c", 1); + TEST_EQUAL(path, "a" SEPARATOR "b" SEPARATOR "c"); + + path.clear(); + sanitize_append_path_element(path, "a...b", 5); + TEST_EQUAL(path, "a...b"); + + path.clear(); + sanitize_append_path_element(path, "a", 1); + sanitize_append_path_element(path, "..", 2); + sanitize_append_path_element(path, "c", 1); + TEST_EQUAL(path, "a" SEPARATOR "c"); + + path.clear(); + sanitize_append_path_element(path, "a", 1); + sanitize_append_path_element(path, "..", 2); + TEST_EQUAL(path, "a"); + + path.clear(); + sanitize_append_path_element(path, "/..", 3); + sanitize_append_path_element(path, ".", 1); + sanitize_append_path_element(path, "c", 1); + TEST_EQUAL(path, "c"); + + path.clear(); + sanitize_append_path_element(path, "dev:", 4); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(path, "dev"); +#else + TEST_EQUAL(path, "dev:"); +#endif + + path.clear(); + sanitize_append_path_element(path, "c:", 2); + sanitize_append_path_element(path, "b", 1); +#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:", 2); + sanitize_append_path_element(path, ".", 1); + sanitize_append_path_element(path, "c", 1); +#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", 2); + sanitize_append_path_element(path, ".", 1); + sanitize_append_path_element(path, "c", 1); + TEST_EQUAL(path, "c" SEPARATOR "c"); + + path.clear(); + sanitize_append_path_element(path, "\b", 1); + TEST_EQUAL(path, "_"); + + path.clear(); + sanitize_append_path_element(path, "\b", 1); + sanitize_append_path_element(path, "filename", 8); + TEST_EQUAL(path, "_" SEPARATOR "filename"); + + path.clear(); + sanitize_append_path_element(path, "filename", 8); + sanitize_append_path_element(path, "\b", 1); + TEST_EQUAL(path, "filename" SEPARATOR "_"); + + path.clear(); + sanitize_append_path_element(path, "abc", 3); + sanitize_append_path_element(path, "", 0); + TEST_EQUAL(path, "abc" SEPARATOR "_"); + + path.clear(); + sanitize_append_path_element(path, "abc", 3); + sanitize_append_path_element(path, " ", 3); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(path, "abc"); +#else + TEST_EQUAL(path, "abc" SEPARATOR " "); +#endif + + path.clear(); + sanitize_append_path_element(path, "", 0); + sanitize_append_path_element(path, "abc", 3); + TEST_EQUAL(path, "_" SEPARATOR "abc"); + + path.clear(); + sanitize_append_path_element(path, "\b?filename=4", 12); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(path, "__filename=4"); +#else + TEST_EQUAL(path, "_?filename=4"); +#endif + + path.clear(); + sanitize_append_path_element(path, "filename=4", 10); + TEST_EQUAL(path, "filename=4"); + + // valid 2-byte sequence + path.clear(); + sanitize_append_path_element(path, "filename\xc2\xa1", 10); + TEST_EQUAL(path, "filename\xc2\xa1"); + + // truncated 2-byte sequence + path.clear(); + sanitize_append_path_element(path, "filename\xc2", 9); + TEST_EQUAL(path, "filename_"); + + // valid 3-byte sequence + path.clear(); + sanitize_append_path_element(path, "filename\xe2\x9f\xb9", 11); + TEST_EQUAL(path, "filename\xe2\x9f\xb9"); + + // truncated 3-byte sequence + path.clear(); + sanitize_append_path_element(path, "filename\xe2\x9f", 10); + TEST_EQUAL(path, "filename_"); + + // truncated 3-byte sequence + path.clear(); + sanitize_append_path_element(path, "filename\xe2", 9); + TEST_EQUAL(path, "filename_"); + + // valid 4-byte sequence + path.clear(); + sanitize_append_path_element(path, "filename\xf0\x9f\x92\x88", 12); + TEST_EQUAL(path, "filename\xf0\x9f\x92\x88"); + + // truncated 4-byte sequence + path.clear(); + sanitize_append_path_element(path, "filename\xf0\x9f\x92", 11); + 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", 19); + 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", 10); + 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", 11); + 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", 12); + TEST_EQUAL(path, "filename_"); +} + +TORRENT_TEST(verify_encoding) +{ + // verify_encoding + std::string test = "\b?filename=4"; + TEST_CHECK(verify_encoding(test)); + TEST_CHECK(test == "\b?filename=4"); + + test = "filename=4"; + TEST_CHECK(verify_encoding(test)); + TEST_CHECK(test == "filename=4"); + + // valid 2-byte sequence + test = "filename\xc2\xa1"; + TEST_CHECK(verify_encoding(test)); + fprintf(stderr, "%s\n", test.c_str()); + TEST_CHECK(test == "filename\xc2\xa1"); + + // truncated 2-byte sequence + test = "filename\xc2"; + TEST_CHECK(!verify_encoding(test)); + fprintf(stderr, "%s\n", test.c_str()); + TEST_CHECK(test == "filename_"); + + // valid 3-byte sequence + test = "filename\xe2\x9f\xb9"; + TEST_CHECK(verify_encoding(test)); + fprintf(stderr, "%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)); + fprintf(stderr, "%s\n", test.c_str()); + TEST_CHECK(test == "filename_"); + + // truncated 3-byte sequence + test = "filename\xe2"; + TEST_CHECK(!verify_encoding(test)); + fprintf(stderr, "%s\n", test.c_str()); + TEST_CHECK(test == "filename_"); + + // valid 4-byte sequence + test = "filename\xf0\x9f\x92\x88"; + TEST_CHECK(verify_encoding(test)); + fprintf(stderr, "%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)); + fprintf(stderr, "%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)); + fprintf(stderr, "%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)); + fprintf(stderr, "%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)); + fprintf(stderr, "%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)); + fprintf(stderr, "%s\n", test.c_str()); + TEST_CHECK(test == "filename____"); +} + +TORRENT_TEST(parse_torrents) +{ + error_code ec; + + // 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 ti(&buf[0], buf.size(), ec); + std::cerr << ti.name() << std::endl; + TEST_CHECK(ti.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[0], buf.size(), ec); + std::cerr << ti2.name() << std::endl; +#ifdef TORRENT_WINDOWS + TEST_EQUAL(ti2.name(), "ctest1test2test3"); +#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[0], buf.size(), ec); + std::cerr << ti3.name() << std::endl; + TEST_EQUAL(ti3.name(), "test2..test3.......test4"); + + std::string root_dir = parent_path(current_working_directory()); + for (int i = 0; i < int(sizeof(test_torrents)/sizeof(test_torrents[0])); ++i) + { + fprintf(stderr, "loading %s\n", test_torrents[i].file); + std::string filename = combine_path(combine_path(root_dir, "test_torrents") + , test_torrents[i].file); + boost::shared_ptr ti(new torrent_info(filename, ec)); + TEST_CHECK(!ec); + if (ec) fprintf(stderr, " loading(\"%s\") -> failed %s\n", filename.c_str() + , ec.message().c_str()); + + if (std::string(test_torrents[i].file) == "whitespace_url.torrent") + { + // make sure we trimmed the url + TEST_CHECK(ti->trackers().size() > 0); + if (ti->trackers().size() > 0) + TEST_CHECK(ti->trackers()[0].url == "udp://test.com/announce"); + } + else if (std::string(test_torrents[i].file) == "duplicate_files.torrent") + { + // make sure we disambiguated the files + TEST_EQUAL(ti->num_files(), 2); + TEST_CHECK(ti->files().file_path(0) == combine_path(combine_path("temp", "foo"), "bar.txt")); + TEST_CHECK(ti->files().file_path(1) == combine_path(combine_path("temp", "foo"), "bar.1.txt")); + } + else if (std::string(test_torrents[i].file) == "pad_file.torrent") + { + TEST_EQUAL(ti->num_files(), 2); + TEST_EQUAL(ti->files().file_flags(0) & file_storage::flag_pad_file, false); + TEST_EQUAL(ti->files().file_flags(1) & file_storage::flag_pad_file, true); + } + else if (std::string(test_torrents[i].file) == "creation_date.torrent") + { + TEST_EQUAL(*ti->creation_date(), 1234567); + } + else if (std::string(test_torrents[i].file) == "duplicate_web_seeds.torrent") + { + TEST_EQUAL(ti->web_seeds().size(), 3); + } + else if (std::string(test_torrents[i].file) == "no_creation_date.torrent") + { + TEST_CHECK(!ti->creation_date()); + } + else if (std::string(test_torrents[i].file) == "url_seed.torrent") + { + TEST_EQUAL(ti->web_seeds().size(), 1); + TEST_EQUAL(ti->web_seeds()[0].url, "http://test.com/file"); +#ifndef TORRENT_NO_DEPRECATE + TEST_EQUAL(ti->http_seeds().size(), 0); + TEST_EQUAL(ti->url_seeds().size(), 1); + TEST_EQUAL(ti->url_seeds()[0], "http://test.com/file"); +#endif + } + else if (std::string(test_torrents[i].file) == "url_seed_multi.torrent") + { + TEST_EQUAL(ti->web_seeds().size(), 1); + TEST_EQUAL(ti->web_seeds()[0].url, "http://test.com/file/"); +#ifndef TORRENT_NO_DEPRECATE + TEST_EQUAL(ti->http_seeds().size(), 0); + TEST_EQUAL(ti->url_seeds().size(), 1); + TEST_EQUAL(ti->url_seeds()[0], "http://test.com/file/"); +#endif + } + else if (std::string(test_torrents[i].file) == "url_seed_multi_space.torrent" + || std::string(test_torrents[i].file) == "url_seed_multi_space_nolist.torrent") + { + TEST_EQUAL(ti->web_seeds().size(), 1); + TEST_EQUAL(ti->web_seeds()[0].url, "http://test.com/test%20file/foo%20bar/"); +#ifndef TORRENT_NO_DEPRECATE + TEST_EQUAL(ti->http_seeds().size(), 0); + TEST_EQUAL(ti->url_seeds().size(), 1); + TEST_EQUAL(ti->url_seeds()[0], "http://test.com/test%20file/foo%20bar/"); +#endif + } + else if (std::string(test_torrents[i].file) == "invalid_name2.torrent") + { + // if, after all invalid characters are removed from the name, it ends up + // being empty, it's set to the info-hash. Some torrents also have an empty name + // in which case it's also set to the info-hash + TEST_EQUAL(ti->name(), "b61560c2918f463768cd122b6d2fdd47b77bdb35"); + } + else if (std::string(test_torrents[i].file) == "invalid_name3.torrent") + { + // windows does not allow trailing spaces in filenames +#ifdef TORRENT_WINDOWS + TEST_EQUAL(ti->name(), "foobar"); +#else + TEST_EQUAL(ti->name(), "foobar "); +#endif + } + else if (std::string(test_torrents[i].file) == "slash_path.torrent") + { + TEST_EQUAL(ti->num_files(), 1); + TEST_EQUAL(ti->files().file_path(0), "temp" SEPARATOR "bar"); + } + else if (std::string(test_torrents[i].file) == "slash_path2.torrent") + { + TEST_EQUAL(ti->num_files(), 1); + TEST_EQUAL(ti->files().file_path(0), "temp" SEPARATOR "abc....def" SEPARATOR "bar"); + } + else if (std::string(test_torrents[i].file) == "slash_path3.torrent") + { + TEST_EQUAL(ti->num_files(), 1); + TEST_EQUAL(ti->files().file_path(0), "temp....abc"); + } + else if (std::string(test_torrents[i].file) == "symlink_zero_size.torrent") + { + TEST_EQUAL(ti->num_files(), 2); + TEST_EQUAL(ti->files().symlink(1), combine_path("foo", "bar")); + } + else if (std::string(test_torrents[i].file) == "pad_file_no_path.torrent") + { + TEST_EQUAL(ti->num_files(), 2); + TEST_EQUAL(ti->files().file_path(1), combine_path(".pad", "0")); + } + + file_storage const& fs = ti->files(); + for (int i = 0; i < fs.num_files(); ++i) + { + int first = ti->map_file(i, 0, 0).piece; + int last = ti->map_file(i, (std::max)(fs.file_size(i)-1, boost::int64_t(0)), 0).piece; + int flags = fs.file_flags(i); + fprintf(stderr, " %11" PRId64 " %c%c%c%c [ %4d, %4d ] %7u %s %s %s%s\n" + , fs.file_size(i) + , (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':'-' + , first, last + , boost::uint32_t(fs.mtime(i)) + , fs.hash(i) != sha1_hash(0) ? to_hex(fs.hash(i).to_string()).c_str() : "" + , fs.file_path(i).c_str() + , flags & file_storage::flag_symlink ? "-> ": "" + , flags & file_storage::flag_symlink ? fs.symlink(i).c_str() : ""); + } + + // test swap +#if !defined TORRENT_NO_DEPRECATE && TORRENT_USE_IOSTREAM + std::stringstream str1; + ti->print(str1); + + torrent_info temp("temp", ec); + temp.swap(*ti); + + std::stringstream str2; + temp.print(str2); + TEST_EQUAL(str1.str(), str2.str()); +#endif + + } + + for (int i = 0; i < int(sizeof(test_error_torrents)/sizeof(test_error_torrents[0])); ++i) + { + error_code ec; + fprintf(stderr, "loading %s\n", test_error_torrents[i].file); + boost::shared_ptr ti(new torrent_info(combine_path( + combine_path(root_dir, "test_torrents"), test_error_torrents[i].file), ec)); + fprintf(stderr, "E: \"%s\"\nexpected: \"%s\"\n", ec.message().c_str() + , test_error_torrents[i].error.message().c_str()); + TEST_CHECK(ec.message() == test_error_torrents[i].error.message()); + } +} + +void test_resolve_duplicates(int test_case) +{ + file_storage fs; + + switch (test_case) + { + case 0: + fs.add_file("test/temporary.txt", 0x4000); + fs.add_file("test/Temporary.txt", 0x4000); + fs.add_file("test/TeMPorArY.txT", 0x4000); + fs.add_file("test/test/TEMPORARY.TXT", 0x4000); + break; + case 1: + fs.add_file("test/b.exe", 0x4000); + fs.add_file("test/B.ExE", 0x4000); + fs.add_file("test/B.exe", 0x4000); + fs.add_file("test/filler", 0x4000); + break; + case 2: + 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 3: + 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; + } + + libtorrent::create_torrent t(fs, 0x4000); + + // calculate the hash for all pieces + int num = t.num_pieces(); + sha1_hash ph; + for (int i = 0; i < num; ++i) + t.set_hash(i, ph); + + std::vector tmp; + std::back_insert_iterator > out(tmp); + + entry tor = t.generate(); + bencode(out, tor); + + torrent_info ti(&tmp[0], tmp.size()); + + char const* filenames[4][4] = + { + { // case 0 + "test/temporary.txt", + "test/Temporary.1.txt", // duplicate of temporary.txt + "test/TeMPorArY.2.txT", // duplicate of temporary.txt + // a file with the same name in a seprate directory is fine + "test/test/TEMPORARY.TXT", + }, + { // case 1 + "test/b.exe", + "test/B.1.ExE", // duplicate of b.exe + "test/B.2.exe", // duplicate of b.exe + "test/filler", + }, + { // case 2 + "test/A/tmp", + "test/a.1", // a file may not have the same name as a directory + "test/A.2", // duplicate of directory a + "test/filler", + }, + { // case 3 + // a subset of this path collides with the next filename + "test/long/path/name/that/collides", + // so this file needs to be renamed, to not collide with the path name + "test/long/path.1", + "test/filler-1", + "test/filler-2", + } + }; + + for (int i = 0; i < ti.num_files(); ++i) + { + std::string p = ti.files().file_path(i); + convert_path_to_posix(p); + fprintf(stderr, "%s == %s\n", p.c_str(), filenames[test_case][i]); + + TEST_EQUAL(p, filenames[test_case][i]); + } +} + +TORRENT_TEST(resolve_duplicates) +{ + for (int i = 0; i < 4; ++i) + test_resolve_duplicates(i); +} + +TORRENT_TEST(empty_file) +{ + error_code ec; + boost::shared_ptr ti = boost::make_shared("", 0, boost::ref(ec)); + TEST_CHECK(ec); +} + +TORRENT_TEST(empty_file2) +{ + try + { + boost::shared_ptr ti = boost::make_shared("", 0); + TEST_ERROR("expected exception thrown"); + } + catch (libtorrent_exception& e) + { + printf("Expected error: %s\n", e.error().message().c_str()); + } +} + +TORRENT_TEST(copy) +{ + using namespace libtorrent; + + boost::shared_ptr a(boost::make_shared( + combine_path(parent_path(current_working_directory()) + , combine_path("test_torrents", "sample.torrent")))); + + char const* expected_files[] = + { + "sample/text_file2.txt", + "sample/.____padding_file/0", + "sample/text_file.txt", + }; + + sha1_hash file_hashes[] = + { + sha1_hash(0), + sha1_hash(0), + sha1_hash("abababababababababab") + }; + + for (int i = 0; i < a->num_files(); ++i) + { + std::string p = a->files().file_path(i); + convert_path_to_posix(p); + TEST_EQUAL(p, expected_files[i]); + fprintf(stderr, "%s\n", p.c_str()); + + TEST_EQUAL(a->files().hash(i), file_hashes[i]); + } + + // copy the torrent_info object + boost::shared_ptr b(boost::make_shared(*a)); + + // clear out the buffer for a, just to make sure b doesn't have any + // references into it by mistake + int s = a->metadata_size(); + memset(a->metadata().get(), 0, s); + + a.reset(); + + TEST_EQUAL(b->num_files(), 3); + + for (int i = 0; i < b->num_files(); ++i) + { + std::string p = b->files().file_path(i); + convert_path_to_posix(p); + TEST_EQUAL(p, expected_files[i]); + fprintf(stderr, "%s\n", p.c_str()); + + TEST_EQUAL(b->files().hash(i), file_hashes[i]); + } +} + + 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/base.torrent b/test/test_torrents/base.torrent new file mode 100644 index 0000000..d2c26db --- /dev/null +++ b/test/test_torrents/base.torrent @@ -0,0 +1 @@ +d10:created by10:libtorrent13:creation datei1359599503e4:infod6:lengthi425e4:name4:temp12:piece lengthi16384e6:pieces20:‚ž¼Œ&¾ÇJW›}ÜA4u,·¼‘‡ee \ No newline at end of file diff --git a/test/test_torrents/creation_date.torrent b/test/test_torrents/creation_date.torrent new file mode 100644 index 0000000..d6d0c41 --- /dev/null +++ b/test/test_torrents/creation_date.torrent @@ -0,0 +1 @@ +d10:created by10:libtorrent13:creation datei1234567e4:infod5:filesld6:lengthi425e4:pathl3:foo7:bar.txteed6:lengthi425e4:pathl3:foo7:var.txteee4:name4:temp12:piece lengthi16384e6:pieces20:01234567890123456789ee diff --git a/test/test_torrents/duplicate_files.torrent b/test/test_torrents/duplicate_files.torrent new file mode 100644 index 0000000..17e9de3 --- /dev/null +++ b/test/test_torrents/duplicate_files.torrent @@ -0,0 +1 @@ +d10:created by10:libtorrent13:creation datei1359599503e4:infod5:filesld6:lengthi425e4:pathl3:foo7:bar.txteed6:lengthi425e4:pathl3:foo7:bar.txteee4:name4:temp12:piece lengthi16384e6:pieces20:‚ž¼Œ&¾ÇJW›}ÜA4u,·¼‘‡ee diff --git a/test/test_torrents/duplicate_web_seeds.torrent b/test/test_torrents/duplicate_web_seeds.torrent new file mode 100644 index 0000000..8a5d8a6 --- /dev/null +++ b/test/test_torrents/duplicate_web_seeds.torrent @@ -0,0 +1 @@ +d8:announce23:udp://test.com/announce10:created by10:libtorrent13:creation datei1359599503e4:infod6:lengthi425e4:name4:temp12:piece lengthi16384e6:pieces20:‚ž¼Œ&¾ÇJW›}ÜA4u,·¼‘‡e8:url-listl21:http://foobar.com/foo21:http://foobar.com/foo21:http://foobar.com/foo24:http://foobar.com/foobar21:http://foobar.com/foo21:http://foobar.com/foo21:http://foobar.com/foo21:http://foobar.com/baree 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_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_root_hash.torrent b/test/test_torrents/invalid_root_hash.torrent new file mode 100644 index 0000000..442ecb6 --- /dev/null +++ b/test/test_torrents/invalid_root_hash.torrent @@ -0,0 +1 @@ +d10:created by10:libtorrent13:creation datei1359599503e4:infod6:lengthi425e4:name4:temp12:piece lengthi16384e9:root hash19:ž¼Œ&¾ÇJW›}ÜA4u,·¼‘‡ee diff --git a/test/test_torrents/invalid_root_hash2.torrent b/test/test_torrents/invalid_root_hash2.torrent new file mode 100644 index 0000000..927fb07 --- /dev/null +++ b/test/test_torrents/invalid_root_hash2.torrent @@ -0,0 +1 @@ +d10:created by10:libtorrent13:creation datei1359599503e4:infod6:lengthi425e4:name4:temp12:piece lengthi16384e9:root hashi1343eee 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/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/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_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/pad_file.torrent b/test/test_torrents/pad_file.torrent new file mode 100644 index 0000000..580233a --- /dev/null +++ b/test/test_torrents/pad_file.torrent @@ -0,0 +1 @@ +d10:created by10:libtorrent13:creation datei1359599503e4:infod5:filesld4:pathl3:foo7:bar.txte6:lengthi45eed4:pathl18:_____padding_file_e6:lengthi2124eee4:name4:temp12:piece lengthi16384e6:pieces20:‚ž¼Œ&¾ÇJW›}ÜA4u,·¼‘‡ee diff --git a/test/test_torrents/pad_file_no_path.torrent b/test/test_torrents/pad_file_no_path.torrent new file mode 100644 index 0000000..d878b2c --- /dev/null +++ b/test/test_torrents/pad_file_no_path.torrent @@ -0,0 +1 @@ +d10:created by10:libtorrent13:creation datei1359599503e4:infod5:filesld4:pathl3:foo7:bar.txte6:lengthi45eed4:attr1:p6:lengthi2124eee4:name4:temp12:piece lengthi16384e6:pieces20:‚ž¼Œ&¾ÇJW›}ÜA4u,·¼‘‡ee diff --git a/test/test_torrents/parent_path.torrent b/test/test_torrents/parent_path.torrent new file mode 100644 index 0000000..d36c30b --- /dev/null +++ b/test/test_torrents/parent_path.torrent @@ -0,0 +1 @@ +d10:created by10:libtorrent13:creation datei1359599503e4:infod5:filesld6:lengthi425e4:pathl2:..2:..3:bareee4:name4:temp12:piece lengthi16384e6:pieces20:‚ž¼Œ&¾ÇJW›}ÜA4u,·¼‘‡ee diff --git a/test/test_torrents/root_hash.torrent b/test/test_torrents/root_hash.torrent new file mode 100644 index 0000000..33666d7 --- /dev/null +++ b/test/test_torrents/root_hash.torrent @@ -0,0 +1 @@ +d10:created by10:libtorrent13:creation datei1359599503e4:infod6:lengthi425e4:name4:temp12:piece lengthi16384e9:root hash20:‚ž¼Œ&¾ÇJW›}ÜA4u,·¼‘‡ee diff --git a/test/test_torrents/sample.torrent b/test/test_torrents/sample.torrent new file mode 100644 index 0000000000000000000000000000000000000000..2793361f9f8405acc17a879c559dd049b6c818b7 GIT binary patch literal 505 zcmb7KK%pg@xd~K~dj+Ho45gQ{DBO7AiDR=_j zqDWL?0`UR}_fLWZ6TZc|`+eViCsN>AmyOPmQD}sLAg~qBCs_HVLEZlIvVfEXElZafu$J1UEFYH#51pI+}h6^F}Xep&YoUr3V(!tS>|I*%FHs&-Wz_Y)FPNsQ*z4iGt z1S2s=Z=KuaNST{1n>8bF^SN=nf97vKo*d5ZZu<|X^U1-})%kt@ + +using namespace libtorrent; +namespace lt = libtorrent; + +// 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:aaaaaaaaaaaaaaaaaaaa2:ip13:test_hostname4:porti1000eed7:peer id20:bbbbabaababababababa2:ip12:another_host4:porti1001eeee"; + error_code ec; + tracker_response resp = parse_tracker_response(response, sizeof(response) - 1 + , ec, false, sha1_hash()); + + TEST_EQUAL(ec, error_code()); + TEST_EQUAL(resp.peers.size(), 2); + if (resp.peers.size() == 2) + { + peer_entry const& e0 = resp.peers[0]; + peer_entry const& e1 = resp.peers[1]; + TEST_EQUAL(e0.hostname, "test_hostname"); + TEST_EQUAL(e0.port, 1000); + TEST_EQUAL(e0.pid, peer_id("aaaaaaaaaaaaaaaaaaaa")); + + TEST_EQUAL(e1.hostname, "another_host"); + TEST_EQUAL(e1.port, 1001); + TEST_EQUAL(e1.pid, peer_id("bbbbabaababababababa")); + } +} + +TORRENT_TEST(parse_peers4) +{ + char const response[] = "d5:peers12:\x01\x02\x03\x04\x30\x10" + "\x09\x08\x07\x06\x20\x10" "e"; + error_code ec; + tracker_response resp = parse_tracker_response(response, sizeof(response) - 1 + , ec, false, sha1_hash()); + + TEST_EQUAL(ec, error_code()); + TEST_EQUAL(resp.peers4.size(), 2); + if (resp.peers.size() == 2) + { + ipv4_peer_entry const& e0 = resp.peers4[0]; + ipv4_peer_entry const& e1 = resp.peers4[1]; + TEST_CHECK(e0.ip == address_v4::from_string("1.2.3.4").to_bytes()); + TEST_EQUAL(e0.port, 0x3010); + + TEST_CHECK(e1.ip == address_v4::from_string("9.8.7.6").to_bytes()); + TEST_EQUAL(e1.port, 0x2010); + } +} + +TORRENT_TEST(parse_i2p_peers) +{ + // d8:completei8e10:incompletei4e8:intervali3600e5:peers352: ... + boost::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"); + } +} + +TORRENT_TEST(parse_interval) +{ + char const response[] = "d8:intervali1042e12:min intervali10e5:peers0:e"; + error_code ec; + tracker_response resp = parse_tracker_response(response, sizeof(response) - 1 + , ec, false, sha1_hash()); + + TEST_EQUAL(ec, error_code()); + TEST_EQUAL(resp.peers.size(), 0); + TEST_EQUAL(resp.peers4.size(), 0); + TEST_EQUAL(resp.interval, 1042); + TEST_EQUAL(resp.min_interval, 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, sizeof(response) - 1 + , ec, false, sha1_hash()); + + TEST_EQUAL(ec, error_code()); + TEST_EQUAL(resp.peers.size(), 0); + TEST_EQUAL(resp.warning_message, "test message"); +} + +TORRENT_TEST(parse_failure_reason) +{ + char const response[] = "d5:peers0:14:failure reason12:test messagee"; + error_code ec; + tracker_response resp = parse_tracker_response(response, sizeof(response) - 1 + , ec, false, sha1_hash()); + + TEST_EQUAL(ec, error_code(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:aaaaaaaaaaaaaaaaaaaad8:completei1e10:incompletei2e10:downloadedi3e11:downloadersi6eeee"; + error_code ec; + tracker_response resp = parse_tracker_response(response, sizeof(response) - 1 + , ec, true, sha1_hash("aaaaaaaaaaaaaaaaaaaa")); + + TEST_EQUAL(ec, error_code()); + TEST_EQUAL(resp.complete, 1); + TEST_EQUAL(resp.incomplete, 2); + TEST_EQUAL(resp.downloaded, 3); + TEST_EQUAL(resp.downloaders, 6); +} + +TORRENT_TEST(parse_scrape_response_with_zero) +{ + char const response[] = "d5:filesd20:aaa\0aaaaaaaaaaaaaaaad8:completei4e10:incompletei5e10:downloadedi6eeee"; + error_code ec; + tracker_response resp = parse_tracker_response(response, sizeof(response) - 1 + , ec, true, sha1_hash("aaa\0aaaaaaaaaaaaaaaa")); + + TEST_EQUAL(ec, error_code()); + TEST_EQUAL(resp.complete, 4); + TEST_EQUAL(resp.incomplete, 5); + TEST_EQUAL(resp.downloaded, 6); + TEST_EQUAL(resp.downloaders, -1); +} + +TORRENT_TEST(parse_external_ip) +{ + char const response[] = "d5:peers0:11:external ip4:\x01\x02\x03\x04" "e"; + error_code ec; + tracker_response resp = parse_tracker_response(response, sizeof(response) - 1 + , ec, false, sha1_hash()); + + TEST_EQUAL(ec, error_code()); + TEST_EQUAL(resp.peers.size(), 0); + TEST_EQUAL(resp.external_ip, address_v4::from_string("1.2.3.4")); +} + +#if TORRENT_USE_IPV6 +TORRENT_TEST(parse_external_ip6) +{ + char const response[] = "d5:peers0:11:external ip16:\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, sizeof(response) - 1 + , ec, false, sha1_hash()); + + TEST_EQUAL(ec, error_code()); + TEST_EQUAL(resp.peers.size(), 0); + TEST_EQUAL(resp.external_ip, address_v6::from_string("f102:0304::ffff")); +} +#endif + +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, NULL, 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; +} + +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" + , error_code(errors::invalid_peer_dict, get_libtorrent_category()), false); +} + +TORRENT_TEST(extract_peer_missing_ip) +{ + // missing IP + peer_entry result = extract_peer("d7:peer id20:abababababababababab4:porti1337ee" + , error_code(errors::invalid_tracker_response, get_libtorrent_category()), false); +} + +TORRENT_TEST(extract_peer_missing_port) +{ + // missing port + peer_entry result = extract_peer("d7:peer id20:abababababababababab2:ip4:abcde" + , error_code(errors::invalid_tracker_response, get_libtorrent_category()), false); +} + +TORRENT_TEST(udp_tracker) +{ + int http_port = start_web_server(); + int udp_port = start_udp_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_str(settings_pack::listen_interfaces, "0.0.0.0:48875"); + + boost::scoped_ptr s(new lt::session(pack)); + + error_code ec; + remove_all("tmp1_tracker", ec); + create_directory("tmp1_tracker", ec); + std::ofstream file(combine_path("tmp1_tracker", "temporary").c_str()); + boost::shared_ptr t = ::create_torrent(&file, "temporary", 16 * 1024, 13, false); + file.close(); + + char tracker_url[200]; + snprintf(tracker_url, sizeof(tracker_url), "http://127.0.0.1:%d/announce", http_port); + t->add_tracker(tracker_url, 0); + + snprintf(tracker_url, sizeof(tracker_url), "udp://127.0.0.1:%d/announce", udp_port); + t->add_tracker(tracker_url, 1); + + add_torrent_params addp; + addp.flags &= ~add_torrent_params::flag_paused; + addp.flags &= ~add_torrent_params::flag_auto_managed; + addp.flags |= add_torrent_params::flag_seed_mode; + addp.ti = t; + addp.save_path = "tmp1_tracker"; + torrent_handle h = s->add_torrent(addp); + + for (int i = 0; i < 50; ++i) + { + print_alerts(*s, "s"); + if (num_udp_announces() == prev_udp_announces + 1) + break; + + test_sleep(100); + fprintf(stderr, "UDP: %d / %d\n", int(num_udp_announces()) + , int(prev_udp_announces) + 1); + } + + // we should have announced to the tracker by now + TEST_EQUAL(num_udp_announces(), prev_udp_announces + 1); + + // if we remove the torrent before it has received the response from the + // tracker, it won't announce again to stop. So, wait a bit before removing. + test_sleep(1000); + + s->remove_torrent(h); + + for (int i = 0; i < 50; ++i) + { + print_alerts(*s, "s", true, true); + if (num_udp_announces() == prev_udp_announces + 2) + break; + + test_sleep(100); + fprintf(stderr, "UDP: %d / %d\n", int(num_udp_announces()) + , int(prev_udp_announces) + 1); + } + + fprintf(stderr, "destructing session\n"); + s.reset(); + fprintf(stderr, "done\n"); + + // we should have announced the stopped event now + TEST_EQUAL(num_udp_announces(), prev_udp_announces + 2); +} + +TORRENT_TEST(http_peers) +{ + int http_port = start_web_server(); + + settings_pack pack = settings(); + pack.set_bool(settings_pack::announce_to_all_trackers, true); + pack.set_bool(settings_pack::announce_to_all_tiers, false); + pack.set_int(settings_pack::tracker_completion_timeout, 2); + pack.set_int(settings_pack::tracker_receive_timeout, 1); + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:39775"); + + boost::scoped_ptr s(new lt::session(pack)); + + error_code ec; + remove_all("tmp2_tracker", ec); + create_directory("tmp2_tracker", ec); + std::ofstream file(combine_path("tmp2_tracker", "temporary").c_str()); + boost::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) + 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 &= ~add_torrent_params::flag_paused; + addp.flags &= ~add_torrent_params::flag_auto_managed; + addp.flags |= add_torrent_params::flag_seed_mode; + addp.ti = t; + addp.save_path = "tmp2_tracker"; + torrent_handle h = s->add_torrent(addp); + + libtorrent::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_CHECK(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 + std::vector peers; + h.get_full_peer_list(peers); + + std::set expected_peers; + expected_peers.insert(tcp::endpoint(address_v4::from_string("65.65.65.65"), 16962)); + expected_peers.insert(tcp::endpoint(address_v4::from_string("67.67.67.67"), 17476)); +#if TORRENT_USE_IPV6 + if (supports_ipv6()) + { + expected_peers.insert(tcp::endpoint(address_v6::from_string("4545:4545:4545:4545:4545:4545:4545:4545"), 17990)); + } +#endif + + TEST_EQUAL(peers.size(), expected_peers.size()); + for (std::vector::iterator i = peers.begin() + , end(peers.end()); i != end; ++i) + { + TEST_EQUAL(expected_peers.count(i->ip), 1); + } + + fprintf(stderr, "destructing session\n"); + s.reset(); + fprintf(stderr, "done\n"); + + fprintf(stderr, "stop_web_server\n"); + stop_web_server(); + fprintf(stderr, "done\n"); +} + +TORRENT_TEST(current_tracker) +{ + // use a invalid tracker port + int http_port = 39527; + + settings_pack pack = settings(); + pack.set_bool(settings_pack::announce_to_all_trackers, true); + pack.set_bool(settings_pack::announce_to_all_tiers, false); + pack.set_int(settings_pack::tracker_completion_timeout, 2); + pack.set_int(settings_pack::tracker_receive_timeout, 1); + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:39775"); + //pack.set_int(settings_pack::alert_mask, alert::tracker_notification); + + boost::scoped_ptr s(new lt::session(pack)); + + error_code ec; + remove_all("tmp3_tracker", ec); + create_directory("tmp3_tracker", ec); + std::ofstream file(combine_path("tmp3_tracker", "temporary").c_str()); + boost::shared_ptr t = ::create_torrent(&file, "temporary", 16 * 1024, 13, false); + file.close(); + + char tracker_url[200]; + 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 &= ~add_torrent_params::flag_paused; + addp.flags &= ~add_torrent_params::flag_auto_managed; + addp.flags |= add_torrent_params::flag_seed_mode; + addp.ti = t; + addp.save_path = "tmp3_tracker"; + torrent_handle h = s->add_torrent(addp); + + libtorrent::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()); + + fprintf(stderr, "destructing session\n"); + s.reset(); + fprintf(stderr, "done\n"); +} + +void test_proxy(bool proxy_trackers) +{ + int http_port = start_web_server(); + + settings_pack pack = settings(); + pack.set_bool(settings_pack::announce_to_all_trackers, true); + pack.set_bool(settings_pack::announce_to_all_tiers, false); + pack.set_int(settings_pack::tracker_completion_timeout, 2); + pack.set_int(settings_pack::tracker_receive_timeout, 1); + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:39775"); + pack.set_bool(settings_pack::force_proxy, true); + + 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); + + boost::scoped_ptr s(new lt::session(pack)); + + error_code ec; + remove_all("tmp2_tracker", ec); + create_directory("tmp2_tracker", ec); + std::ofstream file(combine_path("tmp2_tracker", "temporary").c_str()); + boost::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) + 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 &= ~add_torrent_params::flag_paused; + addp.flags &= ~add_torrent_params::flag_auto_managed; + addp.flags |= add_torrent_params::flag_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 == NULL); + } + else + { + TEST_CHECK(a != NULL); + } + + fprintf(stderr, "destructing session\n"); + s.reset(); + fprintf(stderr, "done\n"); + + fprintf(stderr, "stop_web_server\n"); + stop_web_server(); + fprintf(stderr, "done\n"); +} + +TORRENT_TEST(tracker_proxy) +{ + fprintf(stderr, "\n\nnot proxying tracker connections (expect to reach the tracker)\n\n"); + test_proxy(false); + + fprintf(stderr, "\n\nproxying tracker connections through non-existent proxy (do not expect to reach the tracker)\n\n"); + test_proxy(true); +} + diff --git a/test/test_transfer.cpp b/test/test_transfer.cpp new file mode 100644 index 0000000..9a3bdd9 --- /dev/null +++ b/test/test_transfer.cpp @@ -0,0 +1,457 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/session.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/thread.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/file.hpp" +#include "libtorrent/torrent_info.hpp" + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "test_utils.hpp" + +#include +#include + +#include +#include + +using namespace libtorrent; +namespace lt = libtorrent; + +using boost::tuples::ignore; + +const int mask = alert::all_categories & ~(alert::performance_warning | alert::stats_notification); + +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; +} + +// simulate a full disk +struct test_storage : default_storage +{ + test_storage(storage_params const& params) + : default_storage(params) + , m_written(0) + , m_limit(16 * 1024 * 2) + {} + + virtual void set_file_priority(std::vector const& p + , storage_error& ec) {} + + void set_limit(int lim) + { + mutex::scoped_lock l(m_mutex); + m_limit = lim; + } + + int writev( + file::iovec_t const* bufs + , int num_bufs + , int piece_index + , int offset + , int flags + , storage_error& se) + { + mutex::scoped_lock l(m_mutex); + if (m_written >= m_limit) + { + std::cerr << "storage written: " << m_written << " limit: " << m_limit << std::endl; + error_code ec; + ec = error_code(boost::system::errc::no_space_on_device, generic_category()); + se.ec = ec; + return 0; + } + + for (int i = 0; i < num_bufs; ++i) + m_written += bufs[i].iov_len; + l.unlock(); + return default_storage::writev(bufs, num_bufs, piece_index, offset, flags, se); + } + + virtual ~test_storage() {} + + int m_written; + int m_limit; + mutex m_mutex; +}; + +storage_interface* test_storage_constructor(storage_params const& params) +{ + return new test_storage(params); +} + +void test_transfer(int proxy_type, settings_pack const& sett + , bool test_disk_full = false + , storage_mode_t storage_mode = storage_mode_sparse) +{ + char const* test_name[] = {"no", "SOCKS4", "SOCKS5", "SOCKS5 password", "HTTP", "HTTP password"}; + + fprintf(stderr, "\n\n ==== TESTING %s proxy ==== disk-full: %s\n\n\n" + , test_name[proxy_type], test_disk_full ? "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; + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:48075"); + pack.set_int(settings_pack::alert_mask, mask); + + 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); + + lt::session ses1(pack); + + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:49075"); + lt::session ses2(pack); + + int proxy_port = 0; + if (proxy_type) + { + proxy_port = start_proxy(proxy_type); + + settings_pack pack; + pack.set_str(settings_pack::proxy_username, "testuser"); + pack.set_str(settings_pack::proxy_password, "testpass"); + pack.set_int(settings_pack::proxy_type, proxy_type); + pack.set_int(settings_pack::proxy_port, proxy_port); + pack.set_bool(settings_pack::force_proxy, true); + + // 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.set_str(settings_pack::proxy_hostname, "5.6.7.8"); + ses1.apply_settings(pack); + pack.set_str(settings_pack::proxy_hostname, "127.0.0.1"); + ses1.apply_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_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::alert_mask, mask); + + 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"); + boost::shared_ptr t = ::create_torrent(&file, "temporary", 32 * 1024, 13, false); + file.close(); + + TEST_CHECK(exists(combine_path("tmp1_transfer", "temporary"))); + + add_torrent_params addp(&test_storage_constructor); + addp.flags &= ~add_torrent_params::flag_paused; + addp.flags &= ~add_torrent_params::flag_auto_managed; + + add_torrent_params params; + params.storage_mode = storage_mode; + params.flags &= ~add_torrent_params::flag_paused; + params.flags &= ~add_torrent_params::flag_auto_managed; + + wait_for_listen(ses1, "ses1"); + wait_for_listen(ses2, "ses2"); + + peer_disconnects = 0; + + // test using piece sizes smaller than 16kB + boost::tie(tor1, tor2, ignore) = setup_transfer(&ses1, &ses2, 0 + , true, false, true, "_transfer", 8 * 1024, &t, false, test_disk_full?&addp:¶ms); + + int num_pieces = tor2.torrent_file()->num_pieces(); + std::vector priorities(num_pieces, 1); + + // also test to move the storage of the downloader and the uploader + // to make sure it can handle switching paths + bool test_move_storage = false; + + int upload_mode_timer = 0; + + wait_for_downloading(ses2, "ses2"); + + for (int i = 0; i < 200; ++i) + { + torrent_status st1 = tor1.status(); + torrent_status st2 = tor2.status(); + + print_alerts(ses1, "ses1", true, true, true, &on_alert); + print_alerts(ses2, "ses2", true, true, true, &on_alert); + + if (i % 10 == 0) + { + print_ses_rate(i / 10.f, &st1, &st2); + } + + if (!test_move_storage && st2.progress > 0.25f) + { + test_move_storage = true; + tor1.move_storage("tmp1_transfer_moved"); + tor2.move_storage("tmp2_transfer_moved"); + std::cerr << "moving storage" << std::endl; + } + + // wait 10 loops before we restart the torrent. This lets + // us catch all events that failed (and would put the torrent + // back into upload mode) before we restart it. + + // TODO: factor out the disk-full test into its own unit test + if (test_disk_full && st2.upload_mode && ++upload_mode_timer > 10) + { + test_disk_full = false; + ((test_storage*)tor2.get_storage_impl())->set_limit(16 * 1024 * 1024); + + // if we reset the upload mode too soon, there may be more disk + // jobs failing right after, putting us back in upload mode. So, + // give the disk some time to fail all disk jobs before resetting + // upload mode to false + test_sleep(500); + + // then we need to drain the alert queue, so the peer_disconnects + // counter doesn't get incremented by old alerts + print_alerts(ses1, "ses1", true, true, true, &on_alert); + print_alerts(ses2, "ses2", true, true, true, &on_alert); + + lt::error_code err = tor2.status().errc; + fprintf(stderr, "error: \"%s\"\n", err.message().c_str()); + TEST_CHECK(!err); + tor2.set_upload_mode(false); + + // at this point we probably disconnected the seed + // so we need to reconnect as well + fprintf(stderr, "%s: reconnecting peer\n", time_now_string()); + error_code ec; + tor2.connect_peer(tcp::endpoint(address::from_string("127.0.0.1", ec) + , ses1.listen_port())); + + TEST_CHECK(tor2.status().is_finished == false); + fprintf(stderr, "disconnects: %d\n", peer_disconnects); + TEST_CHECK(peer_disconnects >= 2); + fprintf(stderr, "%s: discovered disk full mode. Raise limit and disable upload-mode\n", time_now_string()); + peer_disconnects = 0; + continue; + } + + if (!test_disk_full && st2.is_seeding) break; + + 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 + || (test_disk_full && st2.errc)); + + if (!test_disk_full && peer_disconnects >= 2) break; + + // if nothing is being transferred after 2 seconds, we're failing the test +// if (!test_disk_full && st1.upload_payload_rate == 0 && i > 20) break; + + test_sleep(100); + } + + 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); +} + +TORRENT_TEST(no_contiguous_buffers) +{ + using namespace libtorrent; + + // test no contiguous_recv_buffers + settings_pack p; + p.set_bool(settings_pack::contiguous_recv_buffer, false); + test_transfer(0, p); + + cleanup(); +} + + // test with all kinds of proxies +TORRENT_TEST(socks5_pw) +{ + using namespace libtorrent; + test_transfer(settings_pack::socks5_pw, settings_pack()); + cleanup(); +} + +TORRENT_TEST(http) +{ + using namespace libtorrent; + test_transfer(settings_pack::http, settings_pack()); + cleanup(); +} + +TORRENT_TEST(http_pw) +{ + using namespace libtorrent; + test_transfer(settings_pack::http_pw, settings_pack()); + cleanup(); +} +/* +TORRENT_TEST(i2p) +{ + using namespace libtorrent; + test_transfer(settings_pack::i2p_proxy, settings_pack()); + cleanup(); +} + +// this test is too flaky. Move it to a sim +TORRENT_TEST(disk_full) +{ + using namespace libtorrent; + // test with a (simulated) full disk + test_transfer(0, settings_pack(), true); + + cleanup(); +} +*/ + +TORRENT_TEST(allow_fast) +{ + using namespace libtorrent; + // test allowed fast + settings_pack p; + p.set_int(settings_pack::allowed_fast_set_size, 2000); + test_transfer(0, p, false); + + cleanup(); +} + +TORRENT_TEST(coalesce_reads) +{ + using namespace libtorrent; + // test allowed fast + settings_pack p; + p.set_int(settings_pack::read_cache_line_size, 16); + p.set_bool(settings_pack::coalesce_reads, true); + test_transfer(0, p, false); + + cleanup(); +} + +TORRENT_TEST(coalesce_writes) +{ + using namespace libtorrent; + // test allowed fast + settings_pack p; + p.set_bool(settings_pack::coalesce_writes, true); + test_transfer(0, p, false); + + cleanup(); +} + + +TORRENT_TEST(allocate) +{ + using namespace libtorrent; + // test storage_mode_allocate + fprintf(stderr, "full allocation mode\n"); + test_transfer(0, settings_pack(), false, storage_mode_allocate); + + cleanup(); +} + diff --git a/test/test_upnp.cpp b/test/test_upnp.cpp new file mode 100644 index 0000000..447bab5 --- /dev/null +++ b/test/test_upnp.cpp @@ -0,0 +1,243 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/upnp.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/socket_io.hpp" // print_endpoint +#include "test.hpp" +#include "setup_transfer.hpp" +#include +#include +#include +#include +#include + +using namespace libtorrent; + +broadcast_socket* sock = 0; +int g_port = 0; + +char const* soap_add_response[] = { + "" + "" + "", + "" + "" + ""}; + +char const* soap_delete_response[] = { + "" + "" + "", + "" + "" + ""}; + +void incoming_msearch(udp::endpoint const& from, char* buffer + , int size) +{ + http_parser p; + bool error = false; + p.incoming(buffer::const_interval(buffer, buffer + size), error); + if (error || !p.header_finished()) + { + std::cerr << "*** malformed HTTP from " + << print_endpoint(from) << std::endl; + return; + } + + if (p.method() != "m-search") return; + + std::cerr << "< incoming m-search from " << from << std::endl; + + char 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]; + int len = snprintf(buf, sizeof(buf), msg, g_port); + + error_code ec; + sock->send(buf, len, ec); + + if (ec) std::cerr << "*** error sending " << ec.message() << std::endl; +} + +void log_callback(char const* err) +{ + std::cerr << "UPnP: " << err << std::endl; + //TODO: store the log and verify that some key messages are there +} + +struct callback_info +{ + int 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; + +void callback(int mapping, address const& ip, int port, int protocol, error_code const& err) +{ + callback_info info = {mapping, port, err}; + callbacks.push_back(info); + std::cerr << "mapping: " << mapping << ", port: " << port << ", IP: " << ip + << ", proto: " << protocol << ", error: \"" << err.message() << "\"\n"; +} + +void run_upnp_test(char const* root_filename, char const* router_model, char const* control_name, int igd_version) +{ + libtorrent::io_service ios; + + 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 == NULL) + { + fprintf(stderr, "failed to open file 'upnp.xml': %s\n", strerror(errno)); + TEST_CHECK(false); + return; + } + fprintf(xml_file, &buf[0], g_port); + fclose(xml_file); + + std::ofstream xml(control_name, std::ios::trunc); + xml.write(soap_add_response[igd_version-1], sizeof(soap_add_response[igd_version-1])-1); + xml.close(); + + sock = new broadcast_socket(udp::endpoint(address_v4::from_string("239.255.255.250") + , 1900)); + + sock->open(&incoming_msearch, ios, ec); + + std::string user_agent = "test agent"; + + boost::shared_ptr upnp_handler = boost::make_shared(boost::ref(ios) + , address_v4::from_string("127.0.0.1") + , user_agent, &callback, &log_callback, false); + upnp_handler->start(); + upnp_handler->discover_device(); + + for (int i = 0; i < 20; ++i) + { + ios.reset(); + ios.poll(ec); + if (ec) + { + fprintf(stderr, "io_service::run(): %s\n", ec.message().c_str()); + ec.clear(); + break; + } + if (upnp_handler->router_model() != "") break; + test_sleep(100); + } + + std::cerr << "router: " << upnp_handler->router_model() << std::endl; + TEST_EQUAL(upnp_handler->router_model(), router_model); + + int mapping1 = upnp_handler->add_mapping(upnp::tcp, 500, 500); + int mapping2 = upnp_handler->add_mapping(upnp::udp, 501, 501); + + for (int i = 0; i < 40; ++i) + { + ios.reset(); + ios.poll(ec); + if (ec) + { + fprintf(stderr, "io_service::run(): %s\n", ec.message().c_str()); + ec.clear(); + break; + } + if (callbacks.size() >= 2) break; + test_sleep(100); + } + + callback_info expected1 = {mapping1, 500, error_code()}; + callback_info expected2 = {mapping2, 501, error_code()}; + TEST_EQUAL(std::count(callbacks.begin(), callbacks.end(), expected1), 1); + TEST_EQUAL(std::count(callbacks.begin(), callbacks.end(), expected2), 1); + + xml.open(control_name, std::ios::trunc); + xml.write(soap_delete_response[igd_version-1], sizeof(soap_delete_response[igd_version-1])-1); + xml.close(); + + upnp_handler->close(); + sock->close(); + + for (int i = 0; i < 40; ++i) + { + ios.reset(); + ios.poll(ec); + if (ec) + { + fprintf(stderr, "io_service::run(): %s\n", ec.message().c_str()); + ec.clear(); + break; + } + if (callbacks.size() >= 4) break; + test_sleep(100); + } + + // there should have been two DeleteMapping calls + TEST_EQUAL(callbacks.size(), 4); + + stop_web_server(); + + callbacks.clear(); + + delete sock; +} + +TORRENT_TEST(upnp) +{ + run_upnp_test(combine_path("..", "root1.xml").c_str(), "Xtreme N GIGABIT Router", "wipconn", 1); + run_upnp_test(combine_path("..", "root2.xml").c_str(), "D-Link Router", "WANIPConnection", 1); + run_upnp_test(combine_path("..", "root3.xml").c_str(), "D-Link Router", "WANIPConnection_2", 2); +} diff --git a/test/test_url_seed.cpp b/test/test_url_seed.cpp new file mode 100644 index 0000000..21c3440 --- /dev/null +++ b/test/test_url_seed.cpp @@ -0,0 +1,68 @@ +/* + +Copyright (c) 2008-2014, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "web_seed_suite.hpp" + +using namespace libtorrent; + +const int proxy = libtorrent::settings_pack::none; + +#ifdef TORRENT_USE_OPENSSL +TORRENT_TEST(url_seed_ssl_keepalive) +{ + run_http_suite(proxy, "https", 1, 0, 0, 1); +} + +TORRENT_TEST(url_seed_ssl) +{ + run_http_suite(proxy, "https", 1, 0, 0, 0); +} +#endif + +TORRENT_TEST(url_seed_keepalive) +{ + run_http_suite(proxy, "http", 1, 0, 0, 1); +} + +TORRENT_TEST(url_seed) +{ + run_http_suite(proxy, "http", 1, 0, 0, 0); +} + +TORRENT_TEST(url_seed_keepalive_rename) +{ + run_http_suite(proxy, "http", 1, 0, 0, 1, 1); +} + + diff --git a/test/test_utf8.cpp b/test/test_utf8.cpp new file mode 100644 index 0000000..e025ea8 --- /dev/null +++ b/test/test_utf8.cpp @@ -0,0 +1,272 @@ +/* + +Copyright (c) 2014, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/utf8.hpp" +#include "libtorrent/ConvertUTF.h" +#include "setup_transfer.hpp" // for load_file +#include "libtorrent/file.hpp" // for combine_path + +#include + +using namespace libtorrent; + +void verify_transforms(char const* utf8_source, int utf8_source_len = -1) +{ + if (utf8_source_len == -1) + utf8_source_len = strlen(utf8_source); + + // utf8 -> utf16 -> utf32 -> utf8 + { + std::vector utf16(utf8_source_len); + UTF8 const* in8 = (UTF8 const*)utf8_source; + UTF16* out16 = &utf16[0]; + ConversionResult ret = ConvertUTF8toUTF16(&in8, in8 + utf8_source_len + , &out16, out16 + utf16.size(), strictConversion); + + TEST_EQUAL(ret, conversionOK); + if (ret != conversionOK && utf8_source_len < 10) + { + for (char const* i = utf8_source; *i != 0; ++i) + fprintf(stderr, "%x ", UTF8(*i)); + } + + std::vector utf32(utf8_source_len); + UTF16 const* in16 = &utf16[0]; + UTF32* out32 = &utf32[0]; + ret = ConvertUTF16toUTF32(&in16, out16 + , &out32, out32 + utf32.size(), strictConversion); + + TEST_EQUAL(ret, conversionOK); + if (ret != conversionOK && utf8_source_len < 10) + { + for (char const* i = utf8_source; *i != 0; ++i) + fprintf(stderr, "%x ", UTF8(*i)); + } + + std::vector utf8(utf8_source_len); + UTF32 const* in32 = &utf32[0]; + UTF8* out8 = &utf8[0]; + ret = ConvertUTF32toUTF8(&in32, out32 + , &out8, out8 + utf8.size(), strictConversion); + + TEST_EQUAL(ret, conversionOK); + if (ret != conversionOK && utf8_source_len < 10) + { + for (char const* i = utf8_source; *i != 0; ++i) + fprintf(stderr, "%x ", UTF8(*i)); + } + + TEST_EQUAL(out8 - &utf8[0], utf8_source_len); + TEST_CHECK(std::equal(&utf8[0], out8, (UTF8 const*)utf8_source)); + } + + // utf8 -> utf32 -> utf16 -> utf8 + { + std::vector utf32(utf8_source_len); + UTF8 const* in8 = (UTF8 const*)utf8_source; + UTF32* out32 = &utf32[0]; + ConversionResult ret = ConvertUTF8toUTF32(&in8, in8 + utf8_source_len + , &out32, out32 + utf32.size(), strictConversion); + + TEST_EQUAL(ret, conversionOK); + if (ret != conversionOK && utf8_source_len < 10) + { + for (char const* i = utf8_source; *i != 0; ++i) + fprintf(stderr, "%x ", UTF8(*i)); + } + + std::vector utf16(utf8_source_len); + UTF32 const* in32 = &utf32[0]; + UTF16* out16 = &utf16[0]; + ret = ConvertUTF32toUTF16(&in32, out32 + , &out16, out16 + utf16.size(), strictConversion); + + TEST_EQUAL(ret, conversionOK); + if (ret != conversionOK && utf8_source_len < 10) + { + for (char const* i = utf8_source; *i != 0; ++i) + fprintf(stderr, "%x ", UTF8(*i)); + } + + std::vector utf8(utf8_source_len); + UTF16 const* in16 = &utf16[0]; + UTF8* out8 = &utf8[0]; + ret = ConvertUTF16toUTF8(&in16, out16 + , &out8, out8 + utf8.size(), strictConversion); + + TEST_EQUAL(ret, conversionOK); + if (ret != conversionOK && utf8_source_len < 10) + { + for (char const* i = utf8_source; *i != 0; ++i) + fprintf(stderr, "%x ", UTF8(*i)); + } + + TEST_EQUAL(out8 - &utf8[0], utf8_source_len); + TEST_CHECK(std::equal(&utf8[0], out8, (UTF8 const*)utf8_source)); + } +} + +void expect_error(char const* utf8, ConversionResult expect) +{ + UTF8 const* in8 = (UTF8 const*)utf8; + std::vector utf32(strlen(utf8)); + UTF32* out32 = &utf32[0]; + ConversionResult ret = ConvertUTF8toUTF32(&in8, in8 + strlen(utf8) + , &out32, out32 + utf32.size(), strictConversion); + + TEST_EQUAL(ret, expect); + if (ret != expect) + { + fprintf(stderr, "%d expected %d\n", ret, expect); + for (char const* i = utf8; *i != 0; ++i) + fprintf(stderr, "%x ", UTF8(*i)); + } + + in8 = (UTF8 const*)utf8; + std::vector utf16(strlen(utf8)); + UTF16* out16 = &utf16[0]; + ret = ConvertUTF8toUTF16(&in8, in8 + strlen(utf8) + , &out16, out16 + utf16.size(), strictConversion); + + TEST_EQUAL(ret, expect); + if (ret != expect) + { + fprintf(stderr, "%d expected %d\n", ret, expect); + for (char const* i = utf8; *i != 0; ++i) + fprintf(stderr, "%x ", UTF8(*i)); + } +} + +TORRENT_TEST(utf8) +{ + std::vector utf8_source; + error_code ec; + load_file(combine_path("..", "utf8_test.txt"), utf8_source, ec, 1000000); + if (ec) fprintf(stderr, "failed to open file: (%d) %s\n", ec.value() + , ec.message().c_str()); + TEST_CHECK(!ec); + + // test lower level conversions + + verify_transforms(&utf8_source[0], utf8_source.size()); + + verify_transforms("\xc3\xb0"); + verify_transforms("\xed\x9f\xbf"); + verify_transforms("\xee\x80\x80"); + verify_transforms("\xef\xbf\xbd"); + verify_transforms("\xf4\x8f\xbf\xbf"); + verify_transforms("\xf0\x91\x80\x80\x30"); + + // Unexpected continuation bytes + expect_error("\x80", sourceIllegal); + expect_error("\xbf", sourceIllegal); + + // Impossible bytes + // The following two bytes cannot appear in a correct UTF-8 string + expect_error("\xff", sourceExhausted); + expect_error("\xfe", sourceExhausted); + expect_error("\xff\xff\xfe\xfe", sourceExhausted); + + // Examples of an overlong ASCII character + expect_error("\xc0\xaf", sourceIllegal); + expect_error("\xe0\x80\xaf", sourceIllegal); + expect_error("\xf0\x80\x80\xaf", sourceIllegal); + expect_error("\xf8\x80\x80\x80\xaf ", sourceIllegal); + expect_error("\xfc\x80\x80\x80\x80\xaf", sourceIllegal); + + // Maximum overlong sequences + expect_error("\xc1\xbf", sourceIllegal); + expect_error("\xe0\x9f\xbf", sourceIllegal); + expect_error("\xf0\x8f\xbf\xbf", sourceIllegal); + expect_error("\xf8\x87\xbf\xbf\xbf", sourceIllegal); + expect_error("\xfc\x83\xbf\xbf\xbf\xbf", sourceIllegal); + + // Overlong representation of the NUL character + expect_error("\xc0\x80", sourceIllegal); + expect_error("\xe0\x80\x80", sourceIllegal); + expect_error("\xf0\x80\x80\x80", sourceIllegal); + expect_error("\xf8\x80\x80\x80\x80", sourceIllegal); + expect_error("\xfc\x80\x80\x80\x80\x80", sourceIllegal); + + // Single UTF-16 surrogates + expect_error("\xed\xa0\x80", sourceIllegal); + expect_error("\xed\xad\xbf", sourceIllegal); + expect_error("\xed\xae\x80", sourceIllegal); + expect_error("\xed\xaf\xbf", sourceIllegal); + expect_error("\xed\xb0\x80", sourceIllegal); + expect_error("\xed\xbe\x80", sourceIllegal); + expect_error("\xed\xbf\xbf", sourceIllegal); + + // Paired UTF-16 surrogates + expect_error("\xed\xa0\x80\xed\xb0\x80", sourceIllegal); + expect_error("\xed\xa0\x80\xed\xbf\xbf", sourceIllegal); + expect_error("\xed\xad\xbf\xed\xb0\x80", sourceIllegal); + expect_error("\xed\xad\xbf\xed\xbf\xbf", sourceIllegal); + expect_error("\xed\xae\x80\xed\xb0\x80", sourceIllegal); + expect_error("\xed\xae\x80\xed\xbf\xbf", sourceIllegal); + expect_error("\xed\xaf\xbf\xed\xb0\x80", sourceIllegal); + expect_error("\xed\xaf\xbf\xed\xbf\xbf", sourceIllegal); + + // test higher level conversions + + std::string utf8; + std::copy(utf8_source.begin(), utf8_source.end(), std::back_inserter(utf8)); + + std::wstring wide; + utf8_conv_result_t ret = utf8_wchar(utf8, wide); + TEST_EQUAL(ret, conversion_ok); + + std::string identity; + ret = wchar_utf8(wide, identity); + TEST_EQUAL(ret, conversion_ok); + + TEST_EQUAL(utf8, identity); +} + +TORRENT_TEST(invalid_encoding) +{ + // thest invalid utf8 encodings. just treat it as "Latin-1" + boost::uint8_t const test_string[] = { + 0xd2, 0xe5, 0xf0, 0xea, 0xf1, 0x20, 0xe8, 0x20, 0xca, 0xe0, 0xe9, 0xea, + 0xee, 0xf1, 0x2e, 0x32, 0x30, 0x31, 0x34, 0x2e, 0x42, 0x44, 0x52, 0x69, + 0x70, 0x2e, 0x31, 0x30, 0x38, 0x30, 0x70, 0x2e, 0x6d, 0x6b, 0x76, 0x00 + }; + std::wstring wide; + utf8_wchar((const char*)test_string, wide); + + std::wstring cmp_wide; + std::copy(test_string, test_string + sizeof(test_string) - 1, + std::back_inserter(cmp_wide)); + TEST_CHECK(wide == cmp_wide); +} + diff --git a/test/test_utils.cpp b/test/test_utils.cpp new file mode 100644 index 0000000..5196347 --- /dev/null +++ b/test/test_utils.cpp @@ -0,0 +1,54 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test_utils.hpp" +#include "libtorrent/time.hpp" + +namespace libtorrent +{ + char const* time_now_string() + { + static const time_point start = clock_type::now(); + static char ret[200]; + int t = total_milliseconds(clock_type::now() - start); + int h = t / 1000 / 60 / 60; + t -= h * 60 * 60 * 1000; + int m = t / 1000 / 60; + t -= m * 60 * 1000; + int s = t / 1000; + t -= s * 1000; + int ms = t; + snprintf(ret, sizeof(ret), "%02d:%02d:%02d.%03d", h, m, s, ms); + return ret; + } +} + diff --git a/test/test_utils.hpp b/test/test_utils.hpp new file mode 100644 index 0000000..3f2cdd7 --- /dev/null +++ b/test/test_utils.hpp @@ -0,0 +1,44 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TEST_UTILS_HPP +#define TEST_UTILS_HPP + +#include "test.hpp" + +namespace libtorrent +{ + EXPORT char const* time_now_string(); +} + +#endif + diff --git a/test/test_utp.cpp b/test/test_utp.cpp new file mode 100644 index 0000000..767e1a1 --- /dev/null +++ b/test/test_utp.cpp @@ -0,0 +1,152 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/session.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/thread.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/file.hpp" +#include +#include + +#include "test.hpp" +#include "setup_transfer.hpp" +#include +#include + +using namespace libtorrent; +namespace lt = libtorrent; +using boost::tuples::ignore; + +void test_transfer() +{ + // in case the previous run was terminated + error_code ec; + remove_all("./tmp1_utp", ec); + remove_all("./tmp2_utp", ec); + + // these are declared before the session objects + // so that they are destructed last. This enables + // the sessions to destruct in parallel + session_proxy p1; + session_proxy p2; + + const int mask = alert::all_categories + & ~(alert::progress_notification + | alert::performance_warning + | alert::stats_notification); + + 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_int(settings_pack::alert_mask, mask); + pack.set_int(settings_pack::out_enc_policy, settings_pack::pe_disabled); + pack.set_int(settings_pack::in_enc_policy, settings_pack::pe_disabled); + pack.set_bool(settings_pack::enable_outgoing_tcp, false); + pack.set_bool(settings_pack::enable_incoming_tcp, false); + pack.set_bool(settings_pack::announce_to_all_trackers, true); + pack.set_bool(settings_pack::announce_to_all_tiers, true); + pack.set_bool(settings_pack::prefer_udp_trackers, false); + pack.set_int(settings_pack::min_reconnect_time, 1); + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:48885"); + lt::session ses1(pack); + + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:49885"); + lt::session ses2(pack); + + torrent_handle tor1; + torrent_handle tor2; + + create_directory("./tmp1_utp", ec); + std::ofstream file("./tmp1_utp/temporary"); + boost::shared_ptr t = ::create_torrent(&file, "temporary", 128 * 1024, 6, false); + file.close(); + + // for performance testing + add_torrent_params atp; + atp.flags &= ~add_torrent_params::flag_paused; + atp.flags &= ~add_torrent_params::flag_auto_managed; +// atp.storage = &disabled_storage_constructor; + + // test using piece sizes smaller than 16kB + boost::tie(tor1, tor2, ignore) = setup_transfer(&ses1, &ses2, 0 + , true, false, true, "_utp", 0, &t, false, &atp); + +#ifdef TORRENT_USE_VALGRIND + const int timeout = 16; +#else + const int timeout = 8; +#endif + + for (int i = 0; i < timeout; ++i) + { + print_alerts(ses1, "ses1", true, true, true); + print_alerts(ses2, "ses2", true, true, true); + + test_sleep(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(); +} + +TORRENT_TEST(utp) +{ + using namespace libtorrent; + + test_transfer(); + + error_code ec; + remove_all("./tmp1_utp", ec); + remove_all("./tmp2_utp", ec); +} + diff --git a/test/test_web_seed.cpp b/test/test_web_seed.cpp new file mode 100644 index 0000000..e3ba37d --- /dev/null +++ b/test/test_web_seed.cpp @@ -0,0 +1,52 @@ +/* + +Copyright (c) 2008-2014, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "web_seed_suite.hpp" + +using namespace libtorrent; + +const int proxy = libtorrent::settings_pack::none; + +#ifdef TORRENT_USE_OPENSSL +TORRENT_TEST(web_seed_ssl) +{ + run_http_suite(proxy, "https", false); +} +#endif + +TORRENT_TEST(web_seed) +{ + run_http_suite(proxy, "http", false); +} + diff --git a/test/test_web_seed_ban.cpp b/test/test_web_seed_ban.cpp new file mode 100644 index 0000000..533b9f0 --- /dev/null +++ b/test/test_web_seed_ban.cpp @@ -0,0 +1,62 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "web_seed_suite.hpp" + +using namespace libtorrent; + +const int proxy = libtorrent::settings_pack::none; + +#ifdef TORRENT_USE_OPENSSL +TORRENT_TEST(http_seed_ssl) +{ + run_http_suite(proxy, "https", 0, 0, 1); +} + +TORRENT_TEST(url_seed_ssl) +{ + run_http_suite(proxy, "https", 1, 0, 1); +} +#endif + +TORRENT_TEST(http_seed) +{ + run_http_suite(proxy, "http", 0, 0, 1); +} + +TORRENT_TEST(url_seed) +{ + run_http_suite(proxy, "http", 1, 0, 1); +} + diff --git a/test/test_web_seed_chunked.cpp b/test/test_web_seed_chunked.cpp new file mode 100644 index 0000000..77afe52 --- /dev/null +++ b/test/test_web_seed_chunked.cpp @@ -0,0 +1,62 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "web_seed_suite.hpp" + +using namespace libtorrent; + +const int proxy = libtorrent::settings_pack::none; + +#ifdef TORRENT_USE_OPENSSL +TORRENT_TEST(web_seed_ssl) +{ + run_http_suite(proxy, "https", 0, 1, 0); +} + +TORRENT_TEST(url_seed_ssl) +{ + run_http_suite(proxy, "https", 1, 1, 0); +} +#endif + +TORRENT_TEST(web_seed) +{ + run_http_suite(proxy, "http", 0, 1, 0); +} + +TORRENT_TEST(url_seed) +{ + run_http_suite(proxy, "http", 1, 1, 0); +} + diff --git a/test/test_web_seed_http.cpp b/test/test_web_seed_http.cpp new file mode 100644 index 0000000..802bd75 --- /dev/null +++ b/test/test_web_seed_http.cpp @@ -0,0 +1,62 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "web_seed_suite.hpp" + +using namespace libtorrent; + +const int proxy = libtorrent::settings_pack::http; + +TORRENT_TEST(web_seed_http) +{ + run_http_suite(proxy, "http", false); +} + +TORRENT_TEST(url_seed_http) +{ + run_http_suite(proxy, "http", true); +} + +#ifdef TORRENT_USE_OPENSSL +TORRENT_TEST(web_seed_https) +{ + run_http_suite(proxy, "https", false); +} + +TORRENT_TEST(url_seed_https) +{ + run_http_suite(proxy, "https", true); +} +#endif + diff --git a/test/test_web_seed_http_pw.cpp b/test/test_web_seed_http_pw.cpp new file mode 100644 index 0000000..067f5e3 --- /dev/null +++ b/test/test_web_seed_http_pw.cpp @@ -0,0 +1,62 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "web_seed_suite.hpp" + +using namespace libtorrent; + +const int proxy = libtorrent::settings_pack::http_pw; + +TORRENT_TEST(web_seed_http_pw) +{ + run_http_suite(proxy, "http", false); +} + +TORRENT_TEST(url_seed_http_pw) +{ + run_http_suite(proxy, "http", true); +} + +#ifdef TORRENT_USE_OPENSSL +TORRENT_TEST(web_seed_http_pw_ssl) +{ + run_http_suite(proxy, "https", false); +} + +TORRENT_TEST(url_seed_http_pw_ssl) +{ + run_http_suite(proxy, "https", true); +} +#endif + diff --git a/test/test_web_seed_redirect.cpp b/test/test_web_seed_redirect.cpp new file mode 100644 index 0000000..860820e --- /dev/null +++ b/test/test_web_seed_redirect.cpp @@ -0,0 +1,105 @@ +/* + +Copyright (c) 2008-2014, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "web_seed_suite.hpp" +#include "settings.hpp" +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/torrent_info.hpp" + +using namespace libtorrent; + +TORRENT_TEST(web_seed_redirect) +{ + using namespace libtorrent; + + error_code ec; + + file_storage fs; + int piece_size = 0x4000; + + char random_data[16000]; + std::generate(random_data, random_data + sizeof(random_data), random_byte); + file f("test_file", file::write_only, ec); + if (ec) + { + fprintf(stderr, "failed to create file \"test_file\": (%d) %s\n" + , ec.value(), ec.message().c_str()); + TEST_ERROR("failed to create file"); + return; + } + file::iovec_t b = { random_data, size_t(16000)}; + f.writev(0, &b, 1, ec); + fs.add_file("test_file", 16000); + + int port = start_web_server(); + + // generate a torrent with pad files to make sure they + // are not requested web seeds + libtorrent::create_torrent t(fs, piece_size, 0x4000); + + char tmp[512]; + 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) + { + fprintf(stderr, "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()); + boost::shared_ptr torrent_file(new torrent_info(&buf[0] + , buf.size(), ec)); + + { + settings_pack p = settings(); + p.set_int(settings_pack::max_queued_disk_bytes, 256 * 1024); + p.set_int(settings_pack::alert_mask, ~(alert::progress_notification | alert::stats_notification)); + libtorrent::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, 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..0ae47be --- /dev/null +++ b/test/test_web_seed_socks4.cpp @@ -0,0 +1,62 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "web_seed_suite.hpp" + +using namespace libtorrent; + +const int proxy = libtorrent::settings_pack::socks4; + +#ifdef TORRENT_USE_OPENSSL +TORRENT_TEST(http_seed_ssl) +{ + run_http_suite(proxy, "https", 0); +} + +TORRENT_TEST(url_seed_ssl) +{ + run_http_suite(proxy, "https", 1); +} +#endif + +TORRENT_TEST(http_seed) +{ + run_http_suite(proxy, "http", 0); +} + +TORRENT_TEST(url_seed) +{ + run_http_suite(proxy, "http", 1); +} + diff --git a/test/test_web_seed_socks5.cpp b/test/test_web_seed_socks5.cpp new file mode 100644 index 0000000..e7a3d57 --- /dev/null +++ b/test/test_web_seed_socks5.cpp @@ -0,0 +1,62 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "web_seed_suite.hpp" + +using namespace libtorrent; + +const int proxy = libtorrent::settings_pack::socks5; + +#ifdef TORRENT_USE_OPENSSL +TORRENT_TEST(http_seed_ssl) +{ + run_http_suite(proxy, "https", 0); +} + +TORRENT_TEST(url_seed_ssl) +{ + run_http_suite(proxy, "https", 1); +} +#endif + +TORRENT_TEST(http_seed) +{ + run_http_suite(proxy, "http", 0); +} + +TORRENT_TEST(url_seed) +{ + run_http_suite(proxy, "http", 1); +} + diff --git a/test/test_web_seed_socks5_no_peers.cpp b/test/test_web_seed_socks5_no_peers.cpp new file mode 100644 index 0000000..802d2ad --- /dev/null +++ b/test/test_web_seed_socks5_no_peers.cpp @@ -0,0 +1,52 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "web_seed_suite.hpp" + +using namespace libtorrent; + +const int proxy = libtorrent::settings_pack::socks5; + +TORRENT_TEST(web_seed_socks5_no_peers_ssl) +{ +#ifdef TORRENT_USE_OPENSSL + run_http_suite(proxy, "https", false, false, false, false, false, false); +#endif +} + +TORRENT_TEST(web_seed_socks5_no_peers) +{ + run_http_suite(proxy, "http", false, false, false, false, false, false); +} + diff --git a/test/test_web_seed_socks5_pw.cpp b/test/test_web_seed_socks5_pw.cpp new file mode 100644 index 0000000..6e2f5c5 --- /dev/null +++ b/test/test_web_seed_socks5_pw.cpp @@ -0,0 +1,62 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "web_seed_suite.hpp" + +using namespace libtorrent; + +const int proxy = libtorrent::settings_pack::socks5_pw; + +#ifdef TORRENT_USE_OPENSSL +TORRENT_TEST(http_seed_ssl) +{ + run_http_suite(proxy, "https", 0); +} + +TORRENT_TEST(url_seed_ssl) +{ + run_http_suite(proxy, "https", 1); +} +#endif + +TORRENT_TEST(http_seed) +{ + run_http_suite(proxy, "http", 0); +} + +TORRENT_TEST(url_seed) +{ + run_http_suite(proxy, "http", 1); +} + diff --git a/test/test_xml.cpp b/test/test_xml.cpp new file mode 100644 index 0000000..5a917ec --- /dev/null +++ b/test/test_xml.cpp @@ -0,0 +1,385 @@ +/* + +Copyright (c) 2012, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/xml_parse.hpp" +#include "libtorrent/upnp.hpp" +#include "test.hpp" +#include + +char upnp_xml[] = +"" +"" +"1" +"0" +"" +"http://192.168.0.1:5678" +"" +"" +"urn:schemas-upnp-org:device:InternetGatewayDevice:1" +"" +"http://192.168.0.1:80" +"D-Link Router" +"D-Link" +"http://www.dlink.com" +"Internet Access Router" +"D-Link Router" +"uuid:upnp-InternetGatewayDevice-1_0-12345678900001" +"123456789001" +"" +"" +"urn:schemas-upnp-org:service:Layer3Forwarding:1" +"urn:upnp-org:serviceId:L3Forwarding1" +"/Layer3Forwarding" +"/Layer3Forwarding" +"/Layer3Forwarding.xml" +"" +"" +"" +"" +"urn:schemas-upnp-org:device:WANDevice:1" +"WANDevice" +"D-Link" +"http://www.dlink.com" +"Internet Access Router" +"D-Link Router" +"1" +"http://support.dlink.com" +"12345678900001" +"uuid:upnp-WANDevice-1_0-12345678900001" +"123456789001" +"" +"" +"" +"urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1" +"" +"urn:upnp-org:serviceId:WANCommonInterfaceConfig" +"/WANCommonInterfaceConfig" +"/WANCommonInterfaceConfig" +"/WANCommonInterfaceConfig.xml" +"" +"" +"" +"" +"urn:schemas-upnp-org:device:WANConnectionDevice:1" +"WAN Connection Device" +"D-Link" +"http://www.dlink.com" +"Internet Access Router" +"D-Link Router" +"1" +"http://support.dlink.com" +"12345678900001" +"uuid:upnp-WANConnectionDevice-1_0-12345678900001" +"123456789001" +"" +"" +"urn:schemas-upnp-org:service:WANIPConnection:1" +"urn:upnp-org:serviceId:WANIPConnection" +"/WANIPConnection" +"/WANIPConnection" +"/WANIPConnection.xml" +"" +"" +"" +"" +"" +"" +"" +""; + +char upnp_xml2[] = +"" +"" +"1" +"0" +"" +"http://192.168.1.1:49152" +"" +"" +"urn:schemas-upnp-org:device:InternetGatewayDevice:1" +"" +"LINKSYS WAG200G Gateway" +"LINKSYS" +"http://www.linksys.com" +"LINKSYS WAG200G Gateway" +"Wireless-G ADSL Home Gateway" +"WAG200G" +"http://www.linksys.com" +"123456789" +"uuid:8d401597-1dd2-11b2-a7d4-001ee5947cac" +"WAG200G" +"" +"" +"urn:schemas-upnp-org:service:Layer3Forwarding:1" +"urn:upnp-org:serviceId:L3Forwarding1" +"/upnp/control/L3Forwarding1" +"/upnp/event/L3Forwarding1" +"/l3frwd.xml" +"" +"" +"" +"" +"urn:schemas-upnp-org:device:WANDevice:1" +"WANDevice" +"LINKSYS" +"http://www.linksys.com/" +"Residential Gateway" +"Internet Connection Sharing" +"1" +"http://www.linksys.com/" +"0000001" +"uuid:8d401596-1dd2-11b2-a7d4-001ee5947cac" +"WAG200G" +"" +"" +"" +"urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1" +"" +"urn:upnp-org:serviceId:WANCommonIFC1" +"/upnp/control/WANCommonIFC1" +"/upnp/event/WANCommonIFC1" +"/cmnicfg.xml" +"" +"" +"" +"" +"urn:schemas-upnp-org:device:WANConnectionDevice:1" +"WANConnectionDevice" +"LINKSYS" +"http://www.linksys.com/" +"Residential Gateway" +"Internet Connection Sharing" +"1" +"http://www.linksys.com/" +"0000001" +"uuid:8d401597-1dd2-11b2-a7d3-001ee5947cac" +"WAG200G" +"" +"" +"" +"urn:schemas-upnp-org:service:WANEthernetLinkConfig:1" +"" +"urn:upnp-org:serviceId:WANEthLinkC1" +"/upnp/control/WANEthLinkC1" +"/upnp/event/WANEthLinkC1" +"/wanelcfg.xml" +"" +"" +"urn:schemas-upnp-org:service:WANPPPConnection:1" +"urn:upnp-org:serviceId:WANPPPConn1" +"/upnp/control/WANPPPConn1" +"/upnp/event/WANPPPConn1" +"/pppcfg.xml" +"" +"" +"" +"" +"" +"" +"urn:schemas-upnp-org:device:LANDevice:1" +"LANDevice" +"LINKSYS" +"http://www.linksys.com/" +"Residential Gateway" +"Residential Gateway" +"1" +"http://www.linksys.com/" +"0000001" +"uuid:8d401596-1dd2-11b2-a7d3-001ee5947cac" +"WAG200G" +"" +"" +"" +"urn:schemas-upnp-org:service:LANHostConfigManagement:1" +"" +"urn:upnp-org:serviceId:LANHostCfg1" +"/upnp/control/LANHostCfg1" +"/upnp/event/LANHostCfg1" +"/lanhostc.xml" +"" +"" +"" +"" +"http://192.168.1.1/index.htm" +"" +""; + +using namespace libtorrent; + +void parser_callback(std::string& out, int token, char const* s, int len + , char const* val, int val_len) +{ + 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, len); + if (token == xml_attribute) + { + TEST_CHECK(val != NULL); + out += "V"; + out.append(val, val_len); + } + else + { + TEST_CHECK(val == NULL); + } +} + +void test_parse(char const* in, char const* expected) +{ + std::string out; + xml_parse(in, in + strlen(in), boost::bind(&parser_callback + , boost::ref(out), _1, _2, _3, _4, _5)); + fprintf(stderr, "in: %s\n out: %s\nexpected: %s\n" + , in, out.c_str(), expected); + TEST_EQUAL(out, expected); +} + +TORRENT_TEST(upnp_parser1) +{ + parse_state xml_s; + xml_parse(upnp_xml, upnp_xml + sizeof(upnp_xml) + , boost::bind(&find_control_url, _1, _2, _3, boost::ref(xml_s))); + + std::cerr << "namespace " << xml_s.service_type << std::endl; + std::cerr << "url_base: " << xml_s.url_base << std::endl; + std::cerr << "control_url: " << xml_s.control_url << std::endl; + std::cerr << "model: " << xml_s.model << std::endl; + TEST_EQUAL(xml_s.url_base, "http://192.168.0.1:5678"); + TEST_EQUAL(xml_s.control_url, "/WANIPConnection"); + TEST_EQUAL(xml_s.model, "D-Link Router"); +} + +TORRENT_TEST(upnp_parser2) +{ + parse_state xml_s; + xml_parse(upnp_xml2, upnp_xml2 + sizeof(upnp_xml2) + , boost::bind(&find_control_url, _1, _2, _3, boost::ref(xml_s))); + + std::cerr << "namespace " << xml_s.service_type << std::endl; + std::cerr << "url_base: " << xml_s.url_base << std::endl; + std::cerr << "control_url: " << xml_s.control_url << std::endl; + std::cerr << "model: " << xml_s.model << std::endl; + TEST_EQUAL(xml_s.url_base, "http://192.168.1.1:49152"); + TEST_EQUAL(xml_s.control_url, "/upnp/control/WANPPPConn1"); + TEST_EQUAL(xml_s.model, "Wireless-G ADSL Home Gateway"); +} + +TORRENT_TEST(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 +#include "setup_transfer.hpp" + +#include + +using namespace libtorrent; +namespace lt = libtorrent; + +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, boost::shared_ptr torrent_file + , int proxy, int port, char const* protocol, bool url_seed + , bool chunked_encoding, bool test_ban, bool keepalive, bool proxy_peers) +{ + using namespace libtorrent; + + 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"}; + + fprintf(stderr, "\n\n ==== TESTING === proxy: %s ==== protocol: %s " + "==== seed: %s === transfer-encoding: %s === corruption: %s " + "==== keepalive: %s\n\n\n" + , test_name[proxy], protocol, url_seed ? "URL seed" : "HTTP seed" + , chunked_encoding ? "chunked": "none", test_ban ? "yes" : "no" + , keepalive ? "yes" : "no"); + + int proxy_port = 0; + if (proxy) + { + proxy_port = start_proxy(proxy); + if (proxy_port < 0) + { + fprintf(stderr, "failed to start proxy"); + return; + } + settings_pack pack; + pack.set_str(settings_pack::proxy_hostname, "127.0.0.1"); + pack.set_str(settings_pack::proxy_username, "testuser"); + pack.set_str(settings_pack::proxy_password, "testpass"); + pack.set_int(settings_pack::proxy_type, (settings_pack::proxy_type_t)proxy); + pack.set_int(settings_pack::proxy_port, proxy_port); + pack.set_bool(settings_pack::proxy_peer_connections, proxy_peers); + ses.apply_settings(pack); + } + else + { + settings_pack pack; + pack.set_str(settings_pack::proxy_hostname, ""); + pack.set_str(settings_pack::proxy_username, ""); + pack.set_str(settings_pack::proxy_password, ""); + pack.set_int(settings_pack::proxy_type, settings_pack::none); + pack.set_int(settings_pack::proxy_port, 0); + pack.set_bool(settings_pack::proxy_peer_connections, proxy_peers); + ses.apply_settings(pack); + } + + add_torrent_params p; + p.flags &= ~add_torrent_params::flag_paused; + p.flags &= ~add_torrent_params::flag_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 |= add_torrent_params::flag_sequential_download; + p.ti = torrent_file; + p.save_path = save_path; + torrent_handle th = ses.add_torrent(p, ec); + 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 boost::int64_t total_size = torrent_file->total_size(); + + file_storage const& fs = torrent_file->files(); + int pad_file_size = 0; + for (int i = 0; i < fs.num_files(); ++i) + { + if (fs.file_flags(i) & file_storage::flag_pad_file) + pad_file_size += 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, NULL); + print_alerts(ses, " >> ses", test_ban, false, false, &on_alert); + + if (test_ban && th.url_seeds().empty() && th.http_seeds().empty()) + { + fprintf(stderr, "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) + { + fprintf(stderr, "SEEDING\n"); + fprintf(stderr, "session.payload: %d session.redundant: %d\n" + , int(cnt["net.recv_payload_bytes"]), int(cnt["net.recv_redundant_bytes"])); + fprintf(stderr, "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; + + test_sleep(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 senr corrupt data and we banned it, we probably didn't + // end up using all the cache anyway + torrent_status st = th.status(); + TEST_EQUAL(st.is_seeding, true); + + if (st.is_seeding) + { + // we need to sleep here a bit to let the session sync with the torrent stats + // commented out because it takes such a long time + for (int i = 0; i < 50; ++i) + { + cnt = get_counters(ses); + if (cnt["disk.read_cache_blocks"] + == (torrent_file->total_size() + 0x3fff) / 0x4000 + && cnt["disk.disk_blocks_in_use"] + == (torrent_file->total_size() + 0x3fff) / 0x4000) + break; + fprintf(stderr, "cache_size: %d/%d\n", int(cnt["disk.read_cache_blocks"]) + , int(cnt["disk.disk_blocks_in_use"])); + test_sleep(100); + } + TEST_CHECK(std::abs(int(cnt["disk.disk_blocks_in_use"] + - (torrent_file->total_size() + 0x3fff) / 0x4000)) <= 2); + } + } + + std::cerr << "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, true, false, &on_alert, true); + + if (!test_ban) + { + file_storage const& fs = torrent_file->files(); + for (int i = 0; i < fs.num_files(); ++i) + { + bool const expect = !fs.pad_file_at(i); + std::string file_path = combine_path(save_path, fs.file_path(i)); + fprintf(stderr, "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 libtorrent; + + std::string save_path = "web_seed"; + save_path += proxy_name[proxy]; + + error_code ec; + int const port = start_web_server(strcmp(protocol, "https") == 0, chunked_encoding, keepalive); + + std::vector test_cases; + + if (test_url_seed) + { + char url[512]; + snprintf(url, sizeof(url), ("%s://127.0.0.1:%d/" + save_path).c_str(), protocol, port); + fprintf(stderr, "testing: %s\n", url); + + create_directories(combine_path(save_path, "torrent_dir"), ec); + + torrent_args args; + + // test case 1 + test_cases.push_back(torrent_args().file("0").file("5,padfile").file("11") + .file("16000").file("368,padfile") + .file("16384,padfile").file("16384,padfile").file("17").file("10") + .file("8000").file("8000").file("1").file("1").file("1").file("1") + .file("1").file("100").file("0").file("1").file("1").file("1") + .file("100").file("1").file("1").file("1").file("1").file("1,padfile") + .file("1,padfile").file("1,padfile").file("1").file("0").file("0") + .file("0").file("1").file("13").file("65000").file("34").file("75") + .file("2").file("30").file("400").file("500").file("23000") + .file("900").file("43000").file("400").file("4300").file("6") + .file("4,padfile") + .name("torrent_dir") + .url_seed(url)); + + // test case 2 (the end of the torrent are padfiles) + test_cases.push_back(torrent_args() + .file("0,padfile") + .file("11") + .file("5") + .file("16000") + .file("368,padfile") + .file("16384,padfile") + .name("torrent_dir") + .url_seed(url)); + + // test case 3 (misaligned) + test_cases.push_back(torrent_args() + .file("16383") + .file("11") + .file("5") + .file("16000") + .name("torrent_dir") + .url_seed(url)); + + // test case 4 (a full piece padfile) + test_cases.push_back(torrent_args() + .file("32768,padfile") + .file("16000") + .file("11") + .file("5") + .name("torrent_dir") + .url_seed(url)); + + // test case 5 (properly aligned padfile) + test_cases.push_back(torrent_args() + .file("32760") + .file("8,padfile") + .file("32760") + .file("8") + .file("32700") + .file("68,padfile") + .file("32000") + .name("torrent_dir") + .url_seed(url)); + + snprintf(url, sizeof(url), ("%s://127.0.0.1:%d/" + save_path + "/test-single-file").c_str(), protocol, port); + + // 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]; + snprintf(url, sizeof(url), "%s://127.0.0.1:%d/%s/seed", protocol, port, save_path.c_str()); + fprintf(stderr, "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)); + } + + for (int a = 0; a < int(test_cases.size()); ++a) + { + fprintf(stderr, "\n\n ==== test case %d ====\n\n\n", a); + + boost::shared_ptr torrent_file = make_test_torrent(test_cases[a]); + + // 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) + { + fprintf(stderr, "error creating hashes for test torrent: %s\n" + , ec.message().c_str()); + TEST_CHECK(false); + return 0; + } + + { + const int mask = alert::all_categories + & ~(alert::progress_notification + | alert::performance_warning + | alert::stats_notification); + + settings_pack pack; + pack.set_int(settings_pack::max_queued_disk_bytes, 256 * 1024); + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:51000"); + pack.set_int(settings_pack::max_retry_port_bind, 1000); + pack.set_int(settings_pack::alert_mask, mask); + 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); + libtorrent::session ses(pack, 0); + + test_transfer(ses, torrent_file, proxy, port, protocol, test_url_seed + , chunked_encoding, test_ban, keepalive, proxy_peers); + + if (test_url_seed && test_rename) + { + torrent_file->rename_file(0, combine_path(save_path, combine_path("torrent_dir", "renamed_test1"))); + test_transfer(ses, torrent_file, 0, port, 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..7086cbd --- /dev/null +++ b/test/web_seed_suite.hpp @@ -0,0 +1,44 @@ +/* + +Copyright (c) 2008-2013, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" + +int EXPORT run_http_suite(int proxy, char const* protocol + , bool test_url_seed, bool chunked_encoding = false, bool test_ban = false + , bool keepalive = true, bool test_rename = false, bool proxy_peers = true); + +void EXPORT test_transfer(libtorrent::session& ses + , boost::shared_ptr torrent_file + , int proxy = 0, int port = 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 100755 index 0000000..6f92032 --- /dev/null +++ b/test/web_server.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python + +import BaseHTTPServer +import SimpleHTTPServer +import sys +import os +import ssl +import gzip +import base64 + +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: + pass + +class http_server_with_timeout(BaseHTTPServer.HTTPServer): + allow_reuse_address = True + timeout = 190 + + def handle_timeout(self): + raise Exception('timeout') + +class http_handler(SimpleHTTPServer.SimpleHTTPRequestHandler): + + def do_GET(s): + + print 'INCOMING-REQUEST: ', s.requestline + print s.headers + + global chunked_encoding + global keepalive + + # if the request contains the hostname and port. strip it + if s.path.startswith('http://') or s.path.startswith('https://'): + s.path = s.path[8:] + s.path = s.path[s.path.find('/'):] + + file_path = os.path.normpath(s.path) + print file_path + print s.path + + if s.path == '/password_protected': + passed = False + if 'Authorization' in s.headers: + auth = s.headers['Authorization'] + passed = auth == 'Basic %s' % base64.b64encode('testuser:testpass') + + if not passed: + s.send_response(401) + s.send_header("Connection", "close") + s.end_headers() + return + + s.path = '/test_file' + file_path = os.path.normpath('/test_file') + + if s.path == '/redirect': + s.send_response(301) + s.send_header("Location", "/test_file") + s.send_header("Connection", "close") + s.end_headers() + elif s.path == '/infinite_redirect': + s.send_response(301) + s.send_header("Location", "/infinite_redirect") + s.send_header("Connection", "close") + s.end_headers() + elif s.path == '/relative/redirect': + s.send_response(301) + s.send_header("Location", "../test_file") + s.send_header("Connection", "close") + s.end_headers() + elif s.path.startswith('/announce'): + s.send_response(200) + response = 'd8:intervali1800e8:completei1e10:incompletei1e' + \ + '5:peers12:AAAABBCCCCDD' + \ + '6:peers618:EEEEEEEEEEEEEEEEFF' + \ + 'e' + s.send_header("Content-Length", "%d" % len(response)) + s.send_header("Connection", "close") + s.end_headers() + s.wfile.write(response) + elif os.path.split(s.path)[1].startswith('seed?'): + query = s.path[6:] + args_raw = query.split('&') + args = {} + for a in args_raw: + kvp = a.split('=') + args[kvp[0]] = kvp[1] + piece = int(args['piece']) + ranges = args['ranges'].split('-') + + filename = '' + try: + filename = os.path.normpath(s.path[1:s.path.find('seed?') + 4]) + print 'filename = %s' % filename + f = open(filename, 'rb') + f.seek(piece * 32 * 1024 + int(ranges[0])) + data = f.read(int(ranges[1]) - int(ranges[0]) + 1) + f.close() + + s.send_response(200) + print 'sending %d bytes' % len(data) + s.send_header("Content-Length", "%d" % len(data)) + s.end_headers() + s.wfile.write(data); + except Exception, e: + print 'FILE ERROR: ', filename, e + s.send_response(404) + s.send_header("Content-Length", "0") + s.end_headers() + else: + filename = '' + try: + filename = os.path.normpath(file_path[1:]) + # serve file by invoking default handler + f = open(filename, 'rb') + size = int(os.stat(filename).st_size) + start_range = 0 + end_range = size + if 'Range' in s.headers: + s.send_response(206) + st, e = s.headers['range'][6:].split('-', 1) + sl = len(st) + el = len(e) + if sl > 0: + start_range = int(st) + if el > 0: + end_range = int(e) + 1 + elif el > 0: + ei = int(e) + if ei < size: + start_range = size - ei + s.send_header('Content-Range', 'bytes ' + str(start_range) \ + + '-' + str(end_range - 1) + '/' + str(size)) + else: + s.send_response(200) + s.send_header('Accept-Ranges', 'bytes') + if chunked_encoding: + s.send_header('Transfer-Encoding', 'chunked') + s.send_header('Content-Length', end_range - start_range) + if filename.endswith('.gz'): + s.send_header('Content-Encoding', 'gzip') + if not keepalive: + s.send_header("Connection", "close") + try: + s.request.shutdown(); + except Exception, e: + print 'Failed to shutdown read-channel of socket: ', e + + s.end_headers() + + f.seek(start_range) + length = end_range - start_range + while length > 0: + to_send = min(length, 0x900) + if chunked_encoding: + s.wfile.write('%x\r\n' % to_send) + data = f.read(to_send) + print 'read %d bytes' % to_send + s.wfile.write(data) + if chunked_encoding: + s.wfile.write('\r\n') + length -= to_send + print 'sent %d bytes (%d bytes left)' % (len(data), length) + if chunked_encoding: + s.wfile.write('0\r\n\r\n') + except Exception, e: + print 'FILE ERROR: ', filename, e + s.send_response(404) + s.send_header("Content-Length", "0") + s.end_headers() + +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' + + 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/Jamfile b/tools/Jamfile new file mode 100644 index 0000000..bf4c291 --- /dev/null +++ b/tools/Jamfile @@ -0,0 +1,39 @@ +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 + @link_libtorrent + : default-build + static + ; + +exe fuzz_torrent : fuzz_torrent.cpp ; +exe parse_access_log : parse_access_log.cpp ; +exe dht : dht_put.cpp : ../ed25519/src ; + diff --git a/tools/Makefile.am b/tools/Makefile.am new file mode 100644 index 0000000..074aaa8 --- /dev/null +++ b/tools/Makefile.am @@ -0,0 +1,31 @@ +tool_programs = \ + fuzz_torrent + +if ENABLE_EXAMPLES +bin_PROGRAMS = $(tool_programs) +endif + +EXTRA_PROGRAMS = $(tool_programs) +EXTRA_DIST = Jamfile \ + parse_bandwidth_log.py \ + parse_buffer_log.py \ + parse_dht_log.py \ + parse_dht_rtt.py \ + parse_dht_stats.py \ + parse_disk_buffer_log.py\ + parse_disk_log.py \ + parse_memory_log.py \ + parse_peer_log.py \ + parse_sample.py \ + parse_session_stats.py \ + parse_utp_log.py + +fuzz_torrent_SOURCES = fuzz_torrent.cpp + +LDADD = $(top_builddir)/src/libtorrent-rasterbar.la + +AM_CPPFLAGS = -ftemplate-depth-50 -I$(top_srcdir)/include @DEBUGFLAGS@ + +AM_LDFLAGS = @BOOST_SYSTEM_LIB@ +#AM_LDFLAGS = $(LDFLAGS) @BOOST_SYSTEM_LIB@ @OPENSSL_LDFLAGS@ @OPENSSL_LIBS@ +#AM_LDFLAGS = @OPENSSL_LDFLAGS@ diff --git a/tools/Makefile.in b/tools/Makefile.in new file mode 100644 index 0000000..45484d9 --- /dev/null +++ b/tools/Makefile.in @@ -0,0 +1,704 @@ +# Makefile.in generated by automake 1.15 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2014 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +target_triplet = @target@ +@ENABLE_EXAMPLES_TRUE@bin_PROGRAMS = $(am__EXEEXT_1) +EXTRA_PROGRAMS = $(am__EXEEXT_1) +subdir = tools +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_boost_base.m4 \ + $(top_srcdir)/m4/ax_boost_chrono.m4 \ + $(top_srcdir)/m4/ax_boost_python.m4 \ + $(top_srcdir)/m4/ax_boost_random.m4 \ + $(top_srcdir)/m4/ax_boost_system.m4 \ + $(top_srcdir)/m4/ax_check_openssl.m4 \ + $(top_srcdir)/m4/ax_pthread.m4 \ + $(top_srcdir)/m4/ax_python_devel.m4 \ + $(top_srcdir)/m4/gettext-lib.m4 $(top_srcdir)/m4/iconv.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/pkgconfig.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__EXEEXT_1 = fuzz_torrent$(EXEEXT) +am__installdirs = "$(DESTDIR)$(bindir)" +PROGRAMS = $(bin_PROGRAMS) +am_fuzz_torrent_OBJECTS = fuzz_torrent.$(OBJEXT) +fuzz_torrent_OBJECTS = $(am_fuzz_torrent_OBJECTS) +fuzz_torrent_LDADD = $(LDADD) +fuzz_torrent_DEPENDENCIES = \ + $(top_builddir)/src/libtorrent-rasterbar.la +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ +depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) +LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CXXFLAGS) $(CXXFLAGS) +AM_V_CXX = $(am__v_CXX_@AM_V@) +am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@) +am__v_CXX_0 = @echo " CXX " $@; +am__v_CXX_1 = +CXXLD = $(CXX) +CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \ + $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CXXLD = $(am__v_CXXLD_@AM_V@) +am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@) +am__v_CXXLD_0 = @echo " CXXLD " $@; +am__v_CXXLD_1 = +SOURCES = $(fuzz_torrent_SOURCES) +DIST_SOURCES = $(fuzz_torrent_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in \ + $(top_srcdir)/build-aux/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BOOST_CHRONO_LIB = @BOOST_CHRONO_LIB@ +BOOST_CPPFLAGS = @BOOST_CPPFLAGS@ +BOOST_LDFLAGS = @BOOST_LDFLAGS@ +BOOST_PYTHON_LIB = @BOOST_PYTHON_LIB@ +BOOST_RANDOM_LIB = @BOOST_RANDOM_LIB@ +BOOST_SYSTEM_LIB = @BOOST_SYSTEM_LIB@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +COMPILETIME_OPTIONS = @COMPILETIME_OPTIONS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEBUGFLAGS = @DEBUGFLAGS@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GREP = @GREP@ +ICONV_LIBS = @ICONV_LIBS@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INTERFACE_VERSION_INFO = @INTERFACE_VERSION_INFO@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBICONV = @LIBICONV@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OPENSSL_INCLUDES = @OPENSSL_INCLUDES@ +OPENSSL_LDFLAGS = @OPENSSL_LDFLAGS@ +OPENSSL_LIBS = @OPENSSL_LIBS@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PTHREAD_CC = @PTHREAD_CC@ +PTHREAD_CFLAGS = @PTHREAD_CFLAGS@ +PTHREAD_LIBS = @PTHREAD_LIBS@ +PYTHON = @PYTHON@ +PYTHON_CPPFLAGS = @PYTHON_CPPFLAGS@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_EXTRA_LDFLAGS = @PYTHON_EXTRA_LDFLAGS@ +PYTHON_EXTRA_LIBS = @PYTHON_EXTRA_LIBS@ +PYTHON_INSTALL_PARAMS = @PYTHON_INSTALL_PARAMS@ +PYTHON_LIBS = @PYTHON_LIBS@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_SITE_PKG = @PYTHON_SITE_PKG@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +ax_pthread_config = @ax_pthread_config@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target = @target@ +target_alias = @target_alias@ +target_cpu = @target_cpu@ +target_os = @target_os@ +target_vendor = @target_vendor@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +tool_programs = \ + fuzz_torrent + +EXTRA_DIST = Jamfile \ + parse_bandwidth_log.py \ + parse_buffer_log.py \ + parse_dht_log.py \ + parse_dht_rtt.py \ + parse_dht_stats.py \ + parse_disk_buffer_log.py\ + parse_disk_log.py \ + parse_memory_log.py \ + parse_peer_log.py \ + parse_sample.py \ + parse_session_stats.py \ + parse_utp_log.py + +fuzz_torrent_SOURCES = fuzz_torrent.cpp +LDADD = $(top_builddir)/src/libtorrent-rasterbar.la +AM_CPPFLAGS = -ftemplate-depth-50 -I$(top_srcdir)/include @DEBUGFLAGS@ +AM_LDFLAGS = @BOOST_SYSTEM_LIB@ +all: all-am + +.SUFFIXES: +.SUFFIXES: .cpp .lo .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign tools/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign tools/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +install-binPROGRAMS: $(bin_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-binPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(bindir)" && rm -f $$files + +clean-binPROGRAMS: + @list='$(bin_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list + +fuzz_torrent$(EXEEXT): $(fuzz_torrent_OBJECTS) $(fuzz_torrent_DEPENDENCIES) $(EXTRA_fuzz_torrent_DEPENDENCIES) + @rm -f fuzz_torrent$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(fuzz_torrent_OBJECTS) $(fuzz_torrent_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fuzz_torrent.Po@am__quote@ + +.cpp.o: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $< + +.cpp.obj: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.cpp.lo: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(PROGRAMS) +installdirs: + for dir in "$(DESTDIR)$(bindir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-binPROGRAMS clean-generic clean-libtool mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-binPROGRAMS + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-binPROGRAMS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am check check-am clean \ + clean-binPROGRAMS clean-generic clean-libtool cscopelist-am \ + ctags ctags-am distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-binPROGRAMS \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man install-pdf \ + install-pdf-am install-ps install-ps-am install-strip \ + installcheck installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am uninstall-binPROGRAMS + +.PRECIOUS: Makefile + +#AM_LDFLAGS = $(LDFLAGS) @BOOST_SYSTEM_LIB@ @OPENSSL_LDFLAGS@ @OPENSSL_LIBS@ +#AM_LDFLAGS = @OPENSSL_LDFLAGS@ + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/tools/fuzz_torrent.cpp b/tools/fuzz_torrent.cpp new file mode 100644 index 0000000..1a5bb9e --- /dev/null +++ b/tools/fuzz_torrent.cpp @@ -0,0 +1,415 @@ +/* + +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 +#include +#include +#include "libtorrent/bdecode.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/error_code.hpp" + +using libtorrent::bdecode_node; +using boost::random::mt19937; +using boost::random::uniform_int_distribution; + +char const* invalid_utf8_sequences[] = +{ +"\x80", +"\xbf", +"\xff", +"\xfe", +"\xff\xff\xfe\xfe", +"\xc0\xaf", +"\xe0\x80\xaf", +"\xf0\x80\x80\xaf", +"\xf8\x80\x80\x80\xaf ", +"\xfc\x80\x80\x80\x80\xaf", +"\xc1\xbf", +"\xe0\x9f\xbf", +"\xf0\x8f\xbf\xbf", +"\xf8\x87\xbf\xbf\xbf", +"\xfc\x83\xbf\xbf\xbf\xbf", +"\xc0\x80", +"\xe0\x80\x80", +"\xf0\x80\x80\x80", +"\xf8\x80\x80\x80\x80", +"\xfc\x80\x80\x80\x80\x80", +"\xed\xa0\x80", +"\xed\xad\xbf", +"\xed\xae\x80", +"\xed\xaf\xbf", +"\xed\xb0\x80", +"\xed\xbe\x80", +"\xed\xbf\xbf", +"\xed\xa0\x80\xed\xb0\x80", +"\xed\xa0\x80\xed\xbf\xbf", +"\xed\xad\xbf\xed\xb0\x80", +"\xed\xad\xbf\xed\xbf\xbf", +"\xed\xae\x80\xed\xb0\x80", +"\xed\xae\x80\xed\xbf\xbf", +"\xed\xaf\xbf\xed\xb0\x80", +"\xed\xaf\xbf\xed\xbf\xbf", +}; + +boost::int64_t g_seed; + +void print_ascii_number(std::string& output, boost::int64_t val) +{ + const bool overflow = g_seed == 1; + const bool underflow = g_seed == 2; + const bool negative = g_seed == 3; + const bool double_negative = g_seed == 4; + const bool zero = g_seed == 5; + g_seed -= 5; + + char const* numbers = "0123456789"; + if (zero) + { + output += '0'; + } + else if (underflow) + { + output += '-'; + for (int i = 0; i < 100; ++i) output += numbers[rand() % 10]; + return; + } + else if (overflow) + { + for (int i = 0; i < 100; ++i) output += numbers[rand() % 10]; + return; + } + else + { + if (negative) output += '-'; + else if (double_negative) output += "--"; + char buf[50]; + snprintf(buf, sizeof(buf), "%" PRId64 "", val); + output += buf; + } +} + +void print_string(std::string& output, std::string str) +{ + const bool empty_string = g_seed == 1; + g_seed -= 1; + if (empty_string) + { + print_ascii_number(output, 0); + output += ':'; + return; + } + + const bool random_string = g_seed > 0 && g_seed <= 1000; + const int str_seed = g_seed - 1; + g_seed -= 1000; + if (random_string) + { + static mt19937 random_engine(str_seed); + uniform_int_distribution d(0, 255); + for (int i = 0; i < str.size(); ++i) + str[i] = d(random_engine); + + print_ascii_number(output, str.size()); + output += ':'; + output += str; + return; + } + + const int num_sequences = (sizeof(invalid_utf8_sequences)/sizeof(char const*)); + const bool invalid_utf8 = g_seed <= num_sequences && g_seed > 0; + + if (invalid_utf8) + str += invalid_utf8_sequences[g_seed-1]; + + g_seed -= num_sequences; + + print_ascii_number(output, str.size()); + output += ':'; + output += str; +} + +void print_terminate(std::string& output) +{ + const bool unterminated = g_seed == 1; + g_seed -= 1; + if (!unterminated) output += 'e'; +} + +void print_int(std::string& output, boost::int64_t value) +{ + const bool double_int = g_seed == 1; + g_seed -= 1; + if (double_int) output += 'i'; + output += 'i'; + print_ascii_number(output, value); + print_terminate(output); +} + +void print_dict(std::string& output) +{ + const bool double_dict = g_seed == 1; + g_seed -= 1; + if (double_dict) output += 'd'; + output += 'd'; +} + +void print_list(std::string& output) +{ + const bool double_list = g_seed == 1; + g_seed -= 1; + if (double_list) output += 'l'; + output += 'l'; +} + +void render_arbitrary_item(std::string& out) +{ + if (g_seed <= 0) return; + + std::string option; + print_int(option, 1337); + if (g_seed <= 0) + { + out += option; + return; + } + + option.clear(); + print_string(option, "abcdefgh"); + if (g_seed <= 0) + { + out += option; + return; + } + + option.clear(); + print_dict(option); + print_string(option, "abcdefgh"); + print_int(option, 1337); + print_terminate(option); + if (g_seed <= 0) + { + out += option; + return; + } + + option.clear(); + print_list(option); + print_string(option, "abcdefgh"); + print_terminate(option); + if (g_seed <= 0) + { + out += option; + return; + } +} + +void render_variant(std::string& out, bdecode_node const& e) +{ + switch (e.type()) + { + case bdecode_node::dict_t: + print_dict(out); + for (int i = 0; i < e.dict_size(); ++i) + { + std::pair item = e.dict_at(i); + const bool duplicate = g_seed == 1; + const bool skipped = g_seed == 2; + g_seed -= 2; + if (duplicate) + { + print_string(out, item.first); + render_variant(out, item.second); + } + if (!skipped) + { + print_string(out, item.first); + render_variant(out, item.second); + } + + render_arbitrary_item(out); + } + print_terminate(out); + break; + case bdecode_node::list_t: + print_list(out); + for (int i = 0; i < e.list_size(); ++i) + { + const bool duplicate = g_seed == 1; + const bool skipped = g_seed == 2; + g_seed -= 2; + if (duplicate) render_variant(out, e.list_at(i)); + + render_arbitrary_item(out); + + if (!skipped) render_variant(out, e.list_at(i)); + } + print_terminate(out); + break; + case bdecode_node::int_t: + print_int(out, e.int_value()); + break; + case bdecode_node::string_t: + print_string(out, e.string_value()); + break; + default: + abort(); + } +} + +int load_file(std::string const& filename, std::vector& v + , libtorrent::error_code& ec, int limit = 8000000) +{ + ec.clear(); + FILE* f = fopen(filename.c_str(), "rb"); + if (f == NULL) + { + 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(s); + if (s == 0) + { + fclose(f); + return 0; + } + + r = 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; +} + +int main(int argc, char const* argv[]) +{ + std::vector buf; + libtorrent::error_code ec; + + if (argc < 2) + { + fprintf(stderr, "usage: fuzz_torrent torrent-file [torrent-file ...]\n"); + return 1; + } + + --argc; + ++argv; + for (;argc > 0; --argc, ++argv) + { + int ret = load_file(*argv, buf, ec); + if (ret < 0) + { + fprintf(stderr, "ERROR loading file: %s\n%s\n" + , *argv, ec.message().c_str()); + continue; + } + + bdecode_node e; + if (buf.size() == 0 || bdecode(&buf[0], &buf[0] + buf.size(), e, ec) != 0) + { + fprintf(stderr, "ERROR parsing file: %s\n%s\n" + , *argv, ec.message().c_str()); + continue; + } + + std::string test_buffer; + int i = 0; + for (i = 0; i < 10000000; ++i) + { + g_seed = i; + test_buffer.clear(); + render_variant(test_buffer, e); + + libtorrent::error_code ec; + libtorrent::torrent_info t(test_buffer.c_str(), test_buffer.size(), ec); + + // TODO: add option to save to file unconditionally (to test other clients) + /* + { + fprintf(stderr, "saving %d\n", i); + char filename[100]; + snprintf(filename, sizeof(filename), "torrents/fuzz-%d.torrent", i); + FILE* f = fopen(filename, "wb+"); + if (f == 0) + { + fprintf(stderr, "ERROR saving file: (%d) %s\n", errno, strerror(errno)); + return 1; + } + fwrite(test_buffer.c_str(), test_buffer.size(), 1, f); + fclose(f); + } + */ + if (g_seed > 0) break; + } + fprintf(stderr, "tested %d variants of %s\n", i, *argv); + } + return 0; +} + diff --git a/tools/parse_bandwidth_log.py b/tools/parse_bandwidth_log.py new file mode 100755 index 0000000..2e144de --- /dev/null +++ b/tools/parse_bandwidth_log.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python +import os, sys, time + +keys = [['upload rate', 'x1y1', 6], ['history entries', 'x1y2', 10], ['queue', 'x1y2', 4]] + +out = open('bandwidth.gnuplot', 'wb') +print >>out, "set term png size 1200,700" +print >>out, 'set output "bandwidth_manager.png"' +print >>out, 'set xrange [0:*]' +print >>out, 'set xlabel "time (ms)"' +print >>out, 'set ylabel "Rate (B/s)"' +print >>out, 'set ytics 10000' +print >>out, 'set y2label "number"' +print >>out, 'set y2range [0:*]' +#print >>out, "set style data lines" +print >>out, "set key box" +print >>out, 'plot', +for k, a, c in keys: + print >>out, ' "%s" using 1:%d title "%s" axes %s with steps,' % (sys.argv[1], c, k, a), +print >>out, 'x=0' +out.close() + +os.system('gnuplot bandwidth.gnuplot'); + diff --git a/tools/parse_buffer_log.py b/tools/parse_buffer_log.py new file mode 100755 index 0000000..9d3257d --- /dev/null +++ b/tools/parse_buffer_log.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python +# 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) + +import os, sys, time + +lines = open(sys.argv[1], 'rb').readlines() + +#keys = ['send_buffer_utilization'] +keys = ['send_buffer_size', 'used_send_buffer', 'protocol_buffer'] +#keys = ['send_buffer_alloc', 'send_buffer', 'allocate_buffer_alloc', 'allocate_buffer', 'protocol_buffer'] +#keys = ['send_buffer_alloc', 'send_buffer', 'allocate_buffer_alloc', 'allocate_buffer', 'protocol_buffer', 'append_send_buffer'] + +average = ['send_buffer_utilization', 'send_buffer_size', 'used_send_buffer'] +average_interval = 120000 +render = 'lines' + +time_limit = -1 +if len(sys.argv) > 2: + time_limit = long(sys.argv[2]) + + +# logfile format: +# +# example: +# 16434 allocate_buffer: 17 +for k in keys: + + last_sample = 0 + average_accumulator = 0 + average_samples = 0 + peak = 0 + + out = open(k + '.dat', 'wb') + eval_average = False + if k in average: + eval_average = True + peak_out = open(k + '_peak.dat', 'wb') + + for l in lines: + l = l.split(' ') + if len(l) != 3: + print l + continue + try: + if l[1] == k + ':': + if time_limit != -1 and long(l[0]) > time_limit: break + time = l[0] + value = l[2] + if eval_average: + while long(time) > last_sample + average_interval: + last_sample = last_sample + average_interval + if average_samples < 1: average_samples = 1 + print >>out, '%d %f' % (last_sample, average_accumulator / average_samples) + print >>peak_out, '%d %f' % (last_sample, peak) + average_accumulator = 0 + average_samples = 0 + peak = 0 + average_accumulator = average_accumulator + float(value) + average_samples = average_samples + 1 + if float(value) > peak: peak = float(value) + else: + print >>out, time + ' ' + value, + except: + print l + + out.close() + peak_out.close() + +out = open('send_buffer.gnuplot', 'wb') +print >>out, "set term png size 1200,700" +print >>out, 'set output "send_buffer.png"' +print >>out, 'set xrange [0:*]' +print >>out, 'set xlabel "time (ms)"' +print >>out, 'set ylabel "bytes (B)"' +print >>out, "set style data lines" +print >>out, "set key box" +print >>out, 'plot', +for k in keys: + if k in average: + print >>out, ' "%s.dat" using 1:2 title "%s %d seconds average" with %s,' % (k, k, average_interval / 1000., render), + print >>out, ' "%s_peak.dat" using 1:2 title "%s %d seconds peak" with %s,' % (k, k, average_interval / 1000., render), + else: + print >>out, ' "%s.dat" using 1:2 title "%s" with %s,' % (k, k, render), +print >>out, 'x=0' +out.close() + +os.system('gnuplot send_buffer.gnuplot') + diff --git a/tools/parse_dht_log.py b/tools/parse_dht_log.py new file mode 100755 index 0000000..8371737 --- /dev/null +++ b/tools/parse_dht_log.py @@ -0,0 +1,329 @@ +#!/usr/bin/env python +import sys +import os +import time +import calendar +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: + l = line.split(' ') + if 'starting DHT tracker with node id:' in line: + our_node_id = l[l.index('id:') + 1].strip() + + try: + if len(l) > 4 and l[2] == '<==' and l[1] == '[dht_tracker]': + ip = l[3].split(':')[0] + if ip not in unique_ips: + unique_ips.add(ip) + json_blob = line.split(l[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 not v 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: pass + + if 'announce-distance:' in line: + idx = l.index('announce-distance:') + + d = int(l[idx+1].strip()) + if not d in announce_histogram: announce_histogram[d] = 0 + announce_histogram[d] += 1 + if 'NODE FAILED' in line: + idx = l.index('fails:') + if int(l[idx+1].strip()) != 1: continue; + idx = l.index('up-time:') + d = int(l[idx+1].strip()) + # quantize + d = d - (d % up_time_quanta) + if not d in node_uptime_histogram: node_uptime_histogram[d] = 0 + node_uptime_histogram[d] += 1 + + search_id = l[2] + ts = l[0] + event = l[3] + + if event == 'RESPONSE': + outstanding = int(l[l.index('invoke-count:')+1]) + nid = l[l.index('id:')+1] + addr = l[l.index('addr:')+1] + last_response = addr + outstanding_searches[search_id].append({ 't': ts, 'd': distance, + 'o': outstanding + 1, 'a':addr, 'e': event,'i':nid, 's':source}) + elif event == 'NEW': + nid = l[l.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 not search_id in outstanding_searches: + print 'orphaned event: %s' % line + else: + outstanding = int(l[l.index('invoke-count:')+1]) + distance = int(l[l.index('distance:')+1]) + nid = l[l.index('id:')+1] + addr = l[l.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(l[l.index('distance:')+1]) + lookup_type = l[l.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: + pass + searches.append(s) + del outstanding_searches[search_id] + + except Exception, 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 not 'last_dist' 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 >>out, '%d\t%d\t%f' % (lookup_times_min[i], lookup_times_max[i], counter / float(len(lookup_times_min))) +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 >>out, '%d\t%f' % (j, counter / float(len(i))) + out.close() + +out = open('dht_lookups.txt', 'w+') +for s in searches: + for i in s: + if i['e'] == 'INVOKE': + print >>out, ' ->', i['t'], 160 - i['d'], i['i'], i['a'] + elif i['e'] == '1ST_TIMEOUT': + print >>out, ' x ', i['t'], 160 - i['d'], i['i'], i['a'] + elif i['e'] == 'TIMEOUT': + print >>out, ' X ', i['t'], 160 - i['d'], i['i'], i['a'] + elif i['e'] == 'ADD': + print >>out, ' + ', i['t'], 160 - i['d'], i['i'], i['a'], i['s'] + elif i['e'] == 'RESPONSE': + print >>out, ' <-', i['t'], 160 - i['d'], i['i'], i['a'] + elif i['e'] == 'PEERS': + print >>out, ' <-', i['t'], 160 - i['d'], i['i'], i['a'] + elif i['e'] == 'ABORTED': + print >>out, 'abort' + elif i['e'] == 'COMPLETED': + print >>out, '***', i['t'], 160 - i['d'], '\n' + elif i['e'] == 'NEW': + print >>out, '===', i['abstime'], i['type'], '===' + print >>out, '<> ', 0, our_node_id, i['i'] +out.close() + +out = open('dht_announce_distribution.dat', 'w+') +print 'announce distribution items: %d' % len(announce_histogram) +for k,v in announce_histogram.items(): + print >>out, '%d %d' % (k, v) + 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 node_uptime_histogram.items(): + total_uptime_nodes += v + +for k,v in sorted(node_uptime_histogram.items()): + s += v + print >>out, '%f %f' % (k / float(60), s / float(total_uptime_nodes)) + print '%f %f' % (k / float(60), s / float(total_uptime_nodes)) +out.close() + + +print 'clients by version' +client_version_histogram = sorted(client_version_histogram.items(), key=lambda x: x[1], reverse=True) +pp.pprint(client_version_histogram) + +print 'clients' +client_histogram = sorted(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..0548f11 --- /dev/null +++ b/tools/parse_dht_rtt.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python + +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 distribution.items(): + print >>f, '%f %d' % ((k + (quantize / 2)) / 1000.0, v) +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..15edd08 --- /dev/null +++ b/tools/parse_dht_stats.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python +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 not 'minute:' 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_disk_buffer_log.py b/tools/parse_disk_buffer_log.py new file mode 100755 index 0000000..e464034 --- /dev/null +++ b/tools/parse_disk_buffer_log.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python + +import os, sys, time + +lines = open(sys.argv[1], 'rb').readlines() + +# logfile format: +# : +# example: +# 16434 read cache: 17 + +key_order = ['receive buffer', 'send buffer', 'released send buffer', 'posted send buffer', + 'received send buffer', 'dispatched send buffer', 'queued send buffer', + 'write cache', 'read cache', 'hash temp'] +colors = ['30f030', '001070', '101080', '2040a0', + '4070d0', '80a0f0', 'f03030', + '80f080', 'f08080', '4040ff'] + +keys = [] +fields = {} +maximum = {} +out = open('disk_buffer_log.dat', 'w+') + +field_sum = {} +field_num_samples = {} +field_timestamp = {} + +for c in key_order: + keys.append(c) + fields[c] = 0 + maximum[c] = 0 + field_sum[c] = 0 + field_num_samples[c] = 0 + field_timestamp[c] = 0 + +last_t = 0 +for l in lines: + try: + t = int(l[0:l.find(' ')]) + c = l[l.find(' ')+1:l.find(':')] + n = int(l[l.find(':')+1:-1]) + except: + print l + continue + + if last_t != t: + print >>out, '%d\t' % last_t, + for i in keys: + print >>out, '%d\t' % maximum[i], + print >>out, '\n', + + if not c in keys: continue + + field_sum[c] += fields[c] * float(t - field_timestamp[c]) + field_timestamp[c] = t + + fields[c] = n + + if n > maximum[c]: maximum[c] = n + + if last_t != t: + last_t = t + maximum = fields + +for i in keys: + print '%s: avg: %f' % (i, field_sum[i] / last_t) +print + +out.close() + +out = open('disk_buffer.gnuplot', 'wb') +print >>out, "set term png size 1200,700" +print >>out, 'set output "disk_buffer.png"' +print >>out, 'set xrange [0:*]' +print >>out, 'set xlabel "time (ms)"' +print >>out, 'set ylabel "buffers"' +print >>out, "set style data lines" +print >>out, "set key box" +print >>out, 'plot', +count = 1 + len(keys) +keys.reverse() +comma = '' +for k in keys: + expr = "$%d" % count + for i in xrange(2, count): expr += "+$%d" % i + count -= 1 + print >>out, ' %s"disk_buffer_log.dat" using 1:(%s) title "%s" with filledcurves x1 lt rgb "#%s"' % (comma, expr, k, colors[count-1]), + comma = ',' +out.close() + +os.system('gnuplot disk_buffer.gnuplot') + diff --git a/tools/parse_disk_log.py b/tools/parse_disk_log.py new file mode 100755 index 0000000..f098746 --- /dev/null +++ b/tools/parse_disk_log.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python +# 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) + +import os, sys, time + +lines = open(sys.argv[1], 'rb').readlines() + +if len(sys.argv) < 2: + print "usage: parse_disk_log.py logfile [seconds]" + sys.exit(1) + +keys = ['write', 'read', 'read-cache-hit', 'hash', 'move', 'release', 'idle', \ + 'delete', 'check_fastresume', 'check_files', 'clear-cache', \ + 'abort_thread', 'abort_torrent', 'save_resume_data', 'rename_file', \ + 'flushing', 'update_settings', 'finalize_file', 'sorting_job', \ + 'check_cache_hit'] +throughput_keys = ['write', 'read', 'read-cache-hit'] + +# logfile format: +# +# example: +# 34523 idle +# 34722 write + +if len(sys.argv) > 2: + quantization = long(sys.argv[2]) * 1000000 +else: + quantization = 1000000 + +out = open('disk_io.dat', 'wb') +out2 = open('disk_throughput.dat', 'wb') +state = 'idle' +time = -1 +start_time = -1 +i = 0 +state_timer = {} +throughput = {} +for k in keys: state_timer[k] = 0 +for k in throughput_keys: throughput[k] = 0 + +for l in lines: + l = l.strip().split() + if len(l) < 2: + print l + continue +# try: + new_time = long(l[0]) + if time == -1: + time = new_time + i = new_time + start_time = new_time + while new_time > i + quantization: + i += quantization + state_timer[state] += i - time + time = i + for k in keys: print >>out, (state_timer[k] / float(quantization) * 100.), + print >>out + print >>out2, time - start_time, + for k in throughput_keys: + print >>out2, throughput[k] * 1000 / float(quantization), + print '-- %s %d' % (k, throughput[k]) + print >>out2 + for k in keys: state_timer[k] = 0 + for k in throughput_keys: throughput[k] = 0 + state_timer[state] += new_time - time + time = new_time + state = l[1] + if state in throughput_keys: + throughput[state] += long(l[2]) +# except: +# print l + +i += quantization +state_timer[state] += i - time +time = i +for k in keys: print >>out, (state_timer[k] / float(quantization) * 100.), +print >>out +print >>out2, time - start_time, +for k in throughput_keys: + print >>out2, throughput[k] * 1000 / float(quantization), + print '-- %s %d' % (k, throughput[k]) +print >>out2 +for k in keys: state_timer[k] = 0 +for k in throughput_keys: throughput[k] = 0 +out.close() +out2.close() + +out = open('disk_io.gnuplot', 'wb') +print >>out, "set term png size 1200,700" + +print >>out, 'set output "disk_throughput.png"' +print >>out, 'set title "disk throughput per %f second(s)"' % (quantization / float(1000000)) +print >>out, 'set ylabel "throughput (kB/s)"' +print >>out, 'plot', +i = 0 +for k in throughput_keys: + print >>out, ' "disk_throughput.dat" using 1:%d title "%s" with lines,' % (i + 2, throughput_keys[i]), + i = i + 1 +print >>out, 'x=0' + +print >>out, 'set output "disk_io.png"' +print >>out, 'set ylabel "utilization (%)"' +print >>out, 'set xrange [0:*]' +print >>out, 'set title "disk io utilization per %f second(s)"' % (quantization / float(1000000)) +print >>out, "set key box" +print >>out, "set style data histogram" +print >>out, "set style histogram rowstacked" +print >>out, "set style fill solid" +print >>out, 'plot', +i = 0 +for k in keys: + if k != 'idle': + print >>out, ' "disk_io.dat" using %d title "%s",' % (i + 1, keys[i]), + i = i + 1 +print >>out, 'x=0' +out.close() + +os.system('gnuplot disk_io.gnuplot'); + diff --git a/tools/parse_memory_log.py b/tools/parse_memory_log.py new file mode 100755 index 0000000..17749d4 --- /dev/null +++ b/tools/parse_memory_log.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python +import os, sys, time + +# usage: memory.log memory_index.log + +lines = open(sys.argv[1], 'rb').readlines() +index = open(sys.argv[2], 'rb').readlines() + +# logfile format: +# #
    +# example: +# #12 38 A 0xd902a0 16 16 0 16 + +allocation_points_to_print = 30 + +def print_allocation_point(ap): + print 'space_time: %d kBms' % (ap['spacetime'] / 1024) + print 'allocations: %d' % ap['allocations'] + print 'peak: %d kB' % (ap['peak'] / 1024) + print 'stack: ' + counter = 0 + for e in ap['stack']: + print '#%d %s' % (counter, e) + counter += 1 + +allocation_points = [] +for l in index: + l = l.split('#') + l.pop(0) + ap = { 'allocations': 0, 'peak': 0, 'spacetime': 0, 'allocation_point': len(allocation_points), 'stack': l} + allocation_points.append(ap); + +for l in lines: + l = l.lstrip('#').rstrip('\n').split(' ') + if len(l) != 8: + print l + continue + try: + ap = int(l[0]) + allocation_points[ap]['allocations'] += 1 + allocation_points[ap]['peak'] = int(l[7]) + allocation_points[ap]['spacetime'] = int(l[6]) + except Exception, e: + print type(e), e, l + +print '=== space time ===' + +hot_ap = [] +allocation_points.sort(key = lambda x:x['spacetime'], reverse=True); +counter = 0 +for ap in allocation_points[0:allocation_points_to_print]: + print '== %d ==' % counter + counter += 1 + print_allocation_point(ap) + hot_ap.append(ap['allocation_point']); + +print '=== allocations ===' + +allocation_points.sort(key = lambda x:x['allocations'], reverse=True); +for ap in allocation_points[0:allocation_points_to_print]: + print_allocation_point(ap) + +print '=== peak ===' + +allocation_points.sort(key = lambda x:x['peak'], reverse=True); +for ap in allocation_points[0:allocation_points_to_print]: + print_allocation_point(ap) + +# generate graph +lines = open(sys.argv[1], 'rb').readlines() + +out = open('memory.dat', 'wb') +cur_line = [0] * allocation_points_to_print +prev_line = [0] * allocation_points_to_print +last_time = 0 + +for l in lines: + l = l.lstrip('#').rstrip('\n').split(' ') + if len(l) != 8: + print l + continue + try: + time = int(l[1]) + if time != last_time: + print >>out, last_time, '\t', + for i in range(allocation_points_to_print): + if cur_line[i] == -1: + print >>out, prev_line[i], '\t', + else: + print >>out, cur_line[i], '\t', + prev_line[i] = cur_line[i] + print >>out + cur_line = [-1] * allocation_points_to_print + last_time = time + + size = int(l[5]) + ap = int(l[0]) + if ap in hot_ap: + index = hot_ap.index(ap) + cur_line[index] = max(cur_line[index], size) + + except Exception, e: + print type(e), e, l + +out.close() + +out = open('memory.gnuplot', 'wb') +print >>out, "set term png size 1200,700" +print >>out, 'set output "memory.png"' +print >>out, 'set xrange [0:*]' +print >>out, 'set xlabel "time (ms)"' +print >>out, 'set ylabel "bytes (B)"' +print >>out, "set style data lines" +print >>out, "set key box" +print >>out, 'plot', +for k in range(allocation_points_to_print): + print >>out, ' "memory.dat" using 1:(', + for i in range(k, allocation_points_to_print): + if i == k: print >>out, '$%d' % (i + 2), + else: print >>out, '+$%d' % (i + 2), + print >>out, ') title "%d" with filledcurves x1, \\' % k +print >>out, 'x=0' +out.close() + +os.system('gnuplot memory.gnuplot'); + diff --git a/tools/parse_peer_log.py b/tools/parse_peer_log.py new file mode 100755 index 0000000..45dc1a6 --- /dev/null +++ b/tools/parse_peer_log.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python + +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 l in f: + t = l.split(': ')[0].split('.')[0] + log_line = False + if ' ==> PIECE' in l: + uploaded_blocks+= 1 + log_line = True + + if ' <== PIECE' in l: + downloaded_blocks+= 1 + log_line = True + + if log_line: + print >>out, '%s\t%d\t%d' % (t, uploaded_blocks, downloaded_blocks) + + out.close() + f.close() + +out = open('peers.gnuplot', 'wb') +print >>out, "set term png size 1200,700" +print >>out, 'set xrange [0:*]' +print >>out, 'set xlabel "time"' +print >>out, 'set ylabel "blocks"' +print >>out, 'set key box' +print >>out, 'set xdata time' +print >>out, 'set timefmt "%H:%M:%S"' +print >>out, 'set title "uploaded blocks"' +print >>out, 'set output "peers_upload.png"' +print >>out, 'plot', +first = True +for n in log_files: + if not first: + print >>out, ',', + first = False + print >>out, ' "%s" using 1:2 title "%s" with steps' % (n, os.path.split(n)[1].split('.log')[0]), +print >>out, '' + +print >>out, 'set title "downloaded blocks"' +print >>out, 'set output "peers_download.png"' +print >>out, 'plot', +first = True +for n in log_files: + if not first: + print >>out, ',', + first = False + print >>out, ' "%s" using 1:3 title "%s" with steps' % (n, os.path.split(n)[1].split('.log')[0]), +print >>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..3961914 --- /dev/null +++ b/tools/parse_sample.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python + +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(l): + indentation = 0 + while indentation < len(l) and l[indentation] == ' ': + indentation += 1 + if indentation == 0: + return (0, 0, '') + + + l = l.strip().split(' ') + samples = int(l[0]) + fun = ' '.join(l[1:]) + + return (indentation, samples, fun) + +fold = -1 + +try: + sample_limit = int(sys.argv[2]) +except: + sample_limit = 5 + +fun_samples = {} + +for l in f: + if 'Sort by top of stack' in l: break + + indentation, samples, fun = parse_line(l) + 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) + if ret != 0 and ret != 256: + print 'system: %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: pass + + script = os.path.join(output_dir, '%s_%04d.gnuplot' % (name, generation)) + out = open(script, 'wb') + print >>out, "set term png size 1200,700" + print >>out, 'set output "%s"' % filename + if not 'allow-negative' in options: + print >>out, 'set yrange [0:*]' + print >>out, "set tics nomirror" + print >>out, "set key box" + print >>out, "set key left top" + + 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: pass + + if options['type'] == histogram: + binwidth = options['binwidth'] + numbins = int(options['numbins']) + + print >>out, 'binwidth=%f' % binwidth + print >>out, 'set boxwidth binwidth' + print >>out, 'bin(x,width)=width*floor(x/width) + binwidth/2' + print >>out, 'set xrange [0:%f]' % (binwidth * numbins) + print >>out, 'set xlabel "%s"' % unit + print >>out, 'set ylabel "number"' + + k = lines[0] + try: + column = keys.index(k) + 2 + except: + print '"%s" not found' % k + return + print >>out, 'plot "%s" using (bin($%d,binwidth)):(1.0) smooth freq with boxes' % (log_file, column) + print >>out, '' + print >>out, '' + print >>out, '' + + elif options['type'] == stacked: + print >>out, 'set xrange [0:*]' + print >>out, 'set ylabel "%s"' % unit + print >>out, 'set xlabel "time (s)"' + print >>out, 'set format y "%%.1s%%c%s";' % short_unit + print >>out, 'set style fill solid 1.0 noborder' + print >>out, 'plot', + column = 2 + first = True + graph = '' + plot_expression = '' + color = 0 + for k in lines: + try: + column = keys.index(k) + 2 + except: + 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 >>out, plot_expression + elif options['type'] == diff: + print >>out, 'set xrange [0:*]' + print >>out, 'set ylabel "%s"' % unit + print >>out, 'set xlabel "time (s)"' + print >>out, 'set format y "%%.1s%%c%s";' % short_unit + column = 2 + first = True + graph = '' + title = '' + for k in lines: + try: + column = keys.index(k) + 2 + except: + print '"%s" not found' % k + continue; + if not first: + graph += '-' + title += ' - ' + graph += '$%d' % column + title += to_title(k) + first = False + print >>out, 'plot "%s" using 1:(%s) title "%s" with step' % (log_file, graph, title) + else: + print >>out, 'set xrange [0:*]' + print >>out, 'set ylabel "%s"' % unit + print >>out, 'set xlabel "time (s)"' + print >>out, 'set format y "%%.1s%%c%s";' % short_unit + print >>out, 'plot', + column = 2 + first = True + color = 0 + for k in lines: + try: + column = keys.index(k) + 2 + except: + print '"%s" not found' % k + continue; + if not first: print >>out, ', ', + axis = 'x1y1' + print >>out, ' "%s" using 1:%d title "%s" axes %s with steps lc rgb "%s"' % (log_file, column, to_title(k), axis, colors[color % len(colors)]), + first = False + color += 1 + print >>out, '' + + print >>out, 'set term png size 150,100' + print >>out, 'set output "%s"' % thumb + print >>out, 'set key off' + print >>out, 'unset tics' + print >>out, 'set format x ""' + print >>out, 'set format y ""' + print >>out, 'set xlabel ""' + print >>out, 'set ylabel ""' + print >>out, 'set y2label ""' + print >>out, 'set rmargin 0' + print >>out, 'set lmargin 0' + print >>out, 'set tmargin 0' + print >>out, 'set bmargin 0' + print >>out, "replot" + 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 >>file, '' % css + + for i in reports: + print >>file, '